在平常的项目开发中,会遇到一个应用中访问多个数据源的需求,本文将通过使用SpringAop+Mybatis与spring-jdbc的
AbstractRoutingDataSource实现动态切换数据源;
1.定义多个数据源的枚举:
public enum DataSourceType {
//第一个数据源
DATASOURCE_ONE,
//第二个数据源
USERCENTER_TWO
}
2.定义动态扫描的注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DynamicDataSourceAnnotation {
//dataSource 自定义注解的参数
DataSourceType dataSource() default DataSourceType.DATASOURCE_ONE;
}
3.定义全局数据源的管理类,默认访问DATASOURCE_ONE
public class DataSourceTypeManager {
private static final ThreadLocal<DataSourceType> dataSourceTypes = new ThreadLocal<DataSourceType>() {
protected DataSourceType initialValue() {
return DataSourceType.DATASOURCE_ONE;
}
};
public static DataSourceType get(){
return dataSourceTypes.get();
}
public static void set(DataSourceType dataSourceType){
dataSourceTypes.set(dataSourceType);
}
public static void reset(){
dataSourceTypes.set(DataSourceType.DATASOURCE_ONE);
}
}
4.实现AbstractRoutingDataSource动态路由数据源:
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(AbstractRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
logger.info("dataSourceType = "+ DataSourceTypeManager.get());
return DataSourceTypeManager.get();
}
}
5.定义AOP的Aspect动态切换数据源:
@Aspect
@Component
@Order(0)
public class DynamicDataSourceAspect {
@Pointcut("execution( * com.test.datasource.manage.dao.mapper.*.*(..))")
public void invanyMethod() {
};
//前置通知,设置成目标数据源
@Before("invanyMethod()")
public void beforeinv(JoinPoint jp) throws Exception {
//获得注解
DynamicDataSourceAnnotation meta = giveAnnotation(jp);
DataSourceTypeManager.set(meta.dataSource());
}
//执行结束后设置成住数据源
/**
* 获得方法上的注解
* @param joinPoint 切入点
* @return
* @throws Exception
*/
private DynamicDataSourceAnnotation giveAnnotation(JoinPoint joinPoint) throws Exception {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(DynamicDataSourceAnnotation.class);
}
return null;
}
@After("invanyMethod()")
public void after(JoinPoint jp) throws Exception {
DataSourceTypeManager.set(DataSourceType.DATASOURCE_ONE);
}
6.mybatis配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 配置第一个数据源 -->
<bean name="dataSource_one" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value=""/>
<property name="url" value="$"/>
<property name="username" value=""/>
<property name="password" value=""/>
<!-- 初始化连接大小 -->
<property name="initialSize" value=""/>
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value=""/>
<!-- 连接池最小空闲 -->
<property name="minIdle" value=""/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value=""/>
</bean>
<!-- 配置第二个数据源 -->
<bean name="dataSource_two" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value=""/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
<!-- 初始化连接大小 -->
<property name="initialSize" value=""/>
<!-- 连接池最大使用连接数量 -->
<property name="maxActive" value=""/>
<!-- 连接池最小空闲 -->
<property name="minIdle" value=""/>
<!-- 获取连接最大等待时间 -->
<property name="maxWait" value=""/>
</bean>
<!-- 多数据源配置 -->
<bean id="dataSource" class="com.test.datasource.manage.mybatis.DynamicDataSource">
<!-- 默认使用dataSourceA的数据源 -->
<property name="defaultTargetDataSource" ref="dataSource_one"></property>
<property name="targetDataSources">
<map key-type="com.test.datasource.manage.mybatis.DataSourceType">
<entry key="DATASOURCE_ONE" value-ref="dataSource_one"></entry>
<entry key="DATASOURCE_TWO" value-ref="dataSource_two"></entry>
</map>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:mappers/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.test.datasource.manage.dao.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="annotationClass" value="com.test.datasource.manage.mybatis.MyBatisRepository"/> //自己定义的mapper扫描注解
</bean>
<!-- AOP自动代理功能 -->
<aop:aspectj-autoproxy ></aop:aspectj-autoproxy>
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyBatisRepository {
}
7.在mybatis的Mapper中的方法中添加注解,就可以进行数据源的动态切换:
@MyBatisRepository
public interface TestMapper {
@DynamicDataSourceAnnotation(dataSource = DataSourceType.DATASOURCE_ONE)
List<Entity> getEntityList(List<Integer> hids);
@DynamicDataSourceAnnotation(dataSource = DataSourceType.DATASOURCE_TWO)
Integer addEntityBatch(List<Entity> entityList);
}
注:动态切换数据源的注解比较灵活,可以在业务service的方法级别注解,此时需要修改aop的aspect,以便能够处理扫描到的DynamicDataSourceAnnotation,若在一个业务方法中使用到多个数据源,则需要在第一调用之后,重新获取spring的bean已获得代理对象进行数据源切换,如下:
@Component
public class TestLogic {
@Autowired
private TestMapper testMapper;
@DynamicDataSourceAnnotation(dataSource = DataSourceType.DATASOURCE_ONE)
public void testMethod1(List<Integer> hids,List<Entity> entityList) {
List<Entity> list = testMapper.getEntityList(hids);
getThis().addEntityBatch(entityList);
}
@DynamicDataSourceAnnotation(dataSource = DataSourceType.DATASOURCE_TWO)
public Ingteger addEntityBatch(List<Entity> entityList){
return testMapper.addEntityBatch(entityList);
}
private TsetLogic getThis() {
return (TestLogic) SpringContextUtil.getBean(this.getClass());
}
}
这种方法实现比较繁琐,个人建议直接在Mapper中的方法添加注解。