文章来自:https://www.jianshu.com/p/fddcc1a6b2d8

1. 继承AbstractRoutingDataSource

AbstractRoutingDataSource 是spring提供的一个多数据源抽象类。spring会在使用事务的地方来调用此类的determineCurrentLookupKey()方法来获取数据源的key值。我们继承此抽象类并实现此方法:

package com.ctitc.collect.manage.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
* @author zongbo
* 实现spring多路由配置,由spring调用
*/
public class DataSourceRouter extends AbstractRoutingDataSource { // 获取数据源名称
protected Object determineCurrentLookupKey() {
return HandleDataSource.getDataSource();
} }

2. 线程内部数据源处理类

DataSourceRouter 类中通过HandleDataSource.getDataSource()获取数据源的key值。此方法应该和线程绑定。

package com.ctitc.collect.manage.datasource;
/**
* 线程相关的数据源处理类
* @author zongbo
*
*/
public class HandleDataSource {
// 数据源名称线程池
private static final ThreadLocal<String> holder = new ThreadLocal<String>(); /**
* 设置数据源
* @param datasource 数据源名称
*/
public static void setDataSource(String datasource) {
holder.set(datasource);
}
/**
* 获取数据源
* @return 数据源名称
*/
public static String getDataSource() {
return holder.get();
}
/**
* 清空数据源
*/
public static void clearDataSource() {
holder.remove();
}
}

3. 自定义数据源注解类

对于spring来说,注解即简单方便且可读性也高。所以,我们也通过注解在service的方法前指定所用的数据源。我们先定义自己的注解类,其中value为数据源的key值。

package com.ctitc.collect.manage.datasource;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 数据源注解类
* @author zongbo
*
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}

4. AOP 拦截service并切换数据源

指定注解以后,我们可以通过AOP拦截所有service方法,在方法执行之前获取方法上的注解:即数据源的key值。

package com.ctitc.collect.manage.datasource;

import java.lang.reflect.Method;
import java.text.MessageFormat; 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.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils; /**
* 切换数据源(不同方法调用不同数据源)
*/
@Aspect
@Component
@Order(1) //请注意:这里order一定要小于tx:annotation-driven的order,即先执行DataSourceAspect切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DataSourceAspect {
static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); /**
* 切入点 service包及子孙包下的所有类
*/
@Pointcut("execution(* com.ctitc.collect.service..*.*(..))")
public void aspect() {
} /**
* 配置前置通知,使用在方法aspect()上注册的切入点
*/
@Before("aspect()")
public void before(JoinPoint point) {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod() ;
DataSource dataSource = null ;
//从类初始化
dataSource = this.getDataSource(target, method) ;
//从接口初始化
if(dataSource == null){
for (Class<?> clazz : target.getInterfaces()) {
dataSource = getDataSource(clazz, method);
if(dataSource != null){
break ;//从某个接口中一旦发现注解,不再循环
}
}
} if(dataSource != null && !StringUtils.isEmpty(dataSource.value()) ){
HandleDataSource.setDataSource(dataSource.value());
}
} @After("aspect()")
public void after(JoinPoint point) {
//使用完记得清空
HandleDataSource.setDataSource(null);
} /**
* 获取方法或类的注解对象DataSource
* @param target 类class
* @param method 方法
* @return DataSource
*/
public DataSource getDataSource(Class<?> target, Method method){
try {
//1.优先方法注解
Class<?>[] types = method.getParameterTypes();
Method m = target.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
return m.getAnnotation(DataSource.class);
}
//2.其次类注解
if (target.isAnnotationPresent(DataSource.class)) {
return target.getAnnotation(DataSource.class);
} } catch (Exception e) {
e.printStackTrace();
logger.error(MessageFormat.format("通过注解切换数据源时发生异常[class={0},method={1}]:"
, target.getName(), method.getName()),e) ;
}
return null ;
}
}

5. 数据源配置

假设我有两个库:业务库和订单库。先要配置这两个数据源

  • 业务数据源
<bean id="busiDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<description>业务数据源</description>
<!-- 数据库基本信息配置 -->
<property name="driverClassName" value="${busi.driverClassName}" />
<property name="url" value="${busi.url}" />
<property name="username" value="${busi.username}" />
<property name="password" value="${busi.password}" />
<!-- 初始化连接数量 -->
<property name="initialSize" value="${druid.initialSize}" />
<!-- 最大并发连接数 -->
<property name="maxActive" value="${druid.maxActive}" />
<!-- 最小空闲连接数 -->
<property name="minIdle" value="${druid.minIdle}" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid.maxWait}" />
<!-- 超过时间限制是否回收 -->
<property name="removeAbandoned" value="${druid.removeAbandoned}" />
<!-- 超过时间限制多长; -->
<property name="removeAbandonedTimeout" value="${druid.removeAbandonedTimeout}" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
<!-- 用来检测连接是否有效的sql,要求是一个查询语句-->
<property name="validationQuery" value="${druid.validationQuery}" />
<!-- 申请连接的时候检测 -->
<property name="testWhileIdle" value="${druid.testWhileIdle}" />
<!-- 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能 -->
<property name="testOnBorrow" value="${druid.testOnBorrow}" />
<!-- 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能 -->
<property name="testOnReturn" value="${druid.testOnReturn}" />
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
<!--属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
监控统计用的filter:stat
日志用的filter:log4j
防御SQL注入的filter:wall -->
<property name="filters" value="${druid.filters}" />
</bean>
  • 订单数据源
<bean id="orderDataSource" class="com.alibaba.druid.pool.DruidDataSource"  parent="busiDataSource">
<description>订单数据源</description>
<property name="driverClassName" value="${order.driverClassName}" />
<property name="url" value="${order.url}" />
<property name="username" value="${order.username}" />
<property name="password" value="${order.password}" /> </bean>
  • dataSource 则是刚刚实现的DataSourceRouter,且需要指定此类的 targetDataSources属性和 defaultTargetDataSource属性。

targetDataSources :数据源列表,key-value形式,即上面配置的两个数据源
defaultTargetDataSource:默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源

<bean id="dataSource" class="com.ctitc.collect.manage.datasource.DataSourceRouter" lazy-init="true">
<description>多数据源路由</description>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<!-- write -->
<entry key="busi" value-ref="busiDataSource" />
<entry key="order" value-ref="orderDataSource" />
</map>
</property>
<!-- 默认数据源,如果未指定数据源 或者指定的数据源不存在的话 默认使用这个数据源 -->
<property name="defaultTargetDataSource" ref="busiDataSource" /> </bean>

6. AOP的顺序问题

由于我使用的注解式事务,和我们的AOP数据源切面有一个顺序的关系。数据源切换必须先执行,数据库事务才能获取到正确的数据源。所以要明确指定 注解式事务和 我们AOP数据源切面的先后顺序。

  • 我们数据源切换的AOP是通过注解来实现的,只需要在AOP类上加上一个order(1)注解即可,其中1代表顺序号。
  • 注解式事务的是通过xml配置启动
<tx:annotation-driven transaction-manager="transactionManager"
proxy-target-class="true" order="2" />




 

7. 示例Demo

在每个service方法前使用@DataSource("数据源key")注解即可。

@Override
@DataSource("busi")
@Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public List<BasVersion> test1() {
// TODO Auto-generated method stub
return coreMapper.getVersion();
} @Override
@DataSource("order")
@Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public List<BasVersion> test2() {
// TODO Auto-generated method stub
return coreMapper.getVersion();
}

小礼物走一走,来简书关注我

作者:heichong
链接:https://www.jianshu.com/p/fddcc1a6b2d8
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Spring MVC+Mybatis 多数据源配置的更多相关文章

  1. spring mvc+mybatis+多数据源切换

    spring mvc+mybatis+多数据源切换,选取oracle,mysql作为例子切换数据源.oracle为默认数据源,在测试的action中,进行mysql和oracle的动态切换. web. ...

  2. spring, spring mvc, mybatis整合文件配置详解

    转自:http://www.cnblogs.com/wxisme/p/4924561.html 使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用 ...

  3. spring boot mybatis 多数据源配置

    package com.xynet.statistics.config.dataresources; import org.springframework.jdbc.datasource.lookup ...

  4. spring boot Mybatis多数据源配置

    关于 有时候,随着业务的发展,项目关联的数据来源会变得越来越复杂,使用的数据库会比较分散,这个时候就会采用多数据源的方式来获取数据.另外,多数据源也有其他好处,例如分布式数据库的读写分离,集成多种数据 ...

  5. Spring Boot + Mybatis 多数据源配置实现读写分离

    本文来自网易云社区 作者:王超 应用场景:项目中有一些报表统计与查询功能,对数据实时性要求不高,因此考虑对报表的统计与查询去操作slave db,减少对master的压力. 根据网上多份资料测试发现总 ...

  6. Spring MVC 使用tomcat中配置的数据源

    Spring MVC 使用tomcat中配置的数据源 配置tomcat数据源 打开tomcat目录下的conf目录,编辑sever.xml目录.在<GlobalNamingResources&g ...

  7. Spring Boot 2.X(五):MyBatis 多数据源配置

    前言 MyBatis 多数据源配置,最近在项目建设中,需要在原有系统上扩展一个新的业务模块,特意将数据库分库,以便减少复杂度.本文直接以简单的代码示例,如何对 MyBatis 多数据源配置. 准备 创 ...

  8. 基于Spring + Spring MVC + Mybatis + shiro 高性能web构建

    一直想写这篇文章,前段时间 痴迷于JavaScript.NodeJs.AngularJS,做了大量的研究,对前后端交互有了更深层次的认识. 今天抽个时间写这篇文章,我有预感,这将是一篇很详细的文章,详 ...

  9. Spring3 + Spring MVC+ Mybatis 3+Mysql 项目整合(注解及源码)

    Spring3 + Spring MVC+ Mybatis 3+Mysql 项目整合(注解及源码) 备注: 之前在Spring3 + Spring MVC+ Mybatis 3+Mysql 项目整合中 ...

随机推荐

  1. Java第三阶段学习(十二、HttpServletRequest与HttpServletResponse)

    一.HttpServletRequest 1.概述: 我们在创建Servlet时会覆盖service()方法,或doGet()/doPost(),这些方法都有两个参数,一个为代表请求的request和 ...

  2. 入门NodeJS

    入门NodeJS https://www.cnblogs.com/dotnetcrazy/p/10118756.html NodeJS 1.环境配置 之前讲ES6的时候有提过一部分Node的知识,简单 ...

  3. Spring中AOP实现

    1.什么是SpringAOP 什么是aop:Aspect Oriented Programming的缩写,面向切面编程,通过预编译和动态代理实现程序功能的 统一维护的一种技术 主要功能:日志记录,性能 ...

  4. grep 详解

    grep 是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.(global search regular expression(RE) and print out the l ...

  5. 基于FPGA dspbuilder的DNLMS滤波器实现

          自适应滤波器一直是信号处理领域的研究热点之一,经过多年的发展,已经被广泛应用于数字通信.回声消除.图像处理等领域.自适应滤波算法的研究始于20世纪50年代末,Widrow和Hoff等人最早 ...

  6. 010.KVM虚机冷迁移

    一 实验环境 原虚机名称:vm01-centos6.8 原虚机所在宿主机:kvm-host-2 迁移后虚机名称:vm01-cloud-centos6.8 迁移后虚机所在宿主机:kvm-host-2 二 ...

  7. 004.Autofs自动挂载

    一 安装autofs [root@imxhy data]# yum -y install autofs 二 编辑自动挂载相关配置 2.1 修改master [root@imxhy ~]# vi /et ...

  8. RegExp.$1

    在学习vue2的compile的模板解析的时候,会出现这个正则表达式,不是很清楚,所有就弄明白下并记录下来. RegExp 是javascript中的一个内置对象.为正则表达式.RegExp.$1是R ...

  9. 微信小程序入门实例之记事本

    主要实现思想都在代码的注释中,项目源码见github 首先上项目目录 app.js文件代码如下: //app.js App({ onLaunch: function() { //调用API从本地缓存中 ...

  10. HTTP协议-MIME类型

    每一个 URL 都代表着一个资源对象,而当我们请求一个网页的时候,看似只请求了一个 URI(统一资源标识符),实际上这个网页可能包含多个 URI,例如图片资源的 URI 和视频资源的 URI 等.此时 ...