Spring+Spring MVC+Mybatis Plus 配置两个(多个)数据库,并且自动切换数据库

    先说一下我做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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值