7 Dynamic DataSource

Wu Jun 2020-01-10 10:25:56
06 Spring > 05 Data.md

一、AOP 实现

AOP 实现多数据源,可读写分离

1 配置文件

dynamic-db.master.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.master.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.master.username = xxx
dynamic-db.master.password = xxx

dynamic-db.slave.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.slave.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.slave.username = xxx
dynamic-db.slave.password = xxx

2 ContextHolder

管理 DataSource

public class DynamicDataSourceContextHolder {
    /**
     * 存储当前 DataSource
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
   
    /**
     * 所有 DataSource 的 key
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();
   
    /**
     * 切换 DataSource
     */
    public static void setDataSourceKey(String key) {
        CONTEXT_HOLDER.set(key);
    }

    /**
     * 获取当前 DataSource,默认为 master
     */
    public static String getDataSourceKey() {
        String key = CONTEXT_HOLDER.get();
        return key == null ? "master" : key;
    }

    /**
     * 清空当前 DataSource
     */
    public static void clearDataSourceKey() {
        CONTEXT_HOLDER.remove();
    }

    /**
     * 判断是否当前 DataSource
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }
}

3 注册动态配置

继承 AbstractRoutingDataSource

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
    //将当前DataSource加入应用上下文
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

4 加载配置

@Slf4j
@Configuration
public class DataSourceConfigurer {
    @Bean("master")
    @Primary
    @ConfigurationProperties(prefix = "dynamic-db.master")
    public DataSource master() {
        return DataSourceBuilder.create().build();
    }
    @Bean("slave")
    @ConfigurationProperties(prefix = "dynamic-db.slave")
    public DataSource slave() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置动态 DataSource
     */
    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
        
        //所有DataSource
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("master", master());
        dataSourceMap.put("slave", slave());

        //设置默认DataSource
        dynamicRoutingDataSource.setDefaultTargetDataSource(master());
        //设置所有DataSource
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);

        //将所有DataSource的key放入,以供判断
        DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
        return dynamicRoutingDataSource;
    }

    /**
     * 将数据源添加到 SqlSession 工厂;获取 mybatis 配置
     */
    @Bean
    @ConfigurationProperties(prefix = "mybatis")
    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        return sqlSessionFactoryBean;
    }

    /**
     * 将数据源添加到事物管理器
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

5 注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

6 Aspect

要用 @Order(0) 注解让这个切面的优先级最高,以免被其他切面(如事务管理器)影响

@Aspect
@Component
@Order(0)
public class DynamicDataSourceAspect {
    //切换
    @Before("@annotation(targetDataSource))")
    public void switchDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        if (DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value())){
            DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value());
        }
    }
    //还原
    @After("@annotation(targetDataSource))")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        DynamicDataSourceContextHolder.clearDataSourceKey();
    }
}

7 实例

读写分离

@Repository
public interface InterMsgMapper {
    @InsertProvider(type = InterMsgProvider.class, method = "insert")
    int insert(InterMsg interMsg);

    @TargetDataSource("slave")
    @SelectProvider(type = InterMsgProvider.class, method = "findMessages")
    List<InterMsg> findMessages(Long userId, Integer limit, Long lastId, Long currentTime);
}

二、普通配置

1 配置文件

dynamic-db.master.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.master.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.master.username = xxx
dynamic-db.master.password = xxx

dynamic-db.slave.driver-class-name = com.mysql.jdbc.Driver
dynamic-db.slave.jdbc-url = jdbc:mysql://somehost:3306/sometable?characterEncoding=utf8&useSSL=false
dynamic-db.slave.username = xxx
dynamic-db.slave.password = xxx

2 DataSourceConfig


@Configuration
public class DataSourceConfig {
    //开启 数据库下划线转驼峰
    @Bean
    @Scope("prototype") //默认单例会使多数据源失效
    @ConfigurationProperties("mybatis.configuration")
    public org.apache.ibatis.session.Configuration globalConfiguration(){
        return new org.apache.ibatis.session.Configuration();
    }

    @Bean("masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "dynamic-db.master")
    public DataSource master() {
        return DataSourceBuilder.create().build();
    }
    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "dynamic-db.slave")
    public DataSource slave() {
        return DataSourceBuilder.create().build();
    }

    //多数据源事务管理
    @Bean(name="tranMagMaster")
    public PlatformTransactionManager masterTransactionManager(@Qualifier("masterDataSource")DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    @Bean(name="tranMagSlave")
    public PlatformTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource")DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

3 JdbcTemplatesConfig

@Configuration
public class JdbcTemplatesConfig{

    //支持JdbcTemplate实现多数据源
    @Bean(name="masterJdbcTemplate")
    public JdbcTemplate masterJdbcTemplate(@Qualifier("masterDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean(name="slaveJdbcTemplate")
    public JdbcTemplate slaveJdbcTemplate(@Qualifier("slaveDataSource") DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

3 MybatisConfigMaster

@Configuration
@MapperScan(basePackages = "com.xxx.mapper.master", 
        sqlSessionTemplateRef = "masterSqlSessionTemplate"
)
public class MybatisConfigMaster{
    @Bean
    SqlSessionTemplate masterSqlSessionTemplate(DataSource masterDataSource, org.apache.ibatis.session.Configuration globalConfiguration) {
        SqlSessionFactory sqlSessionFactory = null;
        try {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setConfiguration(globalConfiguration);
            sqlSessionFactoryBean.setDataSource(masterDataSource);
            sqlSessionFactory = sqlSessionFactoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

4 MybatisConfigSlave

@Configuration
@MapperScan(basePackages = "com.xxx.mapper.slave", 
        sqlSessionTemplateRef = "slaveSqlSessionTemplate"
)
public class MybatisConfigSlave{
    @Bean
    SqlSessionTemplate slaveSqlSessionTemplate(DataSource slaveDataSource, org.apache.ibatis.session.Configuration globalConfiguration) {
        SqlSessionFactory sqlSessionFactory = null;
        try {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setConfiguration(globalConfiguration);
            sqlSessionFactoryBean.setDataSource(slaveDataSource);
            sqlSessionFactory = sqlSessionFactoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

5 使用

在 @MapperScan 配置的目录下写 mapper 就行了