SpringBoot之解决整合多数据源分布式事物问题
SpringBoot之解决整合多数据源分布式事物问题
概念:
上一章只是解决了单事物问题,也就是说同时只能使用自己的数据源,并指定事物管理,才能使用,那么如果同时使用多个数据源,就会产生分布式事物问题
分布式事物问题分两种:
一种是这种一个项目多个数据源的分布式事物问题
还有一种就是多个项目多个数据源之间的分布式事物问题
这一章就来解决一下第一种一个项目多个数据源的分布式事物问题
编写接口:
这样的话就只能指定一个事物管理器,并不能两个数据库的事物都控制到,如果中间出现错误就会一个事物成功,一个事物失败,造成所谓的分布式事物问题
那么如何解决呢?
采用一个统一的事物管理器来同时管理多个数据源
解决方案:
采用jta-atomikos
添加Maven依赖
<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
本文来源于网络,只做技术分享,一概不负任何责任