代码之家  ›  专栏  ›  技术社区  ›  Passionate developer

使用spring和springjdbctemplate的分布式事务

  •  2
  • Passionate developer  · 技术社区  · 8 年前

    我在oracle中有两个数据源和两个模式,我正在执行unittest,但它失败了。如果第二个事务失败,它应该回滚第一个事务。下面是我的代码。

    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;
            }
        }
    
    }
    

    <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
    );
    

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

    我在google上使用了BitronixTransactionManager,但是没有得到任何关于如何配置两个数据源的线索。

    似乎我们可以使用hibernate,但是,我不想使用hibernate。我想要一个带有sql查询的jdbctemplate或者简单的jdbc。

    我得到的错误是

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

    1 回复  |  直到 8 年前
        1
  •  1
  •   Guillaume G.    8 年前

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

    使用Bitronix事务管理器应该可以做到这一点,但是您还必须使用XA datasource:在Oracle的JDBC驱动程序(cf。 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。

    推荐文章