通过启动多个SqlSessionFactoryBean,每个SqlSessionFactoryBean对应一个datasource和指定位置的mapper.xml文件,就可以实现多个数据源了。而不用切换数据源,不用实现AbstractRoutingDataSource
在多数据源下,涉及到多个数据库的写入。Spring的声明式事务在一次请求线程中只能对一个数据源进行控制。因为一个DataSourceTransactionManager无法完成对多数据源的控制
需要多个DataSourceTransactionManager可以完成,但@Transactional注解无法管理多个数据源,这里我们通过变通的方式让@Transactional管理多个DataSourceTransactionManager
将每个datasource都分别和一个DataSourceTransactionManager进行绑定。然后可以通过嵌套事务的方式进行调用
分别创建write_db1.user和write_db2.user
mysql> create database write_db1;
Query OK, 1 row affected (0.14 sec)
mysql> create database write_db2;
Query OK, 1 row affected (0.01 sec)
mysql> create table write_db1.user (
-> id bigint(20) auto_increment not null comment '主键ID',
-> name varchar(30) null default null comment '姓名',
-> primary key (id)
-> );
Query OK, 0 rows affected, 1 warning (0.29 sec)
mysql>
mysql> create table write_db2.user (
-> id bigint(20) auto_increment not null comment '主键ID',
-> name varchar(30) null default null comment '姓名',
-> primary key (id)
-> );
Query OK, 0 rows affected, 1 warning (0.04 sec)
mysql>
mysql
mysql-connector-java
8.0.31
com.alibaba
druid
1.2.15
com.baomidou
mybatis-plus-boot-starter
3.5.2
org.springframework.boot
spring-boot-starter-aop
指定了datasource1和datasource2两个DataSource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 用于读的数据库
spring.datasource.datasource1.url=jdbc:mysql://192.168.28.12:3306/write_db1
spring.datasource.datasource1.username=root
spring.datasource.datasource1.password=Root_123
spring.datasource.datasource1.driver-class-name=com.mysql.cj.jdbc.Driver
# 用于写的数据库
spring.datasource.datasource2.url=jdbc:mysql://192.168.28.12:3306/write_db2
spring.datasource.datasource2.username=root
spring.datasource.datasource2.password=Root_123
spring.datasource.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver
其实就是将datasource1和指定的Mapper接口、Mapper.xml文件进行绑定。然后将datasource1TransactionManager和datasource1绑定,datasource1TransactionTemplate和datasource1TransactionManager进行绑定
说明:
package com.hh.springboottest.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = {"com.hh.springboottest.mapper.datasource1"},
sqlSessionFactoryRef = "datasource1SqlSessionFactory")
public class DataSource1Config {
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
// 向IOC容器添加name = dataSource1的DataSource
@Bean
public DataSource dataSource1() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Bean
@Primary
public SqlSessionFactory datasource1SqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean datasource1SqlSessionFactoryBean = new SqlSessionFactoryBean();
// 指定数据源
datasource1SqlSessionFactoryBean.setDataSource(dataSource);
// 指定数据源对应的mapper.xml文件
datasource1SqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/datasource1/*.xml"));
return datasource1SqlSessionFactoryBean.getObject();
}
@Bean
@Primary
public DataSourceTransactionManager datasource1TransactionManager() {
DataSourceTransactionManager datasource1TransactionManager =
new DataSourceTransactionManager();
datasource1TransactionManager.setDataSource(dataSource1());
return datasource1TransactionManager;
}
@Bean
public TransactionTemplate datasource1TransactionTemplate() {
return new TransactionTemplate(datasource1TransactionManager());
}
}
package com.hh.springboottest.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = {"com.hh.springboottest.mapper.datasource2"},
sqlSessionFactoryRef = "datasource2SqlSessionFactory")
public class DataSource2Config {
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
// 向IOC容器添加name = dataSource2的DataSource
@Bean
public DataSource dataSource2() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Bean
public SqlSessionFactory datasource2SqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean datasource2SqlSessionFactoryBean = new SqlSessionFactoryBean();
// 指定数据源
datasource2SqlSessionFactoryBean.setDataSource(dataSource);
// 指定数据源对应的mapper.xml文件
datasource2SqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/datasource2/*.xml"));
return datasource2SqlSessionFactoryBean.getObject();
}
@Bean
public DataSourceTransactionManager datasource2TransactionManager() {
DataSourceTransactionManager datasource2TransactionManager =
new DataSourceTransactionManager();
datasource2TransactionManager.setDataSource(dataSource2());
return datasource2TransactionManager;
}
@Bean
public TransactionTemplate datasource2TransactionTemplate() {
return new TransactionTemplate(datasource2TransactionManager());
}
}
package com.hh.springboottest.myController;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class User {
private Long id;
private String name;
}
Datasource1UserMapper.java
package com.hh.springboottest.mapper.datasource1;
import com.hh.springboottest.myController.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface Datasource1UserMapper {
public void saveUser(User user);
}
Datasource2UserMapper.java
package com.hh.springboottest.mapper.datasource2;
import com.hh.springboottest.myController.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface Datasource2UserMapper {
public void saveUser(User user);
}
resources/mapper/datasource1/Datasource1UserMapper.xml
INSERT INTO user(id, name) VALUES(#{id}, #{name})
resources/mapper/datasource2/Datasource2UserMapper.xml
INSERT INTO user(id, name) VALUES(#{id}, #{name})
Service接口实现
package com.hh.springboottest.service;
import com.hh.springboottest.myController.User;
public interface UserService {
// 向datasource1的数据库插入数据
public void datasource1SaveUser(User user);
// 向datasource2的数据库插入数据
public void datasource2SaveUser(User user);
// 用于调用datasource1SaveUser和datasource2SaveUser
public void saveMultiUser();
// 嵌套在saveMultiUser里面进行调用
public void saveMultiUserInner();
}
ServiceImpl实现类
说明:
package com.hh.springboottest.service.impl;
import com.hh.springboottest.mapper.datasource1.Datasource1UserMapper;
import com.hh.springboottest.mapper.datasource2.Datasource2UserMapper;
import com.hh.springboottest.myController.User;
import com.hh.springboottest.service.UserService;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserServiceImpl implements UserService {
@Autowired
Datasource1UserMapper datasource1UserMapper;
@Autowired
Datasource2UserMapper datasource2UserMapper;
@Autowired
TransactionTemplate datasource1TransactionTemplate;
@Autowired
TransactionTemplate datasource2TransactionTemplate;
/**
* @Description: 向datasource1的数据库插入数据
*
* @Params: [user]
* @Return: void
*/
@Override
public void datasource1SaveUser(User user) {
datasource1UserMapper.saveUser(user);
}
/**
* @Description: 向datasource2的数据库插入数据
*
* @Params: [user]
* @Return: void
*/
@Override
public void datasource2SaveUser(User user) {
datasource2UserMapper.saveUser(user);
}
/**
* @Description: 用于调用datasource1SaveUser和datasource2SaveUser
*
* 编程式事务方式。被0除,回滚所以插入的数据
*
* @Params: []
* @Return: void
*/
// public void saveMultiUser() {
// datasource1TransactionTemplate.execute((datasource1Status)->{
// datasource2TransactionTemplate.execute((datasource2Status)->{
// try {
// datasource1SaveUser(new User(1L, "read_name1"));
// datasource2SaveUser(new User(1L, "write_name1"));
// datasource1SaveUser(new User(2L, "read_name2"));
// datasource2SaveUser(new User(2L, "write_name2"));
// Integer d = 1 / 0;
// } catch (Exception e) {
// e.printStackTrace();
// datasource1Status.setRollbackOnly();
// datasource2Status.setRollbackOnly();
// return false;
// }
// return true;
// });
// return true;
// });
// }
/**
* @Description: 用于调用datasource1SaveUser和datasource2SaveUser
*
* 声明式事务方式。被0除,回滚所以插入的数据
*
* @Params: []
* @Return: void
*/
@Transactional(transactionManager = "datasource1TransactionManager")
public void saveMultiUser() {
UserService currentUserService = (UserService) AopContext.currentProxy();
currentUserService.saveMultiUserInner();
}
/**
* @Description: 嵌套在saveMultiUser里面进行调用
*
* @Params: []
* @Return: void
*/
@Transactional(transactionManager = "datasource2TransactionManager")
public void saveMultiUserInner() {
datasource1SaveUser(new User(1L, "read_name1"));
datasource2SaveUser(new User(1L, "write_name1"));
datasource1SaveUser(new User(2L, "read_name2"));
datasource2SaveUser(new User(2L, "write_name2"));
Integer myDiv = 1 / 0;
}
}
说明:
package com.hh.springboottest;
import com.hh.springboottest.myController.User;
import com.hh.springboottest.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Slf4j
@SpringBootTest
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
public class MyApplicationTest {
@Autowired
UserService userService;
@Test
public void saveMultiUserTest() {
userService.saveMultiUser();
}
}
运行程序,结果如下:
2022-12-13 22:04:29.857 INFO 21468 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
java.lang.ArithmeticException: / by zero
at com.hh.springboottest.service.impl.UserServiceImpl.saveMultiUserInner(UserServiceImpl.java:117)
......省略部分......
数据库未插入一条数据,因为回滚了
这里只做了一部分的记录,并未运行进行测试
自定注解,然后通过多线程的方式执行多个事务方法
package com.hh.springboottest.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class MultiTransactionAop {
// ComboTransaction类需要自己定义,还未定义
private final ComboTransaction comboTransaction;
@Autowired
public MultiTransactionAop(ComboTransaction comboTransaction) {
this.comboTransaction = comboTransaction
}
@Pointcut("within(com.hh.springboottest.service.impl.*)")
public void pointCut() {
}
@Around("pointCut() && @annotation(multiTransactional)")
public Object inMultiTransactions(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {
return comboTransaction.inCombinedTx(() -> {
try {
return pjp.proceed(); // 执行目标方法
} catch (Throwable throwable) {
if (throwable instanceof RuntimeException) {
throw (RuntimeException) throwable;
}
throw new RuntimeException(throwable);
}
}, multiTransactional.value());
}
}