在springboot中利用JTA做多数据源的事物回滚

JTA 实现原理
很多开发人员都会对 JTA 的内部工作机制感兴趣:我编写的代码没有任何与事务资源(如数据库连接)互动的代码,但是我的操作(数据库更新)却实实在在的被包含在了事务中,那 JTA 究竟是通过何种方式来实现这种透明性的呢? 要理解 JTA 的实现原理首先需要了解其架构:它包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager ) 两部分, 我们可以将资源管理器看做任意类型的持久化数据存储;事务管理器则承担着所有事务参与单元的协调与控制。 根据所面向对象的不同,我们可以将 JTA 的事务管理器和资源管理器理解为两个方面:面向开发人员的使用接口(事务管理器)和面向服务提供商的实现接口(资源管理器)。其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序, 在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调者两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源,其架构如下图所示:

注意这里的druid用1.1.9

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.9</version>
        </dependency>
        <!-- JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
配置第一个数据源
import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
 
 
@Configuration
// // 扫描 Mapper 接口并容器管理
@MapperScan(value ="mapper所在包路径" ,sqlSessionFactoryRef = "moviesSqlSessionFactory")
public class MoviesDataSourceConfig {
 
 
    @Bean(name = "moviesDataSource")
    @Primary
    public DataSource masterDataSource() {
        DruidXADataSource druidXADataSource = new DruidXADataSource();
        druidXADataSource.setUrl("jdbc:mysql://localhost:3306/movies?useUnicode=true&characterEncoding=UTF8&useSSL=false");
        druidXADataSource.setUsername("root");
        druidXADataSource.setPassword("");
 
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(druidXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("moviesDataSource");
        atomikosDataSourceBean.setPoolSize(5);
 
        return atomikosDataSourceBean;
    }
 
    /*
    * 使用这个来做总事务 后面的数据源就不用设置事务了
    * */
    @Bean(name = "transactionManager")
    @Primary
    public JtaTransactionManager regTransactionManager () {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }
 
    @Bean(name = "moviesSqlSessionFactory")
    @Primary
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("moviesDataSource") DataSource masterDataSource)
        throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(masterDataSource);
        return sessionFactory.getObject();
    }
 
 
 
}
注意:不管有多少个数据源只要配置一个 JtaTransactionManager。

还有 DataSource里用的是DruidXADataSource ,而后注册到AtomikosDataSourceBean并且返回。

配置第二个数据源
import com.alibaba.druid.pool.xa.DruidXADataSource;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
import javax.sql.DataSource;
 
@Configuration
// // 扫描 Mapper接口
@MapperScan(value ="com.sunsharing.mapper" ,sqlSessionFactoryRef = "teacherSqlSessionFactory")
public class TeacherDataSourceConfig {
 
 
    @Bean(name = "teacherDataSource")
    public DataSource masterDataSource() {
        DruidXADataSource druidXADataSource = new DruidXADataSource();
        druidXADataSource.setUrl("jdbc:mysql://localhost:3306/reactstu?useUnicode=true&characterEncoding=UTF8&useSSL=false");
        druidXADataSource.setUsername("root");
        druidXADataSource.setPassword("");
 
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(druidXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("teacherDataSource");
        atomikosDataSourceBean.setPoolSize(5);
        return atomikosDataSourceBean;
    }
 
 
    @Bean(name = "teacherSqlSessionFactory")
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("teacherDataSource") DataSource masterDataSource)
        throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(masterDataSource);
        return sessionFactory.getObject();
    }
 
}
这里就不用配置JtaTransactionManager了。

测试:

 @Autowired
    MoviesMapper moviesMapper;
 
    @Autowired
    TeacherMapper teacherMapper;
 
 
    @RequestMapping(method = RequestMethod.GET)
    public MyResponseJson twodata() {
        for (int i = 0;i < 5;i++) {
            if(i < 2) {
                Teacher teacher = new Teacher("3", 10);
                teacherMapper.insertteacher(teacher);
                Movies movies = new Movies("3", "3", 50, 10);
                moviesMapper.insertmovies(movies);
   
            }else {
                throw new RuntimeException();
            }
        }
        return new MyResponseJson(200, "成功!", null);
    }
测试结果:可以回滚
款完善成熟的 JTA 事务实现需要考虑与处理的细节非常多,如性能(提交事务的时候使用多线程方式并发提交事务)、容错(网络,系统异常)等, 其成熟也需要经过较长时间的积累。

猜你喜欢

转载自blog.csdn.net/zpflwy1314/article/details/102916181