springboot-多数据源的事务处理(单服务,操作多个不同的数据库)- atomikos

https://blog.csdn.net/chenjianhuideyueding/article/details/105273172

上一篇文章引入了两个不同的数据源,操作不同的数据库,现在修改下相应的服务实现类(加事务注解和异常代码):

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

/**
 * @author chenjianhui on 2020/04/02
 */
@Service
public class MultiDatasouceServiceImpl implements MultiDatasouceService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Qualifier("jdbcTemplate0")
    @Autowired
    private JdbcTemplate jdbcTemplate0;
    @Qualifier("jdbcTemplate1")
    @Autowired
    private JdbcTemplate jdbcTemplate1;

    @Transactional
    @Override
    public void executeDatasource() {
        jdbcTemplate1.update("INSERT INTO `multi_datasource1`.`black_user` (`app_type`, `user_pk`) VALUES ('1', '3')");
        jdbcTemplate0.update("INSERT INTO `mutil_datasource0`.`black_user` (`app_type`, `user_pk`) VALUES ('1', '3')");
        int i = 10;
        i = i / 0;
    }
}

执行之后,发现multi_datasource1数据库操作成功,multi_datasource0操作失败。

会出现这个情况是因为,multi_datasource0加入到事务管理中,而multi_datasource1没有,因为在配置数据源的信息的时候,multi_datasource0是优先被考虑的数据源,所以这里的@Transactional注解默认就是考虑multi_datasource0数据源,因此对其又事务的管理,发生异常时,进行事务的回滚;但是multi_datasource1没有事务管理,所以是正常插入。

 

以下是使用atomikos进行分布式事务的解决,代码也上全吧(跟上述的描述已经完全没关系了)

这个是分布式事务的一个问题,要解决话,可以引入pom的依赖,比较上一篇发现多了atomikos的依赖:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.caraliu</groupId>
    <artifactId>mutildatasource</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.0.RELEASE</version>
        <relativePath/>
        <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
    </dependencies>


</project>

yml的配置:

##端口号
server:
  port: 9999

  ##数据库url
spring:
  jpa:
   database-platform: org.hibernate.dialect.MySQL5Dialect
  datasource:
   multiDataSource0:
    url: jdbc:mysql://localhost:3306/mutil_datasource0?serverTimezone=GMT%2B8&characterEncoding=UTF-8&useSSL=false&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull
  ##数据库用户名
    username: root
  ##数据库密码
    password: root
  ##数据库驱动
    driver-class-name: com.mysql.jdbc.Driver
  ##数据库url
   multiDataSource1:
    url: jdbc:mysql://localhost:3306/multi_datasource1?serverTimezone=GMT%2B8&characterEncoding=UTF-8&useSSL=false&rewriteBatchedStatements=true&zeroDateTimeBehavior=convertToNull
  ##数据库用户名
    username: root
  ##数据库密码
    password: root
  ##数据库驱动
    driver-class-name: com.mysql.jdbc.Driver

然后上一篇的JdbcTemplateConfig配置修改如下:

@Configuration
public class JdbcTemplateConfig {

    @Value("${spring.datasource.multiDataSource0.url}")
    private String db0Url;
    @Value("${spring.datasource.multiDataSource0.username}")
    private String db0Username;
    @Value("${spring.datasource.multiDataSource0.password}")
    private String db0Password;

    @Value("${spring.datasource.multiDataSource1.url}")
    private String db1Url;
    @Value("${spring.datasource.multiDataSource1.username}")
    private String db1Username;
    @Value("${spring.datasource.multiDataSource1.password}")
    private String db1Password;

    @Bean(name = "dataSource0")
    @Primary
    public DataSource primaryDataSource() {
       //设置 atomikos的数据源
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setUniqueResourceName("primaryDataSource");
        atomikosDataSourceBean.setXaDataSourceClassName(
                "com.mysql.cj.jdbc.MysqlXADataSource");
        Properties properties = new Properties();
        properties.put("URL", db0Url);
        properties.put("user", db0Username);
        properties.put("password", db0Password);
        atomikosDataSourceBean.setXaProperties(properties);
        return atomikosDataSourceBean;
    }

    @Bean(name = "dataSource1")
    public DataSource secondaryDataSource() {
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setUniqueResourceName("secondaryDataSource");
        atomikosDataSourceBean.setXaDataSourceClassName(
                "com.mysql.cj.jdbc.MysqlXADataSource");
        Properties properties = new Properties();
        properties.put("URL", db1Url);
        properties.put("user", db1Username);
        properties.put("password", db1Password);
        atomikosDataSourceBean.setXaProperties(properties);
        return atomikosDataSourceBean;
    }

    @Primary
    @Bean
    JdbcTemplate jdbcTemplate0(@Qualifier("dataSource0") DataSource dataSource0) {
        return new JdbcTemplate(dataSource0);
    }

    @Bean
    JdbcTemplate jdbcTemplate1(@Qualifier("dataSource1") DataSource dataSource1) {
        return new JdbcTemplate(dataSource1);
    }

    /**
     * 注入事物管理器
     * @return
     */
    @Bean(name = "jtm")
    public JtaTransactionManager regTransactionManager () {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }

controller的修改:

@Controller
@RequestMapping(value = "/v1")
public class MultiDatasourceController {

    @Autowired
    private MultiDatasouceService multiDatasouceService;
    @RequestMapping(value = "/mutilDatasource",method = RequestMethod.GET)
    public ResponseEntity<?> mutilDatasource(){
        multiDatasouceService.executeDatasource();
        return new ResponseEntity<Object>(null, HttpStatus.OK);
    }
}
MultiDatasouceServiceImpl实现类的修改:
@Service
public class MultiDatasouceServiceImpl implements MultiDatasouceService {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Qualifier("jdbcTemplate0")
    @Autowired
    private JdbcTemplate jdbcTemplate0;
    @Qualifier("jdbcTemplate1")
    @Autowired
    private JdbcTemplate jdbcTemplate1;

    // transactionManager = "jtm" 指明事务管理器
    @Transactional(transactionManager = "jtm",rollbackFor = RuntimeException.class)
    @Override
    public void executeDatasource() {
        jdbcTemplate1.update("INSERT INTO `multi_datasource1`.`black_user` (`app_type`, `user_pk`) VALUES ('1', '3')");
        jdbcTemplate0.update("INSERT INTO `mutil_datasource0`.`black_user` (`app_type`, `user_pk`) VALUES ('1', '3')");
        int i = 10;
        i = i / 0;
    }
}

启动项目,执行请求发现:两条数据都没有插入,所以分布式事务起作用了。

atomikos是基于XA协议的,就是2pc两阶段提交协议

第一阶段: 涉及到同个事务中的数据库数据源先询问能否提交

第二阶段:如果能,那么提交事务;如果不行那么就回滚事务

2pc是阻塞式的,所以不支持高并发,这个使用比较简单,一般用於单服务,多数据源的场景

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章