【SpringBoot框架篇】4.集成jta-atomikos實現分佈式事務

簡介

1.分佈式事務
說到分佈式事務,可以理解爲,由於分佈式而引起的事務不一致的問題。隨着項目做大,模塊拆分,數據庫拆分。一次包含增刪改操作數據庫涉及到了更新兩個不同物理節點的數據庫,這樣的數據庫事務只能保證自己處理的部分的事務,但是整個的事務就不能保證一致性。

  針對分佈式事務常見的例子有:生成訂單

  例如訂單表和訂單明細表不在一個數據庫中,生成訂單的時候要確保對應的訂單明細信息應該也要同步插入進去,
  如果不用分佈式事務,會出現訂單插入成功,訂單明細數據庫宕機了,訂單明細插入失敗的情況。

2.JTA
JTA(java Transaction API)是JavaEE 13 個開發規範之一。java 事務API,允許應用程序執行分佈式事務處理——在兩個或多個網絡計算機資源上訪問並且更新數據。JDBC驅動程序的JTA支持極大地增強了數據訪問能力。事務最簡單最直接的目的就是保證數據的有效性,數據的一致性。

3.atomikos
atomikos 是一個爲Java平臺提供增值服務的並且開源類事務管理器。

引入依賴

 <!-- jta-atomikos 分佈式事務管理 -->
     <dependency>
         <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jta-atomikos</artifactId>
    </dependency>

修改配置文件

spring:
  application: jta-atomikos
  datasource:
    db1:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/orders?useSSL=false&serverTimezone=GMT%2b8&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
      username: root
      password: 123456
    db2:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/orderdetails?useSSL=false&serverTimezone=GMT%2b8&characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
      username: root
      password: 123456

server:
  port: 8004

添加配置讀取類

db1數據源配置讀取類

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.db1")
@Data
public class DB1Config {
    private  String username;
    private String password;
    private String jdbcUrl;
}

db2數據源配置讀取類

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.db2")
@Data
public class DB2Config {
    private  String username;
    private String password;
    private String jdbcUrl;
}

創建多數據源,管理事務

db1的數據源配置

@Configuration
@MapperScan(basePackages = {"com.ljm.boot.jtaatomikos.mapper.db1"}, sqlSessionFactoryRef = "sqlSessionFactoryDb1")
public class DbSessionFactory1 {


    @Bean(name = "db1")
    public DataSource businessDbDataSource(@Qualifier("DB1Config") Db1Config db1Conf) {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(db1Conf.getJdbcUrl());
        mysqlXaDataSource.setPassword(db1Conf.getPassword());
        mysqlXaDataSource.setUser(db1Conf.getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("db1");
        return xaDataSource;
    }

    @Bean(name="sqlSessionFactoryDb1")
    public SqlSessionFactory sqlSessionFactoryDb1(@Qualifier("db1")DataSource DataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(DataSource);
        return factoryBean.getObject();
    }

    @Bean(name="sqlSessionTemplateDb1")
    public SqlSessionTemplate sqlSessionTemplateDb1(@Qualifier("sqlSessionFactoryDb1") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

db2數據源配置

@MapperScan(basePackages = {"com.ljm.boot.jtaatomikos.mapper.db2"}, sqlSessionFactoryRef = "sqlSessionFactoryDb2")
public class DbSessionFactory2 {


    @Bean(name = "db2")
    public DataSource newhomeDbDataSource(@Qualifier("DB2Config") DB2Config db2Conf) {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(db2Conf.getJdbcUrl());
        mysqlXaDataSource.setPassword(db2Conf.getPassword());
        mysqlXaDataSource.setUser(db2Conf.getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("db2");
        return xaDataSource;
    }


    @Bean(name="sqlSessionFactoryDb2")
    public SqlSessionFactory sqlSessionFactoryDb2(@Qualifier("db2")DataSource DataSource ) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(DataSource);
        return factoryBean.getObject();
    }

    @Bean(name="sqlSessionTemplateDb2")
    public SqlSessionTemplate sqlSessionTemplateDb2(@Qualifier("sqlSessionFactoryDb2") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

數據持久化

db1數據持久化操作

@Mapper
public interface OrderMapper {
    @Insert("INSERT INTO orders(id, name) VALUES(#{id}, #{name})")
    int insert(@Param("id") String id, @Param("name") String name);
}

db2數據持久化操作

@Mapper
public interface OrderDetailMapper {
    @Insert("INSERT INTO orderdetails(id, orderId) VALUES(#{id}, #{orderId})")
    int insert(@Param("id") int id, @Param("orderId") String orderId);
}

數據庫DLL文件

create database orders;
use orders;
create table orders(
id varchar(40),
name varchar(25)
);
create database orderdetails;
use orderdetails;
create table orderdetails(
id int,
ordcerId  varchar(40)
);

模擬事務回滾

@Service
public class OrderServiceImpl implements OrderService {

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private OrderDetailMapper orderDetailMapper;

    @Override
    @Transactional
    public String insertDbaAndDbB(boolean flag) {
        //實際生產環境訂單id應該通關雪花算法生成(生產的id是數字類型,b+樹索引查詢效率會很高),uuid沒有規律,會嚴重影響索引查找速度
        String orderId = UUID.randomUUID().toString();
        //插入訂單
        orderMapper.insert(orderId, "重入門到放棄");
        //模擬報錯,事務會回滾
        if(flag){
            //flag=true的時候,觸發異常,事務回滾,爲false的話,兩個數據庫都能正確插入數據
            System.out.println(1 / 0);
        }
        //插入訂單明細
        orderDetailMapper.insert(1, orderId);
        return "success";
    }
}

項目配套代碼

github地址
要是覺得我寫的對你有點幫助的話,麻煩在github上幫我點 Star

【SpringBoot框架篇】其它文章如下,後續會繼續更新。

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