简体   繁体   中英

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. For reading and writing I use different datasources through 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. 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.

For the JDBCTemplate I have an abstract class that holds the JDBCTemplate, like this:

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:

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:

    @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. 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:

<?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. 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). On other hand, Checked exceptions that are generated from a transactional method will not trigger auto transaction rollback.

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. 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. 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.

As per my knowledge in EJB we can use @ApplicationException(rollback=true) if there is need to transaction to be rolled back automatically.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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