本文就是记录自己的一次学习与测试,内容是atomikos实现分布式事务
学习了很多理论的东西,一些分布式事务、二次提交、三次提交、XA模式等等一些理论,很多都是看个新鲜,所以还是得动手实践,这样怕是能够更好得理解这些理论。
atomikos 是java平台提供的开源事务管理器
环境:spring boot 2.1.2 atomikos jdbc
具体pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cherish_lailai</groupId>
<artifactId>atomikos-jta</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>atomikos-jta</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
三个配置
1、配置文件读取的java配置,两份分别为OrderDBConfig、UserDBConfig
2、数据源配置 DBConfig
package com.cherish_lailai.atomikosjta.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix="mysql.datasource.order")
public class OrderDBConfig {
private String url;
private String username;
private String password;
private int minPoolSize;
private int maxPoolSize;
private int maxLifetime;
private int borrowConnectionTimeout;
private int loginTimeout;
private int maintenanceInterval;
private int maxIdleTime;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMinPoolSize() {
return minPoolSize;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(int maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getBorrowConnectionTimeout() {
return borrowConnectionTimeout;
}
public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {
this.borrowConnectionTimeout = borrowConnectionTimeout;
}
public int getLoginTimeout() {
return loginTimeout;
}
public void setLoginTimeout(int loginTimeout) {
this.loginTimeout = loginTimeout;
}
public int getMaintenanceInterval() {
return maintenanceInterval;
}
public void setMaintenanceInterval(int maintenanceInterval) {
this.maintenanceInterval = maintenanceInterval;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
}
package com.cherish_lailai.atomikosjta.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix="mysql.datasource.user")
public class UserDBConfig {
private String url;
private String username;
private String password;
private int minPoolSize;
private int maxPoolSize;
private int maxLifetime;
private int borrowConnectionTimeout;
private int loginTimeout;
private int maintenanceInterval;
private int maxIdleTime;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMinPoolSize() {
return minPoolSize;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(int maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getBorrowConnectionTimeout() {
return borrowConnectionTimeout;
}
public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {
this.borrowConnectionTimeout = borrowConnectionTimeout;
}
public int getLoginTimeout() {
return loginTimeout;
}
public void setLoginTimeout(int loginTimeout) {
this.loginTimeout = loginTimeout;
}
public int getMaintenanceInterval() {
return maintenanceInterval;
}
public void setMaintenanceInterval(int maintenanceInterval) {
this.maintenanceInterval = maintenanceInterval;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
}
package com.cherish_lailai.atomikosjta.config;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
public class DBConfig {
@Autowired
private UserDBConfig userDBConfig;
@Autowired OrderDBConfig orderDBConifg;
@Bean(name = "userDatasource")
public DataSource userDataSource() throws SQLException {
MysqlXADataSource mysqlXADataSource=new MysqlXADataSource();
mysqlXADataSource.setUrl(userDBConfig.getUrl());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXADataSource.setPassword(userDBConfig.getPassword());
mysqlXADataSource.setUser(userDBConfig.getUsername());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean atomikosDataSourceBean=new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
atomikosDataSourceBean.setUniqueResourceName("userDatasource");
atomikosDataSourceBean.setMinPoolSize(userDBConfig.getMinPoolSize());
atomikosDataSourceBean.setMaxPoolSize(userDBConfig.getMaxPoolSize());
atomikosDataSourceBean.setMaxLifetime(userDBConfig.getMaxLifetime());
atomikosDataSourceBean.setBorrowConnectionTimeout(userDBConfig.getBorrowConnectionTimeout());
atomikosDataSourceBean.setLoginTimeout(userDBConfig.getLoginTimeout());
atomikosDataSourceBean.setMaintenanceInterval(userDBConfig.getMaintenanceInterval());
atomikosDataSourceBean.setMaxIdleTime(userDBConfig.getMaxIdleTime());
return atomikosDataSourceBean;
}
@Bean(name = "userJdbcTemplate")
public JdbcTemplate userJdbcTemplate() throws SQLException {
DataSource userDataSource = userDataSource();
return new JdbcTemplate(userDataSource);
}
@Bean(name = "orderDatasource")
public DataSource orderDataSource() throws SQLException {
MysqlXADataSource mysqlXADataSource=new MysqlXADataSource();
mysqlXADataSource.setUrl(orderDBConifg.getUrl());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXADataSource.setPassword(orderDBConifg.getPassword());
mysqlXADataSource.setUser(orderDBConifg.getUsername());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean atomikosDataSourceBean=new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
atomikosDataSourceBean.setUniqueResourceName("orderDatasource");
atomikosDataSourceBean.setMinPoolSize(orderDBConifg.getMinPoolSize());
atomikosDataSourceBean.setMaxPoolSize(orderDBConifg.getMaxPoolSize());
atomikosDataSourceBean.setMaxLifetime(orderDBConifg.getMaxLifetime());
atomikosDataSourceBean.setBorrowConnectionTimeout(orderDBConifg.getBorrowConnectionTimeout());
atomikosDataSourceBean.setLoginTimeout(orderDBConifg.getLoginTimeout());
atomikosDataSourceBean.setMaintenanceInterval(orderDBConifg.getMaintenanceInterval());
atomikosDataSourceBean.setMaxIdleTime(orderDBConifg.getMaxIdleTime());
return atomikosDataSourceBean;
}
@Bean(name = "orderJdbcTemplate")
public JdbcTemplate orderJdbcTemplate() throws SQLException {
DataSource orderDataSource = orderDataSource();
return new JdbcTemplate(orderDataSource);
}
}
简单得Dao
package com.cherish_lailai.atomikosjta.dao;
import com.cherish_lailai.atomikosjta.entity.Order;
import com.cherish_lailai.atomikosjta.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class OrderRepository {
@Autowired
@Qualifier("orderJdbcTemplate")
private JdbcTemplate orderJdbcTemplate;
// 查询所有的Order
@Transactional
public List<Order> findAllOrder() {
List<Order> orderList = orderJdbcTemplate.query("select * from orderTable",new OrderRowMapper());
return orderList;
}
// 更新所有订单的的oname
public void update(final Order order) {
orderJdbcTemplate.update(
"update orderTable set oname=?",
new Object[]{order.getOname()});
}
// 订单查询RowMapper
class OrderRowMapper implements RowMapper<Order> {
@Override
public Order mapRow(ResultSet rs, int rowNum) throws SQLException {
Order order = new Order();
order.setId(rs.getInt("id"));
order.setOname(rs.getString("oname"));
return order;
}
}
}
package com.cherish_lailai.atomikosjta.dao;
import com.cherish_lailai.atomikosjta.entity.Order;
import com.cherish_lailai.atomikosjta.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import javax.transaction.Transactional;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UserRepository {
@Autowired
@Qualifier("userJdbcTemplate")
private JdbcTemplate userJdbcTemplate;
// 查询所有的用户
@Transactional
public List<User> findAllUser() {
List<User> userList = userJdbcTemplate.query("select * from userTable", new UserRowMapper());
return userList;
}
// 更新所有User的的uname
public void update(final User user) {
userJdbcTemplate.update(
"update userTable set uname=?",
new Object[]{user.getUname()});
}
// 用户查询RowMapper
class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setUname(rs.getString("uname"));
return user;
}
}
}
实体没啥就不贴了
public class Order {
private Integer id;
private String oname;
public class User {
private Integer id;
private String uname;
配置文件
application.properties
#user
mysql.datasource.user.url=jdbc:mysql://localhost:3306/userjta?useUnicode=true&characterEncoding=utf8
mysql.datasource.user.username=root
mysql.datasource.user.password=admin
mysql.datasource.user.minPoolSize=3
mysql.datasource.user.maxPoolSize=25
mysql.datasource.user.maxLifetime=20000
mysql.datasource.user.borrowConnectionTimeout=30
mysql.datasource.user.loginTimeout=30
mysql.datasource.user.maintenanceInterval=60
mysql.datasource.user.maxIdleTime=60
#order
mysql.datasource.order.url=jdbc:mysql://localhost:3306/orderjta?useUnicode=true&characterEncoding=utf8
mysql.datasource.order.username=root
mysql.datasource.order.password=admin
mysql.datasource.order.minPoolSize=3
mysql.datasource.order.maxPoolSize=25
mysql.datasource.order.maxLifetime=20000
mysql.datasource.order.borrowConnectionTimeout=30
mysql.datasource.order.loginTimeout=30
mysql.datasource.order.maintenanceInterval=60
mysql.datasource.order.maxIdleTime=60
测试代码
这里记录下(错误),下面的单元测试时有点问题的,我写完后总觉得不对劲,印象中单元测试的事务默认是会回滚的(具体的也还没去实践,不确定),担心测试结果会影响我的判断,所有如下才是正确的单元测试
@Autowired private TestService testService; @Test public void testTx04(){ testService.test(); }
package com.cherish_lailai.atomikosjta.Server;
import com.cherish_lailai.atomikosjta.dao.OrderRepository;
import com.cherish_lailai.atomikosjta.dao.UserRepository;
import com.cherish_lailai.atomikosjta.entity.Order;
import com.cherish_lailai.atomikosjta.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.transaction.Transactional;
@Component
@Transactional
public class TestService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
public void test(){
User user = new User();
user.setUname("uuuuu");
userRepository.update(user);
// int tmp= 1/0;
Order order = new Order();
order.setOname("ooooo");
orderRepository.update(order);
}
}
下面是原先错误的单元测试,留着以后看看,长个记性
package com.cherish_lailai.atomikosjta;
import com.cherish_lailai.atomikosjta.dao.OrderRepository;
import com.cherish_lailai.atomikosjta.dao.UserRepository;
import com.cherish_lailai.atomikosjta.entity.Order;
import com.cherish_lailai.atomikosjta.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.internal.matchers.Or;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.sql.DataSource;
import javax.transaction.Transactional;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestDataSource {
@Autowired
private DataSource userDatasource;
@Autowired
private DataSource orderDatasource;
@Test
public void testDB(){
System.out.println(userDatasource);
System.out.println(orderDatasource);
}
// 输出
// AtomikosDataSoureBean 'userDatasource'
// AtomikosDataSoureBean 'orderDatasource'
@Autowired
private UserRepository userRepository;
@Autowired
private OrderRepository orderRepository;
@Test
public void testDao(){
List<User> allUser = userRepository.findAllUser();
System.out.println(allUser);
List<Order> allOrder = orderRepository.findAllOrder();
System.out.println(allOrder);
}
// 输出
// [User{id=1, uname='user1'}]
// [Order{id=1, oname='order1'}]
//开始测试事务
// 测试01
// 未使用事务,也跨数据库操作
@Test
public void testTx01(){
User user = new User();
user.setUname("beforException");
userRepository.update(user);
int tmp= 1/0;
user.setUname("afterException");
userRepository.update(user);
}
// 未开始事务
// 数据库查看结果 id=1 uname=beforException
// 结论未执行事务
// 测试02
// 开始事务
@Test
@Transactional
public void testTx02(){
User user = new User();
user.setUname("beforException");
userRepository.update(user);
int tmp= 1/0;
user.setUname("afterException");
userRepository.update(user);
}
// 开启了事务
// 数据库查看结果 还是id=1 uname=user01
// 结论userjta数据库中执行了事务
// 测试03
// 跨数据库操作(分布式事务)
@Test
@Transactional
public void testTx03(){
User user = new User();
user.setUname("uuuuu");
userRepository.update(user);
int tmp= 1/0;
Order order = new Order();
order.setOname("ooooo");
orderRepository.update(order);
}
// 开启了事务
// 数据库查看结果 user还是id=1 uname=user01
// order还是id=1 oname=order01
// 跨数据库操作处于同一个事务中
}
发现多了个文件
清空该文件,再次执行第三个测试(跨数据库操作user与order),查看文件内容
{"id":"192.168.101.1.tm154814032792000001","wasCommitted":false,"participants":[{"uri":"192.168.101.1.tm1","state":"TERMINATED","expires":1548140338485,"resourceName":"userDatasource"}]}
发现大概意思就是说我的user操作没有被提交
总之好像是测出了那个意思,有错望指正,谢谢!
PS:都在使用云笔记,好久没写都快忘了自己也是小编,嘿嘿~