目录
多数据源实现
yml配置文件
配置类
业务代码
案例演示
多数据源事务控制
第一种方式
第二种方式
第三种方式
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
datasource1:
url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: 123456
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
driver-class-name: com.mysql.cj.jdbc.Driver
datasource2:
url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
username: root
password: 123456
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 8081
@Configuration
// 集成mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "com.datasource.dynamic.mybatis.mapper.r",
sqlSessionFactoryRef="rSqlSessionFactory")
public class RMyBatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory rSqlSessionFactory()
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 指定主库
sessionFactory.setDataSource(dataSource2());
// 指定主库对应的mapper.xml文件
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/r/*.xml"));*/
return sessionFactory.getObject();
}
}
@Configuration
// 集成mybatis:
// 1. 指定扫描的mapper接口包(主库)
// 2. 指定使用sqlSessionFactory是哪个(主库)
@MapperScan(basePackages = "com.datasource.dynamic.mybatis.mapper.w",
sqlSessionFactoryRef="wSqlSessionFactory")
public class WMyBatisConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1() {
// 底层会自动拿到spring.datasource中的配置, 创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public SqlSessionFactory wSqlSessionFactory()
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 指定主库
sessionFactory.setDataSource(dataSource1());
// 指定主库对应的mapper.xml文件
/*sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/w/*.xml"));*/
return sessionFactory.getObject();
}
}
理论上来讲,通过mybatis实现多数据源,就是创建出多套mybatis的数据库配置,上述通过两个文件配置了两个不同的数据源。每个数据源负责的mapper文件也是不同的,具体如下:
public interface RFriendMapper {
@Select("SELECT * FROM Frend")
List list();
@Insert("INSERT INTO frend(`name`) VALUES (#{name})")
void save(Friend frend);
}
public interface WFriendMapper {
@Select("SELECT * FROM friend")
List list();
@Insert("INSERT INTO friend(`name`) VALUES (#{name})")
void save(Friend friend);
}
控制层
@RestController
@RequestMapping("friend")
@Slf4j
public class FriendController {
@Autowired
private FriendService friendService;
@GetMapping(value = "select")
public List select(){
return friendService.list();
}
@GetMapping(value = "insert")
public void insert(){
Friend friend = new Friend();
friend.setName("张三");
friendService.save(friend);
}
}
服务层代码
@Service
public class FriendImplService implements FriendService {
@Autowired
private RFriendMapper rFrendMapper;
@Autowired
private WFriendMapper wFrendMapper;
// 读-- 读库
@Override
public List list() {
return rFrendMapper.list();
}
// 保存-- 写库
@Override
public void save(Friend frend) {
wFrendMapper.save(frend);
}
}
dao层代码上边已经贴过了
执行insert方法,datasource1中的friend表新增了数据
执行select方法,查询datasource2中的friend表为空
对于多数据源情况,单一的事务管理器是无法切换的,@Transactionnal是无法管理多个数据源的,需要配置多个事务管理器TransactionManager。
首先启动类开启事务支持
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
@EnableTransactionManagement // 开启事务
public class DynamicMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicMybatisApplication.class, args);
}
}
给之前的两个数据源都加上对应的事务管理器
// datasource1的事务管理器
@Bean
@Primary
public DataSourceTransactionManager wTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource1());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate wTransactionTemplate(){
return new TransactionTemplate(wTransactionManager());
}
// datasource2的事务管理器
@Bean
public DataSourceTransactionManager rTransactionManager(){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource2());
return dataSourceTransactionManager;
}
@Bean
public TransactionTemplate rTransactionTemplate(){
return new TransactionTemplate(rTransactionManager());
}
控制层代码
@GetMapping(value = "save")
public void save() throws Exception {
Friend friend = new Friend();
friend.setName("张三");
friendService.saveAll(friend);
}
服务器层代码
@Autowired
TransactionTemplate wTransactionTemplate;
@Autowired
TransactionTemplate rTransactionTemplate;
public void saveAll(Friend frend) {
wTransactionTemplate.execute((wstatus) -> {
rTransactionTemplate.execute((rstatus) -> {
try {
saveW(frend);
saveR(frend);
//int a = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
wstatus.setRollbackOnly();
rstatus.setRollbackOnly();
return false;
}
return true;
});
return true;
});
}
以上方法通过编程式事务完成事务一致性。
@Transactional(transactionManager = "wTransactionManager")
public void saveAll(Friend frend) throws Exception {
FriendService frendService = (FriendService) AopContext.currentProxy();
frendService.saveAllR(frend);
}
@Transactional(transactionManager = "rTransactionManager")
public void saveAllR(Friend frend) {
saveW(frend);
saveR(frend);
int a = 1 / 0;
}
此种方式需要在启动类上增加注解
@EnableAspectJAutoProxy(exposeProxy = true)
通过上述这种方式,读库写库的异常都会回滚。
通过新增注解的方式,在业务方法上将相关事务管理器写入,在执行方法时通过aop扫描出相关的事务管理器,然后代理执行具体业务方法,当出现异常时,通过循环事务管理器来回滚事务。
具体代码如下:
自定义注解及aop
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {
String[] value() default {};
}
@Aspect
@Component
public class MultiTransactionAop {
private final ComboTransaction comboTransaction;
@Autowired
public MultiTransactionAop(ComboTransaction comboTransaction) {
this.comboTransaction = comboTransaction;
}
@Pointcut("within(com.tuling.datasource.dynamic.mybatis.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());
}
}
aop方法中用到相关类如下
@Component
public class ComboTransaction {
@Autowired
private Db1TxBroker db1TxBroker;
@Autowired
private Db2TxBroker db2TxBroker;
public V inCombinedTx(Callable callable, String[] transactions) {
if (callable == null) {
return null;
}
// 相当于循环 [wTransactionManager,wTransactionManager]
Callable combined = Stream.of(transactions)
.filter(ele -> !StringUtils.isEmpty(ele))
.distinct()
.reduce(callable, (r, tx) -> {
switch (tx) {
case DbTxConstants.DB1_TX:
return () -> db1TxBroker.inTransaction(r);
case DbTxConstants.DB2_TX:
return () -> db2TxBroker.inTransaction(r);
default:
return null;
}
}, (r1, r2) -> r2);
try {
return combined.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Component
public class Db1TxBroker {
@Transactional(DbTxConstants.DB1_TX)
public V inTransaction(Callable callable) {
try {
return callable.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Component
public class Db2TxBroker {
@Transactional(DbTxConstants.DB2_TX)
public V inTransaction(Callable callable) {
try {
return callable.call();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public class DbTxConstants {
public static final String DB1_TX = "wTransactionManager";
public static final String DB2_TX = "rTransactionManager";
}
业务代码如下
控制层代码
@GetMapping(value = "save")
public void save() throws Exception {
Friend friend = new Friend();
friend.setName("张三");
friendService.saveAll(friend);
}
服务层代码
@Autowired
private RFriendMapper rFrendMapper;
@Autowired
private WFriendMapper wFrendMapper;
@Override
@MultiTransactional(value = {DbTxConstants.DB1_TX, DbTxConstants.DB2_TX})
public void saveAll(Friend frend) {
saveW(frend);
saveR(frend);
int a = 1 / 0;
}
// 保存-- 写库
@Override
public void saveW(Friend frend) {
frend.setName("张三");
wFrendMapper.save(frend);
}
// 保存-- 读库
@Override
public void saveR(Friend frend) {
frend.setName("张三");
rFrendMapper.save(frend);
}
案例演示
先清空数据库,然后执行save方法后,报错如下
数据库friend表为空,事务回滚成功