项目上常常需要多个数据源来存取数据,最近项目上试用了Tidb,因为兼容MySql协议,我们可以直接像Mysql一样在项目中进行配置。那么如何快速的将单一的数据源改成多数据源,并能够在项目中动态的切换就是本次要说的事情。下面精简并省略了无关代码,主要使用`AbstractRoutingDataSource`实现,使用AOP切换,在此记录配置过程以便查阅,分分钟解决问题。
yml配置文件中数据源配置:
spring:
datasource:
druid:
localdb:
url: jdbc:mysql://127.0.0.1:3306/story_web?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: ******
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
tidb:
url: jdbc:mysql://127.0.0.1:3306/story_log?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: ******
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
定义枚举,将我们有的数据库都定义在其中:
public enum DBTypeEnum {
localdb("localdb"),
tidb("tidb");
private String value;
DBTypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
数据库上下文,用于记录当前上下文所使用的数据源是谁:
public class DbContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
/**
* 设置数据源
* @param dbTypeEnum
*/
public static void setDbType(DBTypeEnum dbTypeEnum) {
contextHolder.set(dbTypeEnum.getValue());
}
/**
* 取得当前数据源
* @return
*/
public static String getDbType() {
return (String) contextHolder.get();
}
/**
* 清除上下文数据
*/
public static void clearDbType() {
contextHolder.remove();
}
}
通过AbstractRoutingDataSource
抽象类可以动态的切换数据源
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String dbType = DbContextHolder.getDbType();
DbContextHolder.clearDbType();
return dbType;
}
}
数据源配置,创建所有的数据源,添加到数据源路由中,并设置默认数据源:
@EnableTransactionManagement
@Configuration
public class MybatisPlusDsConfig {
@Bean(name = "localdb")
@ConfigurationProperties(prefix = "spring.datasource.druid.localdb")
public DataSource db1() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "tidb")
@ConfigurationProperties(prefix = "spring.datasource.druid.tidb")
public DataSource db2() {
return DruidDataSourceBuilder.create().build();
}
/**
* 动态数据源配置
* @return
*/
@Bean
@Primary
public DataSource multipleDataSource(@Qualifier("localdb") DataSource localdb, @Qualifier("tidb") DataSource tidb) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.localdb.getValue(), localdb);
targetDataSources.put(DBTypeEnum.tidb.getValue(), tidb);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(localdb);
return dynamicDataSource;
}
}
最后一步,通过AOP来切换数据源:
@Slf4j
@Order(1)
@Aspect
@Component
public class DataSourceSwitchAspect {
@Pointcut("execution(* com.story.storyweb.mapper.localmapper..*.*(..))")
private void localMpperAspect() {
}
@Pointcut("execution(* com.story.storyweb.mapper.timapper..*.*(..))")
private void tiMpperAspect() {
}
@Before("localMpperAspect()")
public void db1() {
log.info("切换到localdb数据源...");
DbContextHolder.setDbType(DBTypeEnum.localdb);
}
@Before("tiMpperAspect()")
public void db2() {
log.info("切换到tidb数据源...");
DbContextHolder.setDbType(DBTypeEnum.tidb);
}
}
因为我们使用了mybatis-plus,这里定义两个切点在不同的mapper目录上,可以看到:
com.story.storyweb.mapper.localmapper
目录下的mapper会使用localdb数据源。com.story.storyweb.mapper.timapper
目录下的mapper会切换到tidb数据源。
当然,如果我们的项目不需要事务,那这里的切点放在mapper上并不会有问题。
但当开启了数据库事务,而事务一般会在service上开始,那么,切点放在mapper上,在切换数据源就会遇到错误哦。
这里只为了演示将切点放在了mapper上,实际上,你可以放在你想切换的任何位置。
接下来,工程里的mapper当然按照下面的结构来区分:
/mapper/localmapper
/mapper/timapper
至此,打完收工。