这里使用的是springboot,使用上和spring没区别
首先在配置文件中定义多数据源的url、uername等信息。
#db1数据源
#使用p6spy打印sql
db1.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
db1.datasource.url=jdbc:p6spy:mysql://localhost:3306/db1
db1.datasource.username=root
db1.datasource.password=root
#db2数据源
#使用p6spy打印sql
db2.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
db2.datasource.url=jdbc:p6spy:mysql://localhost:3306/db2
db2.datasource.username=root
db2.datasource.password=root
#mybatis mapper.xml路径
mybatis.mapper-locations=classpath:mybatis-mapper/*.xml
然后定义数据库的dataSource的bean
首先实现一个基础类:
/**
* description:
* 数据源基础信息 及获取数据源的基础方法
* @author wkGui
*/
public class BaseDataSource {
private String driverClassName;
private String url;
private String username;
private String password;
/**
* 配置获取数据源
*/
public DataSource configurationDataSource() throws Exception{
Map propertis = new HashMap<>(8);
propertis.put("url", getUrl());
propertis.put("password", getPassword());
propertis.put("username", getUsername());
propertis.put("driverClassName", getDriverClassName());
return DruidDataSourceFactory.createDataSource(propertis);
}
//省略getter、setter方法
}
然后继承基础方法实现配置两个数据库的dataSource bean
/**
* description:
* db1数据源配置
* @author wkGui
*/
@Configuration
@ConfigurationProperties(prefix = "db1.datasource")
public class Db1DataSourceConfig extends BaseDataSource {
@Bean(name = "db1DataSource")
@Override
public DataSource configurationDataSource() throws Exception {
return super.configurationDataSource();
}
}
/**
* description:
* db2数据源配置
* @author wkGui
*/
@Configuration
@ConfigurationProperties(prefix = "db2.datasource")
public class Db1DataSourceConfig extends BaseDataSource {
@Bean(name = "db2DataSource")
@Override
public DataSource configurationDataSource() throws Exception {
return super.configurationDataSource();
}
}
DataSource是和线程绑定的,动态数据源的配置主要是通过继承AbstractRoutingDataSource类实现的,实现在AbstractRoutingDataSource类中的 protected Object determineCurrentLookupKey()方法来获取数据源,所以我们需要先创建一个多线程线程数据隔离的类来存放DataSource,然后在determineCurrentLookupKey()方法中通过这个类获取当前线程的DataSource,在AbstractRoutingDataSource类中,DataSource是通过Key-value的方式保存的,我们可以通过ThreadLocal来保存Key,从而实现数据源的动态切换:
/**
* description:
* 数据源与Enum的映射
* @author wkGui
*/
public enum DataSourceTypeEnum {
//db1数据库
db1,
//db2数据库
db2
}
/**
* description:
* 保存线程安全的数据源
* @author wkGui
*/
public class ConcurrentDataSourceHolder {
private static final ThreadLocal HOLDER = new ThreadLocal<>();
public static void setDataSource(DataSourceTypeEnum dataSourceTypeEnum){
HOLDER.set(dataSourceTypeEnum);
}
public static DataSourceTypeEnum getDataSource(){
return HOLDER.get();
}
public static void remove(){
HOLDER.remove();
}
}
/**
* description:
* 实现AbstractRoutingDataSource接口
* @author wkGui
*/
public class DynamicDataSource extends AbstractRoutingDataSource{
@Nullable
@Override
protected Object determineCurrentLookupKey() {
return ConcurrentDataSourceHolder.getDataSource();
}
}
然后配置动态数据源类DynamicDataSource、SqlSessionFactory以及DataSourceTransactionManager的bean
/**
* description:
* 动态数据源配置
* SQLSessionFactory配置
* 事务配置
* @author wkGui
*/
@Configuration
public class DataSourceConfig {
@Resource
private Environment env;
/**
* 动态数据源配置(AbstractRoutingDataSource接口)
* @param emrDataSource db1数据源
* @param hisDataSource db1数据源
*/
@Bean(name = "dynamicDataSource")
public DynamicDataSource dataSource(@Qualifier("db1DataSource")DataSource emrDataSource,
@Qualifier("db2DataSource")DataSource hisDataSource){
Map
因为这里db2是只读的,所以基础的事务配置就可以满足,如果两个数据库都有读写操作就需要其他的事务管理方式了。
在每次操作数据库之前,通过ConcurrentDataSourceHolder的setDataSource(DataSourceTypeEnum dataSourceTypeEnum)方法来切换数据源,达到动态切换的目的。
ConcurrentDataSourceHolder.setDataSource(DataSourceTypeEnum.db2);
db2TableMapper.selectByID(1);
但是这样编写代码耦合度会非常高,使用很不方便。
我们先定义一个注解,注解有个value属性,根据其值动态切换数据源:
/**
* description:
*
* @author wkGui
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DynamicDataSourceSign {
DataSourceTypeEnum value();
}
然后使用AOP的Around方式来处理方法,当方法上有我们定义的 DynamicDataSourceSign注解时,根据其value动态切换数据源:
/**
* description:
* 根据DynamicDataSourceSign值动态切换数据源
* @author wkGui
*/
@Component
@Aspect
public class DynamicDataSourceHandle {
@Pointcut("execution(* com.wk..*.*(..))")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
DynamicDataSourceSign annotation = methodSignature.getMethod().getAnnotation(DynamicDataSourceSign.class);
if (annotation != null) {
//如果存在DynamicDataSourceSign 根据DynamicDataSourceSign切换数据源
DataSourceTypeEnum dataSourceTypeEnum = annotation.value();
ConcurrentDataSourceHolder.setDataSource(dataSourceTypeEnum);
}
Object obj = pjp.proceed();
//清理dataSource
ConcurrentDataSourceHolder.remove();
return obj;
}
}
然后将定义的注解加在我们需要动态切换数据源的方法上,就可以通过注解实现的动态切换数据源了。
@DynamicDataSourceSign(DynamicDataSourceSign.db2)
public xxx method1(){
......
}
这样在执行方法时就会通过AOP动态切换到db2的数据源啦。
事务管理在开启时,需要确定数据源,也就是说数据源切换要在事务开启之前,我们可以使用Order来配置执行顺序,在AOP实现类上加Order注解,就可以使数据源切换提前执行,order值越小,执行顺序越靠前。