SpringBoot 整合 MybatisPlus 读写分离实现demo
1. 思路
使用ThreadLocal
进行数据库切换,在枚举中配置数据源的名称,放到map中,调用数据源路由切换数据源,使用aop来进行拦截。
2. 实现
使用ThreadLocal
进行数据库切换:
package com.sql.readwrite.commons.mythread;
import com.sql.readwrite.commons.myenums.DBTypeEnum;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 通过ThreadLocal将数据源设置到每个线程的上下文
* @date 2020/11/16 9:10
*/
public class DBContextHolder {
private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
private static final AtomicInteger counter = new AtomicInteger(-1);
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("切换到master");
}
public static void slave(){
set(DBTypeEnum.SALVE);
System.out.println("切换到slave");
}
}
配置多条数据源:
spring:
datasource:
# 主库配置(写库)
master:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
jdbc-url: jdbc:mysql:///read_write?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
type: com.alibaba.druid.pool.DruidDataSource
# 从库配置(读库)
slave:
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
jdbc-url: jdbc:mysql:///read_write?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
type: com.alibaba.druid.pool.DruidDataSource
都放置在DataSourceCofig
中,进行操作的是Routing数据源:
package com.sql.readwrite.config;
import com.alibaba.druid.DbType;
import com.sql.readwrite.commons.myenums.DBTypeEnum;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @date 2020/11/16 9:03
*/
@Configuration
public class DataSourceConfig {
/**
* 配置主数据库数据源
* @return
*/
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置从数据库数据源
* @return
*/
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 路由数据库,用来通用管理数据源。
* @param masterDataSource
* @param slaveDataSource
* @return
*/
@Bean
public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource){
Map<Object,Object> targetObjectMap = new HashMap<>();
targetObjectMap.put(DBTypeEnum.MASTER,masterDataSource);
targetObjectMap.put(DBTypeEnum.SALVE,slaveDataSource);
MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
// 设置默认的数据源为master
myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
myRoutingDataSource.setTargetDataSources(targetObjectMap);
return myRoutingDataSource;
}
}
用来获取数据库路由的key
package com.sql.readwrite.config;
import com.sql.readwrite.commons.mythread.DBContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
/**
* 获取路由key
* @date 2020/11/16 9:14
*/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
MybatisPlusConfig:
package com.sql.readwrite.config;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
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.jdbc.datasource.LazyConnectionDataSourceProxy;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* @date 2020/11/16 9:18
*/
@EnableTransactionManagement
@Configuration
public class MybatisConfig {
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
/**
* 将配置的路由数据源放到mybatis的数据源中。
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(myRoutingDataSource);
return bean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager(){
return new DataSourceTransactionManager(myRoutingDataSource);
}
}
数据源枚举:
package com.sql.readwrite.commons.myenums;
/**
* @date 2020/11/16 9:09
*/
public enum DBTypeEnum {
MASTER,SALVE
}
自定义注解:
package com.sql.readwrite.annotation;
/**
* @date 2020/11/16 9:26
*/
public @interface Master {
}
aop:
package com.sql.readwrite.aspect;
import com.sql.readwrite.commons.mythread.DBContextHolder;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* @date 2020/11/16 9:23
*/
@Aspect
@Component
public class DataSourceAop {
/**
* 没有Master注解,且是service中的select* 或 get* 方法 拦截。
* 从库切换
*/
@Pointcut(
"!@annotation(com.sql.readwrite.annotation.Master)"+
"&& (" +
"execution(* com.sql.readwrite.service..*.select*(..))"+
"|| " +
"execution(* com.sql.readwrite.service..*.get*(..))" +
")")
public void readPointcut(){
}
/**
* 主库切换
*/
@Pointcut("@annotation(com.sql.readwrite.annotation.Master) " +
"|| execution(* com.sql.readwrite.service..*.insert*(..)) " +
"|| execution(* com.sql.readwrite.service..*.add*(..)) " +
"|| execution(* com.sql.readwrite.service..*.update*(..)) " +
"|| execution(* com.sql.readwrite.service..*.edit*(..)) " +
"|| execution(* com.sql.readwrite.service..*.delete*(..)) " +
"|| execution(* com.sql.readwrite.service..*.remove*(..))")
public void writePointcut() {
}
@Before("readPointcut()")
public void read(){
DBContextHolder.slave();
}
@Before("writePointcut()")
public void write(){
DBContextHolder.master();
}
}
核心的实现都在这了,其它的代码(entity、service、mapper,都是普通的MybatisPlus代码)在gitee上。
gitee:选择“read-write”
测试的时候直接调用 insert 和 select测试就行。