У меня есть следующая схема (упрощенная и имена изменены):
@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
}
По сути, Foo
расширяет Fubar
, что имеет свойство Bar
, загруженное лениво. Как показывает мой комментарий в коде, FetchType.LAZY
был недавно добавлен. Теперь мы получаем ConcurrencyException
от EclipseLink при простом вызове метода getBar
после извлечения записи Foo
из базы данных.
@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
}
}
Вот упрощенная версия FooDao
. Мы используем нечто очень похожее (но не идентичное) на EntityManagerHelper
, описанный в другом посте (по сути просто EnityManager
внутри ThreadLocal
). Для простоты я просто буду использовать EntityManagerHelper
в коде ниже:
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;
}
}
И последнее, но не менее важное: вот трассировка стека, которую мы получаем от 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)
В соответствии с EclipseLink FAQ по устранению взаимоблокировок разработчикам рекомендуется выполнять все отношения LAZY. Почему мы сейчас получаем ConcurrencyException
? Мне нужна помощь эксперта EclipseLink по этому вопросу. Спасибо!