SpringBoot+Mybatis+atomikos实现分布式事务
程序员文章站
2022-05-23 13:01:18
...
背景:随着业务的不断发展,数据量的倍增,单个数据库的性能产生瓶颈,我们可能会对数据库进行分区(也就是我们常说的分库分表),这里所说的分区指的是物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种ACID的集群环境下,再想保证集群的ACID几乎是很难达到,或者即使能达到那么效率和性能会大幅下降,这个时候我们就会用到分布式事务
Atomikos 是干嘛用的?
Atomikos 是一个为Java平台提供增值服务的并且开源类事务管理器,主要用于处理跨数据库事务,比如某个指令在A库和B库都有写操作,业务上要求A库和B库的写操作要具有原子性,这时候就可以用到atomikos。
具体实现请往下看↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
一、引入分布式事务相关jar包
<!-- 阿里 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<!--分布式事务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
二、application.yml属性配置
spring:
datasource:
pre :
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8
user: users
password: 123456
sit :
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf8&characterResultSets=utf8
user: userbyuer
password: 123456
三、配置数据源
-
数据源1的配置类
/**
* 数据源Config1
*/
@Configuration
@MapperScan(basePackages = {"com.xiateng.dao.userbuyer"}, sqlSessionTemplateRef = "sitSqlSessionTemplate")
public class MybatisSitConfig {
@Autowired
@Qualifier("dataSourceSit")
private DataSource dataSource;
/**
* 创建 SqlSessionFactory
* @return
* @throws Exception
*/
@Bean
@Primary
public SqlSessionFactory sitSqlSessionFactory() throws Exception{
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// 设置mybatis的xml所在位置
bean.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath*:com/xiateng/mapper/userbuyer/*.xml"));
return bean.getObject();
}
/**
* 通过 SqlSessionFactory 来创建 SqlSessionTemplate
* @param sqlSessionFactory
* @return
*/
@Bean
@Primary
public SqlSessionTemplate sitSqlSessionTemplate(@Qualifier("sitSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
// SqlSessionTemplate是线程安全的,可以被多个DAO所共享使用
return new SqlSessionTemplate(sqlSessionFactory);
}
}
数据源1相关的实体类:
public class TUserBuyer {
private Long userId;
private Integer bond;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Integer getBond() {
return bond;
}
public void setBond(Integer bond) {
this.bond = bond;
}
}
数据源1相关的dao接口:
public interface TUserBuyerMapper {
int updateByPrimaryKeySelective(TUserBuyer record);
}
数据源1相关的dao接口实现xml:
<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TUserBuyer">
update t_user_buyer
<set>
<if test="bond != null">
bond = #{bond,jdbcType=INTEGER},
</if>
</set>
where user_id = #{userId,jdbcType=BIGINT}
</update>
-
数据源2的配置类
/**
* 数据源Config2
*/
@Configuration
@MapperScan(basePackages = {"com.xiateng.dao.user"}, sqlSessionTemplateRef = "preSqlSessionTemplate")
public class MybatisPreConfig {
@Autowired
// @Qualifier表示查找Spring容器中名字为 preDataSource 的对象
@Qualifier("dataSourcePre")
private DataSource dataSource;
/**
* 创建 SqlSessionFactory
* @return
* @throws Exception
*/
@Bean
@Primary
public SqlSessionFactory preSqlSessionFactory() throws Exception{
// 用来创建 SqlSessionFactory 等同于下面配置
// <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
// <property name="dataSource" ref="dataSource" />
// <property name="mapperLocations" value="classpath:mybatis-mapper/*.xml"/>
// </bean>
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// 设置mybatis的xml所在位置(扫描mybatis的相关xml文件,装配到容器中)
bean.setMapperLocations(new PathMatchingResourcePatternResolver().
getResources("classpath*:com/xiateng/mapper/user/*.xml"));
return bean.getObject();
}
/**
* 通过 SqlSessionFactory 来创建 SqlSessionTemplate
* @param sqlSessionFactory
* @return
*/
@Bean
@Primary
public SqlSessionTemplate preSqlSessionTemplate(@Qualifier("preSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
// SqlSessionTemplate是线程安全的,可以被多个DAO所共享使用
return new SqlSessionTemplate(sqlSessionFactory);
}
}
数据源2相关的实体类:
package com.xiateng.entity;
public class TtUser {
private Long id;
private Long updatedBy;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(Long updatedBy) {
this.updatedBy = updatedBy;
}
}
数据源2相关的dao接口:
public interface TtUserMapper {
int updateByPrimaryKeySelective(TtUser record);
}
数据源2相关的接口实现xml:
<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TtUser">
update t_user
<set>
<if test="updatedBy != null">
updated_by = #{updatedBy,jdbcType=BIGINT}
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
四、service层代码编写
public interface TUserService {
int transactionalTest() throws Exception;
}
@Service
public class TUserServiceImpl implements TUserService{
@Autowired
private TtUserMapper ttUserMapper;
@Autowired
private TUserBuyerMapper tUserBuyerMapper;
/**
* 实现多数据库操作
* @return
*/
@Transactional
public int transactionalTest() throws Exception{
// sit(数据源1)
TUserBuyer tUserBuyer = new TUserBuyer();
tUserBuyer.setUserId(0L);
tUserBuyer.setBond(444);
tUserBuyerMapper.updateByPrimaryKeySelective(tUserBuyer);
// pre(数据源2)
TtUser ttUser = new TtUser();
ttUser.setId(1L);
ttUser.setUpdatedBy(444L);
ttUserMapper.updateByPrimaryKeySelective(ttUser);
//模拟异常
int a = 1/0;
return 1;
}
}
五、Controller层代码
@RequestMapping(value = "/tUserList1")
@ResponseBody
public Map<String, Object> tUserList1(){
Map<String, Object> map = new HashMap<>();
int result = 0;
try {
result = tUserService.transactionalTest();
} catch (Exception e) {
e.printStackTrace();
map.put("result","系统异常!");
return map;
}
map.put("result",result);
return map;
}
测试结果:应我们模拟了一个异常,数据库数据没变,说明发生异常的时候做了回滚操作,达到预期效果!
最后奉上github源码地址:https://github.com/xiatengGG/springboot-atomikos