注:SpringBoot会自动装备bean名为dataSource

在我们知道了SpringBoot会自动装配bean名为dataSource的时候,最基础的但数据源配置文件装配的形式就变成了

@Configuration
public class DataSourceConfig {
@Bean(name = "dataSource")
    public DataSource dataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setUser("root");
            ds.setPassword("****");
            ds.setJdbcUrl("jdbc:mysql://****:3306/****?verifyServerCertificate=false&useUnicode=true&useSSL=false");
            //如果 breakAfterAcquireFailure=true ,一旦pool向数据库请求连接失败,就会标记pool block并关闭pool,
            //这样无论数据库是否恢复正常,应用端都无法从pool拿到连接
            ds.setBreakAfterAcquireFailure(false);

            //连接池中保留的最小连接数,默认为3
            ds.setMinPoolSize(20);

            //连接池中保留的最大连接数。默认值15
            ds.setMaxPoolSize(50);

            //初始化连接池中的连接数  大于等于 minPoolSize
            ds.setInitialPoolSize(20);

            //最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0
            ds.setMaxIdleTime(60);

            //表示 connection能存活的绝对时间
            ds.setMaxConnectionAge(0);

            //当连接池连接耗尽时,客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待。单位毫秒。默认: 0
            ds.setCheckoutTimeout(3000);

            //当连接池中的连接耗尽的时候c3p0 一次同时获取的连接数,默认值: 3
            ds.setAcquireIncrement(10);

            //定义在从数据库获取新连接失败后重复尝试的次数。默认值: 30 ;小于等于0表示无限次
            ds.setAcquireRetryAttempts(0);

            //重新尝试的时间间隔,默认为:1000毫秒
            ds.setAcquireRetryDelay(2000);

            //每60秒检查所有连接池中的空闲连接。默认值: 0,不检查
            ds.setIdleConnectionTestPeriod(60);

            //c3p0将建一张名为C3p0TestTable_NotDelete的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。
            //你不能在这张Test表上进行任何操作,它将只供c3p0测试使用。默认值: null
            ds.setAutomaticTestTable("C3p0TestTable_NotDelete");

            //关闭连接时,是否提交未提交的事务,默认为false,即关闭连接,回滚未提交的事务
            ds.setAutoCommitOnClose(false);

            //true表示在每次从pool内checkout连接的时候测试其有效性,这是个同步操作,因此应用端的每次数据库调用,
            //都会先通过测试sql测试其有效性,如果连接无效,会关闭此连接并剔除出pool,
            //并尝试从pool内取其他连接,默认为false,此特性要慎用,会造成至少多一倍的数据库调用。
            ds.setTestConnectionOnCheckout(false);

            //true表示每次把连接checkin到pool里的时候测试其有效性,
            //因为是个事后操作,所以是异步的,应用端不需要等待测试结果,但同样会造成至少多一倍的数据库调用。
            ds.setTestConnectionOnCheckin(false);

            ds.setNumHelperThreads(3);
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
        return ds;
    }
}

那么在这种情况下,如果我们想装配多个数据源的时候,可以根据思路延申成为

@Configuration
public class DataSourceConfig {
 @Bean("dbcommonMaster")
    @ConfigurationProperties(prefix = "dbcommon.master")
    public DataSource dbcommonMaster() {
        return DataSourceBuilder.create().type(ComboPooledDataSource.class).build();
    }

    @Bean("dbcommonSlave")
    @ConfigurationProperties(prefix = "dbcommon.slave")
    public DataSource dbcommonSlave() {
        return DataSourceBuilder.create().type(ComboPooledDataSource.class).build();
    }

    @Bean("dataSource")
    public DataSource dbcommonMasterSlaveDataSource() {
        MasterSlaveDataSource masterSlaveDataSource = new MasterSlaveDataSource();
        Map<Object,Object> targetDataSources = new HashMap<>();
        targetDataSources.put(Type.Master.value(), dbcommonMaster());
        targetDataSources.put(Type.Slave.value(), dbcommonSlave());
        masterSlaveDataSource.setTargetDataSources(targetDataSources);
        masterSlaveDataSource.setDefaultTargetDataSource(dbcommonMaster());
        return masterSlaveDataSource;
    }
}

而在配置文件中,则使用

dbcommon.master.jdbcUrl=jdbc:mysql://127.0.0.1:3315/****?verifyServerCertificate=false&useUnicode=true&useSSL=false
dbcommon.master.user=root
dbcommon.master.password=***
dbcommon.master.driverClass=com.mysql.jdbc.Driver
dbcommon.master.minPoolSize=20
dbcommon.master.maxPoolSize=50
dbcommon.master.initialPoolSize=20
dbcommon.master.maxIdleTime=60
dbcommon.master.maxConnectionAge=0
dbcommon.master.checkoutTimeout=3000
dbcommon.master.acquireIncrement=10
dbcommon.master.acquireRetryAttempts=0
dbcommon.master.acquireRetryDelay=2000
dbcommon.master.maxStatements=1000
dbcommon.master.idleConnectionTestPeriod=60
dbcommon.master.automaticTestTable=C3p0TestTable_NotDelete
dbcommon.master.autoCommitOnClose=false
dbcommon.master.breakAfterAcquireFailure=false
dbcommon.master.testConnectionOnCheckout=false

这种形式进行配置,有几个,就配置几套.dbcommon.master是根据你的ConfigurationProperties注解中的名字进行装配的
其中的枚举代码:

public enum Type {

    Master("Master"),
    Slave("Slave");

    private final String value;

    Type(String  value) {
        this.value = value;
    }

    public String  value() {
        return this.value;
    }
}

MasterSlaveDataSource代码为

public class MasterSlaveDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String type = DataSourceChoose.getType();
        if(log.isDebugEnabled()) {
            log.debug("MasterSlaveDataSource choose type is [" + type + "]");
        }
        return type;
    }

}

在这里可以可定义一个注解,考虑使用哪个数据源进行操作

注解切面:

@Slf4j
@Aspect
@Component
public class DataSourceAspect  {

    @Around("execution(public * com.framework.code.service..*.*(..)) && " +
            "@annotation(com.framework.core.db.DataSource)")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        try {
            MethodSignature ms = (MethodSignature) point.getSignature();
            Method method = ms.getMethod();
            DataSource db = method.getAnnotation(DataSource.class);

            DataSourceChoose.setType(db.type());
            DataSourceChoose.setDBName(db.dbName());
            
            return point.proceed();
        } finally {
            DataSourceChoose.clean();
        }
    }

}

定义DataSourceChoose

public class DataSourceChoose {

    private static final ThreadLocal<String> MasterSlave = new ThreadLocal<>();

    private static final ThreadLocal<String> DBName = new ThreadLocal<>();

    private static final ThreadLocal<Long> RoutingKey = new ThreadLocal<>();

    public static void setType(Type type) {
        MasterSlave.set(type.value());
    }

    public static void setDBName(DBName name) {
        DBName.set(name.value());
    }

    public static void setRoutingKey(Long routingKey) {
        RoutingKey.set(routingKey);
    }

    public static String getType() {
        return MasterSlave.get();
    }

    public static String getDBName() {
        return DBName.get();
    }

    public static Long getRoutingKey() {
        return RoutingKey.get();
    }

    public static void clean() {
        MasterSlave.remove();
        DBName.remove();
        RoutingKey.remove();
    }
}

注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {

    Type type() default Type.Master;

    String routingKey() default "";

    DBName dbName();
}

注解默认走主库,经过切面赋值以后.获取AbstractRoutingDataSource中的determineCurrentLookupKey,如果没有值,则是默认,如果有值,在切面中赋值get获取之后,AbstractRoutingDataSource类中的方法,会获取到数据源.setTargetDataSources方法中的下标.进行数据源确定,进而确认数据源,在确认数据源之后,使用完毕移除已经存在的数据源,在切面中已经体现