1 | 作者: 夜泊1990 |
一、分布式事务产生的原因(多数据源)
二、分布式解决方案
2.1 什么是JTA
JTA介绍
1
JTA(Java Transaction API)被称为Java事务API,是由Java语言提供的一套解决分布式事务的API标准
分布式事务(JTA)
1
一个分布式事务包括一个事务管理器和一个或多个资源(数据库)
2.2 XA协议
1 | 1. XA协议是JTA的基础 |
2.3 二阶段提交(2PC)
- 什么是二阶段提交
1
二阶段提交顾名思义就是为保证分布式事务的正确运行,在进行事务管理的时候,采用分阶段提交的设计方案,而这里是分两个阶段提交,所以叫二阶段提交
- 二阶段提交的流程
举一个下单的例子(订单服务A中插入一条订单数据,库存服务B中更新库存数据)
第一阶段
1 | 第一阶段步骤: |
第二阶段(第二阶段分两种情况一种是成功事务完成,一种是失败,事务回滚)
- 提交执行事务(成功)
1 | 第二阶段成功分为两步 |
- 提交执行事务(失败/异常)
1 | 二阶段失败分为两步 |
- 二阶段提交的优缺点
1
2
3
4优点: 简单方便
缺点:
1. 在进行事务操作时,会将所有资源锁定,直到事务结束,所以性能较差
2. 如果资源或者事务管理器宕机或者失去联系会出现阻塞或者数据不一致问题
三、Atomikos事务管理器
3.1 Atomikos简介
1 | Atomikos是一款Java/JTA事务处理工具,在SpringBoot的官网文档的分布式事务处理中Atomikos作为一种分布式事务的解决方案. |
1 | 从SpringBoot的官网可以看出,官网介绍非常简单,说了要想使用需要添加依赖 |
3.2 环境构建
业务描述
1
2
3
4
5# 简单测试业务,非真实业务逻辑
例如用户购买商品的业务逻辑,整个业务逻辑涉及到2张表
商品服务:对下单的商品扣除商品库存
订单服务:根据购买需求创建订单测试数据库创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19-- 创建订单库
CREATE DATABASE order_db DEFAULT CHARACTER SET UTF8;
-- 订单表
CREATE TABLE `order`(
order_id INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
product_id INT(11) DEFAULT NULL COMMENT '商品ID'
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT='订单表';
-- 创建商品库
CREATE DATABASE product_db DEFAULT CHARACTER SET UTF8;
-- 商品表
CREATE TABLE product(
product_id INT(11) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID',
price DOUBLE DEFAULT NULL COMMENT '商品价格',
stock INT(11) DEFAULT NULL COMMENT '库存'
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 COMMENT='商品表';映射数据库表的实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29/**
* 订单类
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
public class Order {
//订单ID
private Integer orderId;
//商品ID
private Integer productId;
}
<-------------------------------------分割线----------------------------------->
/**
* 产品类
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
public class Product {
//商品ID
private Integer productId ;
//商品价格
private Double price;
//商品库存
private Integer stock;
}业务代码Mapper层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30/**
* 操作订单表的接口
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
public interface OrderMapper {
/**
* 创建订单
* @param productId: 商品ID
*/
void createOrder(Integer productId);
}
<-------------------------------------分割线----------------------------------->
/**
* 操作商品的表的接口
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
public interface ProductMapper {
/**
* 更新商品库存
* @param productId: 商品ID
* @param count : 添加或者减少的商品数量
*/
void updateStock(@Param("productId") Integer productId,@Param("count")Integer count);
}注册接口的xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.ukoko.multiple.mapper.order.OrderMapper">
<!--
创建订单的SQL
-->
<insert id="createOrder" parameterType="int">
INSERT INTO `order`(product_id) VALUES(#{productId})
</insert>
</mapper>
<-------------------------------------分割线----------------------------------->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.ukoko.multiple.mapper.product.ProductMapper">
<!--
更新商品库存的SQL
-->
<update id="updateStock">
UPDATE product SET stock=stock-#{count} WHERE product_id=#{productId}
</update>
</mapper>业务代码Service层
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37/**
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
public interface BusinessService {
/**
* 下单
* @param productId: 商品ID
* @param count : 下单商品数量
*/
void PlaceOrder(Integer productId,Integer count);
}
<-------------------------------------分割线----------------------------------->
/**
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
@Service
public class BusinessServiceImpl implements BusinessService{
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Transactional
@Override
public void PlaceOrder(Integer productId, Integer count) {
orderMapper.createOrder(productId);
productMapper.updateStock(productId,count);
}
}多数据库配置(application.properties)
1
2
3
4
5
6
7
8
9
10# 数据源配置01
spring.datasource.first.url=jdbc:mysql://192.168.1.134:3307/order_db?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.first.username=root
spring.datasource.first.password=root
spring.datasource.first.driverClassName=com.mysql.cj.jdbc.Driver
# 数据源配置02
spring.datasource.second.url=jdbc:mysql://192.168.1.134:3308/product_db?characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.second.username=root
spring.datasource.second.password=root
spring.datasource.second.driverClassName=com.mysql.cj.jdbc.Driver多数据源配置(配置文件编写)
- DataSourceConfig1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63/**
* 配置数据源product_db
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
@Configuration
@MapperScan(basePackages = {"cn.ukoko.multiple.mapper.product"},sqlSessionTemplateRef = "productSqlSessionTemplate")
public class DataSource1 {
@Value("${spring.datasource.first.url}")
private String url;
@Value("${spring.datasource.first.username}")
private String username;
@Value("${spring.datasource.first.password}")
private String password;
@Value("${spring.datasource.first.driverClassName}")
private String driverClassName;
/**
* 创建product_db数据源
*/
@Bean(name = "productDataSource")
@Primary
public DataSource createProductDataSource(){
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
DruidXADataSource dds = new DruidXADataSource();
dds.setUsername(username);
dds.setPassword(password);
dds.setUrl(url);
dds.setDriverClassName(driverClassName);
ds.setXaDataSource(dds);
ds.setUniqueResourceName("productDataSource");
return ds;
}
@Primary
@Bean(name = "productSqlSessionFactory")
public SqlSessionFactory orderSqlSessionFactory(@Qualifier(value = "productDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//设置别名
sqlSessionFactoryBean.setTypeAliasesPackage("cn.ukoko.multiple.entity");
//设置驼峰
org.apache.ibatis.session.Configuration c = new org.apache.ibatis.session.Configuration();
c.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(c);
//设置映射接口的xml配置文件
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
/**
* 创建SqlSessionTemplate
*/
@Primary
@Bean(name = "productSqlSessionTemplate")
public SqlSessionTemplate orderSqlSessionTemplate(@Qualifier("productSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- DataSourceConfig1.java
- DataSourceConfig2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60/**
* 配置数据源product_db
* @Author 夜泊1990
* @Web https://ukoko.gitee.io/
* @Date 2021年4月23日
* @Email hd1611756908@163.com
*/
@Configuration
@MapperScan(basePackages = {"cn.ukoko.multiple.mapper.order"},sqlSessionTemplateRef = "orderSqlSessionTemplate")
public class DataSource2 {
@Value("${spring.datasource.second.url}")
private String url;
@Value("${spring.datasource.second.username}")
private String username;
@Value("${spring.datasource.second.password}")
private String password;
@Value("${spring.datasource.second.driverClassName}")
private String driverClassName;
/**
* 创建product_db数据源
*/
@Bean(name = "orderDataSource")
public DataSource createProductDataSource(){
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
DruidXADataSource dds = new DruidXADataSource();
dds.setUsername(username);
dds.setPassword(password);
dds.setUrl(url);
dds.setDriverClassName(driverClassName);
ds.setXaDataSource(dds);
ds.setUniqueResourceName("orderDataSource");
return ds;
}
@Bean(name = "orderSqlSessionFactory")
public SqlSessionFactory productSqlSessionFactory(@Qualifier(value = "orderDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
//设置别名
sqlSessionFactoryBean.setTypeAliasesPackage("cn.ukoko.multiple.entity");
//设置驼峰
org.apache.ibatis.session.Configuration c = new org.apache.ibatis.session.Configuration();
c.setMapUnderscoreToCamelCase(true);
sqlSessionFactoryBean.setConfiguration(c);
//设置映射接口的xml配置文件
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
/**
* 创建SqlSessionTemplate
*/
@Bean(name = "orderSqlSessionTemplate")
public SqlSessionTemplate productSqlSessionTemplate(@Qualifier("orderSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}测试完成
- DataSourceConfig2.java
测试用例地址
1
https://gitee.com/ukoko/ukoko-multiple.git