文章来自: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. docker:定制node.js的版本

    本想用alpine,但如果想使用node.js 6.3.1版本的软件, 总会搞不定glibc和libstdc++报一个无法识别版本信息的错误. 搞了一天,算了.使用debian:stretch-sli ...

  2. 使用nginx统一代理dashboard,grafana,Prometheus二级目录访问

    k8s上的这些管理工具必不可少,可以统一在nginx下的二级目录下. ingress是好,但我们不方便使用内部域名,相信么...:) 一,prometheus改造 在prometheus的deploy ...

  3. 了解一下LDC

    转自: Linguistic Data Consortium (LDC) 因为Kaldi里面各种语料涉及到LDC,搜罗一下,好像并没有相应的教程和网站用法.... 搞 NLP 的人经常会听到一个神秘的 ...

  4. JS易错知识点

    JAVASCRIPT易错知识点整理 前言 本文是学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由 ...

  5. 【Java】 大话数据结构(5) 线性表之双向链表

    本文根据<大话数据结构>一书,实现了Java版的双向链表. 在每个数据结点中都有两个指针,分别指向直接后继和直接前驱,这样的链表称为双向链表. 双向链表的结构如图所示: 查找元素可以根据元 ...

  6. jQuery选择器--简洁又全面(转)

    原文:http://www.cr173.com/html/21124_1.html 选择器是jQuery的根基,在jQuery中,对事件处理.遍历DOM和Ajax操作都依赖于选择器.jQuery选择器 ...

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

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

  8. 命令:install

    简介 从命令的名字上来看,会让人误以为这是一个和安装相关的命令. 其实不然,install命令用于复制文件(cp)或创建空目录(mkdir)并设置相关的属性(chown.chmod). 这里的属性包含 ...

  9. ARP欺骗防御工具arpon

    ARP欺骗防御工具arpon   ARP欺骗是局域网最为常见的中人间攻击实施方式.Kali Linux提供一款专用防御工具arpon.该工具提供三种防御方式,如静态ARP防御SARPI.动态ARP防御 ...

  10. 模拟赛T1 素数

    没有链接 描述: 给p,q,求a^2+b^2 = p*q解的个数,p,q是素数 沙雕打表结论题 然后怼了3h吼爆零 题解 首先这是个结论题 然后这是证明 代码 #include <stdio.h ...