1、配置两个数据源信息

package com.site.blog.my.core.config;

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;

/**
 * 关于数据源配置,参考SpringBoot官方文档第79章《Data Access》
 * 79. Data Access
 * 79.1 Configure a Custom DataSource
 * 79.2 Configure Two DataSources
 */

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }

   /* @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }*/

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        //targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }

}

2、配置切换数据源工具类

package com.site.blog.my.core.config;


import java.util.concurrent.atomic.AtomicInteger;

public class DBContextHolder {

    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();

    private static final AtomicInteger counter = new AtomicInteger(-1);

    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() {
        //  轮询
        int index = counter.getAndIncrement() % 2;
        if (counter.get() > 9999) {
            counter.set(-1);
        }
        set(DBTypeEnum.SLAVE1);
        System.out.println("切换到slave1");
       /* if (index == 0) {
            set(DBTypeEnum.SLAVE1);
            System.out.println("切换到slave1");
        }else {
            set(DBTypeEnum.SLAVE2);
            System.out.println("切换到slave2");
        }*/
    }

}

3、配置Aop切面,对不同方法进行过滤,选择不同数据源

package com.site.blog.my.core.config;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(-1)
@Component
public class DataSourceAop {

    @Pointcut("!@annotation(com.site.blog.my.core.annotation.Master) " +
            "&& (execution(* com.site.blog.my.core.service..*.select*(..)) " +
            "|| execution(* com.site.blog.my.core.service..*.get*(..)))" +
            "|| execution(* com.site.blog.my.core.service..*.login*(..)))")
    public void readPointcut() {

    }

    @Pointcut("@annotation(com.site.blog.my.core.annotation.Master) " +
            "|| execution(* com.site.blog.my.core.service..*.insert*(..)) " +
            "|| execution(* com.site.blog.my.core.service..*.add*(..)) " +
            "|| execution(* com.site.blog.my.core.service..*.update*(..)) " +
            "|| execution(* com.site.blog.my.core.service..*.edit*(..)) " +
            "|| execution(* com.site.blog.my.core.service..*.delete*(..)) " +
            "|| execution(* com.site.blog.my.core.service..*.remove*(..))" +
            "|| execution(* com.site.blog.my.core.service..*.save*(..))")
    public void writePointcut() {

    }

    @Before("readPointcut()")
    public void read() {
        DBContextHolder.slave();
    }

    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }


    /**
     * 另一种写法:if...else...  判断哪些需要读从数据库,其余的走主数据库
     */
//    @Before("execution(* com.site.blog.my.core.service.impl.*.*(..))")
//    public void before(JoinPoint jp) {
//        String methodName = jp.getSignature().getName();
//
//        if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
//            DBContextHolder.slave();
//        }else {
//            DBContextHolder.master();
//        }
//    }
}

4、附录数据源配置信息

# read and write datasource
spring.datasource.master.jdbc-url=jdbc:mysql://xxxxx:3306/huxblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=GMT
spring.datasource.master.username=xxxxx
spring.datasource.master.password=xxxxx
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave1.jdbc-url=jdbc:mysql://xxxxx:3306/huxblog?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=GMT
spring.datasource.slave1.username=xxxxx
spring.datasource.slave1.password=xxxxx
spring.datasource.slave1.driver-class-name=com.mysql.cj.jdbc.Driver

5、验证读写分离完成,结合数据库的同步使用,即可完成数据同步以及读写分离功能。 读写分离还可以借助其他工具包实现,如mycat。