做一个积极的人

编码、改bug、提升自己

我有一个乐园,面向编程,春暖花开!

文章目录

  • 一、简要概述
  • 二、代码详解
    • 2.1 `DataSourceConstants` 数据源常量类
    • 2.2 `DataSourceType` 自定义数据源注解
    • 2.3 `MultipleDataSource` 多数据源配置类
    • 2.4 `MultipleDataSourceAop` 多数据源自动切换通知类
  • 三、配置详情
  • 四、测试切换
  • 五、实现原理
  • 六、参考文章

一、简要概述

在做项目的时候遇到需要从两个数据源获取数据,项目使用的Spring + Mybatis环境,看到网上有一些关于多数据源的配置,自己也整理学习一下,然后自动切换实现从不同的数据源获取数据功能。

二、代码详解

2.1 DataSourceConstants 数据源常量类

/*** 数据源名称常量类* 对应 application.xml 中 bean multipleDataSource* @author:dufy* @version:1.0.0* @date 2018/12/17*/
public class DataSourceConstants {/*** 数据源1,默认数据源配置*/public static final String DATASOURCE_1 = "dataSource1";/*** 数据源2*/public static final String DATASOURCE_2 = "dataSource2";}

2.2 DataSourceType 自定义数据源注解

/*** 自定义数据源类型注解** @author:dufy* @version:1.0.0* @date 2018/12/17*/@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceType {String value() default DataSourceConstants.DATASOURCE_1;}

2.3 MultipleDataSource 多数据源配置类

MultipleDataSource 继承 AbstractRoutingDataSource 类,为什么继承这个类就可以了?请看 第五章 :实现原理。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** 自定义多数据源配置类** @author:dufy* @version:1.0.0* @date 2018/12/17*/
public class MultipleDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();/*** 设置数据源* @param dataSource 数据源名称*/public static void setDataSource(String dataSource){dataSourceHolder.set(dataSource);}/*** 获取数据源* @return*/public static String getDatasource() {return dataSourceHolder.get();}/*** 清除数据源*/public static void clearDataSource(){dataSourceHolder.remove();}@Overrideprotected Object determineCurrentLookupKey() {return dataSourceHolder.get();}
}

2.4 MultipleDataSourceAop 多数据源自动切换通知类

注意:请设置 @Order(0)。否则可能出现 数据源切换失败问题! 因为要在事务开启之前就进行判断,并进行切换数据源!

/*** 多数据源自动切换通知类<br>* <p>* 首先判断当前类是否被该DataSourceType注解进行注释,如果没有指定注解,则采用默认的数据源配置; <br>* 如果有,则读取注解中的value值,将数据源切到value指定的数据源** @author:dufy* @version:1.0.0* @date 2018/12/17*/@Aspect    // for aop
@Component // for auto scan
@Order(0)  // execute before @Transactional
public class MultipleDataSourceAop {private final Logger logger = Logger.getLogger(MultipleDataSourceAop.class);/*** 拦截 com.**.servicee中所有的方法,根据配置情况进行数据源切换* com.jiuling.tz.service* com.jiuling.web.service* @param joinPoint* @throws Throwable*/@Before("execution(* com.dufy.*.service.*.*(..))")public void changeDataSource(JoinPoint joinPoint) throws Throwable {try {// 拦截的实体类,就是当前正在执行的serviceClass<?> clazz = joinPoint.getTarget().getClass();MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();// 提取目标对象方法注解和类型注解中的数据源标识Class<?>[] types = method.getParameterTypes();if (clazz.isAnnotationPresent(DataSourceType.class)) {DataSourceType source = clazz.getAnnotation(DataSourceType.class);MultipleDataSource.setDataSource(source.value());logger.info("Service Class 数据源切换至--->" + source.value());}Method m = clazz.getMethod(method.getName(), types);if (m != null && m.isAnnotationPresent(DataSourceType.class)) {DataSourceType source = m.getAnnotation(DataSourceType.class);MultipleDataSource.setDataSource(source.value());logger.info("Service Method 数据源切换至--->" + source.value());}} catch (Exception e) {e.printStackTrace();}}/*** 方法结束后*/@After("execution(* com.dufy.*.service.*.*(..))")public void afterReturning() throws Throwable {try {MultipleDataSource.clearDataSource();logger.debug("数据源已移除!");} catch (Exception e) {e.printStackTrace();logger.debug("数据源移除报错!");}}
}

三、配置详情

applicationContext.xml 中配置详情

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"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" xmlns:p="http://www.springframework.org/schema/p"xmlns:util="http://www.springframework.org/schema/util" xmlns:jdbc="http://www.springframework.org/schema/jdbc"xmlns:cache="http://www.springframework.org/schema/cache"xsi:schemaLocation="http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/jdbchttp://www.springframework.org/schema/jdbc/spring-jdbc.xsdhttp://www.springframework.org/schema/cachehttp://www.springframework.org/schema/cache/spring-cache.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util.xsd"><!-- 自动扫描包 ,将带有注解的类 纳入spring容器管理 --><context:component-scan base-package="com.dufy"></context:component-scan><!-- 引入配置文件 --><bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><list><value>classpath*:jdbc.properties</value></list></property></bean><!-- dataSource1 配置 --><bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性 url、user、password --><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/><!-- 配置初始化大小、最小、最大 --><property name="initialSize" value="${ds.initialSize}"/><property name="minIdle" value="${ds.minIdle}"/><property name="maxActive" value="${ds.maxActive}"/><!-- 配置获取连接等待超时的时间 --><property name="maxWait" value="${ds.maxWait}"/><!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --><property name="timeBetweenEvictionRunsMillis" value="${ds.timeBetweenEvictionRunsMillis}"/><!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --><property name="minEvictableIdleTimeMillis" value="${ds.minEvictableIdleTimeMillis}"/><property name="validationQuery" value="SELECT 'x'"/><property name="testWhileIdle" value="true"/><property name="testOnBorrow" value="false"/><property name="testOnReturn" value="false"/><!-- 打开PSCache,并且指定每个连接上PSCache的大小 --><property name="poolPreparedStatements" value="false"/><property name="maxPoolPreparedStatementPerConnectionSize" value="20"/><!-- 配置监控统计拦截的filters --><property name="filters" value="stat"/></bean><!-- dataSource2 配置--><bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"><!-- 基本属性 url、user、password --><property name="url" value="${jd.jdbc.url}"/><property name="username" value="${jd.jdbc.username}"/><property name="password" value="${jd.jdbc.password}"/><!-- 其他配置省略 --></bean><!--多数据源配置--><bean id="multipleDataSource" class="com.jiuling.core.ds.MultipleDataSource"><property name="defaultTargetDataSource" ref="dataSource1" /><property name="targetDataSources"><map key-type = "java.lang.String"><entry key="dataSource1" value-ref="dataSource1"/><entry key="dataSource2" value-ref="dataSource2"/><!-- 这里还可以加多个dataSource --></map></property></bean><!-- mybatis文件配置,扫描所有mapper文件 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" p:dataSource-ref="multipleDataSource"p:configLocation="classpath:mybatis-config.xml"p:mapperLocations="classpath:com/dufy/*/dao/*.xml"/><!-- spring与mybatis整合配置,扫描所有dao --><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:basePackage="com.dufy.*.dao"p:sqlSessionFactoryBeanName="sqlSessionFactory"/><!-- 对dataSource 数据源进行事务管理 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"p:dataSource-ref="multipleDataSource"/><!-- 事务管理 通知 --><tx:advice id="txAdvice" transaction-manager="transactionManager"><tx:attributes><!-- 对insert,update,delete 开头的方法进行事务管理,只要有异常就回滚 --><tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/><!-- select,count开头的方法,开启只读,提高数据库访问性能 --><tx:method name="select*" read-only="true"/><tx:method name="count*" read-only="true"/><!-- 对其他方法 使用默认的事务管理 --><tx:method name="*"/></tx:attributes></tx:advice><!-- 事务 aop 配置 --><aop:config><aop:pointcut id="serviceMethods" expression="execution(* com.dufy.*.service..*(..))"/><aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/></aop:config><!-- 配置使Spring采用CGLIB代理 --><aop:aspectj-autoproxy proxy-target-class="true"/><!-- 启用对事务注解的支持 --><tx:annotation-driven transaction-manager="transactionManager"/></beans>

jdbc.properties 配置内容

##-------------mysql数据库连接配置 ---------------------###
# dataSource1
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.110:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jdbc.username=test
jdbc.password=123456#配置初始化大小、最小、最大
ds.initialSize=1
ds.minIdle=1
ds.maxActive=20
#配置获取连接等待超时的时间
ds.maxWait=60000#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
ds.timeBetweenEvictionRunsMillis=60000#配置一个连接在池中最小生存的时间,单位是毫秒
ds.minEvictableIdleTimeMillis=300000# dataSource2
jd.jdbc.url=jdbc:mysql://192.168.1.120:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jd.jdbc.username=root
jd.jdbc.password=123456

四、测试切换

注意:测试服务类的包路径,因为只有被AOP拦截的到的指定的Service才会进行数据源的切换。

package com.dufy.web.service.impl.Data1ServiceImpl;/*** 使用 dataSourc1 ,配置dataSourc1的数据源* @author:duf* @version:1.0.0* @date 2018/12/17*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_1)
public class Data1ServiceImpl implements Data1Service {@Resourceprivate Data1Mapper data1Mapper;@Overridepublic List<String> selectCaseByUpdateTime(String name) {List<String> data1 = data1Mapper.selectData1(name);return data1;}}package com.dufy.web.service.impl.Data2ServiceImpl;
/*** 使用 dataSourc2 ,配置dataSourc2的数据源* @author:duf* @version:1.0.0* @date 2018/12/17*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_2)
public class Data2ServiceImpl implements Data2Service {@Resourceprivate Data2Mapper data2Mapper;@Overridepublic List<String> selectCaseByUpdateTime(String name) {List<String> data2 = data2Mapper.selectData2(name);return data2;}}

通过测试后发现,两个Service服务器分别调用自己的配置的数据源进行数据的获取!

五、实现原理

基于AbstractRoutingDataSource 实现 多数据源配置,通过AOP来进行数据源的灵活切换。AOP的相关原理这里不做说明,就简单说一下 AbstractRoutingDataSource ,它是如何切换数据源的!

首先我们继承 AbstractRoutingDataSource 它是一个抽象类,然后要实现它里面的一个抽象方法。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean

实现了InitializingBean,InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。

AbstractRoutingDataSourceafterPropertiesSet方法的实现。

public void afterPropertiesSet() {if (this.targetDataSources == null) {throw new IllegalArgumentException("Property 'targetDataSources' is required");} else {this.resolvedDataSources = new HashMap(this.targetDataSources.size());Iterator var1 = this.targetDataSources.entrySet().iterator();while(var1.hasNext()) {Entry<Object, Object> entry = (Entry)var1.next();Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());this.resolvedDataSources.put(lookupKey, dataSource);}if (this.defaultTargetDataSource != null) {this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);}}
}

afterPropertiesSet方法将配置在 applicationContext.xml中的 targetDataSources 解析并构造出一个HashMap。

然后在实际过程中当需要访问数据库的时候,会首先获取一个Connection,下面看一下获取 Connection 的方法。

public Connection getConnection() throws SQLException {return this.determineTargetDataSource().getConnection();
}public Connection getConnection(String username, String password) throws SQLException {return this.determineTargetDataSource().getConnection(username, password);
}protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");// 这里需要注意,通过子类的 determineCurrentLookupKey 方法 获取 lookupKeyObject lookupKey = this.determineCurrentLookupKey();// 转换为对应的 DataSource 数据源DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {// 如果符合上述条件,则获取默认的数据源,也就是在 ApplicationContext.xml 配置的默认数据源dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");} else {return dataSource;}
}
// 抽象方法,用于子类实现
protected abstract Object determineCurrentLookupKey();

知道了 AbstractRoutingDataSource 的抽象方法后,通过AOP拦截,将Service上面配置不同的数据源进行装配到当前请求的ThreadLocal中, 最后 在获取Connection的时候,就能通过determineCurrentLookupKey方法获取到 设置的数据源。

@Override
protected Object determineCurrentLookupKey() {return dataSourceHolder.get();
}

再次强调必须保证切换数据源的Aspect必须在@Transactional这个Aspect之前执行,使用@Order(0)来保证切换数据源先于@Transactional执行)

六、参考文章

Spring, MyBatis 多数据源的配置和管理

spring配置多数据源涉及事务无法切换解决方案(绝对有效)


谢谢你的阅读,如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到!祝你每天开心愉快!


不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : https://aflyun.blog.csdn.net/

愿你我在人生的路上能都变成最好的自己,能够成为一个独挡一面的人

© 每天都在变得更好的阿飞云

【教程】Spring+Mybatis环境配置多数据源相关推荐

  1. Spring 学习系列 -- Spring + Mybatis 从零开始配置多数据源访问

    目的: 项目中以前有整合mybatis + spring操作数据库,但是以前都是单数据库,现需要实现mybatis访问多数据源,依旧使用spring调用mybatis. 通过注解的方式整合 sprin ...

  2. mybatis配置mysql数据源_springboot+mybatis+Druid配置多数据源(mysql+postgre)

    springboot+mybatis+Druid配置多数据源(mysql+postgre) 引入pom依赖 org.mybatis.spring.boot mybatis-spring-boot-st ...

  3. SpringBoo Mybatis Druid配置多数据源

    SpringBoo Mybatis Druid配置多数据源 前言:当单个数据库无法满足大量读写操作需求的时候,就需要用到多个数据库实现读写分离了.那么,这个时候,就需要去配置多数据源了.那么具体如何配 ...

  4. Xamarin Anroid开发教程之验证环境配置是否正确

    Xamarin Anroid开发教程之验证环境配置是否正确 经过前面几节的内容已经把所有的编程环境设置完成了,但是如何才能确定所有的一切都处理争取并且没有任何错误呢?这就需要使用相应的实例来验证,本节 ...

  5. 2021年最新PyCharm使用教程 --- 2、环境配置

    很多新手朋友对PyCharm的使用无从下手,于是花费了一点时间整理这份PyCharm操作手册, 完整PDF下载: 终于写完了!PyCharm操作手册 V1.0版本 PDF下载 目录如下: 2021年最 ...

  6. spring+SpringMVC+MyBatis之配置多数据源

    数据库准备 1.准备2个数据库,本例以mysql为例 在第一个数据库新建表user -- ---------------------------- -- Table structure for use ...

  7. springMVC+Spring+mybatis整合配置版与注解版

    springMVC+Spring+mybatis整合 , 使用配置版来完成: -----------------------------前端 1.创建web系统,导入jar包: spring的jar包 ...

  8. Mybatis | Mybatis-plus配置多数据源,连接多数据库

    文章目录 前言 业务逻辑 使用Mybatis实现 使用Mybatis-plus实现 前言 ​ 工作的时候,遇到了需要将一个数据库的一些数据插入或更新到另一个数据库.一开始使用insert into T ...

  9. 【spring】使用spring的环境配置及从官网获得配置文件所用代码的方法

    环境配置 1.添加jar包 spring-beans-4.1.3.RELEASE.jar spring-context-4.1.3.RELEASE.jar spring-core-4.1.3.RELE ...

最新文章

  1. 电影情感分析 NLP实战
  2. 【HEVC】1、HM-16.7编码器的基本结构
  3. Python3实现邮件群发
  4. tensorflow随笔-非线性回归
  5. python编程规则_python编程规则
  6. Vue3传值(一) props: 父组件向子组件传值
  7. 《SQL Server 必知必会》读书笔记
  8. Redis主从复制和集群配置
  9. python psutil模块怎么在线下载_python之psutil模块
  10. 啊哈算法—解救小哈(深度优先搜索)
  11. html 长度vm,css vm是什么单位?
  12. onenote 导出为 html,Exporting or convert OneNote 2010 to HTML, not MHTML
  13. opencv半透明填充不规则区域
  14. VScode远程root权限调试
  15. Jenkins基础入门-9-构建触发器之项目之间依赖关系
  16. Java 一个数字、字母、汉字各占几个字节
  17. 书签同步工具android,「效率工具推荐」跨平台在线云书签管理工具
  18. html 怎么转换,将 HTML 转换成任意你想要的形式!
  19. 速腾聚创RS-LiDAR激光雷达点云格式转换
  20. 机器学习中常用的分类算法总结

热门文章

  1. JavaFX报错:Can not set java.awt.XX field com.example.xx
  2. LVDS高速接口测试
  3. matlab figure 视图最大化
  4. RabbitMQ原理及集群的深入剖析
  5. 清华经管创业者加速器·区块链应用实验室“区块链合伙人加速计划”开启报名
  6. 解决ip和域名都能够ping通但是启动nginx无法访问网页的问题
  7. LTI的频域分析matlab,matlab与信号实验——连续lti系统的频域分析x26lc.doc
  8. 副本地图(元宝进入一次刷一只怪)
  9. IP地址的格式 以及 网段地址
  10. 【Git】分布式的版本控制工具(一)