SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

这篇具有很好参考价值的文章主要介绍了SpringBoot整合多数据源,并支持动态新增与切换(详细教程)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

推荐文章:

    1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;

    2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;

    3、java后端接口API性能优化技巧

    4、SpringBoot+MyBatis流式查询,处理大规模数据,提高系统的性能和响应能力。

springboot多数据源配置和切换,springBoot,java,spring boot,后端,java

一、概述

      在项目的开发过程中,遇到了需要从数据库中动态查询新的数据源信息并切换到该数据源做相应的查询操作,这样就产生了动态切换数据源的场景。为了能够灵活地指定具体的数据库,本文基于注解和AOP的方法实现多数据源自动切换。在使用过程中,只需要添加注解就可以使用,简单方便。(代码获取方式:见文章底部(开箱即用))

二、构建核心代码

2.1、AbstractRoutingDataSource构建

package com.wonders.dynamic;import org.springframework.beans.factory.InitializingBean;import org.springframework.jdbc.datasource.AbstractDataSource;import org.springframework.jdbc.datasource.lookup.DataSourceLookup;import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;import org.springframework.lang.Nullable;import org.springframework.util.Assert;import org.springframework.util.CollectionUtils;import javax.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;import java.util.Map;/** * @Description: TODO:抽象类AbstractRoutingDataSource,实现动态数据源切换 * @Author: yyalin * @CreateDate: 2023/7/16 14:40 * @Version: V1.0 */public abstract class AbstractRoutingDataSource extends AbstractDataSource        implements InitializingBean {    //目标数据源map集合,存储将要切换的多数据源bean信息    @Nullable    private Map<Object, Object> targetDataSources;    //未指定数据源时的默认数据源对象    @Nullable    private Object defaultTargetDataSource;    private boolean lenientFallback = true;    //数据源查找接口,通过该接口的getDataSource(String dataSourceName)获取数据源信息    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();    //解析targetDataSources之后的DataSource的map集合    @Nullable    private Map<Object, DataSource> resolvedDataSources;    @Nullable    private DataSource resolvedDefaultDataSource;    //将targetDataSources的内容转化一下放到resolvedDataSources中,将defaultTargetDataSource转为DataSource赋值给resolvedDefaultDataSource    public void afterPropertiesSet() {        //如果目标数据源为空,会抛出异常,在系统配置时应至少传入一个数据源        if (this.targetDataSources == null) {            throw new IllegalArgumentException("Property 'targetDataSources' is required");        } else {            //初始化resolvedDataSources的大小            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());            //遍历目标数据源信息map集合,对其中的key,value进行解析            this.targetDataSources.forEach((key, value) -> {                //resolveSpecifiedLookupKey方法没有做任何处理,只是将key继续返回                Object lookupKey = this.resolveSpecifiedLookupKey(key);                //将目标数据源map集合中的value值(德鲁伊数据源信息)转为DataSource类型                DataSource dataSource = this.resolveSpecifiedDataSource(value);                //将解析之后的key,value放入resolvedDataSources集合中                this.resolvedDataSources.put(lookupKey, dataSource);            });            if (this.defaultTargetDataSource != null) {                //将默认目标数据源信息解析并赋值给resolvedDefaultDataSource                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);            }        }    }    protected Object resolveSpecifiedLookupKey(Object lookupKey) {        return lookupKey;    }    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {        if (dataSource instanceof DataSource) {            return (DataSource)dataSource;        } else if (dataSource instanceof String) {            return this.dataSourceLookup.getDataSource((String)dataSource);        } else {            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);        }    }    //因为AbstractRoutingDataSource继承AbstractDataSource,而AbstractDataSource实现了DataSource接口,所有存在获取数据源连接的方法    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方法拿到当前线程要使用的数据源的名称        Object lookupKey = this.determineCurrentLookupKey();        //去解析之后的数据源信息集合中查询该数据源是否存在,如果没有拿到则使用默认数据源resolvedDefaultDataSource        DataSource 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 + "]");        } else {            return dataSource;        }    }    @Nullable    protected abstract Object determineCurrentLookupKey();}

2.2、DynamicDataSource类

/** * @Description: TODO:动态数据源 * @Author: yyalin * @CreateDate: 2023/7/16 14:46 * @Version: V1.0 *//** * * 调用AddDefineDataSource组件的addDefineDynamicDataSource()方法,获取原来targetdatasources的map, * 并将新的数据源信息添加到map中,并替换targetdatasources中的map * 切换数据源时可以使用@DataSource(value = "数据源名称"),或者DynamicDataSourceContextHolder.setContextKey("数据源名称") */@Data@AllArgsConstructor@NoArgsConstructorpublic class DynamicDataSource extends AbstractRoutingDataSource {    //备份所有数据源信息,    private Map<Object, Object> defineTargetDataSources;    /**     * 决定当前线程使用哪个数据源     */    @Override    protected Object determineCurrentLookupKey() {        return DynamicDataSourceHolder.getDynamicDataSourceKey();    }}

2.3、DynamicDataSourceHolder

/** * @Description: TODO:数据源切换处理 * DynamicDataSourceHolder类主要是设置当前线程的数据源名称, * 移除数据源名称,以及获取当前数据源的名称,便于动态切换 * @Author: yyalin * @CreateDate: 2023/7/16 14:51 * @Version: V1.0 */@Slf4jpublic class DynamicDataSourceHolder {    /**     * 保存动态数据源名称     */    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();    /**     * 设置/切换数据源,决定当前线程使用哪个数据源     */    public static void setDynamicDataSourceKey(String key){        log.info("数据源切换为:{}",key);        DYNAMIC_DATASOURCE_KEY.set(key);    }    /**     * 获取动态数据源名称,默认使用mater数据源     */    public static String getDynamicDataSourceKey(){        String key = DYNAMIC_DATASOURCE_KEY.get();        return key == null ? DbsConstant.mysql_db_01 : key;    }    /**     * 移除当前数据源     */    public static void removeDynamicDataSourceKey(){        log.info("移除数据源:{}",DYNAMIC_DATASOURCE_KEY.get());        DYNAMIC_DATASOURCE_KEY.remove();    }}

2.4、数据源工具类

/** * @Description: TODO:数据源工具类 * @Author: yyalin * @CreateDate: 2023/7/16 15:00 * @Version: V1.0 */@Slf4j@Componentpublic class DataSourceUtils {    @Resource    DynamicDataSource dynamicDataSource;    /**     * @Description: 根据传递的数据源信息测试数据库连接     * @Author zhangyu     */    public DruidDataSource createDataSourceConnection(DataSourceInfo dataSourceInfo) {        DruidDataSource druidDataSource = new DruidDataSource();        druidDataSource.setUrl(dataSourceInfo.getUrl());        druidDataSource.setUsername(dataSourceInfo.getUserName());        druidDataSource.setPassword(dataSourceInfo.getPassword());        druidDataSource.setDriverClassName(dataSourceInfo.getDriverClassName());        druidDataSource.setBreakAfterAcquireFailure(true);        druidDataSource.setConnectionErrorRetryAttempts(0);        try {            druidDataSource.getConnection(2000);            log.info("数据源连接成功");            return druidDataSource;        } catch (SQLException throwables) {            log.error("数据源 {} 连接失败,用户名:{},密码 {}",dataSourceInfo.getUrl(),dataSourceInfo.getUserName(),dataSourceInfo.getPassword());            return null;        }    }    /**     * @Description: 将新增的数据源加入到备份数据源map中     * @Author zhangyu     */    public void addDefineDynamicDataSource(DruidDataSource druidDataSource, String dataSourceName){        Map<Object, Object> defineTargetDataSources = dynamicDataSource.getDefineTargetDataSources();        defineTargetDataSources.put(dataSourceName, druidDataSource);        dynamicDataSource.setTargetDataSources(defineTargetDataSources);        dynamicDataSource.afterPropertiesSet();    }

2.5、DynamicDataSourceConfig

/** * @Description: TODO:数据源信息配置类,读取数据源配置信息并注册成bean。 * @Author: yyalin * @CreateDate: 2023/7/16 14:54 * @Version: V1.0 */@Configuration@MapperScan("com.wonders.mapper")@Slf4jpublic class DynamicDataSourceConfig {    @Bean(name = DbsConstant.mysql_db_01)    @ConfigurationProperties("spring.datasource.mysqldb01")    public DataSource masterDataSource() {        log.info("数据源切换为:{}",DbsConstant.mysql_db_01);        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }    @Bean(name = DbsConstant.mysql_db_02)    @ConfigurationProperties("spring.datasource.mysqldb02")    public DataSource slaveDataSource() {        log.info("数据源切换为:{}",DbsConstant.mysql_db_02);        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }    @Bean(name = DbsConstant.oracle_db_01)    @ConfigurationProperties("spring.datasource.oracledb01")    public DataSource oracleDataSource() {        log.info("数据源切换为oracle:{}",DbsConstant.oracle_db_01);        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }    @Bean    @Primary    public DynamicDataSource dynamicDataSource(){        Map<Object, Object> dataSourceMap = new HashMap<>(3);        dataSourceMap.put(DbsConstant.mysql_db_01,masterDataSource());        dataSourceMap.put(DbsConstant.mysql_db_02,slaveDataSource());        dataSourceMap.put(DbsConstant.oracle_db_01,oracleDataSource());        //设置动态数据源        DynamicDataSource dynamicDataSource = new DynamicDataSource();        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());        dynamicDataSource.setTargetDataSources(dataSourceMap);        //将数据源信息备份在defineTargetDataSources中        dynamicDataSource.setDefineTargetDataSources(dataSourceMap);        return dynamicDataSource;    }}

三、测试代码

/** * @Description: TODO * @Author: yyalin * @CreateDate: 2023/7/16 15:02 * @Version: V1.0 */@Slf4j@Api(tags="动态切换多数据源测试")@RestControllerpublic class TestController {    @Resource    DataSourceUtils dataSourceUtils;    @Autowired    private StudentMapper studentMapper;    @ApiOperation(value="动态切换多数据源测试", notes="test")    @GetMapping("/test")    public Map<String, Object> dynamicDataSourceTest(String id){        Map<String, Object> map = new HashMap<>();        //1、默认库中查询数据        Student student=studentMapper.selectById(id);        map.put("1、默认库中查询到的数据",student);        //2、指定库中查询的数据        DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);        Student student02=studentMapper.selectById(id);        map.put("2、指定库中查询的数据",student02);        //3、从数据库获取连接信息,然后获取数据        //模拟从数据库中获取的连接        DataSourceInfo dataSourceInfo = new DataSourceInfo(                "jdbc:mysql://127.0.0.1:3308/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false",                 "root",                "root",                "mysqldb03",                "com.mysql.cj.jdbc.Driver");        map.put("dataSource",dataSourceInfo);        log.info("数据源信息:{}",dataSourceInfo);        //测试数据源连接        DruidDataSource druidDataSource = dataSourceUtils.createDataSourceConnection(dataSourceInfo);        if (Objects.nonNull(druidDataSource)){            //将新的数据源连接添加到目标数据源map中            dataSourceUtils.addDefineDynamicDataSource(druidDataSource,dataSourceInfo.getDatasourceKey());            //设置当前线程数据源名称-----代码形式            DynamicDataSourceHolder.setDynamicDataSourceKey(dataSourceInfo.getDatasourceKey());            //在新的数据源中查询用户信息            Student student03=studentMapper.selectById(id);            map.put("3、动态数据源查询的数据",student03);            //关闭数据源连接            druidDataSource.close();        }        //4、指定oracle库中查询的数据        DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.oracle_db_01);        Student student04=studentMapper.selectById(id);        map.put("4、指定oracle库中查询的数据",student04);        return map;    }}

测试结果如下:

       从结果中可以明显的看出,通过切换不同的数据源,可以从不同的库中获取不同的数据,包括:常见库Mysql、oracle、sqlserver等数据库相互切换。也可以从数据库的某张表中获取连接信息,实现动态切换数据库。

springboot多数据源配置和切换,springBoot,java,spring boot,后端,java

四、使用注解方式切换数据源

      从上述TestController 中代码不难看出,若要想切换数据源需要在mapper调用之前调用:

DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);

不够简洁优雅,所以下面推荐使用注解的方式来动态进行数据源的切换。

4.1、创建注解类DataSource

/** * @Description: TODO:自定义多数据源切换注解 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 * @Author: yyalin * @CreateDate: 2023/7/17 14:00 * @Version: V1.0 */@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource {    //切换数据源名称,默认mysql_db_01    public String value() default DbsConstant.mysql_db_01;}

4.2、创建切面DataSourceAspect类

/** * @Description: TODO:创建切面DataSourceAspect类 * @Author: yyalin * @CreateDate: 2023/7/17 14:03 * @Version: V1.0 */@Aspect@Componentpublic class DataSourceAspect {    // 设置DataSource注解的切点表达式    @Pointcut("@annotation(com.wonders.dynamic.DataSource)")    public void dynamicDataSourcePointCut(){}    //环绕通知    @Around("dynamicDataSourcePointCut()")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{        String key = getDefineAnnotation(joinPoint).value();        DynamicDataSourceHolder.setDynamicDataSourceKey(key);        try {            return joinPoint.proceed();        } finally {            DynamicDataSourceHolder.removeDynamicDataSourceKey();        }    }    /**     * 功能描述:先判断方法的注解,后判断类的注解,以方法的注解为准     * @MethodName: getDefineAnnotation     * @MethodParam: [joinPoint]     * @Return: com.wonders.dynamic.DataSource     * @Author: yyalin     * @CreateDate: 2023/7/17 14:09     */    private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();        DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);        if (Objects.nonNull(methodSignature)) {            return dataSourceAnnotation;        } else {            Class<?> dsClass = joinPoint.getTarget().getClass();            return dsClass.getAnnotation(DataSource.class);        }    }}

4.3、进行数据源切换

//@Mapper 与 启动类的@MapperScan({"com.example.demo.mapper"}) 二选一即可@Repositorypublic interface StudentMapper extends BaseMapper<Student> {    /**     * 功能描述:在mysql_db_01中查询数据     * @MethodName: findStudentById     * @MethodParam: [id]     * @Return: com.wonders.entity.Student     * @Author: yyalin     * @CreateDate: 2023/7/17 14:20     */    @DataSource(value = DbsConstant.oracle_db_01)    Student findStudentById(String id);}

或在service层

@Servicepublic class StudentServiceImpl implements StudentService{    @Autowired    private StudentMapper studentMapper;    //注解加在实现层才能生效    @DataSource(value = DbsConstant.mysql_db_01)    @Override    public Student findStudentById(String id) {        return studentMapper.selectById(id);    }}

4.3、测试效果

@ApiOperation(value="使用注解方式动态切换多数据源", notes="test02")    @GetMapping("/test02")    public Student test02(String id){        Student student=studentMapper.findStudentById(id);        return student;    }

--结果如下:

springboot多数据源配置和切换,springBoot,java,spring boot,后端,java

五、功能点

    1、使用注解的方式来动态进行数据源的切换;

    2、支持动态新增新的数据源;

    3、支持oracle\mysql等常见数据库切换。

回复:源码,可以获取该项目对应的源码及表结构,开箱即可使用。

springboot多数据源配置和切换,springBoot,java,spring boot,后端,java

springboot多数据源配置和切换,springBoot,java,spring boot,后端,java

springboot多数据源配置和切换,springBoot,java,spring boot,后端,java

更多详细资料,请关注个人微信公众号或搜索“程序猿小杨”添加。

 参考:

http://t.csdn.cn/KCW9r

                                                                                                         觉得有用,请点这里↓↓↓文章来源地址https://www.toymoban.com/news/detail-691865.html

到了这里,关于SpringBoot整合多数据源,并支持动态新增与切换(详细教程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • springboot整合kafka多数据源

    在很多与第三方公司对接的时候,或者处在不同的网络环境下,比如在互联网和政务外网的分布部署服务的时候,我们需要对接多台kafka来达到我们的业务需求,那么当kafka存在多数据源的情况,就与单机的情况有所不同。 单机的情况 如果是单机的kafka我们直接通过springboot自

    2024年02月13日
    浏览(49)
  • Flink CDC 2.4 正式发布,新增 Vitess 数据源,更多连接器支持增量快照,升级 Debezium 版本

    Flink CDC [1] 是基于数据库的日志 CDC 技术,实现了全增量一体化读取的数据集成框架。配合 Flink 优秀的管道能力和丰富的上下游生态,Flink CDC 可以高效实现海量数据的实时集成。 作为新一代的实时数据集成框架,Flink CDC 具有全增量一体化、无锁读取、并行读取、表结构变更

    2024年02月12日
    浏览(44)
  • SpringBoot整合Druid配置多数据源

    目录 1.初始化项目 1.1.初始化工程 1.2.添加依赖 1.3.配置yml文件 1.4.Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹 1.5.配置使用数据源 1.5.1.注解方式 1.5.2.基于AOP手动实现多数据源原生的方式 2.结果展示 Mybatis-Plus:简介 | MyBatis-Plus (baomidou.com) 在正式开始之前,先初始

    2024年02月01日
    浏览(54)
  • MyBatis整合Springboot多数据源实现

    数据源,实际就是数据库连接池,负责管理数据库连接,在 Springboot 中,数据源通常以一个 bean 的形式存在于 IOC 容器中,也就是我们可以通过依赖注入的方式拿到数据源,然后再从数据源中获取数据库连接。 那么什么是多数据源呢,其实就是 IOC 容器中有多个数据源的 bea

    2023年04月22日
    浏览(65)
  • springboot整合druid及多数据源配置

    本篇主要分两部分 ①springboot整合druid的代码配置,以及druid的监控页面演示;②对实际场景中多数据源的配置使用进行讲解。 可以用idea快速生成一个可运行的demo工程,具体可以参考如何快速创建springboot项目 主要用到的依赖如下:  配置数据库需要的配置文件application.yml( 注

    2024年02月12日
    浏览(46)
  • SpringBoot——动态数据源(多数据源自动切换)

    日常的业务开发项目中只会配置一套数据源,如果需要获取其他系统的数据往往是通过调用接口, 或者是通过第三方工具比如kettle将数据同步到自己的数据库中进行访问。 但是也会有需要在项目中引用多数据源的场景。比如如下场景: 自研数据迁移系统,至少需要新、老两

    2024年02月16日
    浏览(40)
  • SpringBoot从数据库读取数据数据源配置信息,动态切换数据源

            首先准备多个数据库,主库smiling-datasource,其它库test1、test2、test3         接下来,我们在主库smiling-datasource中,创建表databasesource,用于存储多数据源相关信息。表结构设计如下         创建好表之后,向表databasesource中存储test1、test2、test3三个数据库的相关配置

    2024年01月16日
    浏览(68)
  • SpringBoot动态切换数据源

      Spring提供一个DataSource实现类用于动态切换数据源—— AbstractRoutingDataSource pom.xml 大概的项目结构 注意:这两个事务管理器,并不能处理分布式事务 链接:https://pan.baidu.com/s/1ymxeKYkI-cx7b5nTQX0KWQ  提取码:6bii  --来自百度网盘超级会员V4的分享                

    2024年02月06日
    浏览(51)
  • springboot多数据源支持自定义连接池

    springboot 多数据源网上的文章很多,但大多都是互相抄袭,虽然可以实现多数据源的效果,但都是使用的默认的连接池,如果盲目使用可能会导致自定义的连接池参数没生效从而引发数据库连接问题。下面是参考官方文档多数据源支持自定义连接池的配置。 https://docs.spring.i

    2024年01月20日
    浏览(48)
  • SpringBoot3整合Druid数据源的解决方案

    druid-spring-boot-3-starter目前最新版本是1.2.20,虽然适配了SpringBoot3,但缺少自动装配的配置文件,会导致加载时报加载驱动异常。 需要手动在resources目录下创建 META-INF/spring/ 目录,并且在 META-INF/spring/ 创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports , 文件中添加如下内容

    2024年03月09日
    浏览(106)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包