功能需求是公司要做一个大的运营平台:
1、运营平台有自身的数据库,维护用户、角色、菜单、部分以及权限等基本功能。
2、运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A、服务B的数据库是独立的。
所以,运营平台至少要连三个库:运营库,A库,B库,并且希望达到针对每个功能请求能够自动切换到对应的数据源(我最终实现是针对Service的方法级别进行切换的,也可以实现针对每个DAO层的方法进行切换。我们系统的功能是相互之间比较独立的)。
第一步:配置多数据源
1、定义数据源:
我采用的数据源是阿里的DruidDataSource(用DBCP也行,这个随便)。配置如下:
<!-- op dataSource -->
<bean id="opDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.master.url}" />
<property name="username" value="${db.master.user}" />
<property name="password" value="${db.master.password}" />
<property name="driverClassName" value="${db.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
<!-- serverA dataSource -->
<bean id="serverADataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.serverA.master.url}" />
<property name="username" value="${db.serverA.master.user}" />
<property name="password" value="${db.serverA.master.password}" />
<property name="driverClassName" value="${db.serverA.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
<!-- serverB dataSource -->
<bean id="serverBDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.serverB.master.url}" />
<property name="username" value="${db.serverB.master.user}" />
<property name="password" value="${db.serverB.master.password}" />
<property name="driverClassName" value="${db.serverB.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
我配置了三个数据源:oPDataSource(运营平台本身的数据源),serverADataSource,serverBDataSource。
我们来看动态切换数据源的AOP实现:
import java.lang.reflect.Proxy;
import org.apache.commons.lang.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
/**
* 数据源切换AOP
*
* @author yuzhu.peng
* @since 2018-01-15
*/
@Aspect
@Order(1)
public class MultipleDataSourceInterceptor {
/**
* 拦截器对所有的业务实现类请求之前进行数据源切换 特别注意,由于用到了多数据源,Mapper的调用最好只在*ServiceImpl,不然调用到非默认数据源的表时,会报表不存在的异常
*
* @param joinPoint
* @throws Throwable
*/
@Before("execution(* com.xxxx.platform.service..*.*ServiceImpl.*(..))")
public void setDataSoruce(JoinPoint joinPoint)
throws Throwable {
Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getName();
if (ClassUtils.isAssignable(clazz, Proxy.class)) {
className = joinPoint.getSignature().getDeclaringTypeName();
}
// 对类名含有serverA的设置为serverA数据源,否则默认为后台的数据源
if (className.contains(".serverA.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverA);
}
else if (className.contains(".serverB.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverB);
}
else {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_OP);
}
}
/**
* 当操作完成时,释放当前的数据源 如果不释放,频繁点击时会发生数据源冲突,本是另一个数据源的表,结果跑到另外一个数据源去,报表不存在
*
* @param joinPoint
* @throws Throwable
*/
@After("execution(* com.xxxx.service..*.*ServiceImpl.*(..))")
public void removeDataSoruce(JoinPoint joinPoint)
throws Throwable {
MultipleDataSource.removeDataSourceKey();
}
}
拦截所有的ServiceImpl方法,根据方法的全限定名去判断属于那个数据源的功能,然后选择相应的数据源,发放执行完后,释放当前的数据源。注意我用到了Spring的 @Order,注解,接下来会讲到,当定义多个AOP的时候,order是很有用的。
其他:
一开始项目中并没有引入事务,所以一切都OK,每次都能访问到正确的数据源,当加入SPring的事务管理后,不能动态切换数据源了(也好像是事务没有生效,反正是二者没有同时有效),后来发现原因是AOP的执行顺序问题,所以用到了上边提到的SPring的Order:
order越小,先被执行。至此,既可以动态切换数据源,又可以成功用事务(在同一个数据源)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持社区。
|