1、springboot2.6+Mybatis静态多数据源(集成JTA(Atomikos案例)实现分布式事务控制)
2、springboot2.6+Mybatis动态多数据源AOP切换(AbstractRoutingDataSource)
3、springboot2.6+Mybatis注解多数据源使用dynamic-datasource-spring-boot-starter为依赖
前两篇博客介绍了用基本的方式做多数据源,可以应对一般的情况,但是遇到一些复杂的情况就需要扩展下功能了,比如:动态增减数据源、数据源分组,纯粹多库 读写分离 一主多从、从其他数据库或者配置中心读取数据源等等。其实就算没有这些需求,使用这个实现多数据源也比之前使用AbstractRoutingDataSource要便捷的多。
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
github: https://github.com/baomidou/dynamic-datasource-spring-boot-starter
文档: https://github.com/baomidou/dynamic-datasource-spring-boot-starter/wiki
通过yml配置好数据源com.baomidou dynamic-datasource-spring-boot-starter 3.1.0 org.springframework.boot spring-boot-starter-aop 2.3.3.RELEASE
spring:
datasource:
dynamic:
primary: master1
strict: false
datasource:
master1:
url: jdbc:mysql://127.0.0.1:3306/master1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: andrew
driver-class-name: com.mysql.cj.jdbc.Driver
master2:
url: jdbc:mysql://127.0.0.1:3306/master2?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8
username: root
password: andrew
driver-class-name: com.mysql.cj.jdbc.Driver
master3:
url: jdbc:oracle:thin:@10.132.212.63:1688:TESTDB
username: flx
password: flx202108
driver-class-name: oracle.jdbc.OracleDriver
logging:
level:
com.xkcoding: debug
com.xkcoding.orm.mybatis.mapper: trace
server:
port: 8080
# servlet:
# context-path: /demo
mybatis:
type-aliases-package: com.orm.mybatis.dsannotation.entity
mapper-locations: classpath:mapper/*/*.xml
configuration:
map-underscore-to-camel-case: true
service层
里面在想要切换数据源的方法上加上@DS注解就行了,也可以加在整个service层上,方法上的注解优先于类上注解
@Service
public class UserServiceImpl {
@Resource
private UserMapper1 userMapper1;
@Resource
private UserMapper2 userMapper2;
@Resource
private AsusPoInfoMapper3 asusPoInfoMapper3;
@DS("master1")
public List findAllUser(){
List list = userMapper1.selectAllUser();
return list;
}
@DS("master2")
public List findAllUser1(){
List list = userMapper2.selectAllUser();
return list;
}
public User findUserById(Long id){
return userMapper1.selectUserById(id);
}
@Transactional //与dynamic不同的是,这两个注解可以一起使用会先切换数据源再事务
@DS("master1") //与dynamic不同的是,这两个注解可以一起使用会先切换数据源再事务
public void insertUser1(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = simpleDateFormat.format(new Date());
String UUID = java.util.UUID.randomUUID().toString().substring(0,5);
User user = User.builder().email("andrew@qq.com"+UUID).name("andrew"+UUID).password("123456"+UUID).phoneNumber("123"+UUID)
.lastUpdateTime(date).createTime(date).status(0).salt("password"+UUID).build();
userMapper1.saveUser(user);
}
@Transactional
@DS("master2")
public void insertUser2(){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String date = simpleDateFormat.format(new Date());
String UUID = java.util.UUID.randomUUID().toString().substring(0,5);
User user = User.builder().email("andrew@qq.com"+UUID).name("andrew"+UUID).password("123456"+UUID).phoneNumber("123"+UUID)
.lastUpdateTime(date).createTime(date).status(0).salt("password"+UUID).build();
userMapper2.saveUser(user);
}
public void testTransitional() {
((UserServiceImpl)AopContext.currentProxy()).insertUser1();
((UserServiceImpl)AopContext.currentProxy()).insertUser2();
((UserServiceImpl)AopContext.currentProxy()).insertOracle();
}
@DS("master3")
public List selectOracle(){
return asusPoInfoMapper3.selectAllAsusPoInfo();
}
@DS("master3")//与dynamic不同的是,这两个注解可以一起使用会先切换数据源再事务
@Transactional
public void insertOracle(){
AsusPoInfo asusPoInfo = AsusPoInfo.builder().id(java.util.UUID.randomUUID().toString().substring(0,20))
.woNo("andrew").po("123456").poLine("poline").cPo("cpo123456").shipType("Direct").build();
asusPoInfoMapper3.insertAsusPoInfo(asusPoInfo);
}
}
测试多数据源回滚
package com.orm.mybatis.dsannotation;
import com.orm.mybatis.dsannotation.entity.AsusPoInfo;
import com.orm.mybatis.dsannotation.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import com.orm.mybatis.dsannotation.serviceImpl.UserServiceImpl;
import javax.annotation.Resource;
import java.util.List;
@SpringBootTest
class SpringbootMybatisDsannotationDatasourceApplicationTests {
//事务测试
@Resource
private UserServiceImpl userService;
@Test
void contextLoads1() {
List list = userService.selectOracle();
System.out.println(list);
}
@Test
void contextLoads2() {
List list = userService.findAllUser();
System.out.println(list);
}
@Test
void contextLoads3() {
List list = userService.findAllUser();
List list1 = userService.findAllUser1();
List list2 = userService.selectOracle();
list.addAll(list1);
System.out.println(list);
System.out.println(list2);
}
@Test
void contextLoads4() {
userService.testTransitional();
}
}
方案的权衡切换数据源成功,而且事务能回滚,但如果是多数据源事务,只能回滚报错的数据源的事务。
- 静态多数据源方案优势在于配置简单并且对业务代码的入侵性极小,缺点也显而易见:我们需要在系统中占用一些资源,而这些资源并不是一直需要,一定程度上会造成资源的浪费。如果你需要在一段业务代码中同时使用多个数据源的数据又要去考虑操作的原子性(事务)可以用spring的jta实现事务,那么这种方案无疑会适合你。(aop和dynamic)动态数据源(AbstractRoutingDataSource)方案配置上看起来配置会稍微复杂一些,但是很好的符合了“即拿即用,即用即还”的设计原则,我们把多个数据源看成了一个池子,然后进行消费。它的缺点正如上文所暴露的那样:我们往往需要在事务的需求下做出妥协。而且由于需要切换环境上下文,在高并发量的系统上进行资源竞争时容易发生死锁等活跃性问题。我们常用它来进行数据库的“读写分离”,不需要在一段业务中同时操作多个数据源。这种动态形式并不能用spring的jta实现,而且其他实现方式(seata等)虽然可以实现,但配置复杂且实用度不高。如果需要使用事务,一定记得使用分布式事务进行Spring自带事务管理的替换,否则将无法进行一致性控制。写到这里本文也就结束,好久没有撰写文章很多东西考虑不是很详尽,谢谢批评指正!
springboot2.6+mybatis
https://gitee.com/liuweiqiang12/springboot-mybatis-dynamic-datasource
springboot2.6+mybatis-plus
https://gitee.com/liuweiqiang12/springboot-mybatis-plus-dynamic-datasource



