Search code examples
javajpaormdeadlockopenjpa

Deadlock in ORM when using OpenJPA


I am getting a deadlock when performing the load testing with Soap UI running 10 every thread makes 10 request to my web service which invokes the persits method of OpenJPA entity manager.

Then I am performing the single requests, all ends well, but in multithreaded environment it ends up with deadlocks.

The error I am getting is:

Deadlock found when trying to get lock; try restarting transaction {prepstmnt 12796448 UPDATE Assessment SET dateCompleted = ? WHERE id = ? [params=?, ?]} [code=1213, state=40001]

The method which is crashing with a deadlock is this:

@Override
public AssessmentKey update(AssessmentKey assessmentKey) {
    OpenJPAEntityManager openJpaEntityMgr = OpenJPAPersistence.cast(entityManager);
    for (Assessment assessment : assessmentKey.getAssessments()) {
        if (!entityManager.contains(assessment) && !openJpaEntityMgr.isDetached(assessment)) {
            LOGGER.debug("Persisting {}", assessment);
            entityManager.persist(assessment);
        }
    }
    return entityManager.merge(assessmentKey);
}

Assessment is an entity of OpenJPA, it is just a normal entity containing JPA annotations. If you'll need the code of it, ask for it.

I understood how deadlocks happening, how to solve them is mentioned in here. But how to solve them in ORM I am not able to find an answer.

My persistence.xml file looks like this:

<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
    <persistence-unit name="com.groupgti.esb.online.tests">
        <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <class>com.groupgti.esb.assessments.model.jpaimpl.Assessment</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
        <validation-mode>NONE</validation-mode>
        <properties>
            <property name="openjpa.Log" value="slf4j"/>
            <property name="openjpa.DataCache" value="false"/>
            <property name="openjpa.ReadLockLevel" value="none"/>
            <property name="openjpa.WriteLockLevel" value="pessimistic-write"/>
            <property name="openjpa.LockTimeout" value="10000"/>
            <property name="openjpa.RuntimeUnenhancedClasses" value="unsupported"/>
        </properties>
    </persistence-unit>
</persistence>

Whole application uses Spring and the classes in which transactions are done is marked @Transactional. How to solve this deadlock issue in ORM?

EDIT:

The AssessmentKey has a relation to Assessment:

@OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, mappedBy = "assessmentKey")
private List<Assessment> assessments = new ArrayList<Assessment>();

And in the Assessment AssessmentKey looks like this:

@ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@Index
@Column(nullable = false)
@ForeignKey
private AssessmentKey assessmentKey;

Solution

  • AssessmentKey and Assessment are OneToMany bidirectional relationship.Both of OneToMany and ManyToOne annotations have CascadeType.PERSIST and others.

    Point is :

    when EntityManager persist Assessment, AssessmentKey would persisted also. You should need to persist or merge AssessmentKey again.

    Let me suggest OR Mapping as follow :

    AssessmentKey.java

    @OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, 
               mappedBy = "assessmentKey", orphanRemoval=true)
    private List<Assessment> assessments;
    

    Assessment.java

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.REFRESH})
    @JoinColumn(name = "ASSESSMENT_ID", referencedColumnName = "ID")
    private AssessmentKey assessmentKey;
    

    DB Operation

    @Override
    public AssessmentKey update(AssessmentKey assessmentKey) {
        OpenJPAEntityManager openJpaEntityMgr = OpenJPAPersistence.cast(entityManager);
        // there may be your operation
        return entityManager.merge(assessmentKey);
    }
    

    I assume, Assessment values of current AssessmentKey object may be as below

    On System                               |   In Existing Database
    ID      Description                     |   ID      Descrition
    1001    AAA         => No Changes       |   1001    AAA
    1002    BBB         => Need to update   |   1002    BB
    null    DDD         => Need to add      |   1003    CCC     => need to remove.
    

    When EntityManager merge AssessmentKey, the EntityManager will does the all of the process for Assessment as I mention in above data structure.

    Note : make sure orphanRemoval=true in OneToMany annotation.