Spring+Mybatis动态切换数据源的方法

网友投稿 427 2023-02-21


Spring+Mybatis动态切换数据源的方法

功能需求是公司要做一个大的运营平台:

1、运营平台有自身的数据库,维护用户、角色、菜单、部分以及权限等基本功能。

2、运营平台还需要提供其他不同服务(服务A,服务B)的后台运营,服务A、服务B的数据库是独立的。

所以,运营平台至少要连三个库:运营库,A库,B库,并且希望达到针对每个功能请求能够自动切换到对应的数据源(我最终实现是针对Service的方法级别进行切换的,也可以实现针对每个DAO层的方法进行切换。我们系统的功能是相互之间比较独立的)。

第一步:配置多数据源

1、定义数据源:

我采用的数据源是阿里的DruidDataSource(用DBCP也行,这个随便)。配置如下:

init-method="init" destroy-method="close">

init-method="init" destroy-method="close">

init-method="init" destroy-method="close">

init-method="init" destroy-method="close">

init-method="init" destroy-method="close">

init-method="init" destroy-method="close">

我配置了三个数据源:oPDataSource(运营平台本身的数据源),serverADataSource,serverBDataSource。

2、配置multipleDataSource

multipleDataSource相当于以上三个数据源的一个代理,真正与Spring/Mybatis相结合的时multipleDataSource,和单独配置的DataSource使用没有分别:

class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">

classpath*:/sqlMapperXml/*.xml

classpath*:/sqlMapperXml/*/*.xml

class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">

class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">

classpath*:/sqlMapperXml/*.xml

classpath*:/sqlMapperXml/*/*.xml

class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">

class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

了解了multipleDataSource所处的位置之后,接下来重点看下multipleDataSource怎么实现,配置文件如下:

实现的java代码如下,不需要过多的解释,很一目了然:XASgESJ

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**

*

* @ClassName: MultipleDataSource

* @Description: 配置多个数据源

* @author: yuzhu.peng

* @date: 2018年1月12日 下午4:37:25

*/

public class MultipleDataSource extends AbstractRoutingDataSource {

private static final ThreadLocal dataSourceKey = new InheritableThreadLocal();

public static void setDataSourceKey(String dataSource) {

dataSourceKey.set(dataSource);

}

@Override

protected Object determineCurrentLookupKey() {

return dataSourceKey.get();

}

public static void removeDataSourceKey() {

dataSourceKey.remove();

}

}

继承自spring的AbstractRoutingDataSource,实现抽象方法determineCurrentLookupKey,这个方法会在每次获得数据库连接Connection的时候之前,决定本次连接的数据源Datasource,可以看下Spring的代码就很清晰了:

/*获取连接*/

public Connection getConnection()

throws SQLException {

return determineTargetDataSource().getConnection();

}

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

/*此处的determineCurrentLookupKey为抽象接口,获取具体的数据源名称*/

Object lookupKey = determineCurrentLookupKey();

DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);

if ((dataSource == null) && (((this.lenientFallback) || (lookupKey == null)))) {

dataSource = this.resolvedDefaultDataSource;

}

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 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越小,先被执行。至此,既可以动态切换数据源,又可以成功用事务(在同一个数据源)。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:关于通讯接口的测试用例的信息
下一篇:php测试接口(php接口压力测试)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~