先说一下我做SSM切换数据库的背景:我参与开发的SPD系统要跟医院的HIS系统对接,对方提供了数据库的存储过程做两个数据库的数据同步,因为院方给HIS方的接口费只有几千块,对方就把所有的流程抛给我们,预先说好的使用WebService来传输数据,最后也变成了他们只提供存储过程,汗!
下面是实现的代码:
XML:
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- dataSource -->
<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<!-- 这里是配置自己的数据库连接 -->
<property name="driverClassName" value="${db.master.driver}" />
<property name="url" value="${db.master.url}" />
<property name="username" value="${db.master.user}" />
<property name="password" value="${db.master.password}" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="mergeStat,wall,log4j2" />
<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="true" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
</bean>
<!-- dataSource1 -->
<bean id="dataSource1"
class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
destroy-method="close">
<!-- 这里是配置自己的数据库连接 -->
<property name="driverClassName" value="${db.master.driver}" />
<property name="url" value="${db1.master.url}" />
<property name="username" value="${db1.master.user}" />
<property name="password" value="${db1.master.password}" />
<!-- 配置监控统计拦截的filters -->
<property name="filters" value="mergeStat,wall,log4j2" />
<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="true" />
<property name="testOnReturn" value="true" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
</bean>
<!-- 装多个数据源,默认数据源为dataSource -->
<bean id="dataSourceOption" class="com.lhkj.commons.datasource.DynamicDataSourceUtils">
<!--默认链接dataSource-->
<property name="defaultTargetDataSource" ref="dataSource"/>
<property name="targetDataSources">
<map>
<entry key="dataSource" value-ref="dataSource"/>
<entry key="dataSource1" value-ref="dataSource1"/>
</map>
</property>
</bean>
<!-- Spring整合Mybatis -->
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSourceOption" />
<!-- 自动扫描Mapping.xml文件 -->
<property name="mapperLocations"
value="classpath*:sqlMapperXml/*.xml"></property>
<property name="configLocation"
value="classpath:xml/mybatis-config.xml"></property>
<property name="typeAliasesPackage" value="com.lhkj.model" />
<property name="globalConfig" ref="globalConfig" />
</bean>
<bean id="refreshMapperCache"
class="com.lhkj.commons.scan.RefreshMapperXml">
<!-- 扫描的映射mapper.xml的文件路径 这个地方要注意mapper的文件,多数据源情况下,只能扫描自己数据源下的mapper,否则会报异常 -->
<property name="packageSearchPath"
value="classpath*:sqlMapperXml/*.xml" />
<!-- 配置自己的数据源 -->
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
<!-- MP 全局配置 -->
<bean id="globalConfig"
class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<!-- <property name="idType" value="0"/> -->
<property name="dbColumnUnderline" value="true" />
</bean>
<!-- MyBatis 动态实现 -->
<bean id="mapperScannerConfigurer"
class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 对Dao 接口动态实现,需要知道接口在哪 -->
<property name="basePackage" value="com.lhkj.mapper" />
</bean>
<!-- 事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务注解 -->
<tx:annotation-driven
transaction-manager="transactionManager" proxy-target-class="true" />
<!-- 事务管理 属性 -->
<tx:advice id="transactionAdvice"
transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*TransferTaskMQ" propagation="REQUIRED"
read-only="true" />
<tx:method name="pdaSelect*" propagation="REQUIRED"
read-only="true" />
<tx:method name="select*" propagation="REQUIRED"
read-only="true" />
<tx:method name="transferConsumer" propagation="NESTED"
rollback-for="Exception" />
<tx:method name="*" propagation="REQUIRED"
rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config expose-proxy="true" proxy-target-class="true">
<aop:advisor advice-ref="transactionAdvice"
pointcut="execution(* com.lhkj.service..*.*(..)) || execution(* com.lhkj.task..*.*(..)) || execution(* com.lhkj.commons.scan.MybatisSqlInterceptor.selectRecordCenter(..))" />
</aop:config>
</beans>
ChooseDataSource .java
//自定义注解
package com.lhkj.commons.datasource;
import java.lang.annotation.*;
/**
* 功能描述: <br>
* 〈
* 动态切换数据源自定义注解类
* 〉
*
* @className: ChooseDataSource
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:21
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChooseDataSource {
/**
* 数据库名称
*/
String dataSourceName() default "dataSource";
}
ChooseDataSourceAspect.java
package com.lhkj.commons.datasource;
import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
/**
* 功能描述: <br>
* 〈
* Aop动态切换数据源的具体实现
* 〉
*
* @className: ChooseDataSource
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:21
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
@Aspect
@Component
public class ChooseDataSourceAspect {
/**
* 功能描述: <br>
* 〈
* 配置接入点,切入controller层
* 〉
*
* @className: ChooseDataSourceAspect
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:25
* @param: []
* @return: void
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
@Pointcut("execution(* com.lhkj.service..*.*(..)) || execution(* com.lhkj.outservice..*.*(..))")
private void controllerAspect() {
}
/* public void doBefore(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
Object[] args = joinPoint.getArgs();
//获取数据库名称参数
ChooseDataSource chooseDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(ChooseDataSource.class);
System.err.println(chooseDataSource);
// ChooseDataSource chooseDataSource = method.getAnnotation(ChooseDataSource.class);
if (chooseDataSource != null) {
String dataSourceName = chooseDataSource.dataSourceName();
//检查数据库名称是否存在
if (DataSourceCheckUtils.check(dataSourceName)) {
DynamicDataSourceHolder.putDataSourceName(dataSourceName);
} else {
DynamicDataSourceHolder.putDataSourceName("dataSource");
}
}
return joinPoint.proceed();
} */
/**
* 功能描述: <br>
* 〈
* 配置切面动态切换数据源
* 〉
*
* @className: ChooseDataSourceAspect
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:25
* @param: [joinPoint 切点]
* @return: java.lang.Object
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
@Around("controllerAspect()")
public Object permission(ProceedingJoinPoint joinPoint) throws Throwable {
Object target = joinPoint.getTarget();
Object[] args = joinPoint.getArgs();
Method method = getMethod(joinPoint, args);
//获取数据库名称参数
ChooseDataSource chooseDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(ChooseDataSource.class);
System.err.println(chooseDataSource);
// ChooseDataSource chooseDataSource = method.getAnnotation(ChooseDataSource.class);
if (chooseDataSource != null) {
String dataSourceName = chooseDataSource.dataSourceName();
//检查数据库名称是否存在
if (DataSourceCheckUtils.check(dataSourceName)) {
DynamicDataSourceHolder.putDataSourceName(dataSourceName);
} else {
DynamicDataSourceHolder.putDataSourceName("dataSource");
}
}
return joinPoint.proceed();
}
/**
* 功能描述: <br>
* 〈
* 获取切面信息
* 〉
*
* @className: ChooseDataSourceAspect
* @author: hf
* @version: 1.0.0
* @date: 2018/12/7 15:28
* @param: [joinPoint 切点, args 参数]
* @return: java.lang.reflect.Method
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
private Method getMethod(ProceedingJoinPoint joinPoint, Object[] args) throws NoSuchMethodException {
String methodName = joinPoint.getSignature().getName();
Class clazz = joinPoint.getTarget().getClass();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (methodName.equals(method.getName())) {
return method;
}
}
return null;
}
}
DataSourceCheckUtils.java
package com.lhkj.commons.datasource;
import java.util.Arrays;
import java.util.List;
/**
* 功能描述: <br>
* 〈
* 判断数据源是否存在
* 〉
*
* @className: DataSourceCheckUtils
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:16
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
public class DataSourceCheckUtils {
static List<String> souList = Arrays.asList("dataSource", "dataSource1");
/**
* 功能描述: <br>
* 〈
* 检查动态切换的数据源是否存在
* 〉
*
* @className: DataSourceCheckUtils
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:16
* @param: [dataSourceName 传递过来的数据源名称]
* @return: boolean
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
public static boolean check(String dataSourceName) {
boolean flag = false;
if (souList.contains(dataSourceName)) {
flag = true;
}
return flag;
}
}
DynamicDataSourceHolder.java
package com.lhkj.commons.datasource;
/**
* 功能描述: <br>
* 〈
* 解决线程安全问题
* 〉
*
* @className: DynamicDataSourceHolder
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:18
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
public class DynamicDataSourceHolder {
//解决线程安全问题
private static final ThreadLocal<String> holder = new ThreadLocal<String>();
public static void putDataSourceName(String dataName) {
holder.set(dataName); //最终是为了执行这行代码完成切换数据库
}
public static String getDataSourceName() {
return holder.get();
}
//默认链接dataSource服务器
public static class DataSourceName {
public final static String dataSource = "dataSource";
}
}
DynamicDataSourceUtils.java
package com.lhkj.commons.datasource;
import org.aopalliance.aop.Advice;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 功能描述: <br>
* 〈
* 动态切换数据源工具类
* 〉
*
* @className: DynamicDataSourceUtils
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:18
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
public class DynamicDataSourceUtils extends AbstractRoutingDataSource implements Advice {
/**
* 功能描述: <br>
* 〈
* 重写抽象方法、根据dataSourceKey决定当前数据库
* 〉
*
* @className: DynamicDataSourceUtils
* @author: hf
* @version: 1.0.0
* @date: 2018/12/25 18:19
* @param: []
* @return: java.lang.Object
* <p>
* History:
* <author:> <time:> <version:> <desc:>
*/
@Override
protected Object determineCurrentLookupKey() {
String dataSourceName = DynamicDataSourceHolder.getDataSourceName();
if (dataSourceName == null) {
dataSourceName = "dataSource";
}
System.out.println("当前选择的数据源是:" + dataSourceName);
return dataSourceName;
}
}
controller:
package com.lhkj.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.lhkj.commons.base.BaseController;
import com.lhkj.outservice.IOrdPurchService;
import com.lhkj.service.ISysRoleService;
/**
* <p>
* 角色管理 前端控制器
* </p>
*
* @author LHKJ
* @since 2018-04-30
*/
@Controller
@RequestMapping("/sysRole")
public class SysRoleController extends BaseController {
@Autowired
private ISysRoleService sysRoleService;
@Autowired
private IOrdPurchService ordPurchServiceImpl;
// ISysRoleService和IOrdPurchService在不同的service包下
@PostMapping("/fileDownload")
@ResponseBody
public void fileDownload() {
try {
sysRoleService.tt(); //由于定义了aop的环绕方法,所以在执行是会先执行aop
System.out.println("----------------------------------------------------------");
System.out.println("----------------------------------------------------------");
ordPurchServiceImpl.commit(null, 0, null); //由于定义了aop的环绕方法,所以在执行是会先执行aop
//
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
OrdPurchServiceImpl.java
package com.lhkj.outservice.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.lhkj.commons.datasource.ChooseDataSource;
import com.lhkj.commons.shiro.ShiroUser;
import com.lhkj.mapper.OrdPurchMapper;
import com.lhkj.model.OrdPurch;
import com.lhkj.outservice.IOrdPurchService;
/**
* <p>
* 采购订单 服务实现类
* </p>
*
* @author zhixuan.wang
* @since 2018-12-03
*/
@Service
public class OrdPurchServiceImpl extends ServiceImpl<OrdPurchMapper, OrdPurch> implements IOrdPurchService {
@Autowired
private OrdPurchMapper ordPurchMapper;
@ChooseDataSource(dataSourceName = "dataSource1")
@Override
public void commit(String workflowkey, long purchid, ShiroUser shiroUser) throws Exception {
OrdPurch ordPurch = ordPurchMapper.selectById(2);
System.err.println("dateSoure1:" + ordPurch);
}
}
SysRoleServiceImpl.java
package com.lhkj.service.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.lhkj.commons.datasource.ChooseDataSource;
import com.lhkj.commons.result.Tree;
import com.lhkj.commons.shiro.ShiroUser;
import com.lhkj.commons.utils.MVCConstUtils;
import com.lhkj.mapper.SysRoleMapper;
import com.lhkj.model.SysResource;
import com.lhkj.model.SysRole;
import com.lhkj.service.ISysRoleService;
/**
* <p>
* 角色表 服务实现类
* </p>
*
* @author LHKJ
* @since 2018-04-28
*/
@Service
public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> implements ISysRoleService {
@Autowired
private SysRoleMapper sysRoleMapper;
@ChooseDataSource(dataSourceName = "dataSource")
@Override
public void tt() throws Exception {
System.err.println("dateSoure:"+sysRoleMapper.selectById(1));
}
}
按上面的流程就能做出多个数据源的切换,出了这些贴出来的代码外,还需要扫描自己建的那几个类:
<context:component-scan base-package="com.lhkj.commons.datasource"></context:component-scan>
不然aop的类扫不到,不能进去环绕方法执行。
切换多个数据源总归是使用aop执行DynamicDataSourceHolder.java里面的holder.set(dataName); 换句话说,只要能确保holder.set(dataName)里面的dataName是我们想要切换的数据源(xml配置数据库的ID)中间用什么方法判断都没问题!
Mybatis和Mybatis Plus的切换方式相似而且Mybatis网上有很多的例子参考,说到底还是只要能确保holder.set(dataName)里面的dataName是我们想要切换的数据源。
原文参考:https://blog.csdn.net/weixin_44187730/article/details/85252717