上一章只是解决了单事物问题,也就是说同时只能使用自己的数据源,并指定事物管理,才能使用,那么如果同时使用多个数据源,就会产生分布式事物问题
分布式事物问题分两种:
一种是这种一个项目多个数据源的分布式事物问题
还有一种就是多个项目多个数据源之间的分布式事物问题
这一章就来解决一下第一种一个项目多个数据源的分布式事物问题
这样的话就只能指定一个事物管理器,并不能两个数据库的事物都控制到,如果中间出现错误就会一个事物成功,一个事物失败,造成所谓的分布式事物问题
那么如何解决呢?
采用一个统一的事物管理器来同时管理多个数据源
采用jta-atomikos
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
修改配置 文件也就是之前写的application-dts.yml
spring:
datasource:
###源数据库
springboot:
jdbc-url: jdbc:mysql://192.168.0.23:3306/springboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
maxLifetime: 20000
maxPoolSize: 25
minPoolSize: 3
uniqueResourceName: springbootDatasource
###新数据库
springbootdts:
jdbc-url: jdbc:mysql://192.168.0.23:3306/springboot_dts?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
borrowConnectionTimeout: 30
loginTimeout: 30
maintenanceInterval: 60
maxIdleTime: 60
maxLifetime: 20000
maxPoolSize: 25
minPoolSize: 3
uniqueResourceName: springbootdtsDatasource
创建两个模型类用于存储两个配置
SpringBootConfig
package com.springboot.demo.model;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.datasource.springboot")
@Data
public class SpringBootConfig {
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;
private String testQuery;
private String uniqueResourceName;
}
SpringBootDtsConfig
package com.springboot.demo.model;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "spring.datasource.springbootdts")
@Data
public class SpringBootDtsConfig {
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;
private String testQuery;
private String uniqueResourceName;
}
修改启动类
修改之前写的两个Mapper扫描配置类
修改后的SpringBootDataSourceConfig.java
package com.springboot.demo.config;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.springboot.demo.model.SpringBootConfig;
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.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "com.springboot.demo.springboot.mapper", sqlSessionFactoryRef = "springbootSqlSessionFactory")
public class SpringBootDataSourceConfig {
/**
* 将会员db注册到容器中
*
* @return
*/
@Bean(name = "springbootDataSource")
// @ConfigurationProperties(prefix = "spring.datasource.springboot")
public DataSource springbootDataSource(SpringBootConfig springBootConfig) throws SQLException {
// return DataSourceBuilder.create().build();
// 1.创建MysqlXADataSource
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(springBootConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(springBootConfig.getPassWord());
mysqlXaDataSource.setUser(springBootConfig.getUserName());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
// 2.将本地事务注册到创 Atomikos全局事务
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName(springBootConfig.getUniqueResourceName());
xaDataSource.setMinPoolSize(springBootConfig.getMinPoolSize());
xaDataSource.setMaxPoolSize(springBootConfig.getMaxPoolSize());
xaDataSource.setMaxLifetime(springBootConfig.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(springBootConfig.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(springBootConfig.getLoginTimeout());
xaDataSource.setMaintenanceInterval(springBootConfig.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(springBootConfig.getMaxIdleTime());
xaDataSource.setTestQuery(springBootConfig.getTestQuery());
return xaDataSource;
}
/**
* 将会员SqlSessionFactory注册到容器中
*
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "springbootSqlSessionFactory")
public SqlSessionFactory springbootSqlSessionFactory(@Qualifier("springbootDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean.getObject();
}
/**
* 创建会员管理器
*
* @param dataSource
* @return
*/
/*@Bean(name = "springbootTransactionManager")
public DataSourceTransactionManager springbootTransactionManager(@Qualifier("springbootDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}*/
/**
* 创建订单sqlSesion模版
*
* @param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "springbootSqlSessionTemplate")
public SqlSessionTemplate springbootSqlSessionTemplate(
@Qualifier("springbootSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
修改后的SpringBootDtsDataSourceConfig.java
package com.springboot.demo.config;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import com.springboot.demo.model.SpringBootDtsConfig;
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.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@MapperScan(basePackages = "com.springboot.demo.springbootdts.mapper", sqlSessionFactoryRef = "springbootdtsSqlSessionFactory")
public class SpringBootDtsDataSourceConfig {
/**
* 将会员db注册到容器中
*
* @return
*/
@Bean(name = "springbootdtsDataSource")
// @ConfigurationProperties(prefix = "spring.datasource.springbootdts")
public DataSource springbootdtsDataSource(SpringBootDtsConfig springBootDtsConfig) throws SQLException {
// return DataSourceBuilder.create().build();
// 1.创建MysqlXADataSource
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(springBootDtsConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(springBootDtsConfig.getPassWord());
mysqlXaDataSource.setUser(springBootDtsConfig.getUserName());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
// 2.将本地事务注册到创 Atomikos全局事务
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName(springBootDtsConfig.getUniqueResourceName());
xaDataSource.setMinPoolSize(springBootDtsConfig.getMinPoolSize());
xaDataSource.setMaxPoolSize(springBootDtsConfig.getMaxPoolSize());
xaDataSource.setMaxLifetime(springBootDtsConfig.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(springBootDtsConfig.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(springBootDtsConfig.getLoginTimeout());
xaDataSource.setMaintenanceInterval(springBootDtsConfig.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(springBootDtsConfig.getMaxIdleTime());
xaDataSource.setTestQuery(springBootDtsConfig.getTestQuery());
return xaDataSource;
}
/**
* 将会员SqlSessionFactory注册到容器中
*
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "springbootdtsSqlSessionFactory")
public SqlSessionFactory springbootdtsSqlSessionFactory(@Qualifier("springbootdtsDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean.getObject();
}
/**
* 创建会员管理器
*
* @param dataSource
* @return
*/
/* @Bean(name = "springbootdtsTransactionManager")
public DataSourceTransactionManager springbootdtsTransactionManager(@Qualifier("springbootdtsDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}*/
/**
* 创建订单sqlSesion模版
*
* @param sqlSessionFactory
* @return
* @throws Exception
*/
@Bean(name = "springbootdtsSqlSessionTemplate")
public SqlSessionTemplate springbootdtsSqlSessionTemplate(
@Qualifier("springbootdtsSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
修改接口去除掉注解中指定的事物管理器
启动的时候出现了一个小问题,找了一会,发现是yml配置文件和模型没有对上,修改一下jdbc-url改成url,因为是我们自己装载数据源就不用设置为jdbc-url了
再次启动,清空数据库,先测试正确的
访问报错:因为之前在user2接口上加了事物注解,并指定了事物管理器所以报错了,因为那个事物管理器已经不存在了,注释掉就可以
再次启动测试
测试成功..为什么是error呢,这是个失误因为我返回的就是error
查看数据库
成功了,接下来测试报错的
因为是写死的所以需要修改重启
测试输入num为0
后端报错
查看数据库数据
并没有插入,到此单项目多数据源分布式事物问题解决,开心...
作者:彼岸舞
时间:2021\01\28
内容关于:SpringBoot
本文来源于网络,只做技术分享,一概不负任何责任