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是阻塞式的,所以不支持高併發,這個使用比較簡單,一般用於單服務,多數據源的場景

 

 

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