【Java多数据源实现教程】实现动态数据源、多数据源切换方式_java 多数据源-程序员宅基地

技术标签: spring  经验分享  分布式与微服务  java  mybatis  

前言

在这里插入图片描述

本文为 【Java多数据源实现教程】 相关知识,由于自己最近在做导师的项目的时候需要使用这种技术,于是自学了相关技术原理与实现,并将其整理如下,具体包含:多数据源的典型使用场景(包含业务复杂场景读写分离场景),多数据源实现原理及实现方法(包含通过AbstractRoutingDataSource实现动态数据源多数据源切换方式Spring集成多个MyBatis框架实现多数据源),多数据源事务控制(包含只使用主库TransactionManger一个方法开启2个事务),dynamic-datasource多数源组件等~

博主主页:小新要变强 的主页
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
Java微服务开源项目可参考:企业级Java微服务开源项目(开源框架,用于学习、毕设、公司项目、私活等,减少开发工作,让您只关注业务!)


目录

在这里插入图片描述

一、多数据源的典型使用场景

在实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况。以下是两种典型场景:

1️⃣业务复杂(数据量大)

数据分布在不同的数据库中,数据库拆了, 应用没拆。 一个公司多个子项目,各用各的数据库,涉及数据共享…
在这里插入图片描述

2️⃣读写分离

  • 为了解决数据库的读性能瓶颈(读比写性能更高, 写锁会影响读阻塞,从而影响读的性能)
  • 很多数据库拥主从架构。也就是,一台主数据库服务器,是对外提供增删改业务的生产服务器;另一(多)台从数据库服务器,主要进行读的操作。ꞏ
  • 可以通过中间件(ShardingSphere、mycat、mysql-proxy 、TDDL …),但是有一些规模较小的公司,没有专门的中间件团队搭建读写分离基础设施,因此需要业务开发人员自行实现读写分离。

在这里插入图片描述

这里的架构与上图类似。不同的是,在读写分离中,主库和从库的数据库是一致的(不考虑主从延迟)。数据更新操作(insert、update、delete)都是在主库上进行,主库将数据变更信息同步给从库。在查询时,可以在从库上进行,从而分担主库的压力。

二、多数据源实现原理及实现方法

原理:

对于大多数的java应用,都使用了spring框架,spring-jdbc模块提供AbstractRoutingDataSource,其内部可以包含了多个DataSource,然后在运行时来动态的访问哪个数据库。这种方式访问数据库的架构图如下所示:

在这里插入图片描述

应用直接操作的是AbstractRoutingDataSource的实现类,告诉AbstractRoutingDataSource访问哪个数据库,然后由AbstractRoutingDataSource从事先配置好的数据源(ds1、ds2)选择一个,来访问对应的数据库。

在这里插入图片描述

  • (1)当执行数据库持久化操作,只要集成了Spring就一定会通过DataSourceUtils获取Connection
  • (2)通过Spring注入的DataSource获取Connection即可执行数据库操作。所以思路就是:只需配置一个实现了DataSource的Bean, 然后根据业务动态提供Connection即可
  • (3)其实Spring已经提供一个DataSource实现类用于动态切换数据源——AbstractRoutingDataSource
  • (4)分析AbstractRoutingDataSource即可实现动态数据源切换。

1️⃣通过AbstractRoutingDataSource实现动态数据源

通过这个类可以实现动态数据源切换。如下是这个类的成员变量:

private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private Map<Object, DataSource> resolvedDataSources;
  • targetDataSources保存了key和数据库连接的映射关系
  • defaultTargetDataSource标识默认的连接
  • resolvedDataSources这个数据结构是通过targetDataSources构建而来,存储结构也是数据库标识和数据源的映射关系

而AbstractRoutingDataSource实现了InitializingBean接口,并实现了afterPropertiesSet方法。afterPropertiesSet方法是初始化bean的时候执行,通常用作数据初始化。(resolvedDataSources就是在这里赋值)

@Override
public void afterPropertiesSet() {
    
...
this.resolvedDataSources = new HashMap<Object, DataSource(this.targetDataSources.size());//初始化resolvedDataSources
//循环targetDataSources,并添加到resolvedDataSources中
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
    
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
...
}
  • 所以,我们只需创建AbstractRoutingDataSource实现类DynamicDataSource然后初始化targetDataSources和key为数据源标识(可以是字符串、枚举、都行,因为标识是Object)、defaultTargetDataSource即可
  • 后续当调用AbstractRoutingDataSource.getConnection 会接着调用提供的模板方法:determineTargetDataSource
  • 通过determineTargetDataSource该方法返回的数据库标识从resolvedDataSources中拿到对应的数据源
  • 所以,我们只需DynamicDataSource中实现determineTargetDataSource为其提供一个数据库标识

总结,在整个代码中我们只需做4件大事:

  • (1)定义AbstractRoutingDataSource实现类DynamicDataSource
  • (2)初始化时为targetDataSources设置不同数据源的DataSource和标识、及defaultTargetDataSource
  • (3)在determineTargetDataSource中提供对应的数据源标识即可
  • (4)切换数据源标识即可

什么到这还不会? 附上代码:

(1)配置多数据源和 AbstractRoutingDataSource的自定义实现类:DynamicDataSource

配置多数据:

application.yml:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    datasource1:
      url: jdbc:mysql://127.0.0.1:3306/datasource1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
    datasource2:
      url: jdbc:mysql://127.0.0.1:3306/datasource2?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF8&useSSL=false
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver

DynamicDataSourceConfig.java:

@Configuration
public class DynamicDataSourceConfig {
    

    @Bean
    @ConfigurationProperties("spring.datasource.datasource1")
    public DataSource firstDataSource(){
    
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.datasource2")
    public DataSource secondDataSource(){
    
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
    
        Map<Object, Object> targetDataSources = new HashMap<>(5);
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }

}

DynamicDataSource.java:

public class DynamicDataSource extends AbstractRoutingDataSource {
    

    /**
    * ThreadLocal 用于提供线程局部变量,在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
    * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】,相当于线程的 private static 类型变量。
    */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();

    /**
    * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
    *
    * @param defaultTargetDataSource 默认数据源
    * @param targetDataSources 目标数据源
    */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object>
targetDataSources) {
    
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
    
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
    
        CONTEXT_HOLDER.set(dataSource);
    }

    public static String getDataSource() {
    
        return CONTEXT_HOLDER.get();
    }

    public static void clearDataSource() {
    
        CONTEXT_HOLDER.remove();
    }

}

2️⃣多数据源切换方式

多数据源切换方式需要根据我们的具体需求进行选择:

(1)AOP+自定义注解

用于不同业务的数据源: 一般利用AOP,结合自定义注解动态切换数据源

  • (1)自定义注解
@Target({
    ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface WR {
    
    String value() default "W";
}
  • (2)切面类
@Component
@Aspect
public class DynamicDataSourceAspect {
    
    // 前置通知
    @Before("within(com.tuling.dynamic.datasource.service.impl.*) && @annotation(wr)")
    public void before(JoinPoint joinPoint, WR wr){
    
        System.out.println(wr.value());
    }
}
  • (3)使用注解
@Service
public class FrendImplService implements FrendService {
    
    @Autowired
    FrendMapper frendMapper;

    @Override
    @WR("R") // 库2
    public List<Frend> list() {
    
        return frendMapper.list();
    }

    @Override
    @WR("W") // 库1
    public void save(Frend frend) {
    
        frendMapper.save(frend);
    }
}

(2)MyBatis插件

用于读写分离的数据源:如果是MyBatis可以结合插件实现读写分离动态切换数据源

@Intercepts(
        {
    @Signature(type = Executor.class, method = "update", args = {
    MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {
    MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class})})
public class DynamicDataSourcePlugin implements Interceptor {
    

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    

        Object[] objects = invocation.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];
        // 读方法
        if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) {
    
            DynamicDataSource.name.set("R");
        } else {
    
            // 写方法
            DynamicDataSource.name.set("W");
        }
        // 修改当前线程要选择的数据源的key
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
    
        if (target instanceof Executor) {
    
            return Plugin.wrap(target, this);
        } else {
    
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
    

    }
}

3️⃣Spring集成多个MyBatis框架实现多数据源

在这里插入图片描述

WDataSourceConfig.java:

@MapperScan(basePackages = "com.tuling.dynamic.datasource.mapper.w", sqlSessionFactoryRef = "wSqlSessionFactory")
public class WDataSourceConfig {
    
    @Bean
    @Primary
    public SqlSessionFactory wSqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource1) throws Exception {
    
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource1);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/w/*.xml"));
        /*主库设置sql控制台打印*/
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setLogImpl(StdOutImpl.class);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
}

RDataSourceConfig.java:

@MapperScan(basePackages = "com.tuling.dynamic.datasource.mapper.r", sqlSessionFactoryRef = "rSqlSessionFactory")
public class RMyBatisConfig {
    
    @Bean
    public SqlSessionFactory rSqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource2) throws Exception {
    
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource2);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/r/*.xml"));
        /*从库设置sql控制台打印*/
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setLogImpl(StdOutImpl.class);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
}

后记

在这里插入图片描述
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_42146402/article/details/127907963

智能推荐

用JavaScript实现选项卡功能,当选择全选按钮的时候,下边的复选框所以按钮被选中,当取消一个复选框按钮时,全选框按钮失去效果,当再次把所有复选框按钮选择实现全选_、实现操作复选框,要求是可以选择部分选项,也可以全选,全选按钮的状态根据选中的选项个数自动变化,即全-程序员宅基地

文章浏览阅读779次。点击全选,让下边的按钮都被选中,以及复选框的交互简单上代码啦~~CSS样式<style> * { padding: 0; margin: 0; } .wrap { width: 300px; margin: 100px auto 0; } table { border-collapse: _、实现操作复选框,要求是可以选择部分选项,也可以全选,全选按钮的状态根据选中的选项个数自动变化,即全选按钮的状态回自动取消或者自动勾选。

http请求工具类HttpClientUtil(get使用body,post乱码问题解决)_((httpentityenclosingrequestbase) httppost.setenti-程序员宅基地

文章浏览阅读3.4k次。最近很多发送http请求的需求存在,书写下util1:配置需要的依赖在pom.xml中配置http相关依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <s..._((httpentityenclosingrequestbase) httppost.setentity(new inputstreamentity(i

【原创】超全自用idea常用插件记录_idea feign插件-程序员宅基地

文章浏览阅读1.7k次。注:idea插件可以使用账号同步,建议使用账号同步进行设置,这里作为使用记录_idea feign插件

回文日期_输出回文日期-程序员宅基地

文章浏览阅读3.1k次。链接:https://ac.nowcoder.com/acm/contest/216/A来源:牛客网 题目描述众所周知,小K是nowcoder的暴政苟管理,所以小K很擅长踢树,虽然本题与踢树无关小K喜欢将日期排列成yyyy-mm-dd的形式(位数不足添零补齐)的形式,虽然这与小K只会做回文字符串这道水题无关,但小K觉得日期组成的回文串也是挺可爱的。作为一个凉心出题人,小K决定给你一..._输出回文日期

深入了解C语言(函数的参数传递和函数使用参数的方法) _c语言中prog03_06了解函数-程序员宅基地

文章浏览阅读1k次。 深入了解C语言(函数的参数传递和函数使用参数的方法) 深入了解C语言(函数的参数传递和函数使用参数的方法)tangl_99(原作) C语言生成的代码在执行效率上比其它高级语言都高.现在让我们来看看C语言生成的代码具体是什么样子的.当你看完本文对于C语言的了解一定会更深一步了. 本文通过一个个实际案例程序来讲解C语言. 研究案例一 工具: Turboc C v2.0,Debug_c语言中prog03_06了解函数

Android App开发-简单控件(4)——按钮触控和图像显示_通过按钮的点击事件控制图片的现实和隐藏-程序员宅基地

文章浏览阅读1.1k次,点赞18次,收藏14次。本节介绍了按钮控件的常见用法,包括:如何设置大小写属性与点击属性,如何响应按钮的点击事件和长按事件,如何禁用按钮又该如何启用按钮,等等。_通过按钮的点击事件控制图片的现实和隐藏

随便推点

【Linux】文件系统-程序员宅基地

文章浏览阅读1.7k次,点赞27次,收藏22次。了解磁盘的物理结构、磁盘的具体物理结构、逻辑抽象、软硬连接,动静态库

python实现ks算法_python, 在信用评级中,计算KS statistic值-程序员宅基地

文章浏览阅读456次。# -*- coding: utf-8 -*-import pandas as pdfrom sklearn.grid_search import GridSearchCVfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScalerfrom sklearn.u..._ks_statistic

类加载过程 与 代码的执行顺序_类加载后代码的执行顺序-程序员宅基地

文章浏览阅读5k次。https://www.cnblogs.com/ysocean/p/8194428.html 代码的执行顺序https://www.jianshu.com/p/3556a6cca7e5类加载过程_类加载后代码的执行顺序

Oracle LiveLabs实验:Introduction to Oracle Spatial Studio_oracle_spatial 可视化-程序员宅基地

文章浏览阅读601次。本实验介绍了适用于 Oracle Spatial Studio。他既可以在云上,也可以在本地作为Java应用部署。介绍详见这里。此实验申请地址在这里,时间为120分钟。此实验的帮助见这里。本实验使用的地图为OpenStreetMap,即免费的维基世界地图。此实验会自动创建一个ADW,需要通过OCI Console完成初始化配置,然后可以通过网页访问Spatial Studio简介在本次研讨会中,您将探索 Spatial Studio 用于自助式空间分析和可视化的功能。 使用交通事故、警察局和警察_oracle_spatial 可视化

UG\NX二次开发 改变Block UI界面的尺寸_ug二次开发 调整 对话框大小-程序员宅基地

文章浏览阅读1.3k次。改变Block UI界面的尺寸_ug二次开发 调整 对话框大小

基于深度学习的股票预测(完整版,有代码)_基于深度学习的股票操纵识别研究python代码-程序员宅基地

文章浏览阅读1.3w次,点赞18次,收藏291次。基于深度学习的股票预测数据获取数据转换LSTM模型搭建训练模型预测结果数据获取采用tushare的数据接口(不知道tushare的筒子们自行百度一下,简而言之其免费提供各类金融数据 , 助力智能投资与创新型投资。)python可以直接使用pip安装tushare!pip install tushareCollecting tushare Downloading https://files.pythonhosted.org/packages/17/76/dc6784a1c07ec040e74_基于深度学习的股票操纵识别研究python代码

推荐文章

热门文章

相关标签