springboot2.0系列八(多数据源分布式事务)

SpringBoot整合事物管理

 Springboot默认集成事物,只主要在方法上加上@Transactional即可

SpringBoot分布式事物管理

当项目在连接多个数据库时可能会发生事务问题,即一个库的事务不可能去操作另一个数据库的事务,这时就需要使用atomikos对数据库的事务进行统一的管理

使用springboot+jta+atomikos 分布式事物管理

Atomikos 是一个为Java平台提供增值服务的并且开源类事务管理器。

项目结构

实现代码

Pom

 

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springboot-distributed-transaction</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-distributed-transaction</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <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>
        <!-- mysql 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- mybatis 依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- jta-atomikos依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>



    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

新增配置文件信息

# Mysql 1

  mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai

  mysql.datasource.test1.username = root

  mysql.datasource.test1.password = 123@qq

  

  mysql.datasource.test1.minPoolSize = 3

  mysql.datasource.test1.maxPoolSize = 25

  mysql.datasource.test1.maxLifetime = 20000

  mysql.datasource.test1.borrowConnectionTimeout = 30

  mysql.datasource.test1.loginTimeout = 30

  mysql.datasource.test1.maintenanceInterval = 60

  mysql.datasource.test1.maxIdleTime = 60

  

  # Mysql 2

  mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai

  mysql.datasource.test2.username =root

  mysql.datasource.test2.password =123@qq

  

  mysql.datasource.test2.minPoolSize = 3

  mysql.datasource.test2.maxPoolSize = 25

  mysql.datasource.test2.maxLifetime = 20000

  mysql.datasource.test2.borrowConnectionTimeout = 30

  mysql.datasource.test2.loginTimeout = 30

  mysql.datasource.test2.maintenanceInterval = 60

  mysql.datasource.test2.maxIdleTime = 60

 

 读取配置文件信息

DBConfig1 

@Data

@ConfigurationProperties(prefix = "mysql.datasource.test1")

public class DBConfig1 {



      private String url;

      private String username;

      private String password;

      private int minPoolSize;

      private int maxPoolSize;

      private int maxLifetime;

      private int borrowConnectionTimeout;

      private int loginTimeout;

      private int maintenanceInterval;

      private int maxIdleTime;

      private String testQuery;

}

DBConfig2  

@Data

@ConfigurationProperties(prefix = "mysql.datasource.test2")

public class DBConfig2 {



      private String url;

      private String username;

      private String password;

      private int minPoolSize;

      private int maxPoolSize;

      private int maxLifetime;

      private int borrowConnectionTimeout;

      private int loginTimeout;

      private int maintenanceInterval;

      private int maxIdleTime;

      private String testQuery;

}

 

创建多数据源

在类上加上@MapperScan(basePackages = “xxx”,sqlSessionTemplateRef = “xxx”)注解为分包的servicemapper层提供不同数据源

可以看出,jta+atomikos的原理是将不同数据源的本地事务都交给全局事务来处理。

MyBatisConfig1 

package com.example.springbootdistributedtransaction.config;



import com.atomikos.jdbc.AtomikosDataSourceBean;

import com.mysql.cj.jdbc.MysqlXADataSource;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;



import javax.sql.DataSource;

import java.sql.SQLException;



@Configuration

// basePackages 最好分开配置 如果放在同一个文件夹可能会报错

@MapperScan(basePackages = "com.example.springbootdistributedtransaction.test01", sqlSessionTemplateRef = "testSqlSessionTemplate")

public class MyBatisConfig1 {



   // 配置数据源

   @Primary

   @Bean(name = "testDataSource")

   public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {

      MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();

      mysqlXaDataSource.setUrl(testConfig.getUrl());

      mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

      mysqlXaDataSource.setPassword(testConfig.getPassword());

      mysqlXaDataSource.setUser(testConfig.getUsername());

      mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);



      AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();

      xaDataSource.setXaDataSource(mysqlXaDataSource);

      xaDataSource.setUniqueResourceName("testDataSource");



      xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());

      xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());

      xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());

      xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());

      xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());

      xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());

      xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());

      xaDataSource.setTestQuery(testConfig.getTestQuery());

      return xaDataSource;

   }



   @Primary

   @Bean(name = "testSqlSessionFactory")

   public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource)

         throws Exception {

      SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

      bean.setDataSource(dataSource);

      return bean.getObject();

   }



   @Primary

   @Bean(name = "testSqlSessionTemplate")

   public SqlSessionTemplate testSqlSessionTemplate(

         @Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {

      return new SqlSessionTemplate(sqlSessionFactory);

   }

}

MyBatisConfig2  

package com.example.springbootdistributedtransaction.config;

import com.atomikos.jdbc.AtomikosDataSourceBean;



import com.mysql.cj.jdbc.MysqlXADataSource;

import org.apache.ibatis.session.SqlSessionFactory;

import org.mybatis.spring.SqlSessionFactoryBean;

import org.mybatis.spring.SqlSessionTemplate;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.Primary;





import javax.sql.DataSource;

import java.sql.SQLException;



@Configuration

@MapperScan(basePackages = "com.example.springbootdistributedtransaction.test02", sqlSessionTemplateRef = "test2SqlSessionTemplate")

public class MyBatisConfig2 {



   // 配置数据源

   @Bean(name = "test2DataSource")

   public DataSource testDataSource(DBConfig2 testConfig) throws SQLException {

      MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();

      mysqlXaDataSource.setUrl(testConfig.getUrl());

      mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

      mysqlXaDataSource.setPassword(testConfig.getPassword());

      mysqlXaDataSource.setUser(testConfig.getUsername());

      mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);



      AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();

      xaDataSource.setXaDataSource(mysqlXaDataSource);

      xaDataSource.setUniqueResourceName("test2DataSource");



      xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());

      xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());

      xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());

      xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());

      xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());

      xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());

      xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());

      xaDataSource.setTestQuery(testConfig.getTestQuery());

      return xaDataSource;

   }



   @Bean(name = "test2SqlSessionFactory")

   public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)

         throws Exception {

      SqlSessionFactoryBean bean = new SqlSessionFactoryBean();

      bean.setDataSource(dataSource);

      return bean.getObject();

   }



   @Bean(name = "test2SqlSessionTemplate")

   public SqlSessionTemplate testSqlSessionTemplate(

         @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {

      return new SqlSessionTemplate(sqlSessionFactory);

   }

}

 user1Mapper

package com.example.springbootdistributedtransaction.test01.mapper;

  

  import org.apache.ibatis.annotations.Insert;

  import org.apache.ibatis.annotations.Param;

  

  public interface User1Mapper {

   @Insert("insert into users values(null,#{name},#{age});")

   public int addUser(@Param("name") String name, @Param("age") Integer age);

}

 

User2Mapper

package com.example.springbootdistributedtransaction.test02.mapper;

  

  import org.apache.ibatis.annotations.Insert;

  import org.apache.ibatis.annotations.Param;

  

  public interface User2Mapper {

   @Insert("insert into users values(null,#{name},#{age});")

   public int addUser(@Param("name") String name, @Param("age") Integer age);

}

UserService

只需要在同时对两个数据源进行SQL操作的service的方法上加上 @Transactional注解,不需要指定事务管理器,就可以实现分布式事务管理

 

package com.example.springbootdistributedtransaction;

  

  import com.example.springbootdistributedtransaction.test01.mapper.User1Mapper;

  import com.example.springbootdistributedtransaction.test02.mapper.User2Mapper;

  import org.springframework.beans.factory.annotation.Autowired;

  import org.springframework.stereotype.Service;

  import org.springframework.transaction.annotation.Transactional;

  

  @Service

  public class UserService {

    @Autowired

    User1Mapper user1Mapper;

  

    @Autowired

    User2Mapper user2Mapper;

  

  

     @Transactional

    public int addUser( String name,  Integer age){

        user1Mapper.addUser(name,age);

        user2Mapper.addUser(name,age);

        int i=1/0;

  

        return  1;

    }

  

}

 

UserController

package com.example.springbootdistributedtransaction;

  

  

  import org.springframework.beans.factory.annotation.Autowired;

  import org.springframework.web.bind.annotation.GetMapping;

  import org.springframework.web.bind.annotation.RestController;

  

  @RestController

  public class userController {

    @Autowired

    UserService userService;

  

    @GetMapping("user")

    public boolean addUser( String name,  Integer age) {

    userService.addUser(name,age);

    return true;

    }

}

 

 

 启动加载配置

@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })

package com.example.springbootdistributedtransaction;

  

  import com.example.springbootdistributedtransaction.config.DBConfig1;

  import com.example.springbootdistributedtransaction.config.DBConfig2;

  import org.springframework.boot.SpringApplication;

  import org.springframework.boot.autoconfigure.SpringBootApplication;

  import org.springframework.boot.context.properties.EnableConfigurationProperties;

  

  @EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })

  @SpringBootApplication

  public class SpringbootDistributedTransactionApplication {

  

    public static void main(String[] args) {

        SpringApplication.run(SpringbootDistributedTransactionApplication.class, args);

    }

  

}

 

 

效果

异常后数据库没有添加数据,请求回滚

 

UserServie去掉@Transactional注解后,异常后两条数据都添加成功。

猜你喜欢

转载自blog.csdn.net/wota5037/article/details/111415436
今日推荐