springboot整合mybatis连接多个数据库
数据库准备
DROP TABLE IF EXISTS `order_exp`;
CREATE TABLE `order_exp` (
`id` bigint(22) NOT NULL AUTO_INCREMENT COMMENT '订单的主键',
`order_no` varchar(50) NOT NULL COMMENT '订单的编号',
`order_note` varchar(100) NOT NULL COMMENT '订单的说明',
`insert_time` datetime NOT NULL COMMENT '插入订单的时间',
`expire_duration` bigint(22) NOT NULL COMMENT '订单的过期时长,单位秒',
`expire_time` datetime NOT NULL COMMENT '订单的过期时间',
`order_status` smallint(6) NOT NULL DEFAULT '0' COMMENT '订单的状态,0:未支付;1:已支付;-1:已过期,关闭',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8;
gradle依赖
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.9'
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.0'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.springframework.boot:spring-boot-starter-test'
}
核心方法
多数据源和枚举类
public class DBContextHolder {
/*保存系统中存在的数据源的标识符,然后通过该标识符定位到实际的数据源实体*/
private static final ThreadLocal<DBTypeEnum> contextHolder
= new ThreadLocal<DBTypeEnum>();
public static void set(DBTypeEnum dbTypeEnum){
contextHolder.set(dbTypeEnum);
}
public static DBTypeEnum get(){
return contextHolder.get();
}
public static void master(){
set(DBTypeEnum.MASTER);
System.out.println("切换到主库-----------------------");
}
public static void slave(){
set(DBTypeEnum.SLAVE);//轮询
System.out.println("切换到从库-----------------------");
}
}
public enum DBTypeEnum {
MASTER,SLAVE;
}
AOP定义切点、切面,Master注解的类和增删改的方法都走主库,非master注解和一些查询方法走从库。
@Aspect
@Component
public class DataSourceAop {
/*从库的切点,没有标注Master注解,并且方法名为select和get开头的方法,走从库*/
@Pointcut("!@annotation(com.sss.annotation.Master) " +
"&& (execution(* com.sss.service..*.select*(..)) " +
"|| execution(* com.sss.service..*.get*(..))" +
"|| execution(* com.sss.service..*.find*(..))" +
"|| execution(* com.sss.service..*.query*(..)))")
public void slavePointcut() {
}
/*主库的切点,或者标注了Master注解或者方法名为insert、update等开头的方法,走主库*/
@Pointcut("@annotation(com.sss.annotation.Master) " +
"|| execution(* com.sss.service..*.insert*(..)) " +
"|| execution(* com.sss.service..*.add*(..)) " +
"|| execution(* com.sss.service..*.update*(..)) " +
"|| execution(* com.sss.service..*.edit*(..)) " +
"|| execution(* com.sss.service..*.delete*(..)) " +
"|| execution(* com.sss.service..*.remove*(..))")
public void masterPointcut() {
}
@Before("slavePointcut()")
public void slave() {
DBContextHolder.slave();
}
@Before("masterPointcut()")
public void master() {
DBContextHolder.master();
}
}
```java
public class MyRoutingDataSource extends AbstractRoutingDataSource {
/*在运行时, 根据某种规则,比如key值,比如当前线程的id来动态切换到真正的DataSource上*/
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
多数据源的配置
spring:
datasource:
master:
jdbc-url: jdbc:mysql://192.168.0.103:3306/test?serverTimezone=GMT%2b8&autoReconnect=true&useUnicode=true&characterEncoding=utf-8&autocommit=false
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
slave1:
jdbc-url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2b8&autoReconnect=true&useUnicode=true&characterEncoding=utf-8&autocommit=false
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
@Configuration
public class DataSourceConfig {
/*主库的数据源*/
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return create().build();
}
/*从库1的数据源*/
@Bean
@ConfigurationProperties("spring.datasource.slave1")
public DataSource slave1DataSource() {
return create().build();
}
/*自定义数据源,内部持有了主库和从库的数据源,
通过某种机制让应用程序在进行数据读写时,按业务情况走主库或者从库*/
@Bean
public DataSource myRoutingDataSource(
@Qualifier("masterDataSource")DataSource masterDataSource,
@Qualifier("slave1DataSource")DataSource slaveDataSource){
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.MASTER,masterDataSource);
targetDataSources.put(DBTypeEnum.SLAVE,slaveDataSource);
MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
myRoutingDataSource.setTargetDataSources(targetDataSources);
/*当执行的方法没有被Aop拦截时,缺省使用的数据源*/
myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
return myRoutingDataSource;
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringbootMultipleDatasourceApplication.class)
public class SpringbootMultipleDatasourceApplicationTests {
@Autowired
private ProcessOrder processOrder;
@Test
public void insertOrders() {
processOrder.insertOrders(5);
}
@Test
public void queryOrders() {
processOrder.findOrders();
processOrder.findOrdersMaster();
}
}
测试结果
插入结果:
切换到主库-----------------------
2019-10-19 12:49:24.728 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Preparing: insert into order_exp (order_no, order_note, insert_time, expire_duration,expire_time, order_status) values (?, ?, now(), ?, DATE_ADD(now(),INTERVAL ? SECOND), ?)
2019-10-19 12:49:24.779 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Parameters: DD00_16S(String), 海王5排16号,过期时长:DD00_16S(String), 16(Long), 16(Long), 0(Short)
2019-10-19 12:49:24.824 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : <== Updates: 1
2019-10-19 12:49:24.845 INFO 5036 --- [ Test worker] com.sss.service.ProcessOrder : 保存订单到DB:DD00_16S
2019-10-19 12:49:24.846 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Preparing: insert into order_exp (order_no, order_note, insert_time, expire_duration,expire_time, order_status) values (?, ?, now(), ?, DATE_ADD(now(),INTERVAL ? SECOND), ?)
2019-10-19 12:49:24.847 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Parameters: DD00_24S(String), 海王5排24号,过期时长:DD00_24S(String), 24(Long), 24(Long), 0(Short)
2019-10-19 12:49:24.891 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : <== Updates: 1
2019-10-19 12:49:24.892 INFO 5036 --- [ Test worker] com.sss.service.ProcessOrder : 保存订单到DB:DD00_24S
2019-10-19 12:49:24.892 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Preparing: insert into order_exp (order_no, order_note, insert_time, expire_duration,expire_time, order_status) values (?, ?, now(), ?, DATE_ADD(now(),INTERVAL ? SECOND), ?)
2019-10-19 12:49:24.893 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Parameters: DD00_9S(String), 海王5排9号,过期时长:DD00_9S(String), 9(Long), 9(Long), 0(Short)
2019-10-19 12:49:24.936 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : <== Updates: 1
2019-10-19 12:49:24.937 INFO 5036 --- [ Test worker] com.sss.service.ProcessOrder : 保存订单到DB:DD00_9S
2019-10-19 12:49:24.938 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Preparing: insert into order_exp (order_no, order_note, insert_time, expire_duration,expire_time, order_status) values (?, ?, now(), ?, DATE_ADD(now(),INTERVAL ? SECOND), ?)
2019-10-19 12:49:24.939 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Parameters: DD00_23S(String), 海王5排23号,过期时长:DD00_23S(String), 23(Long), 23(Long), 0(Short)
2019-10-19 12:49:24.981 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : <== Updates: 1
2019-10-19 12:49:24.982 INFO 5036 --- [ Test worker] com.sss.service.ProcessOrder : 保存订单到DB:DD00_23S
2019-10-19 12:49:24.983 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Preparing: insert into order_exp (order_no, order_note, insert_time, expire_duration,expire_time, order_status) values (?, ?, now(), ?, DATE_ADD(now(),INTERVAL ? SECOND), ?)
2019-10-19 12:49:24.983 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : ==> Parameters: DD00_17S(String), 海王5排17号,过期时长:DD00_17S(String), 17(Long), 17(Long), 0(Short)
2019-10-19 12:49:25.027 DEBUG 5036 --- [ Test worker] c.s.dao.OrderExpMapper.insertDelayOrder : <== Updates: 1
2019-10-19 12:49:25.028 INFO 5036 --- [ Test worker] com.sss.service.ProcessOrder : 保存订单到DB:DD00_17S
2019-10-19 12:49:25.173 INFO 5036 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2019-10-19 12:49:25.174 INFO 5036 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2019-10-19 12:49:25.295 INFO 5036 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
BUILD SUCCESSFUL in 15s
查询结果:
切换到从库-----------------------
2019-10-19 12:50:03.163 INFO 19608 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-10-19 12:50:05.560 INFO 19608 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-10-19 12:50:05.573 DEBUG 19608 --- [ Test worker] c.s.d.OrderExpMapper.selectUnPayOrders : ==> Preparing: select id, order_no, order_note, insert_time, expire_duration, expire_time, order_status from order_exp where order_status = 0
2019-10-19 12:50:05.617 DEBUG 19608 --- [ Test worker] c.s.d.OrderExpMapper.selectUnPayOrders : ==> Parameters:
2019-10-19 12:50:05.660 DEBUG 19608 --- [ Test worker] c.s.d.OrderExpMapper.selectUnPayOrders : <== Total: 0
2019-10-19 12:50:05.676 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : 发现了表中还有[0]个未到期未支付的订单!
切换到主库-----------------------
2019-10-19 12:50:05.676 INFO 19608 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Starting...
2019-10-19 12:50:06.024 INFO 19608 --- [ Test worker] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Start completed.
2019-10-19 12:50:06.025 DEBUG 19608 --- [ Test worker] c.s.d.OrderExpMapper.selectUnPayOrders : ==> Preparing: select id, order_no, order_note, insert_time, expire_duration, expire_time, order_status from order_exp where order_status = 0
2019-10-19 12:50:06.025 DEBUG 19608 --- [ Test worker] c.s.d.OrderExpMapper.selectUnPayOrders : ==> Parameters:
2019-10-19 12:50:06.053 DEBUG 19608 --- [ Test worker] c.s.d.OrderExpMapper.selectUnPayOrders : <== Total: 10
2019-10-19 12:50:06.053 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : 发现了表中还有[10]个未到期未支付的订单!
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=16, orderNo=DD00_12S, orderNote=海王5排12号,过期时长:DD00_12S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=17, orderNo=DD00_22S, orderNote=海王5排22号,过期时长:DD00_22S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=18, orderNo=DD00_15S, orderNote=海王5排15号,过期时长:DD00_15S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=19, orderNo=DD00_17S, orderNote=海王5排17号,过期时长:DD00_17S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=20, orderNo=DD00_18S, orderNote=海王5排18号,过期时长:DD00_18S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=21, orderNo=DD00_16S, orderNote=海王5排16号,过期时长:DD00_16S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=22, orderNo=DD00_24S, orderNote=海王5排24号,过期时长:DD00_24S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=23, orderNo=DD00_9S, orderNote=海王5排9号,过期时长:DD00_9S, orderStatus=0]
2019-10-19 12:50:06.054 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=24, orderNo=DD00_23S, orderNote=海王5排23号,过期时长:DD00_23S, orderStatus=0]
2019-10-19 12:50:06.055 INFO 19608 --- [ Test worker] com.sss.service.ProcessOrder : OrderExp [id=25, orderNo=DD00_17S, orderNote=海王5排17号,过期时长:DD00_17S, orderStatus=0]
2019-10-19 12:50:06.128 INFO 19608 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
2019-10-19 12:50:06.130 INFO 19608 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2019-10-19 12:50:06.146 INFO 19608 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2019-10-19 12:50:06.146 INFO 19608 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown initiated...
2019-10-19 12:50:06.388 INFO 19608 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-2 - Shutdown completed.
BUILD SUCCESSFUL in 14s
源代码
https://github.com/smith5623/springboot_demos/tree/master/springboot-multiple-datasource