0

I have the following schema (simplified and names changed):

@Entity
@Table(name = "fubar")
@Inheritance(strategy = InheritanceType.JOINED)
public class Fubar {

  @ManyToOne(fetch = FetchType.LAZY) // LAZY was recently added
  @JoinColumn(name = "bar_id")
  private Bar bar;

  public Bar getBar() {
    return bar;
  }
}

@Entity
@Table(name = "foo")
public class Foo extends Fubar {
 // Columns omitted for brevity
}

Basically, Foo extends Fubar, which has a lazily-loaded Bar property. As my comment in the code indicates, FetchType.LAZY was recently added. Now we are getting a ConcurrencyException from EclipseLink when simply calling the getBar method after retrieving a Foo record from the database.

@Named
@RequestScoped
public class FooBean {

  @Inject
  private FooDao fooDao;

  @PostConstruct
  public void initialize() {
    long fooId = getSelectedFooId();
    Foo foo = fooDao.findById(fooId);
    foo.getBar(); // Boom! -> ConcurrencyException
  }
}

Here's a simplified version of the FooDao. We are using something very similar (but not identical) to the EntityManagerHelper described in another post (essentially just EnityManager inside of ThreadLocal). For simplicity, I'll just use the EntityManagerHelper in the code below:

public class FooDao {

  public Foo findById(long id) {
    EntityManager em = EntityManagerHelper.getEntityManager();

    List<Foo> foos = em
        .createQuery("SELECT f FROM Foo f WHERE f.id = :id", Foo.class)
        .setParameter("id", id)
        .setMaxResults(1)
        .getResultList();

    return (foos.size() > 0) ? foos.get(0) : null;
  }
}

Last, but not least, here's the stacktrace we are getting from EclipseLink:

org.eclipse.persistence.exceptions.ConcurrencyException: 
Exception Description: A signal was attempted before wait() on ConcurrencyManager. This normally means that an attempt was made to 
commit or rollback a transaction before it was started, or to rollback a transaction twice.
    at org.eclipse.persistence.exceptions.ConcurrencyException.signalAttemptedBeforeWait(ConcurrencyException.java:84) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.helper.ConcurrencyManager.releaseReadLock(ConcurrencyManager.java:468) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.identitymaps.CacheKey.releaseReadLock(CacheKey.java:468) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.cloneAndRegisterObject(UnitOfWorkImpl.java:1041) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.cloneAndRegisterObject(UnitOfWorkImpl.java:955) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.sessions.UnitOfWorkIdentityMapAccessor.getAndCloneCacheKeyFromParent(UnitOfWorkIdentityMapAccessor.java:209) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.sessions.UnitOfWorkIdentityMapAccessor.getFromIdentityMap(UnitOfWorkIdentityMapAccessor.java:137) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.registerExistingObject(UnitOfWorkImpl.java:3942) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.queries.ObjectBuildingQuery.registerIndividualResult(ObjectBuildingQuery.java:448) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.queries.ReadObjectQuery.registerResultInUnitOfWork(ReadObjectQuery.java:887) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.checkEarlyReturn(ObjectLevelReadQuery.java:880) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.mappings.OneToOneMapping.checkCacheForBatchKey(OneToOneMapping.java:835) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.mappings.ForeignReferenceMapping.extractResultFromBatchQuery(ForeignReferenceMapping.java:531) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.indirection.BatchValueHolder.instantiate(BatchValueHolder.java:58) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.indirection.QueryBasedValueHolder.instantiate(QueryBasedValueHolder.java:116) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.indirection.DatabaseValueHolder.getValue(DatabaseValueHolder.java:89) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiateImpl(UnitOfWorkValueHolder.java:173) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.indirection.UnitOfWorkValueHolder.instantiate(UnitOfWorkValueHolder.java:234) ~[org.eclipse.persistence.core.jar:na]
    at org.eclipse.persistence.internal.indirection.DatabaseValueHolder.getValue(DatabaseValueHolder.java:89) ~[org.eclipse.persistence.core.jar:na]
    at com.mycompany.model.Foo._persistence_get_bar(Foo.java)
    at com.mycompany.model.Foo.getBar(Foo.java:16)
    at com.mycompany.beans.FooBean.initialize(FooBean.java:20)

According to the EclipseLink FAQ for resolving deadlocks, developers are encouraged to make all relationships LAZY. Why are we now getting a ConcurrencyException? I need an EclipseLink expert's help on this one. Thanks!

battmanz
  • 2,266
  • 4
  • 23
  • 32
  • Well, I'm pretty sure `@PostConstruct public void initialize()` is not wrapped in a transaction. You need a transaction around methods that access lazy properties in order to keep the persistence context open. Also, if you're planning on always accessing `Fubar.bar` after loading it from the data store, then `FetchType.LAZY` doesn't make much sense – crizzis Apr 24 '19 at 16:51
  • The error indicates your EntityManager returned from EntityManagerHelper.getEntityManager() is shared somehow. Maybe not among threads, but certainly something else is using your EntityManagerHelper and operating on the context in ways it shouldn't. Turn on EclipseLink logging to fine or finest and see what is happening leading up to this error. – Chris Apr 24 '19 at 17:21
  • @crizzis, you are correct that it is not in a transaction. So all lazily-initialized properties must be accessed in a transaction? Do you have a link to some good documentation about that? – battmanz Apr 24 '19 at 18:04
  • @Chris, could it be that it is shared by the EclipseLink lazy loading class/process/thread (basically, the code created by static weaving)? – battmanz Apr 24 '19 at 18:08
  • Weaving is a static process at the class loader level. Lazy loaded properties, within EclipseLink anyway, do not need a transaction to be loaded - they can be accessed as long as the persistence context that loaded the entity being accessed is available. This error is because the context is not available; it thinks a separate process is doing something to it, such as committing your transaction or something of that nature. Check that the transaction handling is done within this thread, as some servers do asynchronous processing. In which case, access the values within the transaction. – Chris Apr 24 '19 at 18:46
  • @battmanz Perhaps I wasn't clear enough, the bottom line of my comment was: you need to keep the persistence context open, and that's *usually* done by wrapping all calls to the `EntityManager` in a transaction. That's not the only way, though; for more info, see [the JPA spec](https://download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf?AuthParam=1556197887_707e149a4ee05a480b0e9a33e07ac3b4), sections 3.2.3 and 3.3 – crizzis Apr 25 '19 at 13:15

0 Answers0