版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhouhao88410234/article/details/88673979
分布式事务
首先我们应知道,事务是为了保证数据的一致性而产生的。那么分布式事务,顾名思义,就是我们要保证分布在不同数据库、不同服务器、不同应用(微服务)之间的数据一致性。
为什么需要分布式事务?
为了保证数据的一致性,可能需要不同的数据库之间的数据要么同时成功,要么同时失败,否则可能导致产生一些脏数据,也可能滋生 Bug。
在这种情况下,分布式事务思想应运而生。
SpringBoot 集成 Atomikos 实现分布式事务
Atomikos 简介
Atomikos 是一个为 Java 平台提供增值服务的开源类事务管理器。
以下是包括在这个开源版本中的一些功能:
- 全面崩溃 / 重启恢复;
- 兼容标准的 SUN 公司 JTA API;
- 嵌套事务;
- 为 XA 和非 XA 提供内置的 JDBC 适配器。
注释:XA 协议由 Tuxedo 首先提出的,并交给 X/Open 组织,作为资源管理器(数据库)与事务管理器的接口标准。目前,Oracle、Informix、DB2 和 Sybase 等各大数据库厂家都提供对 XA 的支持。XA 协议采用两阶段提交方式来管理分布式事务。XA 接口提供资源管理器与事务管理器之间进行通信的标准接口。XA 协议包括两套函数,以 xa_ 开头的及以 ax_ 开头的。
具体实现
1.在本地创建两个数据库:test01,test02,并且创建相同的数据库表:
2.改造工程,在 pom.xml 增加以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
3.修改配置文件 application.yml 如下:
server:
port: 8080
spring:
redis:
host: localhost
port: 6379
mysql:
datasource:
test1:
url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8
username: root
password: 1qaz2wsx
minPoolSize: 3
maxPoolSize: 25
maxLifetime: 20000
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
testQuery: select 1
test2:
url: jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
username: root
password: 1qaz2wsx
minPoolSize: 3
maxPoolSize: 25
maxLifetime: 20000
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
testQuery: select 1
4.创建以下类:
@SpringBootConfiguration
@MapperScan(basePackages = "com.lynn.demo.test01", sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig1 {
// 配置数据源
@Primary
@Bean(name = "dataSource")
public DataSource dataSource(DBConfig1 config) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(config.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(config.getPassword());
mysqlXaDataSource.setUser(config.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource");
xaDataSource.setMinPoolSize(config.getMinPoolSize());
xaDataSource.setMaxPoolSize(config.getMaxPoolSize());
xaDataSource.setMaxLifetime(config.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(config.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(config.getLoginTimeout());
xaDataSource.setMaintenanceInterval(config.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(config.getMaxIdleTime());
xaDataSource.setTestQuery(config.getTestQuery());
return xaDataSource;
}
@Primary
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Primary
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
@SpringBootConfiguration
//basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "com.lynn.demo.test02", sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfig2 {
// 配置数据源
@Bean(name = "dataSource2")
public DataSource dataSource(DBConfig2 config) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(config.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(config.getPassword());
mysqlXaDataSource.setUser(config.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("dataSource2");
xaDataSource.setMinPoolSize(config.getMinPoolSize());
xaDataSource.setMaxPoolSize(config.getMaxPoolSize());
xaDataSource.setMaxLifetime(config.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(config.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(config.getLoginTimeout());
xaDataSource.setMaintenanceInterval(config.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(config.getMaxIdleTime());
xaDataSource.setTestQuery(config.getTestQuery());
return xaDataSource;
}
@Bean(name = "sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
@Bean(name = "sqlSessionTemplate2")
public SqlSessionTemplate sqlSessionTemplate(
@Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
创建以下 mapper:
@Mapper
public interface UserMapper1 {
@Insert("insert into test_user(name,age) values(#{name},#{age})")
void addUser(@Param("name")String name,@Param("age") int age);
}
@Mapper
public interface UserMapper2 {
@Insert("insert into test_user(name,age) values(#{name},#{age})")
void addUser(@Param("name") String name,@Param("age") int age);
}
创建 service 类:
@Service
public class UserService {
@Autowired
private UserMapper1 userMapper1;
@Autowired
private UserMapper2 userMapper2;
@Transactional
public void addUser(User user)throws Exception{
userMapper1.addUser(user.getName(),user.getAge());
userMapper2.addUser(user.getName(),user.getAge());
}
}
5.创建单元测试类进行测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class TestDB {
@Autowired
private UserService userService;
@Test
public void test(){
User user = new User();
user.setName("lynn");
user.setAge(10);
try {
userService.addUser(user);
}catch (Exception e){
e.printStackTrace();
}
}
}
经过测试,如果没有报错,则数据被分别添加到两个数据库表中,如果有报错,则数据不会增加。
注:不适合2个不同的微服务及应用操作2个不同的库保持事务一致性