文章目录
spring boot默认继承事务,只要在方法上添加@Transaction注解即可,但是这种只适用与单数据源,在多数据源下就不再适用,比如在一个service方法中,执行两个操作,往数据库1中插入一条数据,往数据库2中插入一条数据,但如果往数据库2中插入数据时失败,需要将数据库1事务回滚,此时只简单的使用@Transaction就无法实现了,此时就用到了分布式事务。
spring2.5以前,对分布式多数据源不支持,但是在spring3.0之后针对这个问题的做出了解决方案,具体你们可以看看这篇博文:被忽略的Spring3小改进—支持多数据源的@Transactional事务注解 ,但由于本文使用的不是spring3.0,所以我们这里给出了另一种解决方案:使用springboot+jta+atomikos 实现分布式事物管理
项目目录结构如下:
1. 添加依赖(pom.xml)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
2. 配置数据库连接信息 (application.yml)
// url,name,passward等属性值会封装到DBConfig1中的属性
spring:
datasource:
test1:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.22.58:3306/intern_dev?autoReconnect=true&useSSL=false&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: intern
password: intern
maxPoolSize: 5
test2:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/intern?autoReconnect=true&useSSL=false&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: root
password: root
3. 读取配置文件信息
第一个数据源配置:
package com.tfjybj.intern.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
// 与application.properties配置文件前缀一致
@ConfigurationProperties(prefix = "spring.datasource.test1")
public class DBConfig1 {
private String url;
private String username;
private String password;
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;
}
}
第二个数据源信息:
package com.tfjybj.intern.config;/*
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.datasource.test2")
public class DBConfig2 {
private String url;
private String username;
private String password;
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;
}
}
4. 创建多数据源
创建第一个数据源
package com.tfjybj.intern.config;/*
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
// 扫描的Dao层
@MapperScan(basePackages = "com.tfjybj.intern.provider.dao1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class TestMyBatisConfig1 {
// 配置数据源
@Primary
@Bean(name = "test1DataSource")
public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
// 创建MYsql实现XA规范的分布式数据源
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
// 设置连接信息
mysqlXaDataSource.setUrl(testConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(testConfig.getPassword());
mysqlXaDataSource.setUser(testConfig.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
// 数据源改为Atomikos,将事务交给Atomikos统一管理
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("test1DataSource");
// xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
// xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
// xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
// xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
// xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
// xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
// xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
// xaDataSource.setTestQuery(testConfig.getTestQuery());
return xaDataSource;
}
@Bean(name = "test1SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper1/UserMapper.xml"));
// 通常项目中配置为*,扫描所有的mapper
// bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper1/*.xml"));
return bean.getObject();
}
@Bean(name = "test1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
创建第二个数据源
package com.tfjybj.intern.config;/*
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "com.tfjybj.intern.provider.dao2", sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class TestMyBatisConfig2 {
// 配置数据源
@Bean(name = "test2DataSource")
public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(testConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(testConfig.getPassword());
mysqlXaDataSource.setUser(testConfig.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("test2DataSource");
// xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
// xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
// xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
// xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
// xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
// xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
// xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
// xaDataSource.setTestQuery(testConfig.getTestQuery());
return xaDataSource;
}
@Bean(name = "test2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper2/ResumeMapper.xml"));
return bean.getObject();
}
@Bean(name = "test2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
1.在进行Mapper扫描时@MapperScan(basePackages = “com.tfjybj.intern.provider.dao2”, sqlSessionTemplateRef = “test2SqlSessionTemplate”),basePackages不同的数据源要设置为不同的,放在一个文件夹中我这报错了
2.SqlSessionTemplate是个线称安全的类,每运行一个SqlSessionTemplate时,它就会重新获取一个新的SqlSession,所以每个方法都有一个独立的SqlSession,这意味着它是线称安全的
3.核心思想:将事务(数据源)交给Automikos管理
5. 启动项添加@EnableConfigurationProperties
@EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效,如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入
@EnableConfigurationProperties(value = {DBConfig1.class, DBConfig2.class})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class InternApplication {
public static void main(String[] args) {
SpringApplication.run(InternApplication.class, args);
}
}
如果不添加改注解,在创建数据源代码中会报错:Could not autowire. No beans of ‘DBConfig1’ type found.
6. 测试使用事务注解
@Transactional(rollbackFor = {Exception.class})
public ItooResult findById(@ApiParam(value = "主键id", required = true) @PathVariable String id) {
// 往第一个数据源中插入数据
userService.insertUser(userEntity);
// 往第二个数据源中插入数据
resumeService.insertResume(resumeEntity);
。。。。
}
在本事务中,任何地方出错,事务都会回滚,即这两个数据库都不会插入数据,这样就实现了多数据源的分布式事务
下面说一下其中用到的知识点:
1. JTA: 即Java Transaction API,JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接.更多详情参见:https://blog.csdn.net/wrs120/article/details/90136782#32_JTA_45
2. Atomikos: 是一个为Java平台提供增值服务的并且开源类事务管理器TM,将所有的事务将给Atomikos统一管理