Background: With the continuous development of business and the doubling of the amount of data, the performance of a single database creates a bottleneck. We may partition the database (that is, the sub-database and sub-table we often call). The partition mentioned here refers to the physical After partitioning, different libraries may be on different servers. At this time, the ACID of a single database can no longer adapt to this situation. In this ACID cluster environment, it is almost difficult to ensure the ACID of the cluster. Achieve, or even if it can achieve that efficiency and performance will be greatly reduced, this time we will use distributed transactions
What is Atomikos for?
Atomikos is an open source transaction manager that provides value-added services for the Java platform. It is mainly used to handle cross-database transactions. For example, a certain instruction is written in library A and library B, and the business requires the writing of library A and library B. The operation must be atomic, and atomikos can be used at this time.
For specific implementation, please look down↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
1. Introduce jar packages related to distributed transactions
<!-- 阿里 连接池 -->
<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>
Two, application.yml property configuration
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
Three, configure the data source
Create two Atomikos data sources
@Configuration
public class DataSourceConfig {
// 将这个对象放入spring容器中(交给Spring管理)
@Bean
// 读取 application.yml 中的配置参数映射成为一个对象
@ConfigurationProperties(prefix = "spring.datasource.pre")
public XADataSource getDataSource1(){
// 创建XA连接池
return new MysqlXADataSource();
}
/**
* 创建Atomikos数据源
* 注解@DependsOn("druidXADataSourcePre"),在名为druidXADataSourcePre的bean实例化后加载当前bean
* @param xaDataSource
* @return
*/
@Bean
@DependsOn("getDataSource1")
@Primary
public DataSource dataSourcePre(@Qualifier("getDataSource1") XADataSource xaDataSource){
//这里的AtomikosDataSourceBean使用的是spring提供的
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(xaDataSource);
return atomikosDataSourceBean;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.sit")
public XADataSource getDataSource2(){
// 创建XA连接池
return new MysqlXADataSource();
}
@Bean
@DependsOn("getDataSource2")
public DataSource dataSourceSit(@Qualifier("getDataSource2") XADataSource xaDataSource){
//这里的AtomikosDataSourceBean使用的是spring提供的
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(xaDataSource);
return atomikosDataSourceBean;
}
}
-
The sitSqlSessionTemplate configuration class of data source 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);
}
}
Entity class related to data source 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;
}
}
Dao interface related to data source 1:
public interface TUserBuyerMapper {
int updateByPrimaryKeySelective(TUserBuyer record);
}
The dao interface related to data source 1 implements 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>
-
The sitSqlSessionTemplate configuration class of data source 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);
}
}
Entity classes related to data source 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;
}
}
Data source 2 related dao interface:
public interface TtUserMapper {
int updateByPrimaryKeySelective(TtUser record);
}
Data source 2 related interface implementation 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>
Four, service layer code writing
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;
}
}
Five, Controller layer code
@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;
}
Test result: Should we simulate an exception, the database data has not changed, indicating that a rollback operation was performed when the exception occurred, and the expected effect was achieved!
Finally, I provide the github source address: https://github.com/xiatengGG/springboot-atomikos