使用Spring和Spring JDBCTemplate进行分布式事务

时间:2018-08-19 17:46:05

标签: spring jdbc spring-jdbc jdbctemplate spring-transactions

我在oracle中有两个具有两个模式的两个数据源,我正在执行单元测试,但是失败了。我想如果第二笔交易失败,那么它应该回滚第一笔交易。下面是我的代码。

package com.test.db;

import static org.junit.Assert.assertEquals;

import java.util.Date;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;

//@EnableTransactionManagement
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/META-INF/spring/data-source-context.xml")
public class MultipleDatasourceTests {

    private JdbcTemplate jdbcTemplate;
    private JdbcTemplate otherJdbcTemplate;

    @Autowired
    public void setDataSources(@Qualifier("dataSource") DataSource dataSource,
            @Qualifier("otherDataSource") DataSource otherDataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.otherJdbcTemplate = new JdbcTemplate(otherDataSource);
    }

    @BeforeTransaction
    public void clearData() {
        jdbcTemplate.update("delete from T_ORACLE1");
        otherJdbcTemplate.update("delete from T_ORACLE1");
    }

    @AfterTransaction
    public void checkPostConditions() {

        int count = jdbcTemplate.queryForInt("select count(*) from T_ORACLE1");
        // This change was rolled back by the test framework
        assertEquals(0, count);

        count = otherJdbcTemplate.queryForInt("select count(*) from T_ORACLE1");
        // This rolls back as well if the connections are managed together
        assertEquals(0, count);

    }


    /**
     * Vanilla test case for two inserts into two data sources. Both should roll
     * back.
     * 
     * @throws Exception
     */
    @Transactional
    @Test
    public void testInsertIntoTwoDataSources() throws Exception {
        jdbcTemplate.update("delete from T_ORACLE1");
        otherJdbcTemplate.update("delete from T_ORACLE2");

        int count = jdbcTemplate.update(
                "INSERT into T_ORACLE1 (id,name,foo_date) values (?,?,null)", 0,
                "foo");
        assertEquals(1, count);

        count = otherJdbcTemplate
                .update(
                        "INSERT into T_ORACLE2 (id,operation,name,audit_date) values (?,?,?,?)",
                        1, "INSERT", "foo", new Date());
        assertEquals(1, count);

    }

    /**
     * Shows how to check the operation on the inner data source to see if it
     * has already been committed, and if it has do something different, instead
     * of just hitting a {@link DataIntegrityViolationException}.
     * 
     * @throws Exception
     */
    @Transactional
    @Test
    public void testInsertWithCheckForDuplicates() throws Exception {


        int count = jdbcTemplate.update(
                "INSERT into T_ORACLE1 (id,name,foo_date) values (?,?,null)", 0,
                "foo");
        assertEquals(1, count);

        count = otherJdbcTemplate.update(
                        "UPDATE T_ORACLE2 set operation=?, name=?, audit_date=? where id=?",
                        "UPDATE", "foo", new Date(), 0);

        if (count == 0) {
            count = otherJdbcTemplate.update(
                            "INSERT into T_ORACLE2 (id,operation,name,audit_date) values (?,?,?,?)",
                            0, "INSERT", "foo", new Date());
        }

        assertEquals(1, count);

    }
}

XML

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
    http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.test.*"/>

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        <property name="url" value="jdbc:oracle:thin:@//localhost:1521/TESTDB1" />
        <property name="username" value="ORACLE1"/>
        <property name="password" value="ORACLE1"/>
    </bean>

    <bean id="otherDataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
        <property name="url" value="jdbc:oracle:thin:@//localhost:1521/TESTDB2" />
        <property name="username" value="ORACLE2"/>
        <property name="password" value="ORACLE2"/>
    </bean>

    <bean id="transactionManager" class="com.test.db.MultiTransactionManager">
        <property name="transactionManagers">
            <list>
                <bean
                    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                    <property name="dataSource" ref="dataSource" />
                </bean>
                <bean
                    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                    <property name="dataSource" ref="otherDataSource" />
                </bean>
            </list>
        </property>
    </bean>

</beans>


package com.test.db;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;


public class MultiTransactionManager extends
        AbstractPlatformTransactionManager {

    private List<PlatformTransactionManager> transactionManagers = new ArrayList<PlatformTransactionManager>();
    private ArrayList<PlatformTransactionManager> reversed;

    public void setTransactionManagers(
            List<PlatformTransactionManager> transactionManagers) {
        this.transactionManagers = transactionManagers;
        reversed = new ArrayList<PlatformTransactionManager>(
                transactionManagers);
        Collections.reverse(reversed);
    }

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition)
            throws TransactionException {
        @SuppressWarnings("unchecked")
        List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) transaction;
        for (PlatformTransactionManager transactionManager : transactionManagers) {
            DefaultTransactionStatus element = (DefaultTransactionStatus) transactionManager
                    .getTransaction(definition);
            list.add(0, element);
        }
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status)
            throws TransactionException {
        @SuppressWarnings("unchecked")
        List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) status
                .getTransaction();
        int i = 0;
        for (PlatformTransactionManager transactionManager : reversed) {
            TransactionStatus local = list.get(i++);
            try {
                transactionManager.commit(local);
            } catch (TransactionException e) {
                logger.error("Error in commit", e);
                // Rollback will ensue as long as rollbackOnCommitFailure=true
                throw e;
            }
        }
    }

    @Override
    protected Object doGetTransaction() throws TransactionException {
        return new ArrayList<DefaultTransactionStatus>();
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status)
            throws TransactionException {
        @SuppressWarnings("unchecked")
        List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) status
                .getTransaction();
        int i = 0;
        TransactionException lastException = null;
        for (PlatformTransactionManager transactionManager : reversed) {
            TransactionStatus local = list.get(i++);
            try {
                transactionManager.rollback(local);
            } catch (TransactionException e) {
                // Log exception and try to complete rollback 
                lastException = e;
                logger.error("Error in rollback", e);
            }
        }
        if (lastException!=null) {
            throw lastException;
        }
    }

}

我的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.springsource.open</groupId>
    <artifactId>spring-best-db-db</artifactId>
    <version>2.0.0.CI-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Spring Best Efforts DB-DB</name>
    <description><![CDATA[Sample project showing multi DataSource transaction 
processing with Spring using best efforts 1PC.
]]> </description>
    <properties>
        <maven.test.failure.ignore>true</maven.test.failure.ignore>
        <spring.framework.version>4.1.4.RELEASE</spring.framework.version>
    </properties>
    <profiles>
        <profile>
            <id>strict</id>
            <properties>
                <maven.test.failure.ignore>false</maven.test.failure.ignore>
            </properties>
        </profile>
        <profile>
            <id>fast</id>
            <properties>
                <maven.test.skip>true</maven.test.skip>
            </properties>
        </profile>
    </profiles>
    <dependencies>
        <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1</version>
            <exclusions>
                <exclusion>
                    <groupId>avalon-framework</groupId>
                    <artifactId>avalon-framework</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>logkit</groupId>
                    <artifactId>logkit</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derby</artifactId>
            <version>10.2.1.6</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.9</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>2.4</version>
            <scope>test</scope>
        </dependency>
        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.framework.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.framework.version}</version>
        </dependency>

        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>12.1.0.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.github.marcus-nl.btm/btm-spring -->
        <dependency>
            <groupId>com.github.marcus-nl.btm</groupId>
            <artifactId>btm-spring</artifactId>
            <version>3.0.0-mk1</version>
        </dependency>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>6.0</version>
        </dependency>
    </dependencies>

</project>

TABLE IN ORACLE1 database
create table T_ORACLE1 (
    id integer not null primary key,
    name varchar(80),
    foo_date timestamp
);

ORACLE2数据库中的表

create table T_ORACLE2 (
    id integer not null primary key,
    operation varchar(20),
    name varchar(80),
    audit_date timestamp
);

我用Google搜索使用BitronixTransactionManager,但没有任何线索来配置两个数据源。

似乎我们可以使用休眠模式,但是我不想使用休眠模式。我想要带sql查询的jdbctemplate或简单的jdbc。

我得到的错误是

java.lang.IllegalStateException:无法激活事务同步-已激活     在org.springframework.transaction.support.TransactionSynchronizationManager.initSynchronization(TransactionSynchronizationManager.java:270)

我是分布式交易的新手。您能帮我吗?可以使用两个数据源,但可以使用一个事务管理器吗?

1 个答案:

答案 0 :(得分:1)

如果要实现分布式事务,则必须使用XA数据源,并且事务管理器也必须支持XA事务。

应该使用Bitronix事务管理器来执行此操作,但是您也必须使用XA数据源:Oracle的JDBC驱动程序中似乎可以使用基于Oracle的实现(请参见https://docs.oracle.com/cd/E17904_01/web.1111/e13731/thirdpartytx.htm#WLJTA266)。

您可以在此处找到Bitronix的Spring配置示例:https://www.snip2code.com/Snippet/652599/Example-distributed-XA-transaction-confi/,只需确保调整数据源属性以使用oracle.jdbc.xa.client.OracleXADataSource而不是PostgreSQL。

但是请注意,XA /分布式事务并不是灵丹妙药,也无法解决某些类型的问题(例如,网络故障);您应该真正走这条路。