I am trying to use Hibernate Search for a new project. We have Hibernate and Spring without JPA. I m getting the following exception when Hibernate Search tries to update the index file thru the event listeners. I have read around on this issue but nothing seems to work for me. I would really appreciate any help.
Environment:
Spring 3.0.5
Hibernate 3.5.4
Hibernate Search 3.2.1
Exception:
TransactionSy E org.springframework.transaction.support.TransactionSynchronizationUtils invokeAfterCompletion TransactionSynchronization.afterCompletion threw exception
org.hibernate.AssertionFailure: Exception releasing cache locks
at org.hibernate.engine.ActionQueue$AfterTransactionCompletionProcessQueue.afterTransactionCompletion(ActionQueue.java:584)
at org.hibernate.engine.ActionQueue.afterTransactionCompletion(ActionQueue.java:204)
at org.hibernate.impl.SessionImpl.afterTransactionCompletion(SessionImpl.java:594)
at org.springframework.orm.hibernate3.SpringSessionSynchronization.afterCompletion(SpringSessionSynchronization.java:229)
at org.springframework.transaction.support.TransactionSynchronizationUtils.invokeAfterCompletion(TransactionSynchronizationUtils.java:168)
at org.springframework.transaction.jta.JtaAfterCompletionSynchronization.afterCompletion(JtaAfterCompletionSynchronization.java:62)
at com.ibm.ws.uow.ComponentContextSynchronizationWrapper.afterCompletion(ComponentContextSynchronizationWrapper.java:83)
at com.ibm.tx.jta.RegisteredSyncs.coreDistributeAfter(RegisteredSyncs.java:357)
at com.ibm.ws.tx.jta.RegisteredSyncs.distributeAfter(RegisteredSyncs.java:317)
at com.ibm.tx.jta.TransactionImpl.distributeAfter(TransactionImpl.java:2933)
at com.ibm.tx.jta.TransactionImpl.postCompletion(TransactionImpl.java:2892)
at com.ibm.tx.jta.TransactionImpl.postCompletion(TransactionImpl.java:2805)
at com.ibm.tx.jta.TransactionImpl.commitXAResources(TransactionImpl.java:1775)
at com.ibm.ws.tx.jta.TransactionImpl.stage1CommitProcessing(TransactionImpl.java:497)
at com.ibm.tx.jta.TransactionImpl.processCommit(TransactionImpl.java:978)
at com.ibm.tx.jta.TransactionImpl.commit(TransactionImpl.java:913)
at com.ibm.ws.tx.jta.TranManagerImpl.commit(TranManagerImpl.java:369)
at com.ibm.tx.jta.TranManagerSet.commit(TranManagerSet.java:161)
at com.ibm.ws.uow.UOWManagerImpl.uowCommit(UOWManagerImpl.java:1172)
at com.ibm.ws.uow.UOWManagerImpl.uowEnd(UOWManagerImpl.java:1142)
at com.ibm.ws.uow.UOWManagerImpl.runUnderNewUOW(UOWManagerImpl.java:1092)
at com.ibm.ws.uow.UOWManagerImpl.runUnderUOW(UOWManagerImpl.java:626)
at org.springframework.transaction.jta.WebSphereUowTransactionManager.execute(WebSphereUowTransactionManager.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:127)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
at com.test.service.inventory.InventoryServiceImpl$$EnhancerByCGLIB$$847cc0d8.updateInventoy(<generated>)
at com.test.web.servlet.SearchServlet.doPost(SearchServlet.java:51)
at com.test.web.servlet.SearchServlet.doGet(SearchServlet.java:75)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:718)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:831)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1449)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:790)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:443)
at com.ibm.ws.webcontainer.servlet.ServletWrapperImpl.handleRequest(ServletWrapperImpl.java:175)
at com.ibm.ws.webcontainer.servlet.CacheServletWrapper.handleRequest(CacheServletWrapper.java:91)
at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:859)
at com.ibm.ws.webcontainer.WSWebContainer.handleRequest(WSWebContainer.java:1557)
at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:173)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:455)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewInformation(HttpInboundLink.java:384)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.ready(HttpInboundLink.java:272)
at com.ibm.ws.tcp.channel.impl.NewConnectionInitialReadCallback.sendToDiscriminators(NewConnectionInitialReadCallback.java:214)
at com.ibm.ws.tcp.channel.impl.NewConnectionInitialReadCallback.complete(NewConnectionInitialReadCallback.java:113)
at com.ibm.ws.tcp.channel.impl.AioReadCompletionListener.futureCompleted(AioReadCompletionListener.java:165)
at com.ibm.io.async.AbstractAsyncFuture.invokeCallback(AbstractAsyncFuture.java:217)
at com.ibm.io.async.AsyncChannelFuture.fireCompletionActions(AsyncChannelFuture.java:161)
at com.ibm.io.async.AsyncFuture.completed(AsyncFuture.java:138)
at com.ibm.io.async.ResultHandler.complete(ResultHandler.java:202)
at com.ibm.io.async.ResultHandler.runEventProcessingLoop(ResultHandler.java:766)
at com.ibm.io.async.ResultHandler$2.run(ResultHandler.java:896)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1527)
Caused by: org.hibernate.HibernateException: Error while indexing in Hibernate Search (ater transaction completion)
at org.hibernate.search.backend.impl.EventSourceTransactionContext$DelegateToSynchronizationOnAfterTx.doAfterTransactionCompletion(EventSourceTransactionContext.java:179)
at org.hibernate.engine.ActionQueue$AfterTransactionCompletionProcessQueue.afterTransactionCompletion(ActionQueue.java:577)
... 51 more
Caused by: org.hibernate.annotations.common.AssertionFailure: Access a Sealed WorkQueue which has not been sealed
at org.hibernate.search.backend.WorkQueue.getSealedQueue(WorkQueue.java:87)
at org.hibernate.search.backend.impl.BatchedQueueingProcessor.performWorks(BatchedQueueingProcessor.java:280)
at org.hibernate.search.backend.impl.PostTransactionWorkQueueSynchronization.afterCompletion(PostTransactionWorkQueueSynchronization.java:96)
at org.hibernate.search.backend.impl.EventSourceTransactionContext$DelegateToSynchronizationOnAfterTx.doAfterTransactionCompletion(EventSourceTransactionContext.java:176)
... 52 more
I know there was a fix for a similar problem [#HSEARCH-540] but that does not seem to be working for me.
Here is my Spring context:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="com.test.entity.inventory" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.jdbc.batch_size">40</prop>
<!-- Hibernate Search properties -->
<prop key="hibernate.search.default.indexBase">/POC/index</prop>
<prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.FSDirectoryProvider</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.WebSphereUowTransactionManager" />
<tx:annotation-driven />
<bean id="inventoryDao"
class="com.test.dao.inventory.InventoryDao">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
Here is my inventoryDao method that is throwing the exception.
#Transactional
public Inventory updateInventoy(long id) {
Inventory inv = null;
Session session = sessionFactory.getCurrentSession();
inv = (Inventory) session.load(Inventory.class, id);
inv.setPrice(100000);
inv.setModel("Testing123");
return inv;
}
The database get updated fine however my index is not getting updated.
Please help. Thanks in advance..
To keep the index in sync with the database you have to wrap your Hibernate session in a full-text session, like so:
FullTextSession session = Search.getFullTextSession(sessionFactory.getCurrentSession());
Then use the full-text session in place of the wrapped session.
Related
I'm having an issue with Spring and Hibernate Search. Here is my code:
#Slf4j
#Repository
public class DefaultIndexBuilderDao implements IndexBuilderDao {
#PersistenceContext
#Getter
#Setter
private EntityManager entityManager;
#Override
public void rebuildIndex() {
try {
log.debug("Starting the reindex process...");
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(getEntityManager());
fullTextEntityManager.createIndexer().startAndWait();
log.debug("Reindex complete.");
} catch( InterruptedException e ) {
log.warn("Error rebuilding index: {}", e.getMessage(), e);
}
}
}
I'm getting:
Caused by: java.lang.ClassCastException: com.sun.proxy.$Proxy62 cannot be cast to org.hibernate.engine.spi.SessionImplementor
at org.hibernate.search.impl.FullTextSessionImpl.<init>(FullTextSessionImpl.java:62)
at org.hibernate.search.impl.ImplementationFactory.createFullTextSession(ImplementationFactory.java:35)
at org.hibernate.search.Search.getFullTextSession(Search.java:45)
at com.domainwww.dao.DefaultIndexBuilderDao.rebuildIndex(DefaultIndexBuilderDao.java:38)
at com.domainwww.service.DefaultIndexBuilderService.rebuildIndex(DefaultIndexBuilderService.java:30)
at com.domainwww.beans.admin.IndexBean.reindex(IndexBean.java:29)
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.apache.el.parser.AstValue.invoke(AstValue.java:247)
at org.apache.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:267)
at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:107)
at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:87)
... 57 more
Here are the versions from my POM (properties) (in case this is a version conflict)
<spring.version>5.3.1</spring.version>
<hibernate.version>5.4.24.Final</hibernate.version>
<hibernate.search.version>5.11.7.Final</hibernate.search.version>
Here is by bean for entityManager:
<bean id="entityManager" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="net.xxxx,com.xxxx.dbmanager3.settings" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.dialect.storage_engine">innodb</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.NoCacheProvider</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.connection.autocommit">false</prop>
<prop key="hibernate.search.default.directory_provider">filesystem</prop>
<prop key="hibernate.search.default.indexBase">/opt/xxx/lucene/indexes</prop>
</props>
</property>
</bean>
So I get that Spring is injecting a Proxy, so how should I be obtaining the FullTextEntityManager, unwrapping seems to leave me with the same error? Thanks!
This is typically what happens when using Hibernate Search 5.11.5.Final and below with Spring boot 2.4 / Spring 5.3. See https://github.com/spring-projects/spring-framework/issues/26090
Given the ClassCastException occurs at FullTextSessionImpl.java:62, I suspect you are not actually using Hibernate Search 5.11.7.Final: in Hibernate Search 5.11.7.Final, this line is just an instanceof test. In 5.11.5.Final and below, this line actually is a cast.
I see you set property hibernate.search.version to 5.11.7.Final, but did you use this property anywhere in your POM? Spring Boot doesn't manage the dependency to Hibernate Search, so you need to specify the version yourself in your <dependency> markup.
In my WebSphere 9 application thats built on Spring 5 / Hibernate 5 I get this error when retrieving the current Hibernate session with org.hibernate.SessionFactory.getCurrentSession():
[9/26/19 10:41:22:405 CEST] 0001f184 DefaultMessag W org.springframework.jms.listener.AbstractMessageListenerContainer invokeErrorHandler Execution of JMS message listener failed, and no ErrorHandler has been set.
java.lang.IllegalStateException: An exception occured during the cumulation before the processingId could be created: java.lang.NullPointerException
at com.tsystems.cc4.service.cumulation.CumulationHandlerImpl.cumulate(CumulationHandlerImpl.java:138)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:90)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
at java.lang.reflect.Method.invoke(Method.java:508)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at com.tsystems.cc4.service.cumulation.NonClosingOpenSessionInterceptor.invoke(NonClosingOpenSessionInterceptor.java:40)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy184.cumulate(Unknown Source)
at com.tsystems.cc4.service.cumulation.ScheduleCommandHandler.invoke(ScheduleCommandHandler.java:62)
at com.tsystems.cc4.service.invocation.listener.InvocationWrapperListener.onMessage(InvocationWrapperListener.java:128)
at org.springframework.jms.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:736)
at org.springframework.jms.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:696)
at org.springframework.jms.listener.AbstractMessageListenerContainer.doExecuteListener(AbstractMessageListenerContainer.java:674)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.doReceiveAndExecute(AbstractPollingMessageListenerContainer.java:318)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:245)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1189)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1081)
at javax.enterprise.concurrent.ManagedExecutors$RunnableAdapter.run(ManagedExecutors.java:211)
at com.ibm.ws.asynchbeans.SubmittedTask.run(SubmittedTask.java:711)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1909)
Caused by: java.lang.NullPointerException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:90)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
at java.lang.reflect.Method.invoke(Method.java:508)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:266)
at org.springframework.orm.hibernate5.SessionFactoryUtils.getFlushMode(SessionFactoryUtils.java:125)
at org.springframework.orm.hibernate5.SpringSessionContext.currentSession(SpringSessionContext.java:100)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:456)
at com.tsystems.cc4.control.dao.impl.AbstractHibernateDAO.getSession(AbstractHibernateDAO.java:51)
at com.tsystems.cc4.control.dao.impl.GenericCrudDAOImpl.readInReadOnlyMode(GenericCrudDAOImpl.java:107)
at com.tsystems.cc4.service.cumulation.CumulationHandlerImpl.cumulate(CumulationHandlerImpl.java:120)
... 23 more
[9/26/19 10:41:22:415 CEST] 0001f184 TransactionSy E org.springframework.transaction.support.TransactionSynchronizationUtils triggerBeforeCompletion TransactionSynchronization.beforeCompletion threw exception
java.lang.NullPointerException
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCompletion(SpringSessionSynchronization.java:110)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCompletion(TransactionSynchronizationUtils.java:107)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCompletion(AbstractPlatformTransactionManager.java:935)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:826)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:702)
at org.springframework.jms.listener.AbstractPollingMessageListenerContainer.receiveAndExecute(AbstractPollingMessageListenerContainer.java:251)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.invokeListener(DefaultMessageListenerContainer.java:1189)
at org.springframework.jms.listener.DefaultMessageListenerContainer$AsyncMessageListenerInvoker.run(DefaultMessageListenerContainer.java:1081)
at javax.enterprise.concurrent.ManagedExecutors$RunnableAdapter.run(ManagedExecutors.java:211)
at com.ibm.ws.asynchbeans.SubmittedTask.run(SubmittedTask.java:711)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1909)
The application code successfully ran on Spring 4.3.14.RELEASE + Hibernate 4.2.21.Final.
The problem occurs since we upgraded to Spring 5.1.1.RELEASE + Hibernate 5.2.9.Final.
Spring configuration before (Spring 4.3.14.RELEASE + Hibernate 4.2.21.Final):
<bean id="cumulationConfigDAO" class="com.tsystems.cc4.control.dao.impl.GenericCrudDAOImpl">
<constructor-arg value="com.tsystems.cc4.control.model.cumulation.CumulationConfig" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="controlDataSource" />
<property name="packagesToScan" value="com.tsystems.cc4.control.model" />
<property name="hibernateProperties">
<value>
hibernate.default_schema=CC4
hibernate.dialect=org.hibernate.dialect.${jdbc.dialect}Dialect
org.jboss.logging.provider=slf4j
${hib_tx_platform}
${hib_tx_factory}
</value>
</property>
</bean>
Properties:
# Settings for DB2
jdbc.dialect=DB2
# Hibernate JTA Transaction Platform configuration
hib_tx_platform=hibernate.transaction.jta.platform=org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform
hib_tx_factory=hibernate.transaction.factory_class=org.hibernate.transaction.CMTTransactionFactory
Spring configuration after (Spring 5.1.1.RELEASE + Hibernate 5.2.9.Final):
<bean id="cumulationConfigDAO" class="com.tsystems.cc4.control.dao.impl.GenericCrudDAOImpl">
<constructor-arg name="entityClass" value="com.tsystems.cc4.control.model.cumulation.CumulationConfig" />
<constructor-arg name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="controlDataSource" />
<property name="packagesToScan" value="com.tsystems.cc4.control.model" />
<property name="hibernateProperties">
<value>
hibernate.default_schema=CC4
hibernate.dialect=org.hibernate.dialect.${jdbc.dialect}Dialect
org.jboss.logging.provider=slf4j
${hib_tx_platform}
${hib_tx_factory}
hibernate.id.new_generator_mappings=false
hibernate.allow_update_outside_transaction=true
</value>
</property>
</bean>
Properties:
# Settings for DB2
jdbc.dialect=DB2
# Hibernate JTA Transaction Platform configuration
hib_tx_platform=hibernate.transaction.jta.platform=org.hibernate.service.jta.platform.internal.WebSphereExtendedJtaPlatform
hib_tx_factory=hibernate.transaction.factory_class=org.hibernate.transaction.CMTTransactionFactory
Solved. The reason for the problem was some specialized stuff we had built in former times to work around problems after migrating from Hibernate 3 to 4. The Spring context contained non-standard stuff like this:
<!-- The Hibernate interceptor, which takes care of opening and closing
hibernate session around method calls. -->
<bean id="hibernateInterceptor"
class="com.tsystems.cc4.service.cumulation.NonClosingOpenSessionInterceptor">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
<!-- A proxy with the hibernate interceptor wired in so it can access
the persistent context -->
<bean id="cumulationHandler" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="com.tsystems.cc4.service.cumulation.CumulationHandlerImpl" />
</property>
<property name="proxyInterfaces">
<value>com.tsystems.cc4.service.cumulation.CumulationHandler</value>
</property>
<property name="interceptorNames">
<value>hibernateInterceptor</value>
</property>
</bean>
Removing those beans solved the problem.
Here's from the class comment of NonClosingOpenSessionInterceptor: "Specialized version of org.springframework.orm.hibernate4.support.OpenSessionInterceptor that - if necessary - opens a Hibernate session and registers it with Spring's TransactionSynchronizationManager without closing it after the method call. This is necessary if closing the Hibernate session should occur through the transaction manager during transaction completion.
This class has been created to work around the "org.hibernate.SessionException: Session is closed!" problem that occurs when using org.springframework.orm.hibernate4.support.OpenSessionInterceptor in conjunction with Spring's PlatformTransactionManager."
used #Transactional
to either service Method or Dao Method?
Versions in use:
Spring 4.1.6.RELEASE, Hibernate 4.3.10.Final, Atomikos 3.9.26
We are in the process of upgrading our main webapp to Hibernate 4. We mainly use HibernateTemplate and JdbcTemplate for access to multiple databases (DB2 and Oracle) with Atomikos as JTA-TransactionManager.
The problem: While using only HibernateTemplate or only JdbcTemplates in a single transaction works fine, using JdbcTemplate and HibernateTemplate together in one transaction causes StaleStateExceptions in certain cases.
Here is an example where the problem occurs - the code is wrapped in a TransactionalProxyFactoryBean with PROPAGATION_REQUIRED:
public class MyServiceImpl extends HibernateDaoSupport implements MyService {
private static final Log log = LogFactory.getLog(MyServiceImpl.class);
private JdbcTemplate jdbcTemplate;
#Override
public void execute() {
// save new entity instance with HibernateTemplate
MyEntity e = new MyEntity();
e.setMyProperty("first value");
getHibernateTemplate().save(e);
// use JdbcTemplate to access DB
String sql = "select * from my_table";
getJdbcTemplate().query(sql, new RowCallbackHandler() {
#Override
public void processRow(ResultSet rs) throws SQLException {
// process rows
}
});
// update entity instance with HibernateTemplate
e.setMyProperty("second value");
getHibernateTemplate().saveOrUpdate(e);
// make sure the flush occurs immediately. This is needed in to demonstrate the problem. (Otherwise the property UPDATE would be cached and issued on commit, just after Spring closed the connection used for the JdbcTemplate and the problem would not show)
getHibernateTemplate().flush();
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
Our conclusions: The exception is basically caused by different ways HibernateTemplate and JdbcTemplate accquire and release the database connection.
The HibernateTemplate directly delegates to Hibernate which uses the connection release mode AFTER_STATEMENT (set by Spring if a JtaTransactionManager is provided). This causes Hibernate to get a connection from the Atomikos connection pool, perform the SQL and close its connection which doesn't close the physical connection but returns it to the connection pool.
The JdbcTemplate uses Spring's DataSourceUtils.getConnection(...) to get a connection from the Atomikos connection pool, performs the SQL and calls DataSourceUtils.releaseConnection(...) which itself doesn't call Connection.close(). The connection isn't closed by Spring in DataSourceUtils.releaseConnection(...) (and in consequence not returned to the connection pool) but bound to the thread for reuse in DataSourceUtils.getConnection(...).
So it seems as if in a JTA context, Spring teaches Hibernate to use connection release mode AFTER_STATEMENT (which is also recommeded by Hibernate for JTA) but behaves totally different in it's DataSourceUtils.
In detail, we tracked down the cause like following:
The StaleStateException is thrown because the UPDATE-Statement for setting "second value" at the entity does not affect any row in the database.
This is because the UPDATE-Statement happens on another connection than the INSERT-Statement.
This is because the original connection used by the INSERT-Statement is still considered in use by the connection pool.
This is because close() is never called on the first connection after it was used for the JdbcTemplate.
This is because DataSourceUtils.releaseConnection(...) which is called by the JdbcTemplate when finished doesn't call Connection.close() in a JTA-Transaction-Context.
Things we tried and failed at:
Make Hibernate use AFTER_TRANSACTION or ON_CLOSE as connection release mode - prevented by Spring as SpringJtaSessionContext with it's AFTER_STATEMENT is hardcoded.
Configure Spring close the DB connection on connection release.
What are we doing wrong?
Any configuration we forgot?
Is it a Spring/Hibernate problem at all or should the Atomikos connection pool behave differently by not waiting for a call to Connection.close() before making the connection available again?
Thanks a lot for your help!
Spring context for Hibernate and JTA configuration:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="jtaTransactionManager" ref="transactionManager" />
<property name="hibernateProperties">
<props>
<!-- Stripped down configuration for the toy project to reproduce the problem -->
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.dialect">com.company.DB2Dialect</prop>
<!-- hibernate.transaction.factory_class and hibernate.transaction.jta.platform are implicitly set by setting the jtaTransactionManager property -->
<!-- Properties wie normally use in production
<prop key="hibernate.dialect">com.company.DB2Dialect</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="hibernate.order_inserts">true</prop>
<prop key="hibernate.order_updates">true</prop>
<prop key="hibernate.generate_statistics">false</prop>
<prop key="hibernate.use_outer_join">true</prop>
<prop key="hibernate.jdbc.batch_versioned_data">true</prop>
<prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
<prop key="hibernate.jdbc.batch_size">100</prop> -->
</props>
</property>
<property name="mappingLocations">
<list>
<value>classpath*:**/*.hbm.xml</value>
</list>
</property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRouter"
scope="singleton">
<property name="targetDataSources">
<map>
<entry key="ISOLATION_REPEATABLE_READ" value="java:comp/env/jdbc/wawi_rr" />
<entry key="ISOLATION_READ_UNCOMMITTED" value="java:comp/env/jdbc/wawi_ru" />
<entry key="ISOLATION_READ_COMMITTED" value="java:comp/env/jdbc/wawi_rc" />
<entry key="ISOLATION_SERIALIZABLE" value="java:comp/env/jdbc/wawi_s" />
</map>
</property>
<property name="defaultTargetDataSource" value="java:comp/env/jdbc/wawi" />
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName">
<value>java:comp/env/TransactionManager</value>
</property>
<property name="allowCustomIsolationLevels">
<value>true</value>
</property>
</bean>
Spring context for Service configuration:
<bean id="myService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target">
<ref bean="myServiceTarget" />
</property>
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
</props>
</property>
</bean>
<bean id="myServiceTarget" class="org.example.MyServiceImpl">
<property name="sessionFactory" ref="sessionFactory" />
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<bean id="myMBean" class="org.example.MyMBean">
<property name="myService" ref="myService" />
</bean>
Stacktrace:
org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at org.springframework.orm.hibernate4.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:205)
at org.springframework.orm.hibernate4.HibernateTemplate.doExecute(HibernateTemplate.java:343)
at org.springframework.orm.hibernate4.HibernateTemplate.executeWithNativeSession(HibernateTemplate.java:308)
at org.springframework.orm.hibernate4.HibernateTemplate.flush(HibernateTemplate.java:837)
at org.example.MyServiceImpl.execute(MyServiceImpl.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
at com.sun.proxy.$Proxy13.execute(Unknown Source)
at org.example.MyMBean.execute(MyMBean.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:75)
at sun.reflect.GeneratedMethodAccessor31.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:279)
at javax.management.modelmbean.RequiredModelMBean$4.run(RequiredModelMBean.java:1245)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:76)
at javax.management.modelmbean.RequiredModelMBean.invokeMethod(RequiredModelMBean.java:1239)
at javax.management.modelmbean.RequiredModelMBean.invoke(RequiredModelMBean.java:1077)
at org.springframework.jmx.export.SpringModelMBean.invoke(SpringModelMBean.java:90)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487)
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420)
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
at sun.rmi.transport.Transport$1.run(Transport.java:177)
at sun.rmi.transport.Transport$1.run(Transport.java:174)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:556)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:811)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:670)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:81)
at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:73)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:63)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3281)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
at org.springframework.orm.hibernate4.HibernateTemplate$27.doInHibernate(HibernateTemplate.java:840)
at org.springframework.orm.hibernate4.HibernateTemplate.doExecute(HibernateTemplate.java:340)
... 54 more
I am writing a JTA transaction management module for the Spring application by Atomikos, everything assumed to be properly setup:
<bean id="dataSource_JDBC_01" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName"><value>dataSource01</value></property>
<property name="xaDataSourceClassName"><value>${database_01.xadriver}</value></property>
<property name="xaProperties">
<props>
<prop key="databaseName">${database_01.username}</prop>
<prop key="user">${database_01.username}</prop>
<prop key="password">${database_01.password}</prop>
<prop key="url">${database_01.url}</prop>
</props>
</property>
<property name="poolSize"><value>1</value></property>
</bean>
<jee:jndi-lookup id="dataSource_01" jndi-name="jdbc/DataSource01" default-ref="dataSource_JDBC_01" />
Values to those placeholders are quoted from here:
database_01.xadriver=oracle.jdbc.xa.client.OracleXADataSource
database_01.url=jdbc\:oracle\:thin\:#localhost\:1521\:orcl
database_01.username=USER_01
database_01.password=PASS_01
But Atomikos throw an exception:
no writeable property 'url' in class 'oracle.jdbc.xa.client.OracleXADataSource'
2014-12-11 12:00:23,098 -- WARN -- com.atomikos.jdbc.AtomikosSQLException -- Cannot initialize AtomikosDataSourceBean
com.atomikos.beans.PropertyException: no writeable property 'url' in class
'oracle.jdbc.xa.client.OracleXADataSource'
at com.atomikos.beans.PropertyUtils.getSetter(PropertyUtils.java:286)
at com.atomikos.beans.PropertyUtils.setDirectProperty(PropertyUtils.java:200)
at com.atomikos.beans.PropertyUtils.setProperty(PropertyUtils.java:110)
at com.atomikos.beans.PropertyUtils.setProperties(PropertyUtils.java:186)
Followed by:
javax.naming.NamingException: Another resource already exists with name dataSource01 - pick a different name
I really cannot tell what is wrong here.
Turns out it just a matter of capitalize the name of property URL. From Oracle API document class OracleXADataSource has a setter method as setURL(), so that the bean name should use
<prop key="URL">${database_01.url}</prop>
instead of
<prop key="url">${database_01.url}</prop>
Cheers..
The test below was working in my application with Hibernate3. When I upgraded it to hibernate4, it started failing.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(inheritLocations=false,locations={
"/hibernate/spring-SF-tests.xml",
"/hibernate/spring-transaction.xml",
"/hibernate/testBeans.xml"
,"/hibernate/spring-audit.xml",
"/hibernate/iwrs-mail-beans-test.xml",
"/hibernate/fake-audit-meaning.xml"
})
#TransactionConfiguration(transactionManager="txManager", defaultRollback=true)
public class CodeAuditIntegrationTest extends CodeIntegrationTest {
#Autowired
private SessionFactory auditFactory;
#Before
public void cleanAudit(){
auditFactory.getCurrentSession().createQuery("delete from AuditLogRecord").executeUpdate();
}
#Test
public void clinicalTrialAssociationTest() {
super.clinicalTrialAssociationTest();
}
}
Which is failing with:
org.hibernate.HibernateException: No Session found for current thread
at
org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97)
at
org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:881)
I have 2 different session factories in my app: sessionFactory and auditFactory. Both are configured in spring (see [1]).
The problem is that in an hibernate4 configuration the property exposeTransactionAwareSessionFactory was removed. I had that set to true for my auditFactory. I believe removing this property makes the injected auditFactory not being able to pick up the session in the transaction (as txManger is configured for sessionFactory), therefore yielding the error.
Questions:
how can I make the auditFactory have transactions managed by spring on this test?
Is that what was happening with the exposeTransactionAwareSessionFactory property in hibernate 3?
The only alternative I see is to wrap all the code that uses auditFactory in a Helper class annotated with #Transactional(otherTxManager). I did try that, but I got a couple of additional problems there:
I needed to use a separate DataSource (else I'd get [2])
Using 2 separate datasources I'll get a similar error on my Cucumber tests, now related with the transactionManager [3]
[1] The relavant XML configuration:
<bean id="txManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<bean id="sessionFactory" scope="singleton"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2dll}</prop>
<prop key="hibernate.hbm2ddl.import_files">${hibernate.sql_scripts}</prop>
</props>
</property>
<property name="packagesToScan">...</property>
<property name="annotatedPackages">...</property>
<property name="mappingLocations">...</property>
<property name="dataSource" ref="c3p0DataSource" />
<property name="entityInterceptor" ref="auditInterceptor" />
</bean>
<bean id="auditFactory" scope="singleton"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="exposeTransactionAwareSessionFactory"> -->needs to be removed in hibernate4!
<value>true</value>
</property>
<property name="mappingLocations">...</property>
<property name="packagesToScan">...</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2dll}</prop>
</props>
</property>
<property name="dataSource" ref="c3p0DataSource" />
</bean>
[2] Problem with using the same dataSource for the two factories:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! HibernateTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single HibernateTransactionManager for all transactions on a single DataSource, no matter whether Hibernate or JDBC access.
at org.springframework.orm.hibernate4.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:329)
[3] Problem in cucumber tests due to declaring 2 transaction managers:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single bean but found 2: txAudit,txManager