IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springboot动态数据源原理分析及实现 -> 正文阅读

[Java知识库]springboot动态数据源原理分析及实现

作者:token keyword

springboot动态数据源实现的两个关键要素

说起动态数据源,大家肯定都觉得是个很神秘的东西,其实若仔细研究就会发现并不复杂,具体来说,只有两步:
第一、spring容器中注入两个数据源。
第二、在运行时根据业务需要获取特定的数据源。

读者老爷:老哥你这不是废话吗,看完你这个讲解,发现它就是讲解,屁用没有。
作者老弟:别急麻,现实中咱们解决问题就是,从外到里,一层一层递进。咱们就从第一步spring容器注入多数据源开始递进。
首先咱们要搞明白的,如何注入数据源及注入的数据源由谁来管理呢?
注入数据源我就不讲解了就是声明几个bean,关键是这个多个数据源由谁管理,具体的是哪个类来管理?

讲这个之前,咱们梳理一下平常使用spring+mybatis的操作数据库的流程,基本上就是如下四步。

datasource–>sqlSessionfactory–>connnection–>crud。

大家仔细考虑,sqlsessionFactory会关心你用什么数据源吗?显然它不会,只要你给一个它能用的数据源,他就能帮你干事。所以咱们要实现动态数据源就得在dataSource这里做文章
那怎么做呢?
试想一下,如果咱们有一个自定义的包装类,它继承了DataSource,并且咱们在这个包装类内部维护一个存储多个数据源的MAP,当调用的时候咱们根据业务逻辑传过来的key,动态获取对应的dataSource不就实现这个功能了吗。你看动态数据源它本质就是这么个原理。

同时spring这个框架老哥其实已经帮我们实现了这个包装类,org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
AbstractRoutingDataSource是一个抽象类,该类中determineTargetDataSource()方法将调用determineCurrentLookupKey()方法来动态获取数据源
我们要做的就是写一个子类继承该类,然后重写里面的determineCurrentLookupKey()方法,用于自定义key的获取方式,一般的我们将数据源的key存放在ThreadLocal中。定义好子类之后,咱们就将声明好的多个数据源配置到该子类中,然后将该类注入到spring容器中,同时交给sqlSessionFactory。
在这里插入图片描述

自定义DynamicDataSource类,包含两块:

1、重写determineCurrentLookupKey()方法,用于从当前线程中获取对应的数据源。
2、CONTEXT_HOLDER 一个ThreaLocal用于数据源与当前线程的绑定与解绑。

package com.app.microservice.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.ArrayDeque;
import java.util.Deque;


/**
 * @author:whh
 * @date: 2022-04-28 21:11
 * <p></p>
 */
public class DynamicDatasource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return peek();
    }


    /**
     * 将当前线程与数据源的key进行绑定
     */
    private static final ThreadLocal<Deque<String>> CONTEXT_HOLDER = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new ArrayDeque();
        }
    };

    /**
     * 获得当前线程数据源
     *
     * @return 数据源名称
     */
    public static String peek() {
        return CONTEXT_HOLDER.get().peek();
    }

    /**
     * 设置当前线程数据源
     *
     * @param dataSource 数据源名称
     */
    public static void push(String dataSource) {
        CONTEXT_HOLDER.get().push(dataSource);
    }

    /**
     * 清空当前线程数据源
     */
    public static void poll() {
        Deque<String> deque = CONTEXT_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            CONTEXT_HOLDER.remove();
        }
    }

}

动态数据源配置类 DynamicDataSourceConfig,,这里为了演示方便,我只是做了个样例,里面具体数据源的相关配置(primary、slave)以及sqlSessionFactory,还需要大家自己定义。

package com.app.microservice.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author:whh
 * @date: 2022-04-28 21:25
 * <p></p>
 */
@Configuration
public class DynamicDataSourceConfig {


    /**
     * 主数据源
     * 这里只是做了个样例,具体的参数大家自行配置
     * @return
     */
    @Primary
    @Bean("primaryDatasource")
    public DruidDataSource primary(){
        DruidDataSource primary = new DruidDataSource();
        return primary;
    }

    /**
     * 从数据源
     * @return
     */
    @Bean("slaveDatasource")
    public DruidDataSource slave(){
        DruidDataSource slave = new DruidDataSource();
        return slave;
    }


    @Bean
    public DynamicDatasource dynamicDatasource(@Qualifier("primaryDatasource") DruidDataSource primary,
                                               @Qualifier("slaveDatasource") DruidDataSource slave){
        DynamicDatasource dynamicDatasource = new DynamicDatasource();
        //配置多数据源
        Map<Object, Object> targetDataSources = new ConcurrentHashMap<>();
        targetDataSources.put("primaryDatasource",primary);
        targetDataSources.put("slaveDatasource",slave);
        dynamicDatasource.setTargetDataSources(targetDataSources);
        //配置主数据源
        dynamicDatasource.setDefaultTargetDataSource(primary);
        //初始化父类
        dynamicDatasource.afterPropertiesSet();
        return dynamicDatasource;
    }


    /**
     * 配置sqlSessionFactory
     * @param dynamicDatasource
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDatasource dynamicDatasource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDatasource);
        //配置mapper路径及配置自定义拦截器
        //...........
        return sqlSessionFactoryBean.getObject();
    }
}

以上咱们就做好了动态数据源的配置,那么接下来就是在业务代码中进行数据源的动态切换。

其实现也不复杂,具体的就是使用AOP技术,这里我们可以自定义一个注解,然后在需要用到的业务逻辑上标注该注解,并在注解参数中声明使用数据源的key,然后咱们使用AOP技术对标有该注解的方法进行拦截,然后切换到指定的数据源即可。

注解+AOP实现数据源切换

package com.app.microservice.config;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author:whh
 * @date: 2022-04-28 21:58
 * <p></p>
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect{
    @Pointcut("@annotation(com.app.microservice.config.DataSourceAnnotation)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSourceAnnotation dataSourceAnnotation = method.getAnnotation(DataSourceAnnotation.class);
        if (dataSourceAnnotation != null) {
            DynamicDatasource.push(dataSourceAnnotation.value().name());
        }
        try {
            return point.proceed();
        } finally {
        //释放
            DynamicDatasource.poll();
        }
    }
}

好啦,大功告成!!!!

总结

不知到,有没有人困惑,在讲解org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource动态数据源原理时,我说determineTargetDataSource()方法将调用determineCurrentLookupKey()方法来动态获取数据源
但是determineTargetDataSource()这个方法是谁调用的呢?
其实这个问题困惑了我,虽然知道最终这个会由持久层框架mybatis来获取真正的数据源,但是mybatis里面应该不会直接是用determineTargetDataSource()这个方法来获取数据源呀,如果真是这样那耦合也太大了,,后来我在翻看AbstractRoutingDataSource这个类发现了这个两个方法,unwrap()和isWrapperFor(),发现它在这里调用了determineTargetDataSource(),并且unwrap其实是DataSource接口继承而来,,,那么这个问题就清楚了,,持久成框架获取的肯定是DataSource类型的,所以它不用关心子类到底是谁,只要你这个子类实现了DataSource接口,那么它就可以去包装获取正真的数据源。看到此我真正感受它的巧妙以及面向接口编程的强大。

在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-30 08:33:07  更:2022-04-30 08:33:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:17:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码