一、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 就行了