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,一经查实,立即删除!

相关文章

C#如何Json转字符串;字符串转Json;Newtonsoft.Json(Json.Net)

什么是JSON C#如何Json转字符串 字符串转Json Newtonsoft.Json(Json.Net)学习笔记&#xff08;转&#xff09; 转载于:https://www.cnblogs.com/macT/p/9040901.html

mysql更改root用户密码

不同版本命令不一样 MySQL 5.7.6 and later: ALTER USER ‘root’’localhost’ IDENTIFIED BY ‘MyNewPass’; MySQL 5.7.5 and earlier: SET PASSWORD FOR ‘root’’localhost’ PASSWORD(‘MyNewPass’);

python2.7 安装numpy no module name zlib

CentOS6.7默认安装Python-2.6 而numpy支持python2.7以上&#xff0c;所以需要安装Python2.7&#xff0c;安装的步骤就不细说了&#xff0c;编译安装,然后再建立软连接。 之后安装numpy时报错no module named setuptools &#xff0c;pip 安装或者源码安装&#xff0c;我采用源…

java : nio 学习

https://github.com/astutesparrow/nio https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html 转载于:https://www.cnblogs.com/chen-msg/p/9041510.html

AWS DevOps – 配合Jenkins和CodeDeploy实现代码自动化部署

AWS DevOps – 配合Jenkins和CodeDeploy实现代码自动化部署 Amazon ElastiCache 连接至 Redis 节点 通过 AWS Command Line Interface使用高级别 s3 命令 在 Linux 上安装 AWS Command Line Interface 在Amazon EC2主机上安装Zmodem协议传输工具lrzsz 安装lrzsz出现configure: …

pip install mysqlclient报错fails with EnvironmentError: mysql_config not found

解决&#xff1a; CentOS yum install mysql-devel 再重新执行pip install mysqlclient解决

管理外部程序的流程

本部分包含&#xff1a; 关于外部程序DBA任务来启用外部过程调用关于外部程序 外部程序是用C&#xff0c;C &#xff0c;Java或其他语言编写的程序&#xff0c;编译并存储在数据库之外&#xff0c;然后由用户会话调用。例如&#xff0c;PL / SQL程序单元可以调用执行特殊用途处…

ssh登陆慢/xhell访问主机慢

用ssh连其他linux机器&#xff0c;会等待10-30秒才有提示输入密码。严重影响工作效率。登录很慢&#xff0c;登录上去后速度正常&#xff0c;这种情况主要有两种可能的原因&#xff1a; DNS反向解析的问题 OpenSSH在用户登录的时候会验证IP&#xff0c;它根据用户的IP使用反向…

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

围观神龙架构首次开箱&#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…

SVN数据代码迁移Windows2012ServerR2

场景描述&#xff1a;服务器迁移&#xff0c;需要把SVN服务从主机A迁移到主机B 环境&#xff1a;两台服务器都是windows-server-2012R2 目录&#xff1a;A、B目录及结构相同 SVN-Server_Home: C:\Program Files\VisualSVN Server\bin Repositories_HOME:D:\Repositories 第一…

程序员怎样锻炼编程思维(学习方法)

1.明确学习目的 学习编程对大多数IT业人员来说都是非常有用的。学编程&#xff0c;做一名编程人员&#xff0c;从个人角度讲&#xff0c;可以解决在软件使用中所遇到的问题&#xff0c;改进现有软件&#xff0c;可以为自己找到一份理想的工作添加重要得砝码&#xff0c;有利于在…

vi或vim查找替换

1、查找 命令模式下 输入 向下查找 :/ 向上查找 :? 比如要查找带有DNS的 :/DNS 回车即可。 2、替换 &#xff1a;s/vivian/sky/ 替换当前行第一个 vivian 为 sky &#xff1a;s/vivian/sky/g 替换当前行所有 vivian 为 sky &#xff1a;n&#xff0c;$s/vivian/…

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

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

mysql连接超时timeout问题

mysql默认连接超时时间为8小时&#xff0c;需要设置长一些 第一种方式直接改my.cnfvi /etc/my.cnf[mysqld] wait_timeout31536000 interactive_timeout31536000 需要重启mysql service mysqld restart 第二种方式&#xff0c;更改变量&#xff0c;不需重启mysqlmsyql> s…

windows server 2012 R2 standard 评估版过期重启

安装的windows server 2012 standard eval评估版&#xff0c;安装完成后有10天的激活时间&#xff0c;过期后&#xff0c;每隔一个小时自动关机。 解决方法&#xff1a;将评估版转成正式版后&#xff0c;再通过KMS激活。 将评估版转成standard正式版的方法&#xff1a; &…

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

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

删除注册表报错“无法删除所有指定的值”

更改文件的所以者及权限。 如删除RCM 右键“权限”—》“高级”—-》更改所有者&#xff0c;权限改为完全控制即可。

Mac本如何卸载MySQL

Mac本如何卸载MySQL 在Mac上卸载MySQL上一件非常麻烦的事&#xff0c;如果没有卸载干净&#xff0c;就会无法安装新的MySQL   怎样才能完全卸载MySQL呢&#xff1f;(包括所有数据库) 执行以下操作: #打开终端窗口 #使用mysqldump备份你的数据库 #停止数据库服务器 sudo rm…