SpringBoot2.x系列教程68--Spring Boot+JPA+Jta-atomikos實現分佈式事務
作者:一一哥
我在上一章節中介紹了分佈式事務的相關理論知識,本章節中我會在SpringBoot中結合JPA,整合jta-atomikos來實現多數據源環境下的分佈式事務。
一. 多數據源環境下分佈式事務代碼實現
代碼環境:
- Spring Boot2.2.5
- mysql-connector-java8.0.11
創建數據庫對應的表
db1數據庫中的goods表:

db4數據庫中的user表:

1. 創建web項目
我們按照之前的經驗,創建一個web程序,並將之改造成Spring Boot項目,具體過程略。

2. 添加依賴包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--版本不能太高-->
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
要注意mysql-connector-java的版本不要太高,否則可能會產生一些異常。
3. 創建application.yml配置文件
spring:
jpa:
database: mysql
show-sql: true
hibernate:
ddl-auto: update
database-platform: org.hibernate.dialect.MySQL57Dialect
4. 創建兩個數據源對應的實體類
db1數據源的實體類Goods
package com.yyg.boot.entity.db1;
import lombok.Data;
import javax.persistence.*;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/3
* @Description db1中的商品表
*/
@Entity
@Table(name = "goods")
@Data
public class Goods {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
}
db4數據源的實體類User
package com.yyg.boot.entity.db2;
import lombok.Data;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/3
* @Description db4中的用戶表
*/
@Entity
@Table(name = "user")
@Data
@ToString
public class User implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column
private String username;
@Column
private Date birthday;
@Column
private String sex;
@Column
private String address;
}
5. 創建2個數據源倉庫類
db1數據源倉庫類GoodsRepository
package com.yyg.boot.dao.db01;
import com.yyg.boot.entity.db1.Goods;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/3
* @Description Description
*/
@Repository
public interface GoodsRepository extends JpaRepository<Goods, Long>,JpaSpecificationExecutor<Goods> {
}
db4數據源倉庫類UserRepository
package com.yyg.boot.dao.db02;
import com.yyg.boot.entity.db2.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/3
* @Description Description
*/
@Repository
public interface UserRepository extends JpaRepository<User, Long>,JpaSpecificationExecutor<User> {
}
6. 配置數據源及事務管理器等
這裏的配置是最關鍵的,一般代碼出問題都是因爲這裏有問題。
這裏針對兩個數據庫,分別設置兩個數據源goodsDataSource與userDataSource。這裏的DataSource必須是XADataSource類型的數據源才行,並且利用AtomikosDataSourceBean講數據源配置進去。
還有分別設置實體類管理工廠goodsEntityManagerFactory與userEntityManagerFactory。
再分別設置事務管理器goodsTransactionManager與userTransactionManager。
還要創建一個用來整合多個事務管理器的JtaTransactionManager,負責回滾到多個數據源中。
但要注意區分是否是Primary配置。
db1數據源配置類GoodsDataSourceConfig
package com.yyg.boot.config;
import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Objects;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/18
* @Description 商品數據源配置類
*/
@Configuration
@EnableJpaRepositories(basePackages = {"com.yyg.boot.dao.db01"}, entityManagerFactoryRef = "goodsEntityManagerFactory", transactionManagerRef = "goodsTransactionManager")
public class GoodsDataSourceConfig {
@Resource
private JpaProperties jpaProperties;
/**
* 第一個數據源
*/
@Primary
@Bean(name = "goodsDataSource")
public DataSource goodsDataSource() {
DruidXADataSource druidXADataSource = new DruidXADataSource();
druidXADataSource.setUrl("jdbc:mysql://localhost:3306/db1?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
druidXADataSource.setUsername("root");
druidXADataSource.setPassword("syc");
druidXADataSource.setDefaultAutoCommit(false);
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(druidXADataSource);
atomikosDataSourceBean.setUniqueResourceName("goodsDataSource");
atomikosDataSourceBean.setPoolSize(5);
return atomikosDataSourceBean;
}
/**
* 商品實體類管理工廠
*/
@Primary
@Bean(name = "goodsEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean goodsEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(goodsDataSource())
.properties(jpaProperties.getProperties())
//設置實體類所在位置:類或包
.packages("com.yyg.boot.entity.db1")
//持久化單元名稱
.persistenceUnit("goodsPersistenceUnit")
.build();
}
/**
* 商品事務管理器
*/
@Primary
@Bean(name = "goodsTransactionManager")
public PlatformTransactionManager goodsTransactionManager(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(Objects.requireNonNull(goodsEntityManagerFactory(builder).getObject()));
}
}
db4數據源配置類GoodsDataSourceConfig
package com.yyg.boot.config;
import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Objects;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/18
* @Description 商品數據源配置類
*/
@Configuration
@EnableJpaRepositories(basePackages = {"com.yyg.boot.dao.db02"},entityManagerFactoryRef = "userEntityManagerFactory",transactionManagerRef = "userTransactionManager")
public class UserDataSourceConfig {
/**
* 自動注入jpa配置
*/
@Resource
private JpaProperties jpaProperties;
@Bean(name = "userDataSource")
public DataSource db4DataSource() {
DruidXADataSource druidXADataSource = new DruidXADataSource();
druidXADataSource.setUrl("jdbc:mysql://localhost:3306/db4?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
druidXADataSource.setUsername("root");
druidXADataSource.setPassword("syc");
druidXADataSource.setDefaultAutoCommit(false);
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(druidXADataSource);
atomikosDataSourceBean.setUniqueResourceName("userDataSource");
atomikosDataSourceBean.setPoolSize(5);
return atomikosDataSourceBean;
}
/**
* 將數據源、連接池、以及其他配置策略進行封裝返回給事務管理器
* 自動裝配時當出現多個Bean候選者時,被註解爲@Primary的Bean將作爲首選者,否則將拋出異常
*/
@Bean(name = "userEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean userEntityManagerFactory(EntityManagerFactoryBuilder builder){
return builder.dataSource(db4DataSource())
.properties(jpaProperties.getProperties())
//設置實體類所在位置:類或包
.packages("com.yyg.boot.entity.db2")
//持久化單元名稱
.persistenceUnit("userPersistenceUnit")
.build();
}
/**
* 返回數據源的事務管理器
*/
@Bean(name = "userTransactionManager")
public PlatformTransactionManager userTransactionManager(EntityManagerFactoryBuilder builder){
return new JpaTransactionManager(Objects.requireNonNull(userEntityManagerFactory(builder).getObject()));
}
}
創建用來整合多數據源的事務管理器JtaTransactionManager
package com.yyg.boot.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.transaction.UserTransaction;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/19
* @Description Description
*/
@Configuration
public class JtaTransactionManagerConfig {
@Primary
@Bean(name = "jtaTransactionManager")
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
}
7. 創建Service及其實現類
注意:
此處一定要通過@Transactional註解來明確指明所用的事務管理器,否則當多數據源時可能只能執行一個數據裏的增刪改操作,且此處應該設置Spring的事務傳播屬性爲propagation =Propagation.NEVER,不使用單個數據源自己的事務,統一交由jtaTransactionManager的事務來管理。
定義GoodsService接口
package com.yyg.boot.service;
import com.yyg.boot.entity.db1.Goods;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/7
* @Description Description
*/
public interface GoodsService {
List<Goods> findAll();
Goods addGoods(Goods goods);
}
GoodsServiceImpl實現
package com.yyg.boot.service.impl;
import com.yyg.boot.dao.db01.GoodsRepository;
import com.yyg.boot.entity.db1.Goods;
import com.yyg.boot.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/7
* @Description Description
*
* 注意:此處一定要通過@Transactional註解來明確指明所用的事務管理器,否則當多數據源時可能只能執行一個數據裏的增刪改操作.
* 且此處應該設置Spring的事務傳播屬性爲propagation =Propagation.NEVER,不使用單個數據源自己的事務,統一交由jtaTransactionManager的事務來管理.
*/
@Service
@Transactional(transactionManager = "goodsTransactionManager",rollbackFor = Exception.class,propagation =Propagation.NEVER)
public class GoodsServiceImpl implements GoodsService {
@Autowired
private GoodsRepository goodsRepository;
@Override
public List<Goods> findAll() {
return goodsRepository.findAll();
}
@Override
public Goods addGoods(Goods goods) {
try {
return goodsRepository.save(goods);
}catch (Exception e){
//強制手動事務回滾
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return null;
}
}
定義UserService接口
package com.yyg.boot.service;
import com.yyg.boot.entity.db2.User;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/7
* @Description Description
*/
public interface UserService {
List<User> findAll();
User addUser(User user);
}
UserServiceImpl實現
package com.yyg.boot.service.impl;
import com.yyg.boot.dao.db02.UserRepository;
import com.yyg.boot.entity.db2.User;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.util.List;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/7
* @Description Description
* 注意:此處一定要通過@Transactional註解來明確指明所用的事務管理器,否則當多數據源時可能只能執行一個數據裏的增刪改操作.
* 且此處應該設置Spring的事務傳播屬性爲propagation =Propagation.NEVER,不使用單個數據源自己的事務,統一交由jtaTransactionManager的事務來管理.
*/
@Service
@Transactional(transactionManager = "userTransactionManager",rollbackFor = Exception.class,propagation =Propagation.NEVER)
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public List<User> findAll() {
return userRepository.findAll();
}
@Override
public User addUser(User user) {
try {
return userRepository.save(user);
}catch (Exception e){
//強制手動事務回滾
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return null;
}
}
8. 創建Controller進行測試
package com.yyg.boot.web;
import com.yyg.boot.entity.db1.Goods;
import com.yyg.boot.entity.db2.User;
import com.yyg.boot.service.GoodsService;
import com.yyg.boot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/3
* @Description Description
*/
@RestController
public class TransactionController {
@Autowired
private UserService userService;
@Autowired
private GoodsService goodsService;
@Transactional(value = "jtaTransactionManager", rollbackFor = Exception.class)
@GetMapping("/add")
public ResponseEntity addTest() {
Goods goods = new Goods();
goods.setId(44L);
goods.setName("iPhone se");
goodsService.addGoods(goods);
User user = new User();
user.setSex("男");
user.setUsername("一一哥");
user.setAddress("上海");
user.setBirthday(new Date());
userService.addUser(user);
return ResponseEntity.ok("添加成功");
}
//在該接口中故意製造一個除零異常
@Transactional(value = "jtaTransactionManager", rollbackFor = Exception.class)
@GetMapping("/add2")
public ResponseEntity addTest2() {
Goods goods = new Goods();
goods.setId(44L);
goods.setName("iPhone se");
goodsService.addGoods(goods);
User user = new User();
user.setSex("男");
user.setUsername("一一哥");
user.setAddress("上海");
user.setBirthday(new Date());
userService.addUser(user);
int i=10/0;
return ResponseEntity.ok("添加成功");
}
}
9. 創建入口類
package com.yyg.boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @Author 一一哥Sun
* @Date Created in 2020/4/18
* @Description Description
* (exclude = {DataSourceAutoConfiguration.class,DataSourceTransactionManagerAutoConfiguration.class})
*/
@SpringBootApplication
//手動進行事務管理
@EnableTransactionManagement
public class TransactionApplication {
public static void main(String[] args){
SpringApplication.run(TransactionApplication.class,args);
}
}
10. 完整代碼結構


11. 啓動項目進行測試
測試add接口

可以看到,當插入數據沒有異常發生時,兩個數據庫都可以正常插入數據。

測試add2接口

因爲我在add2接口中故意製造了一個除零異常,此時代碼會發生異常。

我們再看數據庫中,會發現並沒有產生新數據,還是和之前的數據一樣,說明多數據源環境下,利用jta-atomikos實現了分佈式事務的代碼實現。