使用Atomikos时遇到了一个奇怪的问题.
我有一个小测试应用程序(Spring Hibernate).它使用两个不同的数据源,我需要在非Java EE容器上测试JTA功能(在我的例子中是Tomcat).
当我使用MySQL作为数据库时,一切都得到保存而没有问题.但是当我切换到PostgreSQL数据时却没有保存到数据库中.
有趣的是,如果我没有使用@Transactional并手动开始和提交事务 – 一切正常.但是当使用@Transactional时 – 数据没有被保存.我可以看到hibernate_sequence表在数据库中得到了更新(数字增加了),而只有数据本身没有.尽管事实上我在Atomikos日志中看到数据已经提交等等,所以这一切再次使用SAME代码可以正常使用MySQL.仅在使用PostgreSQL时才会出现此问题.
我已经在PostgreSQL版本9.4(Linux上为64位)和9.5(Windows 10上为64位)以及使用不同的PostgreSQL JDBC driver versions(JDBC4和JDBC3)上进行了测试.
测试的Atomikos版本:4.0.4(截至目前)和3.9.3.
春季版:4.0.9.
Hibernate版本:3.6.10.Final.
注意
我需要那些确切的Spring和Hibernate版本来模拟我需要Atomikos集成的工作应用程序.
配置和代码示例
这是我的spring.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<tx:annotation-driven />
<tx:jta-transaction-manager />
<context:component-scan base-package="com.byteslounge.spring.tx.service.impl" />
<context:component-scan base-package="com.byteslounge.spring.tx.servlet" />
<context:component-scan base-package="com.byteslounge.spring.tx.entity" />
<bean id="sessionFactory1" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedClasses">
<list>
<value>com.byteslounge.spring.tx.entity.TableOne</value>
</list>
</property>
<property name="dataSource" ref="dataSource1" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.connection.autocommit">false</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.current_session_context_class">jta</prop>
<prop key="javax.persistence.transactionType">jta</prop>
<prop key="hibernate.transaction.factory_class">com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory </prop>
<prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop>
</props>
</property>
</bean>
<bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName" value="DataSource1" />
<property name="xaDataSource" ref="dataBase1" />
<property name="maxPoolSize" value="20" />
<property name="minPoolSize" value="10"/>
</bean>
<bean id="dataBase1" class="org.postgresql.xa.PGXADataSource" lazy-init="true">
<property name="user" value="tester1" />
<property name="password" value="123456" />
<property name="serverName" value="localhost" />
<property name="portNumber" value="5432" />
<property name="databaseName" value="test_db1" />
<property name="url" value="jdbc:postgresql://localhost:5432/test_db1" />
</bean>
<bean id="sessionFactory2" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="annotatedClasses">
<list>
<value>com.byteslounge.spring.tx.entity.TableTwo</value>
</list>
</property>
<property name="dataSource" ref="dataSource2" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.connection.autocommit">false</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.current_session_context_class">jta</prop>
<prop key="javax.persistence.transactionType">jta</prop>
<prop key="hibernate.transaction.factory_class">com.atomikos.icatch.jta.hibernate3.AtomikosJTATransactionFactory </prop>
<prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop>
</props>
</property>
</bean>
<bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close">
<property name="uniqueResourceName" value="DataSource2" />
<property name="xaDataSource" ref="dataBase2" />
<property name="maxPoolSize" value="20" />
<property name="minPoolSize" value="10"/>
</bean>
<bean id="dataBase2" class="org.postgresql.xa.PGXADataSource" lazy-init="true">
<property name="user" value="tester2" />
<property name="password" value="123456" />
<property name="serverName" value="localhost" />
<property name="portNumber" value="5432" />
<property name="databaseName" value="test_db2" />
<property name="url" value="jdbc:postgresql://localhost:5432/test_db2" />
</bean>
<!-- Atomikos -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close">
<property name="forceShutdown" value="false" />
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
<property name="transactionTimeout" value="3000" />
</bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" depends-on="atomikosTransactionManager,atomikosUserTransaction">
<property name="transactionManager" ref="atomikosTransactionManager" />
<property name="userTransaction" ref="atomikosUserTransaction" />
<property name="allowCustomIsolationLevels" value="true" />
</bean>
<!-- DAO -->
<bean id="tableOneDao" class="com.byteslounge.spring.tx.dao.impl.TableOneDaoImpl">
<property name="sessionFactory" ref="sessionFactory1" />
</bean>
<bean id="tableTwoDao" class="com.byteslounge.spring.tx.dao.impl.TableTwoDaoImpl">
<property name="sessionFactory" ref="sessionFactory2" />
</bean>
</beans>
这是一个代码示例,用于说明如何保存数据:
@Transactional(rollbackFor=Exception.class)
public void persist(TableOne tableOne, TableTwo tableTwo) throws Exception {
tableOneDao.save(tableOne);
tableTwoDao.save(tableTwo);
}
DAO类具有相同的保存逻辑:
@Service
public class TableOneDaoImpl extends HibernateDaoSupport implements TableOneDao {
@Override
public void save(TableOne tableOne) throws Exception {
Session session = null;
try {
session = getSessionFactory().openSession();
session.save(tableOne);
} catch (Exception e) {
throw new Exception("Could not save tableOne!", e);
} finally {
if (session != null) {
session.close();
}
}
}
}
并且听到我在执行数据保存逻辑时得到的日志:
13:48:58.521 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.imp.CompositeTransactionManagerImp - createCompositeTransaction ( 10000 ): created new ROOT transaction with id 192.168.50.67.tm147946973850200001
13:48:58.605 [http-bio-8080-exec-3] DEBUG org.hibernate.impl.SessionImpl - opened session at timestamp: 14794697385
13:48:58.610 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.imp.CompositeTransactionImp - registerSynchronization ( com.atomikos.icatch.jta.Sync2Sync@57e48ad3 ) for transaction 192.168.50.67.tm147946973850200001
13:48:58.610 [http-bio-8080-exec-3] DEBUG org.hibernate.jdbc.JDBCContext - successfully registered Synchronization
13:48:58.612 [http-bio-8080-exec-3] DEBUG org.hibernate.jdbc.AbstractBatcher - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
13:48:58.612 [http-bio-8080-exec-3] DEBUG org.hibernate.jdbc.ConnectionManager - opening JDBC connection
13:48:58.612 [http-bio-8080-exec-3] DEBUG com.atomikos.jdbc.AbstractDataSourceBean - AtomikosDataSoureBean 'DataSource1': getConnection()...
13:48:58.612 [http-bio-8080-exec-3] INFO com.atomikos.jdbc.AbstractDataSourceBean - AtomikosDataSoureBean 'DataSource1': init...
13:48:58.613 [http-bio-8080-exec-3] DEBUG org.hibernate.SQL - select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
13:48:58.616 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.imp.CompositeTransactionImp - addParticipant ( XAResourceTransaction: 3139322E3136382E35302E36372E746D313437393436393733383530323030303031:3139322E3136382E35302E36372E746D31 ) for transaction 192.168.50.67.tm147946973850200001
13:48:58.616 [http-bio-8080-exec-3] DEBUG com.atomikos.datasource.xa.XAResourceTransaction - XAResource.start ( 3139322E3136382E35302E36372E746D313437393436393733383530323030303031:3139322E3136382E35302E36372E746D31 , XAResource.TMNOFLAGS ) on resource DataSource1 represented by XAResource instance org.postgresql.xa.PGXAConnection@7b57a36a
13:48:58.617 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.imp.CompositeTransactionImp - registerSynchronization ( com.atomikos.jdbc.AtomikosConnectionProxy$JdbcRequeueSynchronization@ede1e016 ) for transaction 192.168.50.67.tm147946973850200001
13:48:58.617 [http-bio-8080-exec-3] DEBUG com.atomikos.jdbc.AtomikosConnectionProxy - atomikos connection proxy for Pooled connection wrapping physical connection org.postgresql.jdbc3g.Jdbc3gConnection@2a39bdf5: calling prepareStatement(select nextval ('hibernate_sequence'))...
13:48:58.627 [http-bio-8080-exec-3] DEBUG org.hibernate.id.SequenceGenerator - Sequence identifier generated: BasicHolder[java.lang.Integer[43]]
13:48:58.627 [http-bio-8080-exec-3] DEBUG org.hibernate.jdbc.AbstractBatcher - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
13:48:58.627 [http-bio-8080-exec-3] DEBUG org.hibernate.jdbc.ConnectionManager - aggressively releasing JDBC connection
13:48:58.627 [http-bio-8080-exec-3] DEBUG org.hibernate.jdbc.ConnectionManager - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
13:48:58.627 [http-bio-8080-exec-3] DEBUG com.atomikos.jdbc.AtomikosConnectionProxy - atomikos connection proxy for Pooled connection wrapping physical connection org.postgresql.jdbc3g.Jdbc3gConnection@2a39bdf5: isClosed()...
13:48:58.627 [http-bio-8080-exec-3] DEBUG com.atomikos.jdbc.AtomikosConnectionProxy - atomikos connection proxy for Pooled connection wrapping physical connection org.postgresql.jdbc3g.Jdbc3gConnection@2a39bdf5: calling getWarnings...
13:48:58.627 [http-bio-8080-exec-3] DEBUG com.atomikos.jdbc.AtomikosConnectionProxy - atomikos connection proxy for Pooled connection wrapping physical connection org.postgresql.jdbc3g.Jdbc3gConnection@2a39bdf5: calling clearWarnings...
13:48:58.627 [http-bio-8080-exec-3] DEBUG com.atomikos.jdbc.AtomikosConnectionProxy - atomikos connection proxy for Pooled connection wrapping physical connection org.postgresql.jdbc3g.Jdbc3gConnection@2a39bdf5: close()...
13:48:58.627 [http-bio-8080-exec-3] DEBUG com.atomikos.datasource.xa.XAResourceTransaction - XAResource.end ( 3139322E3136382E35302E36372E746D313437393436393733383530323030303031:3139322E3136382E35302E36372E746D31 , XAResource.TMSUCCESS ) on resource DataSource1 represented by XAResource instance org.postgresql.xa.PGXAConnection@7b57a36a
13:48:58.628 [http-bio-8080-exec-3] DEBUG org.hibernate.event.def.AbstractSaveEventListener - generated identifier: 43, using strategy: org.hibernate.id.SequenceGenerator
13:48:58.638 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.jta.Sync2Sync - beforeCompletion() called on Synchronization: org.hibernate.transaction.synchronization.HibernateSynchronizationImpl@3aa81ddb
13:48:58.638 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.imp.CompositeTransactionImp - commit() done (by application) of transaction 192.168.50.67.tm147946973850200001
13:48:58.641 [http-bio-8080-exec-3] DEBUG com.atomikos.datasource.xa.XAResourceTransaction - XAResource.commit ( 3139322E3136382E35302E36372E746D313437393436393733383530323030303031:3139322E3136382E35302E36372E746D31 , true ) on resource DataSource1 represented by XAResource instance org.postgresql.xa.PGXAConnection@7b57a36a
13:48:58.643 [http-bio-8080-exec-3] DEBUG com.atomikos.icatch.jta.Sync2Sync - afterCompletion ( STATUS_COMMITTED ) called on Synchronization: org.hibernate.transaction.synchronization.HibernateSynchronizationImpl@3aa81ddb
我还将postgresql.conf中的max_prepared_transactions参数设置为至少与max_connections一样大,就像在PostgreSQL的相关Atomikos documentation page上推荐的那样.
我想再次强调,相同的代码适用于MySQL.只有在配置此Web应用程序以使用PostgreSQL时才会出现这个奇怪的问题.
谁知道什么可能是一个问题?
解决方法:
解
好的,神秘解决了!事实证明,我需要在会话对象上显式调用flush().
以下答案给了我一个提示:
Hibernate not saving Object in the Database?
例
这是一个工作示例(注意必须在关闭会话之前调用session.flush()):
@Service
public class TableOneDaoImpl extends HibernateDaoSupport implements TableOneDao {
@Override
public void save(TableOne tableOne) throws Exception {
Session session = null;
try {
session = getSessionFactory().openSession();
session.save(tableOne);
session.flush();
} catch (Exception e) {
throw new Exception("Could not save tableOne!", e);
} finally {
if (session != null) {
session.close();
}
}
}
}
再次,当使用MySQL时,没有明确的刷新就可以正常工作.
有趣的观察
上面提到的测试Web应用程序使用了纯Hibernate配置和Hibernate的SessionFactory(即没有persistence.xml和JPA entityManager).但我也有相同的测试应用程序配置为使用JPA.在该应用程序中,一切都可以正常工作,而无需使用MySQL和PostgreSQL进行明确的刷新.