Atomikos:使用 PostgreSQL 时数据未保存

Atomikos: data not getting saved when using PostgreSQL

我在使用 Atomikos 时遇到了一个奇怪的问题。

我有一个小型测试应用程序 (Spring + Hibernate)。它使用两个不同的数据源,我需要在非 Java EE 容器(在我的例子中是 Tomcat)上测试 JTA 功能。

当我使用 MySQL 作为数据库时,所有内容都可以毫无问题地保存。但是当我切换到 PostgreSQL 时,数据没有保存到数据库中。

有趣的是,如果我不使用 @Transactional 并手动开始和提交事务 - 一切正常。但是当使用 @Transactional - 数据没有被保存。我可以看到 hibernate_sequence table 确实在数据库中得到了更新(数字增加了),只有数据本身没有。所有这一切,尽管我在 Atomikos 日志中看到数据已提交等等。同样的代码同样适用于 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.
Spring版本:4.0.9.
休眠版本: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 设置了 max_prepared_transactions parameter in the postgresql.conf to be at least as large as the max_connections as it was recommended on the related Atomikos documentation page


我想再次强调,相同的代码可以与 MySQL 一起使用。只有在将此 Web 应用程序配置为使用 PostgreSQL 时才会出现此奇怪问题。

有人知道可能是什么问题吗?

解决方案

OK,谜底揭晓!事实证明,我需要在 session 对象上显式调用 flush()

下面的回答给了我一个提示:
Hibernate not saving Object in the Database?


例子

这是一个工作示例(注意 session.flush() 必须在关闭 之前调用 a session):

@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。