Spring MVC+Mybatis 多数据源配置

文章来自: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
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载于:https://www.cnblogs.com/xiaofengfeng/p/9039382.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/570853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

围观神龙架构首次开箱,现场直播暴力拆机

围观神龙架构首次开箱&#xff0c;现场直播暴力拆机 发布时间&#xff1a;2018-05-16 13:43:01686人关注34人参与阿里云X-Dragon大事记2017年4月&#xff1a;阿里云X-Dragon项目立项&#xff1b;2017年10月&#xff1a;阿里云正式推出基于X-Dragon架构的弹性裸金属服务器&#…

windows-server-2012R2离线中文语言包安装

1、离线包下载地址http://download.csdn.net/detail/github_38358734/9858412 2、安装方法&#xff1a; 解压离线包 Dism /online /Add-Package /PackagePath:C:\test\LangPacks\lp.cab 大概10分钟&#xff0c;完成。 然后重启电脑&#xff0c;到控制面板语言区域选项&…

文字闪烁效果

效果图&#xff1a; HTML Code: <a class"blink" href"#" target"_blank"> 扁平化设计看上去非常简单、直观扁平化设计看上去非常简单、直去非化设计看上去非常简单、直观扁平化设计看上去非常简单、直观扁平常简单</a> JQuery Code…

BZOJ 3295: [Cqoi2011]动态逆序对 cdq分治

https://www.lydsy.com/JudgeOnline/problem.php?id3295 这个妹妹我曾见过的~~~ 之前应该在校内oj写了&#xff0c;似乎还写过题解&#xff1f;发现没写博客就重新水一遍代码水一篇博客好了。 把找逆序对的过程想成一个一个往里塞数字然后找每个数字可以组成的逆序对&#xff…

p1、查询端口号占用,根据端口查看进程信息/p

2017年6月份的时候&#xff0c;我就着手在公司推广git&#xff0c;首先我自己尝试搭建了GitLab来管理代码&#xff0c;并且通过以下博客记录了GitLab的搭建&#xff0c;以及GitLab备份&#xff0c;GitLab升级等事情。 git学习——>在CenterOS系统上安装GitLab并自定义域名访…

point-position2修改版

说明&#xff1a; 在共面直线测试中&#xff0c;由于计算误差等原因&#xff0c;共面条件判断不准&#xff0c;但计算结果依然正确。 // point-position2.cpp : 定义控制台应用程序的入口点。 #include "stdafx.h" #include <stdio.h> #include <iostream&g…

Linux学习总结(十六)系统用户及用户组管理

先来认识两个文件/etc/passwd/etc/shadow我们打印出首尾三行&#xff0c;来了解下&#xff1a;每行由&#xff1a;分割为7段&#xff0c;每段含义为&#xff1a;第一段&#xff1a;用户名&#xff0c;比如root 用户&#xff0c;普通用户test,lv,test1第二段&#xff1a;早期存放…

hadoop综合大作业

Hadoop综合大作业 要求&#xff1a; 1.用Hive对爬虫大作业产生的文本文件&#xff08;或者英文词频统计下载的英文长篇小说&#xff09;词频统计。 词频统计的截图如下&#xff1a; 上次我所使用的文章是一篇中文文章&#xff0c;所以这次我用了一篇英文文档来进行分词&#xf…

MPI对道路车辆情况的Nagel-Schreckenberg 模型进行蒙特卡洛模拟

平台Ubuntu 16.04&#xff0c;Linux下MPI环境的安装见链接&#xff1a;https://blog.csdn.net/lusongno1/article/details/61709460据 Nagel-Schreckenberg 模型&#xff0c;车辆的运动满足以下规则&#xff1a;1. 假设当前速度是 v &#xff0c;和前一辆车的距离为d。2. 如…

Android 中.aar文件生成方法与用法

https://i.cnblogs.com/EditPosts.aspx?opt1 无论是用Eclipse还是用Android Studio做android开发&#xff0c;都会接触到jar包&#xff0c;全称应该是&#xff1a;Java Archive&#xff0c;即java归档文件。在用AS的过程中&#xff0c;你会发现有aar这么个东西&#xff0c;经查…

windows10上安装mysql

环境&#xff1a;windwos 10&#xff08;1511&#xff09; 64bit、mysql 5.7.14 一、下载mysql1. 在浏览器里打开mysql的官网http://www.mysql.com/2. 进入页面顶部的"Downloads"3. 打开页面底部的“Community(GPL) Downloads” 4. 在页面中间的位置找到我们windows上…

sql server 内存初探

sql server 内存初探 原文:sql server 内存初探一. 前言 对于sql server 这个产品来说&#xff0c;内存这块是最重要的一个资源&#xff0c; 当我们新建一个会话&#xff0c;相同的sql语句查询第二次查询时间往往会比第一次快&#xff0c;特别是在sql统计或大量查询数据输出时&…

使用TcpClient的例程

例子1&#xff1a; ///假定一切工作正常 ///连接后发送一次消息&#xff0c;然后不停接受消息并且打印 主要API说明 TcpClient clientnew TcpClient(); client.Connect("127.0.0.1",8888); NetworkStream streamclient.GetStream(); 发送&#xff1a; stream.Write(o…

20172324 2017-2018-2《程序设计与数据结构》实验三报告

20172324 2017-2018-2《程序设计与数据结构》实验三报告 课程&#xff1a;《程序设计与数据结构》 班级&#xff1a; 1723 姓名&#xff1a; 曾程 学号&#xff1a;20172324 实验教师&#xff1a;王志强 实验日期&#xff1a;2018年5月23日 必修/选修&#xff1a; 必修 一、实验…

mysql if--else

SQL之case when then用法 case具有两种格式。简单case函数和case搜索函数。 --简单case函数 case sexwhen 1 then 男when 2 then 女’else 其他 end --case搜索函数 case when sex 1 then 男when sex 2 then 女else 其他 end 这两种方式&#xff0c;可以实现相同的功能。简…

笔记41 Spring Web Flow——Demo

订购披萨的应用整体比较比较复杂&#xff0c;现拿出其中一个简化版的流程&#xff1a;即用户访问首页&#xff0c;然后输入电话号&#xff08;假定未注册&#xff09;后跳转到注册页面&#xff0c;注册完成后跳转到配送区域检查页面&#xff0c;最后再跳转回首页。通过这个简单…

CSS3弹性盒子Flex

CSS3弹性盒子Flex 基础知识和术语 原文链接&#xff1a;https://css-tricks.com/snippets/css/a-guide-to-flexbox/ 父级&#xff08;flex容器&#xff09;的属性 &#xff03;显示 这定义了一个flex容器; 内联或块取决于给定的值。它为所有直接的孩子提供了一个弹性环境。 .co…

cloudera manager的7180 web界面访问不了的解决办法(图文详解)

说在前面的话 我的机器是总共4台&#xff0c;分别为ubuntucmbigdata1、ubuntucmbigdata2、ubuntucmbigdata3和ubuntucmbigdata4。&#xff08;注意啦&#xff0c;以下是针对Ubuntu系统的&#xff09; 在ubuntucmbigdata1上执行了 sudo apt-get install cloudera-manager-daemon…

2018 ios开发者账号同意新协议加联系电话教程

苹果开发者账号经常会更新协议&#xff0c;需要同意新的协议账号才能正常使用。 1、首先登录苹果开发者中心https://developer.apple.com/account/ 会出现下面飘红的提示&#xff0c;就是提示你要同意新协议。因为苹果规则的改变&#xff0c;需要先到appid管理中心加个联系手机…

Django REST FRAMEWORK swagger(一)框架详解

Django REST FRAMEWORK swagger&#xff08;一、框架详解&#xff09; 一.Django REST SWAGGER框架图 具体见下图 二.说明 RESTFul说明 每一个URI代表一种资源&#xff1b; 客户端和服务器之间&#xff0c;传递这种资源的某种表现层&#xff1b; 客户端通过四个HTTP动词&…