多数据源配置(二):代码实现多数据源配置
由于MyBatisPlus对于MyBatis只做升级不做修改,是无条件兼容的,所以我现在建Spring的项目都是直接整合MyBatisPlus,就是为了方便快速开发。SpringBoot项目整合MyBatisPlus,配置多数据源的主从同步,跟传统的单数据源配置还是有很大的区别。关键是要做好读写分离,主库增、删、改,从库查。
一、新建一个SpringBoot项目
略
二、导入相关依赖
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.4</version>
</dependency>
三、yaml中配置多数据源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
druid:
master:
username: your_name
password: your_pwd
url: jdbc:mysql://*.*.*.*:*/*?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 1
minIdle: 1
maxActive: 20
slave:
username: your_name
password: your_pwd
url: jdbc:mysql://*.*.*.*:*/*?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 1
minIdle: 1
maxActive: 20
四、代码实现
1.新建一个枚举类DBTypeEnum,标识主从数据源
public enum DBTypeEnum {
MASTER("master"), SLAVE("slave");
private String value;
DBTypeEnum(String value){
this.value = value;
}
public String getValue() {
return value;
}
}
2.多数据源切换处理类DBContextHolder
public class DBContextHolder {
private static final ThreadLocal<DBTypeEnum> contextHolder = new InheritableThreadLocal<>();
public static void set(DBTypeEnum dbType) {
contextHolder.set(dbType);
}
public static DBTypeEnum get() {
return contextHolder.get();
}
public static void master() {
set(DBTypeEnum.MASTER);
System.out.println("切换到master数据源");
}
public static void slave() {
set(DBTypeEnum.SLAVE);
System.out.println("切换到slave数据源");
}
public static void cleanAll() {
contextHolder.remove();
}
}
3.动态数据源MyRoutingDataSource
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
4.数据源配置类DataSourceConfig
@Configuration
@MapperScan("com.ebt.demo.mapper") //对应mapper接口所在包扫描
public class DataSourceConfig {
/**
* mybatisPlus分页配置
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
/**
* 主数据源
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 从数据源
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* 动态数据源配置
*/
@Bean
public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DBTypeEnum.MASTER, masterDataSource);
targetDataSource.put(DBTypeEnum.SLAVE, slaveDataSource);
MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
//找不到用默认数据源
myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
//可选择目标数据源
myRoutingDataSource.setTargetDataSources(targetDataSource);
return myRoutingDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(myRoutingDataSource(masterDataSource(),slaveDataSource()));
//mapper下xml文件位置
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*.xml"));
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setGlobalConfig(globalConfig());
return sqlSessionFactory.getObject();
}
/**
* mybatisPlus自定义配置
*/
public GlobalConfig globalConfig(){
GlobalConfig globalConfig = new GlobalConfig();
GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig();
dbConfig.setIdType(IdType.AUTO);
globalConfig.setDbConfig(dbConfig);
return globalConfig;
}
}
5.自定义注解,在service上可以指定方法调用master或slave
@Target({
ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
DBTypeEnum value() default DBTypeEnum.MASTER;
}
6.切面儿AOP配置(这一步很关键)
对于MybatisPlus在Service层已经实现的方法(get*、list*、page*、count*、select*、query*、save*、update*、remove*、delete*),就直接在切面这边定义“读”或者“写”,切换对应的数据源。而对于其他的方法,则需要加上@DataSource的注解来区分。
@Aspect
@Order(-1)
@Component
public class DataSourceAop {
@Pointcut("(execution(* com.test.service..*.get*(..)) " +
"|| execution(* com.test.service..*.list*(..)) " +
"|| execution(* com.test.service..*.page*(..)) " +
"|| execution(* com.test.service..*.count*(..)) " +
"|| execution(* com.test.service..*.select*(..)) " +
"|| execution(* com.test.service..*.query*(..)))")
public void readPointcut() {
}
@Pointcut("execution(* com.test.service..*.save*(..)) " +
"|| execution(* com.test.service..*.update*(..)) " +
"|| execution(* com.test.service..*.remove*(..)) " +
"|| execution(* com.test.service..*.delete*(..)) ")
public void writePointcut() {
}
@Pointcut("@within(com.test.util.DataSource) " +
"|| @annotation(com.test.util.DataSource)")
public void pointcut(){
}
@Before("readPointcut()")
public void read() {
DBContextHolder.slave();
}
@Before("writePointcut()")
public void write() {
DBContextHolder.master();
}
@Before("pointcut() && @annotation(dataSource)")
public void doBefore(DataSource dataSource){
String value = dataSource.value().getValue();
if (value.equals(DBTypeEnum.MASTER.getValue())) {
DBContextHolder.master();
}
if (value.equals(DBTypeEnum.SLAVE.getValue())) {
DBContextHolder.slave();
}
}
@After("readPointcut()")
public void cleanRead() {
DBContextHolder.cleanAll();
}
@After("writePointcut()")
public void cleanWrite() {
DBContextHolder.cleanAll();
}
@After("pointcut()")
public void clean() {
DBContextHolder.cleanAll();
}
}
至此,我们已经全部整合完毕了,接下来就可以测试了!