当前位置: 首页 > 知识库问答 >
问题:

使用spring和spring jdbctemplate的分布式事务

云令
2023-03-14

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

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

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

我是分布式事务的新手。你能帮我做这件事吗?是否可以使用两个datasource而只使用一个TransactionManager?

共有1个答案

黄德明
2023-03-14

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

使用Bitronix事务管理器应该可以做到这一点,但是您还必须使用XA datasource:基于Oracle的实现似乎可以在Oracle的JDBC驱动程序中使用(参见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/,只需确保将datasources属性调整为使用oracle.jdbc.xa.client.oraclexadatasource而不是PostgreSQL。

但是,请注意,XA/分布式事务不是灵丹妙药,不能处理某些类型的问题(例如网络故障);在走那条路之前,你真的应该考虑一下可能的选择。

 类似资料:
  • Spring Boot通过使用Atomikos或Bitronix嵌入式事务管理器支持跨多个XA资源的分布式JTA事务。 部署到合适的Java EE Application Server时,也支持JTA事务。 检测到JTA环境时,Spring的JtaTransactionManager用于管理事务。 自动配置的JMS,DataSource和JPA bean已升级为支持XA事务。 您可以使用标准的Sp

  • 我们计划在单个事务中上传blob中的一些文件和Cosmos数据库中的数据 是否可以在Azure Cosmos DB和Azure blob存储之间实现分布式事务?如果任何操作失败,则还应恢复其他操作。 如果不可能,那么是否有任何理想的方法可以通过任何Azure组件实现此功能?

  • ShardingSphereTransactionManager SPI 名称 详细说明 ShardingSphereTransactionManager 分布式事务管理器 已知实现类 详细说明 XAShardingSphereTransactionManager 基于 XA 的分布式事务管理器 SeataATShardingSphereTransactionManager 基于 Seata 的分

  • ShardingSphere-Proxy 接入的分布式事务 API 同 ShardingSphere-JDBC 保持一致,支持 LOCAL,XA,BASE 类型的事务。 XA 事务 ShardingSphere-Proxy 原生支持 XA 事务,默认的事务管理器为 Atomikos。 可以通过在 ShardingSphere-Proxy 的 conf 目录中添加 jta.properties 来定

  • 通过 Apache ShardingSphere 使用分布式事务,与本地事务并无区别。 除了透明化分布式事务的使用之外,Apache ShardingSphere 还能够在每次数据库访问时切换分布式事务类型。 支持的事务类型包括 本地事务、XA事务 和 柔性事务。可在创建数据库连接之前设置,缺省为 Apache ShardingSphere 启动时的默认事务类型。

  • 背景 数据库事务需要满足 ACID(原子性、一致性、隔离性、持久性)四个特性。 原子性(Atomicity)指事务作为整体来执行,要么全部执行,要么全不执行。 一致性(Consistency)指事务应确保数据从一个一致的状态转变为另一个一致的状态。 隔离性(Isolation)指多个事务并发执行时,一个事务的执行不应影响其他事务的执行。 持久性(Durability)指已提交的事务修改数据会被持久