简体   繁体   English

以编程方式切换到JDBCTemplate时,Spring事务不会回滚

[英]Spring transaction doesn't rollback when switching to JDBCTemplate programmatically

I have this use case in which I need to get the data from one Oracle schema and insert them to another schema, table by table. 我有一个用例,其中我需要从一个Oracle模式中获取数据并将它们按表插入到另一个模式中。 For reading and writing I use different datasources through JDBCTemplate. 为了进行读写,我通过JDBCTemplate使用了不同的数据源。 The switching between them is done within the code. 它们之间的切换是在代码中完成的。 Additionally I have a Hibernate connection, that I use to read data from configuration tables. 另外,我有一个Hibernate连接,用于从配置表中读取数据。 This is also my default connection, the one that is set through autowiring when the application starts. 这也是我的默认连接,它是在应用程序启动时通过自动装配设置的。 I am using Spring 4, Hibernate 4.3 and Oracle 11. 我正在使用Spring 4,Hibernate 4.3和Oracle 11。

For the JDBCTemplate I have an abstract class that holds the JDBCTemplate, like this: 对于JDBCTemplate,我有一个包含JDBCTemplate的抽象类,如下所示:

public abstract class GenericDao implements SystemChangedListener {

    private NamedParameterJdbcTemplate jdbcTemplate;    
    /**
     * Initializing the bean with the definition data source through @Autowired
     * @param definitionDataSource as instance of @DataSource
     */

    @Autowired
    private void setDataSource(DataSource definitionDataSource) {
        this.jdbcTemplate = new NamedParameterJdbcTemplate(definitionDataSource);
    }

    public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate(){
        return this.jdbcTemplate;
    }

    @Override
    public void updateDataSource(DataSource dataSource) {
        this.setDataSource(dataSource);
    }
}

The interface SystemChangedListener defines the updateDataSource method which is called, when the DataSource is switched through a Service method, like this: 当通过Service方法切换DataSource时,接口SystemChangedListener定义了updateDataSource方法,如下所示:

public class SystemServiceImpl implements SystemService, SystemChangable {

    private List<GenericDao> daoList;


    @Autowired
    public void setDaoList(final List<GenericDao> daoList){
        this.daoList = daoList; 
    }

    @Override
    public void notifyDaos(SystemDTO activeSystem) {
        logger.debug("Notifying DAO of change in datasource...");
        for(GenericDao dao : this.daoList){
            dao.updateDataSource(activeSystem.getDataSource());
        }
        logger.debug("...done.");
    }

@Override
public Boolean switchSystem(final SystemDTO toSystem) {
    logger.info("Switching active system...");
    notifyDaos(toSystem);   
    logger.info("Active system and datasource switched to: " + toSystem.getName());
    return true;
}

}

The switching works perfectly for reading so far. 到目前为止,该开关非常适合阅读。 I can switch between schemas with no problem, but if for some reason during the copying I get an exception the transaction doesn't get rolled back. 我可以毫无问题地在模式之间进行切换,但是如果由于某种原因在复制过程中出现异常,则事务不会回滚。

This is my copyint method: 这是我的copyint方法:

    @Transactional(rollbackFor = RuntimeException.class, propagation=Propagation.REQUIRED)
    public void replicateSystem(String fromSystem, String toSystem) throws ApplicationException {

        // FIXME: pass the user as information
        // TODO: actually the method should take some model from the view and transform it in DTOs and stuff

        StringBuffer protocolMessageBuf = new StringBuffer();
        ReplicationProtocolEntryDTO replicationDTO = new ReplicationProtocolEntryDTO();
        String userName = "xxx";
        Date startTimeStamp = new Date();

        try {
            replicationStatusService.markRunningReplication();

            List<ManagedTableReplicationDTO> replications = retrieveActiveManageTableReplications(fromSystem, toSystem);
            protocolMessageBuf.append("Table count: ");
            protocolMessageBuf.append(replications.size());
            protocolMessageBuf.append(". ");            

            for (ManagedTableReplicationDTO repDTO : replications) {
                protocolMessageBuf.append(repDTO.getTableToReplicate());
                protocolMessageBuf.append(": ");

                logger.info("Switching to source system: " + repDTO.getSourceSystem());
                SystemDTO system = systemService.retrieveSystem(repDTO.getSourceSystem());
                systemService.switchSystem(system);

                ManagedTableDTO managedTable = managedTableService.retrieveAllManagedTableData(repDTO.getTableToReplicate());
                protocolMessageBuf.append(managedTable.getRows() != null ? managedTable.getRows().size() : null);
                protocolMessageBuf.append("; ");
                ManagedTableUtils managedTableUtils = new ManagedTableUtils();

                List<String> inserts = managedTableUtils.createTableInserts(managedTable);

                logger.info("Switching to target system: " + repDTO.getSourceSystem());
                SystemDTO targetSystem = systemService.retrieveSystem(repDTO.getTargetSystem());
                systemService.switchSystem(targetSystem);

                // TODO: what about constraints? foreign keys
                logger.info("Cleaning up data in target table: " + repDTO.getTargetSystem());

                managedTableService.cleanData(repDTO.getTableToReplicate());

                /*
                managedTableDao.deleteContents(repDTO.getTableToReplicate());
                */
                // importing the data
                managedTableService.importData(inserts);
                /*
                for (String insrt : inserts) {
                    managedTableDao.executeSqlInsert(insrt);
                }       
*/
                protocolMessageBuf.append("Replication successful.");
            }
        } catch (ApplicationException ae) {
            protocolMessageBuf.append("ERROR: ");
            protocolMessageBuf.append(ae.getMessage());
            throw new RuntimeException("Error replicating a table. Rollback.");
        } finally {
            replicationDTO = this.prepareProtocolRecord(userName, startTimeStamp, protocolMessageBuf.toString(), fromSystem, toSystem);
            replicationProtocolService.writeProtocolEntry(replicationDTO);
            replicationStatusService.markFinishedReplication();
        }
    }

What I do is, I retrieve a list with tables whose content should be copied and in a loop, generate insert statements for them, delete the contents of the target table and execute the inserts with 我要做的是,检索包含要复制其内容的表的列表,并循环执行,为它们生成插入语句,删除目标表的内容,然后使用

public void executeSqlInsert(String insert) throws DataAccessException {
        getNamedParameterJdbcTemplate().getJdbcOperations().execute(insert);    
}

In this the correct DataSource is used - the DataSource of the target system. 在这种情况下,将使用正确的数据源-目标系统的数据源。 When, for instance there's an SQLException somwhere during insertion of the data, the deleting of the data is still committed and the data of the target table get lost. 例如,当在数据插入期间出现SQLException异常时,仍然会删除数据并丢失目标表的数据。 I have no problem with getting exceptions. 我没有异常的问题。 In fact this is part of the requirement - all the exceptions should get protocolled and the whole copying process must be rolled back if there are exceptions. 实际上,这是要求的一部分-所有异常都应进行协议化,如果有异常,则必须回滚整个复制过程。

Here's my db.xml: 这是我的db.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:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
       http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!-- Scans within the base package of the application for @Components to configure as beans -->
    <bean id="placeholderConfig"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:/db.properties" />
    </bean>

     <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
            p:packagesToScan="de.telekom.cldb.admin"
            p:dataSource-ref="dataSource"
            p:jpaPropertyMap-ref="jpaPropertyMap"
            p:jpaVendorAdapter-ref="hibernateVendor" />

    <bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="showSql" value="true" />
        <property name="generateDdl" value="true" />
        <property name="databasePlatform" value="${db.dialect}" />
    </bean>

    <!-- system 'definition' data source -->
    <bean id="dataSource"
          class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="${db.driver}"
          p:url="${db.url}"
          p:username="${db.username}"
          p:password="${db.password}" />
          <!-- 
          p:maxActive="${dbcp.maxActive}"
          p:maxIdle="${dbcp.maxIdle}"
          p:maxWait="${dbcp.maxWait}"/>
           -->

    <util:map id="jpaPropertyMap">
        <entry key="generateDdl" value="false"/>
        <entry key="hibernate.hbm2ddl.auto" value="validate"/>
        <entry key="hibernate.dialect" value="${db.dialect}"/>
        <entry key="hibernate.default_schema" value="${db.schema}"/>
        <entry key="hibernate.format_sql" value="false"/>
        <entry key="hibernate.show_sql" value="true"/>
    </util:map>


    <tx:annotation-driven transaction-manager="transactionManager" />

    <!-- supports both JDBCTemplate connections and JPA -->
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

So my problem is that the transaction isn't rolled back. 所以我的问题是交易不会回滚。 And also, I don't see any clues in the log file, that a trnsaction is started at all. 而且,在日志文件中我看不到任何线索,表明根本就没有启动trnsaction。 What am I doing wrong? 我究竟做错了什么?

Thank you for the help! 感谢您的帮助! al

As I said in my comment, by default,spring framework mark a transaction for rollback in the case of runtime ie unchecked exceptions (any exception that is an subclass of RuntimeException also included in this). 就像我在评论中说的那样,默认情况下,spring框架在运行时(即未经检查的异常)(任何包含RuntimeException的子类的异常也包括在其中)中将事务标记为回滚。 On other hand, Checked exceptions that are generated from a transactional method will not trigger auto transaction rollback. 另一方面,从事务方法生成的Checked异常不会触发自动事务回滚。

Why? 为什么? It's simple, As we learned checked exceptions are necessary(must) for handling or throwing out. 很简单,因为我们了解到,检查异常对于处理或抛出异常是必需的。 so as you did, throwing the checked exception out of the transactional method will tell spring framework that (this thrown exception is occurred and) you know what you're doing, resulting framework skip rollback part. 因此,就像您所做的那样,将已检查的异常从事务方法中抛出将告诉Spring框架(发生此抛出的异常,并且)您知道自己在做什么,从而导致框架跳过回滚部分。 In case of unchecked exception it's considered as a bug or a bad exception handling, so transaction is rolled back to avoid data corruption. 如果发生未经检查的异常,则将其视为错误或不良的异常处理,因此应回滚事务以避免数据损坏。

According to your code of replicateSystem method where you have have checked for ApplicationException , ApplicationException do not trigger automatic rollback. 根据已检查ApplicationExceptionreplicateSystem方法的代码, ApplicationException不会触发自动回滚。 because when the exception is occur the client (application) has an opportunity to recover. 因为发生异常时,客户端(应用程序)有机会恢复。

According to Docs application exceptions are that do not extend RuntimeException. 根据Docs,应用程序异常是不会扩展RuntimeException的。

As per my knowledge in EJB we can use @ApplicationException(rollback=true) if there is need to transaction to be rolled back automatically. 据我对EJB的了解,如果需要自动回滚事务,则可以使用@ApplicationException(rollback=true)

I'm not sure? 我不确定? but I think the problem in this point 但我认为这是问题所在

// TODO: what about constraints? foreign keys

            logger.info("Cleaning up data in target table: " +   repDTO.getTargetSystem());
            managedTableService.cleanData(repDTO.getTableToReplicate());

If the clearing of tables goes throw trunc some_table then at this point Oracle commit transaction. 如果清除表时会抛出trunc some_table那么此时Oracle提交事务。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM