SpringBoot+MyBatisPlus+Mysql多数据源配置(二)

多数据源配置(二):代码实现多数据源配置

由于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();
    }
}

至此,我们已经全部整合完毕了,接下来就可以测试了!

猜你喜欢

转载自blog.csdn.net/weixin_50989469/article/details/120760627