Spring+Mybatis动态切换数据源的⽅法
功能需求是公司要做⼀个⼤的运营平台:
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}" />springframework事务
<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。
2、配置multipleDataSource
multipleDataSource相当于以上三个数据源的⼀个代理,真正与Spring/Mybatis相结合的时multipleDataSource,和单独配置的DataSource使⽤没有分别:
<!-- Spring整合Mybatis:配置multipleDatasource -->
<bean id="sqlSessionFactory"
class="batisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource" />
<!-- ⾃动扫描l⽂件 -->
<property name="mapperLocations">
<list>
<value>classpath*:/sqlMapperXml/*.xml</value>
<value>classpath*:/sqlMapperXml/*/*.xml</value>
</list>
</property>
<property name="configLocation" value="classpath:l"></property>
<property name="typeAliasesPackage" value="com.del" />
<property name="globalConfig" ref="globalConfig" />
<property name="plugins">
<array>
<!-- 分页插件配置 -->
<bean id="paginationInterceptor"
class="batisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="mysql" />
<property name="optimizeType" value="aliDruid" />
</bean>
</array>
</property>
</bean>
<!-- MyBatis 动态实现 -->
<bean id="mapperScannerConfigurer" class="batis.spring.mapper.MapperScannerConfigurer">
<!-- 对Dao 接⼝动态实现,需要知道接⼝在哪 -->
<property name="basePackage" value="com.XXX.platform.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- MP 全局配置 -->
<bean id="globalConfig" class="ity.GlobalConfiguration">
<property name="idType" value="0" />
<property name="dbColumnUnderline" value="true" />
</bean>
<!-- 事务管理配置multipleDataSource -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"></property>
</bean>
了解了multipleDataSource所处的位置之后,接下来重点看下multipleDataSource怎么实现,配置⽂件如下:
<bean id="multipleDataSource" class="platformmons.db.MultipleDataSource">
<property name="defaultTargetDataSource" ref="opDataSource" />
<property name="targetDataSources">
<map>
<entry key="opDataSource" value-ref="opDataSource" />
<entry key="serverADataSource" value-ref="serverADataSource" />
<entry key="serverBDataSource" value-ref="serverBDataSource" />
</map>
</property>
</bean>
实现的Java代码如下,不需要过多的解释,很⼀⽬了然:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
* @ClassName: MultipleDataSource
* @Description: 配置多个数据源<br>
* @author: yuzhu.peng
* @date: 2018年1⽉12⽇下午4:37:25
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
();
}
public static void removeDataSourceKey() {
}
}
继承⾃spring的AbstractRoutingDataSource,实现抽象⽅法determineCurrentLookupKey,这个⽅法会在每次获得数据库连接Connection的时
候之前,决定本次连接的数据源Datasource,可以看下Spring的代码就很清晰了:
/*获取连接*/
public Connection getConnection()
throws SQLException {
return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
/*此处的determineCurrentLookupKey为抽象接⼝,获取具体的数据源名称*/
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = ((lookupKey);
if ((dataSource == null) && (((this.lenientFallback) || (lookupKey == null)))) {
dataSource = solvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
/*抽象接⼝:也即我们的multipleDataSource实现的接⼝*/
protected abstract Object determineCurrentLookupKey();
第⼆步:每次请求(Service⽅法级别)动态切换数据源
实现思路是利⽤Spring的AOP思想,拦截每次的Service⽅法调⽤,然后根据⽅法的整体路径名,动态切换multipleDataSource中的数据的key。我们的项⽬,针对不同服务也即不同数据库的操作,是彼此之间互相独⽴的,不太建议在同⼀个service⽅法中调⽤不同的数据源,这样的话需要将动态判断是否需要切换的频次(AOP拦截的频次)放在DAO级别,也就是SQL级别。另外,还不⽅便进⾏事务管理。
我们来看动态切换数据源的AOP实现:
import flect.Proxy;
import org.apachemons.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 annotation.Order;
/**
* 数据源切换AOP
*
* @author yuzhu.peng
* @since 2018-01-15
*/
@Aspect
@Order(1)
public class MultipleDataSourceInterceptor {
/**
* 对所有的业务实现类请求之前进⾏数据源切换特别注意,由于⽤到了多数据源,Mapper的调⽤最好只在*ServiceImpl,不然调⽤到⾮默认数据源的表时,会报表不存在的异常 *
* @param joinPoint
* @throws Throwable
*/
@Before("execution(* platform.service..*.*ServiceImpl.*(..))")
public void setDataSoruce(JoinPoint joinPoint)
throws Throwable {
Class<?> clazz = Target().getClass();
String className = Name();
if (ClassUtils.isAssignable(clazz, Proxy.class)) {
className = Signature().getDeclaringTypeName();
}
// 对类名含有serverA的设置为serverA数据源,否则默认为后台的数据源
if (ains(".serverA.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverA);
}
else if (ains(".serverB.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverB);
}
else {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_OP);
}
}
/**
* 当操作完成时,释放当前的数据源如果不释放,频繁点击时会发⽣数据源冲突,本是另⼀个数据源的表,结果跑到另外⼀个数据源去,报表不存在
*
* @param joinPoint
* @throws Throwable
*/
@After("execution(* service..*.*ServiceImpl.*(..))")
public void removeDataSoruce(JoinPoint joinPoint)
throws Throwable {
}
}
拦截所有的ServiceImpl⽅法,根据⽅法的全限定名去判断属于那个数据源的功能,然后选择相应的数据源,发放执⾏完后,释放当前的数据源。注意我⽤到了Spring的 @Order,注解,接下来会讲到,当定义多个AOP的时候,order是很有⽤的。
其他:
⼀开始项⽬中并没有引⼊事务,所以⼀切都OK,每次都能访问到正确的数据源,当加⼊SPring的事务管
理后,不能动态切换数据源了(也好像是事务没有⽣效,反正是⼆者没有同时有效),后来发现原因是AOP的执⾏顺序问题,所以⽤到了上边提到的SPring的Order:
order越⼩,先被执⾏。⾄此,既可以动态切换数据源,⼜可以成功⽤事务(在同⼀个数据源)。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论