Hibernate Envers: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)

varunkr picture varunkr · Jun 17, 2017 · Viewed 12k times · Source

In my java app I am using Hibernate Envers to listen for persist events. My listener class looks like this.

@Component
public class DataCreationListener extends EnversPostInsertEventListenerImpl {

    private static final long serialVersionUID = 1L;

    @Autowired
    DataService DataService;

    public DataCreationListener() {
        super(null);
    }

    @Override
    public void onPostInsert(PostInsertEvent event) {

        if (event.getEntity() instanceof DataDAO) {
            Data Data = DataService.fromDao((DataDAO) event.getEntity());
            // other stuff
        }
    }

}

In the fromDAO method I convert the DAO(entity type) object to an api type object. This is the fromDAO method

@Override
    public Data fromDao(DataDAO data) {
        if (data == null) {
            return null;
        }
        Data api = new Data();
        api.setId(data.getIdUser());
        api.setAddress(data.getAddress());
        api.setCity(data.getCity());
        api.setCountry(getCountryFromId(data.getId()));

        return api;
    }

Error occurs when doing getCountryFromId(data.getId())

This method involves a Spring Data JPA repository call where the error occurs. The repository call looks like this

repository.findByCountry_Id(dataId);

Upon debugging I see that dataId has a non null value, repository is non null and the query should give a valid result. But somehow this error occurs

04:15:10.504 [http-nio-8080-exec-2] ERROR org.hibernate.AssertionFailure - HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in my.app.dbAccess.models.DataDAO entry (don't flush the Session after an exception occurs)
org.hibernate.AssertionFailure: null id in my.app.dbAccess.models.DataDAO entry (don't flush the Session after an exception occurs)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:60)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:175)
    at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:135)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:216)
    at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:85)
    at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:44)
    at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1251)
    at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1319)
    at org.hibernate.internal.QueryImpl.list(QueryImpl.java:87)
    at org.hibernate.jpa.internal.QueryImpl.list(QueryImpl.java:606)
    at org.hibernate.jpa.internal.QueryImpl.getSingleResult(QueryImpl.java:529)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.count(SimpleJpaRepository.java:486)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:503)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:488)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:280)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy137.count(Unknown Source)
    at my.app.dbAccess.generated.proxy.CountryRepositoryImpl.findByCountry_Id(CountryRepositoryImpl.java:800)
    at my.app.services.DataServiceImpl.getCountryFromId(DataServiceImpl.java:131)
    at my.app.services.DataServiceImpl.fromDao(DataServiceImpl.java:109)
    at my.app.services.DataServiceImpl.fromDao(DataServiceImpl.java:1)
    at my.app.services.DataServiceImpl.fromDao(DataServiceImpl.java:118)
    at my.app.services.DataServiceImpl.fromDao(DataServiceImpl.java:1)
    at my.app.services.DataServiceImpl$$FastClassBySpringCGLIB$$72be48ea.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:651)
    at my.app.services.DataServiceImpl$$EnhancerBySpringCGLIB$$c0525c49.fromDao(<generated>)
    at my.app.event.consumers.DataCreationListener.onPostInsert(DataCreationListener.java:40)
    at org.hibernate.action.internal.EntityIdentityInsertAction.postInsert(EntityIdentityInsertAction.java:156)
    at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:102)
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:597)

Is it because I am making a query from ithe onPostInsert method? Is doing a query from this method not safe? i don't see any problem in the data, my hunch is that it has something to do with hibernate envers but I can't figure out. Can someone please point me in the right way. Thanks !!

Answer

Michele Mariotti picture Michele Mariotti · Jun 17, 2017

Is it because I am making a query from ithe onPostInsert method?

Likely. From what I see, a beforehand flush has been triggered from the query.
More likely you are using Hibernate generated IDs (most likely db generated).

Is doing a query from this method not safe?

Depending on the specific case, it could be unsafe.
For sure, it breaks bulk/batch inserts, strongly degrading performace.

Try suspending the current transaction to prevent the flush and execute the query outside.
You may get outdated results, but this seems not a problem in this case.

Don't know how to do it in spring, in JavaEE you can call an EJB method like:

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) // alternatively use REQUIRES_NEW
public Country getCountryFromId(Object id) {
    ....
}

I'm sure there's a spring equivalent.

i don't see any problem in the data, my hunch is that it has something to do with hibernate envers but I can't figure out.

There's a problem in the data: the ID is null - has not been assigned yet.
I use application-generated IDs (generally UUID or SUID) to avoid this category of problems.

Envers performs its audit on a listener too, but there's no evidence in the ST and we don't which listener runs first - yours or Envers.

In both cases, this seems unrelated. I'd check other things (prevent auto-flush) first.