Spring(AbstractRoutingDataSource)实现动态数据源切换-- 转载

springboot之AbstractRoutingDataSource抽象类实现动态多数据源切换原理解析 之前写过两篇实现动态多数据源的文章: 第一种:半自动化模式https://blog.csdn.net/yaomingyang/article/details/100807675 第二种:完全自动化配置模式:https://blog.csdn.net/yaomingyang/article/details/117089979 但是一直未对其实现的核心抽象类AbstractRoutingDataSource进行讲解,今天我们就来看下这个抽象类的源码实现原理;先看下部分源码: /** * Abstract {@ 阅读详情

原始出处:http://linhongyu.blog.51cto.com/6373370/1615895

一、前言

    近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需要。

    那么问题来了,该如何解决多数据源问题呢?不光是要配置多个数据源,还得能灵活动态的切换数据源。以spring+hibernate框架项目为例(引用:http://blog.youkuaiyun.com/wangpeng047/article/details/8866239博客的图片):

    

    单个数据源绑定给sessionFactory,再在Dao层操作,若多个数据源的话,那不是就成了下图:

    

    可见,sessionFactory都写死在了Dao层,若我再添加个数据源的话,则又得添加一个sessionFactory。所以比较好的做法应该是下图:

    接下来就为大家讲解下如何用spring来整合这些数据源,同样以spring+hibernate配置为例。

 

二、实现原理

    1、扩展Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上。)

    从AbstractRoutingDataSource的源码中:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
 

    我们可以看到,它继承了AbstractDataSource,而AbstractDataSource不就是javax.sql.DataSource的子类,So我们可以分析下它的getConnection方法:

复制代码
public Connection getConnection() throws SQLException {  
    return determineTargetDataSource().getConnection();  
}  
   
public Connection getConnection(String username, String password) throws SQLException {  
     return determineTargetDataSource().getConnection(username, password);  
}
复制代码
 

    获取连接的方法中,重点是determineTargetDataSource()方法,看源码:

复制代码
/** 
     * Retrieve the current target DataSource. Determines the 
     * {@link #determineCurrentLookupKey() current lookup key}, performs 
     * a lookup in the {@link #setTargetDataSources targetDataSources} map, 
     * falls back to the specified 
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary. 
     * @see #determineCurrentLookupKey() 
     */  
    protected DataSource determineTargetDataSource() {  
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");  
        Object lookupKey = determineCurrentLookupKey();  
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);  
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {  
            dataSource = this.resolvedDefaultDataSource;  
        }  
        if (dataSource == null) {  
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");  
        }  
        return dataSource;  
    }
复制代码
 

    上面这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractRoutingDataSource类中的一个抽象方法,而它的返回值是你所要用的数据源dataSource的key值,有了这个key值,resolvedDataSource(这是个map,由配置文件中设置好后存入的)就从中取出对应的DataSource,如果找不到,就用配置默认的数据源。

    看完源码,应该有点启发了吧,没错!你要扩展AbstractRoutingDataSource类,并重写其中的determineCurrentLookupKey()方法,来实现数据源的切换:

复制代码
package com.datasource.test.util.database;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
/**
 * 获取数据源(依赖于spring)
 * @author linhy
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
}
复制代码
 

    DataSourceHolder这个类则是我们自己封装的对数据源进行操作的类:

复制代码
package com.datasource.test.util.database;
 
/**
 * 数据源操作
 * @author linhy
 */
public class DataSourceHolder {
    //线程本地环境
    private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
    //设置数据源
    public static void setDataSource(String customerType) {
        dataSources.set(customerType);
    }
    //获取数据源
    public static String getDataSource() {
        return (String) dataSources.get();
    }
    //清除数据源
    public static void clearDataSource() {
        dataSources.remove();
    }
 
}
复制代码
 

    2、有人就要问,那你setDataSource这方法是要在什么时候执行呢?当然是在你需要切换数据源的时候执行啦。手动在代码中调用写死吗?这是多蠢的方法,当然要让它动态咯。所以我们可以应用spring aop来设置,把配置的数据源类型都设置成为注解标签,在service层中需要切换数据源的方法上,写上注解标签,调用相应方法切换数据源咯(就跟你设置事务一样):

@DataSource(name=DataSource.slave1)
public List getProducts(){

 当然,注解标签的用法可能很少人用到,但它可是个好东西哦,大大的帮助了我们开发:

复制代码
package com.datasource.test.util.database;
 
import java.lang.annotation.*;
 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default DataSource.master;
 
    public static String master = "dataSource1";
 
    public static String slave1 = "dataSource2";
 
    public static String slave2 = "dataSource3";
 
}
复制代码

三、配置文件

    为了精简篇幅,省略了无关本内容主题的配置。

    项目中单独分离出application-database.xml,关于数据源配置的文件。

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring 数据库相关配置 放在这里 -->
<beans xmlns="http://www.springframework.org/schema/beans"
       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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
 
    <bean id = "dataSource1" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">   
        <property name="url" value="${db1.url}"/>
        <property name = "user" value = "${db1.user}"/>
        <property name = "password" value = "${db1.pwd}"/>
        <property name="autoReconnect" value="true"/>
        <property name="useUnicode"  value="true"/>
        <property name="characterEncoding" value="UTF-8"/>
    </bean>
 
    <bean id = "dataSource2" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
        <property name="url" value="${db2.url}"/>
        <property name = "user" value = "${db2.user}"/>
        <property name = "password" value = "${db2.pwd}"/>
        <property name="autoReconnect" value="true"/>
        <property name="useUnicode"  value="true"/>
        <property name="characterEncoding" value="UTF-8"/>
    </bean>
 
    <bean id = "dataSource3" class = "com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
        <property name="url" value="${db3.url}"/>
        <property name = "user" value = "${db3.user}"/>
        <property name = "password" value = "${db3.pwd}"/>
        <property name="autoReconnect" value="true"/>
        <property name="useUnicode"  value="true"/>
        <property name="characterEncoding" value="UTF-8"/>
    </bean>
    <!-- 配置多数据源映射关系 -->
    <bean id="dataSource" class="com.datasource.test.util.database.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
        <entry key="dataSource1" value-ref="dataSource1"></entry>
                <entry key="dataSource2" value-ref="dataSource2"></entry>
                <entry key="dataSource3" value-ref="dataSource3"></entry>
            </map>
        </property>
    <!-- 默认目标数据源为你主库数据源 -->
        <property name="defaultTargetDataSource" ref="dataSource1"/>
    </bean>
 
    <bean id="sessionFactoryHibernate" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">com.datasource.test.util.database.ExtendedMySQLDialect</prop>
                <prop key="hibernate.show_sql">${SHOWSQL}</prop>
                <prop key="hibernate.format_sql">${SHOWSQL}</prop>
                <prop key="query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
                <prop key="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</prop>
                <prop key="hibernate.c3p0.max_size">30</prop>
                <prop key="hibernate.c3p0.min_size">5</prop>
                <prop key="hibernate.c3p0.timeout">120</prop>
                <prop key="hibernate.c3p0.idle_test_period">120</prop>
                <prop key="hibernate.c3p0.acquire_increment">2</prop>
                <prop key="hibernate.c3p0.validate">true</prop>
                <prop key="hibernate.c3p0.max_statements">100</prop>
            </props>
        </property>
    </bean>
 
    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate">
        <property name="sessionFactory" ref="sessionFactoryHibernate"/>
    </bean>
 
    <bean id="dataSourceExchange" class="com.datasource.test.util.database.DataSourceExchange"/>
 
    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactoryHibernate"/>
    </bean>
 
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="add*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="update*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="modify*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="edit*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="del*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="save*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="send*" propagation="NESTED" rollback-for="Exception"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="search*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="count*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
 
    <aop:config>
        <aop:pointcut id="service" expression="execution(* com.datasource..*.service.*.*(..))"/>
        <!-- 关键配置,切换数据源一定要比持久层代码更先执行(事务也算持久层代码) -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="service" order="2"/>
        <aop:advisor advice-ref="dataSourceExchange" pointcut-ref="service" order="1"/>
    </aop:config>
 
</beans>
复制代码

四、疑问

    多数据源切换是成功了,但牵涉到事务呢?单数据源事务是ok的,但如果多数据源需要同时使用一个事务呢?这个问题有点头大,网络上有人提出用atomikos开源项目实现JTA分布式事务处理。你怎么看?

浅谈使用Spring多数据源路由AbstractRoutingDatasource实现动态切换数据源 使用Spring多数据源路由AbstractRoutingDatasource实现动态切换数据源 阅读详情

相关推荐

从数学到机床:RTCP背后的几何变换与工业实现

本文深入解析RTCP(Rotary Tool Center Point)技术在五轴数控加工中的核心作用,通过几何变换与运动学原理实现刀尖点跟随,确保加工精度。从数学基础到工业实现,详细探讨了算法架构、机床结构适配及精度保障方法,为数控系统开发和高效加工提供关键技术支撑。

jump7的博客 258

AbstractRoutingDataSource详解,分析多数据源切换原理

在spring中有一个抽象类AbstractRoutingDataSource类,通过这个类可以实现动态数据源切换。如下是这个类的成员变量 // 是存储将要切换的多数据源bean信息,一般是<beanName,DataSource-bean> private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; private Map<Object, DataSource

stay foolish 1万+

Cesium实战:5分钟搞定离线地图与地形加载(附避坑指南)

本文详细介绍了Cesium在内网环境下的离线部署实战方案,重点解决了影像与地形数据的本地加载问题。通过逐步指导项目配置、本地瓦片与自定义地形数据的处理,并优化相机控制体验,帮助开发者快速构建安全、高效的三维地球应用,避免常见的401错误与加载失败陷阱。

weixin_30632089的博客 281

Spring.动态数据源AbstractRoutingDataSource (分库组件,原理分析,源码,实现代码)

分析和设计 如果需要动态数据源,实现一个DataSource代理,在获取Connection时做手脚。 说明:一个请求进来,数据源对象不能变的,获取的Connection是每次都需要获取的。搞一个数据源代理对象,然后在getConnection方法返回不同数据源Connection。 AbstractRoutingDataSource Spring提供的版本 追getConnection方法: 看看protected DataSource determineTargetDat...

记录 分享 成就 5210

S32K344 Flash Driver实战:手把手教你实现任意字节写入与扇区解锁(含C40_Ip避坑指南)

本文深入解析S32K344微控制器的Flash驱动开发,重点解决C40_Ip驱动库的8字节对齐限制和扇区保护锁问题。通过实战案例和代码示例,详细介绍了如何实现任意字节写入、扇区解锁及性能优化策略,为Bootloader和OTA升级等应用提供可靠解决方案。

weixin_33713707的博客 432

【即插即用模块】Transformer篇 | CVPR 2025 | SADA:多尺度+可变形采样,完美替换自注意力实现涨点!

文章摘要 本文提出了一种尺度自适应可变形注意力模块(SADA),用于处理图像修复中的结构化伪影问题。SADA通过多尺度多头机制捕获远程依赖关系,并引入可变形采样偏移来有效处理非均匀空间分布的模式。该模块适用于底层视觉任务,可替代ViT自注意力,实验证明能显著提升性能。代码已开源,支持即插即用集成。 核心特点: 多尺度头适应不同大小的重复模式 可变形偏移优化非均匀分布采样 轻量级设计,易于集成到现有架构 适用场景:图像去噪、超分辨率等底层视觉任务。

主要更新底层视觉(去噪、超分等)相关的科研内容,形式为【论文精读】+【论文复现】 1431

动态数据源-spring-AbstractRoutingDataSource

spring的动态数据源AbstractRoutingDataSource本质用的是多态和本地线程栈使用场景:当一个系统中需要多个数据库参与,我公司的业务是每个公司一个数据库,多个公司用同一个域名同一个网站,用的公司名(公司号)登录时。

weixin_39937571的博客 6586

MATLAB - 激光雷达 - 相机联合标定(Lidar-Camera Calibration)

激光雷达传感器和相机通常在自动驾驶应用中结合使用,因为激光雷达传感器收集三维空间信息,而相机则以二维图像捕捉空间的外观和纹理。您可以融合来自这些传感器的数据来改进物体检测和分类。激光雷达-相机标定可以估算出一个变换矩阵,给出两个传感器之间的相对旋转和平移。在进行激光雷达-相机数据融合时,您可以使用该矩阵。本图说明了激光雷达和相机标定 (LCC) 过程的工作流程,我们使用棋盘格作为标定对象。我们从激光雷达和相机数据中提取棋盘角和平面,然后在它们的坐标系之间建立几何关系,进行标定。

机器人控制与仿真应用 7371

Spring(AbstractRoutingDataSource)实现动态数据源切换--转载

原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一、前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需要。 那么问题来了,该如何解决多数据源问题呢?不光是要配置多个数据源,还得能灵活动...

132

17track单号查询逆向分析

这就说明该网站很有可能存在其它检测点,而检测本地替换最大可能的就是检测了代码格式化或者代码长度。也一样,要么导致网站卡死,要么会导致网页加载异常,这里可能就难到不少人了,让我们一步一步解决。而这个站主要是检测了代码格式化,无限循环检测造成了网站卡死,经多次测试发现检测点共三处。方法中代码很长,但是经调试后发现,它其实只返回一个固定的值,这里直接写死即可。同样的,虽然看起来很长,但是跟过之后就会发现流程很简单,这里主要就是对。追加了一些值,是一条很长的语句,整理代码。,核心流程也很简单,稍微跟一下就出来了,

pyzzd的博客 1623

从 Claude Code 到 SAP ADT,vibing-steampunk 把 ABAP 开发带进 Agentic 工程现场

vibing-steampunk(vsp)是一个连接SAP ADT API与AI编码代理的桥梁工具,通过MCP协议实现ABAP开发的自动化。它绕开传统GUI脚本方式,直接利用ADT REST API进行代码读写、调试和部署。该工具提供包分析、边界检查、LUW分析等功能,并创新性地采用Hyperfocused模式和方法级编辑来优化token使用效率。通过上下文压缩技术,vsp能智能提取关键代码片段,显著提升AI处理ABAP代码的精准度,为SAP系统现代化改造提供了高效的技术路径。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 190

Spring(AbstractRoutingDataSource)实现动态数据源切换(转载)

一、前言     近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需要。     那么问题来了,该如何解决多数据源问题呢?不光是要配置多个数据源,还得能灵活动态的切换数据源。以spring+hibernate框架项目为例(引用:http://blog.csdn.net/wan

zhanglujie2008的专栏 477

Python爬虫实战:爬取国家企业信用信息公示系统(最新反反爬技术解析)

在当今大数据时代,企业信用数据对于市场分析、风险评估和商业决策具有重要价值。国家企业信用信息公示系统作为官方权威的企业信息平台,包含了全国各类企业的注册信息、行政处罚、经营异常等关键数据。复杂的反爬机制(动态Token、验证码、请求频率限制)数据渲染方式多样(部分内容由JavaScript动态加载)法律与合规风险(需遵守《网络安全法》和网站Robots协议)本文将详细介绍如何使用Python最新技术栈构建一个高效、稳定的企业信用信息爬虫,并重点解析应对各种反爬策略的解决方案。

2201_76125261的博客 2696

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

spring 动态数据源切换实例

本文章摘编、转载需要注明来源 http://blog.csdn.net/shadowsick/article/details/8878448 我们很多项目中业务都需要涉及到多个数据源,最简单的做法就是直接在java代码里面lookup需要的数据源,但是这样的做法很明显耦合度太高了, 而且当逻辑流程不够严谨的时候就会出现各种大家不愿意看到的问题,由于我们现在的大多项目已经离不开spr

shadowsick的专栏 3万+

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/u012881904/article/details/77449710AbstractRoutingDataSource动态数据源切换上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他...

weixin_30697239的博客 708

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

Spring(AbstractRoutingDataSource)实现动态数据源切换

pring(AbstractRoutingDataSource)实现动态数据源切换--转载 原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一、前言     近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直接把数据写入项目B的数据库中。这种需求,在数据同步与定时任务中经常需

guolong1983811的专栏 4720

详解AbstractRoutingDataSource(动态数据源切换)实现原理(转载)

转载地址:详解AbstractRoutingDataSource(动态数据源切换)实现原理 多数据源让人最头痛的,不是配置多个数据源,而是如何能灵活动态的切换数据源。例如在一个spring和hibernate的框架的项目中,我们在spring配置中往往是配置一个dataSource来连接数据库,然后绑定给sessionFactory,在dao层代码中再指定sessionFactory来进行数据库...

uzong 5450

spring+mybatis动态切换数据源

位置:org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource作者:CoderJia链接:https://www.zhihu.com/question/64413824/answer/4846961832来源:知乎著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。是 Spring 提供的一个抽象类,它是DataSource的子类,用于实现数据源的动态路由。

蓝天白云 203

利用AbstractRoutingDataSource实现动态数据源切换 (二、Spring+Hibernate)

用spring 的AbstractRoutingDataSource解决了这个问题。 原理如图:   项目采用的是hibernate,直接在spring.xml设置sessionFactory的dataSource属性为动态数据源即可。 因为项目所有数据库结构都一致,为了避免每次设置数据源的时候要改一堆参数,修改了spring AbstractRoutingDataSour...

To Strive To Seek To Find Not To Yield 399

利用AbstractRoutingDataSource实现动态数据源切换 (Spring+Hibernate)

转载之:http://exceptioneye.iteye.com/blog/1698064         this.resolvedDataSources = new HashMap(this.targetDataSources.size());           for (Map.Entry entry : this.targetDataSources.entr

上班搞IT,下班搞ITF。 8184
下一篇: Spring Boot 快速入门
异常的昵称
博客等级 码龄10年 18粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值