springboot+atomikos+多数据源管理事务(mysql 8.0)
jta:Java Transaction API,即是java中对事务处理的api 即 api即是接口的意思
atomikos:Atomikos TransactionsEssentials 是一个为Java平台提供增值服务的并且开源类事务管理器
1.结构
2.pom依赖
我这里使用本地数据库是mysql8,
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <!--<version>2.0.0.RELEASE</version>--> <version>2.1.0.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version><!-- 1.3.0以上的版本没有@MapperScan以及@Select注解 --> </dependency> <!-- automatic+jta的分布式事务管理 --> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jta-atomikos --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <!--boot 2.1默认 mysql8的版本; boot 2.0默认mysql5版本--> <version>8.0.13</version> <!--<version>5.1.46</version>--> <!--<scope>runtime</scope>--> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> <version>1.18.2</version> </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-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies>
3.创建本地数据库+表
4.application.yml
server: port: 8080 servlet: # # 项目contextPath context-path: /manyDatasource spring: application: name: manyDatasource datasource: # spring.datasource.test1 # druid: test1: # jdbc-url,url,jdbcurl哪个合适用哪个 jdbcurl: jdbc:mysql://localhost:3306/test1?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 username: root password: 123456 initial-size: 1 min-idle: 1 max-active: 20 test-on-borrow: true # driver-class-name: com.mysql.jdbc.Driver # 下面是最新的mysql8版本推荐的驱动 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 下面是另外加的配置数据源的参数 minPoolSize: 3 maxPoolSize: 25 maxLifetime: 20000 borrowConnectionTimeout: 30 loginTimeout: 30 maintenanceInterval: 60 maxIdleTime: 60 test2: jdbcurl: jdbc:mysql://localhost:3306/test2?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource minPoolSize: 3 maxPoolSize: 25 maxLifetime: 20000 borrowConnectionTimeout: 30 loginTimeout: 30 maintenanceInterval: 60 maxIdleTime: 60 mybatis: mapper-locations: classpath:mapper/*.xml #设置静态资源路径,多个以逗号分隔 spring.resources.static-locations: classpath:static/,file:static/ # 日志配置 logging: level: czs: debug org.springframework: WARN org.spring.springboot.dao: debug
5.实体类
ps.使用lombok插件挺方便的~ id数据库主键自增
@Data public class User { private Integer id; private String name; private long age; }
6.mapper接口
UserMapper1:
public interface UserMapper1 { // 查询语句 @Select("SELECT * FROM users WHERE NAME = #{name}") User findByName(@Param("name") String name); // 添加 @Insert("INSERT INTO users(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); }
UserMapper2:
public interface UserMapper2 { // 查询语句 @Select("SELECT * FROM users WHERE NAME = #{name}") User findByName(@Param("name") String name); // 添加 @Insert("INSERT INTO users(NAME, AGE) VALUES(#{name}, #{age})") int insert(@Param("name") String name, @Param("age") Integer age); }
7.service
ManyService1:
@Service public class ManyService1 { @Autowired private UserMapper1 userMapper1; @Autowired private UserMapper2 userMapper2; /* @Transactional(transactionManager = "test1TransactionManager",rollbackFor = Exception.class) public int insert(String name, Integer age) { int i = userMapper1.insert(name, age); System.out.println("userMapper1.insert结束~ :" + i); // int a = 1 / 0;//手动异常 return i; }*/ // 开启事务,由于使用jta+atomikos解决分布式事务,所以此处不必再指定事务 @Transactional public int insert(String name, Integer age) { int insert = userMapper1.insert(name, age); //int i = 1 / age;// 赋值age为0故意引发事务 return insert; } //http://localhost:8080/manyDatasource/insertDb1AndDb2?name=tom3&age=2 // 开启事务,由于使用jta+atomikos解决分布式事务,所以此处不必再指定事务 @Transactional public int insertDb1AndDb2(String name, Integer age) { int insert = userMapper1.insert(name, age); int insert2 = userMapper2.insert(name, age); int i = 1 / age;// 赋值age为0故意引发事务 return insert + insert2; } }
ManyService2:
@Service public class ManyService2 { @Autowired private UserMapper2 userMapper2; @Transactional(transactionManager = "test2TransactionManager",rollbackFor = Exception.class) public int insert(String name, Integer age) { int i = userMapper2.insert(name, age); System.out.println("userMapper2.insert结束~ :" + null); int a = 1 / 0;//手动异常 return i; } }
8.Controller
@RestController public class ManyController { @Autowired private ManyService1 manyService1; @Resource private ManyService2 manyService2; @RequestMapping(value = "datasource1") public int datasource1(String name, Integer age) { return manyService1.insert(name, age); } @RequestMapping(value = "datasource2") public int datasource2(String name, Integer age) { return manyService2.insert(name, age); } /** * @Param: * @Description: 这里测试两个service两个数据源的事务(不加上atomikos插件的情况下测试, *使用DataSource1Config和DataSource2Config 两个配置类, 关闭DBConfig1, DBConfig2和MyBatisConfig1, MyBatisConfig1两个类) * @Author: zyf 2019/5/10 */ //http://localhost:8080/manyDatasource/testManyTrans?name=tom4&age=2 @RequestMapping(value = "testManyTrans") public int testManyTrans(String name, Integer age) { int i = 0; int i1 = manyService1.insert(name, age); System.out.println("manyService1.insert :" + i1); /* 第二个事务中会手动造成一个异常~, 但是第一个事务执行完毕了,保存到了数据库 */ int i2 = manyService2.insert(name, age); System.out.println("manyService2.insert :" + i2); return i; } /** * @Param: * @Description: 这里测试使用atomikos插件测试多数据源事务 * @Author: zyf 2019/5/10 */ //http://localhost:8080/manyDatasource/insertDb1AndDb2?name=tom5&age=2 //http://localhost:8080/manyDatasource/insertDb1AndDb2?name=tom6&age=0 //测试除数为0后的事务管理 @RequestMapping(value = "insertDb1AndDb2") public int insertDb1AndDb2(String name, Integer age) { return manyService1.insertDb1AndDb2(name, age); } }
9.配置数据源(*******重点总是在最后********)
DBConfig1:
@Data @ConfigurationProperties(prefix = "spring.datasource.test1") // 注意这个前缀要和application.yml文件的前缀一样 public class DBConfig1 { // @Value("${mysql.datasource.test1.jdbcurl}") //@Value("${jdbcurl}") private String jdbcurl; //private String url; // 比如这个url在properties中是这样子的mysql.datasource.test1.username = root 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; }
DBConfig2:
@Data @ConfigurationProperties(prefix = "spring.datasource.test2")// 注意这个前缀要和application.yml文件的前缀一样 public class DBConfig2 { //@Value("${spring.datasource.test2.jdbcurl}") //@Value("${jdbcurl}") //private String url; private String jdbcurl; 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; }
上面两个配置类作用: 将application.yml配置文件中配置自动封装到实体类字段中,然后赋值给atomikos类型的数据源.(下面两个具体配置数据源)
MyBatisConfig1:
// 配置数据源 //@Bean(name = "testDataSource") //test1DataSource @Bean(name = "test1DataSource") //test1DataSource public DataSource testDataSource(DBConfig1 testConfig) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); //mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setUrl(testConfig.getJdbcurl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(testConfig.getPassword()); mysqlXaDataSource.setUser(testConfig.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); // 将本地事务注册到创 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); return bean.getObject(); } @Bean(name = "test1SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); }
MyBatisConfig2 :
@Configuration @MapperScan(basePackages = "czs.mapper2", sqlSessionTemplateRef = "test2SqlSessionTemplate") public class MyBatisConfig2 { // 配置数据源 @Bean(name = "test2DataSource") public DataSource testDataSource(DBConfig2 testConfig) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); //mysqlXaDataSource.setUrl(testConfig.getUrl()); mysqlXaDataSource.setUrl(testConfig.getJdbcurl()); 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); return bean.getObject(); } @Bean(name = "test2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate( @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
10.测试
http://localhost:8080/manyDatasource/insertDb1AndDb2?name=tom5&age=2
结果: test1和test2数据库都插入数据~
http://localhost:8080/manyDatasource/insertDb1AndDb2?name=tom6&age=0 (两个insert操作后,手动异常)
结果: test1和test2数据库都未插入数据~
GitHub传送门: https://github.com/ColoZhu/springbootmanyDatasource
参考出处: https://blog.csdn.net/qq_36138324/article/details/81612890