动态数据源配置druid+mybatis

2023-11-11

本方案不限数据库数量完全动态配置,支持不同的数据库部署在不同的服务器上。(mybatis-plus没测试,下个版本用oracle配的时候尝试plus)

一 、这次我们使用Mysql,本地现在有两个个数据库用于测试。

如图 

二、下一步我们看一下Druid继承关系

我们可以看到想要配置DataSource其实非常简单,继承DruidDataSource就可以调用

getConnection方法了

三、下面直接开始上配置(简单的一个小例子其他的自己扩展吧)


#连接池  使用阿里的druid
spring:
  application:
    name: mall_user
  datasource:
    url: jdbc:mysql://${IP}:3306/${NAME}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8&useSSL=false
    username:
    password:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver

四、开始上代码

1、数据源配置管理类(DataSourceConfig.java)

package com.taipingyang.config.datasource2;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 *  数据源配置管理。
 *  @author zz
 *  @date 2021/1/10 20:46
 */
@MapperScan(basePackages="com.taipingyang.Mapper", value="sqlSessionFactory")
@Configuration
public class DataSourceConfig {
    /**
     * 根据配置参数创建数据源。使用派生的子类。
     *
     * @return 数据源
     */
    @Bean(name="dataSource")
    @ConfigurationProperties(prefix="spring.datasource")
    public DruidDataSource getDataSource() {DataSourceBuilder.create().type(DynamicDataSource.class).build();
        return build;
    }


    /**
     * 创建会话工厂。
     * @param dataSource 数据源
     * @return 会话工厂
     */
    @Bean(name="sqlSessionFactory")
    public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        try {
            return bean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


}

 

2、定义动态数据源

(1)首先增加一个数据库标识类,用于区分不同的数据库(DBIdentifier.java)

由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。而微服务支持多线程并发的,采用线程变量。

package com.taipingyang.config.datasource2;


/**
 * 数据库标识管理类。用于区分数据源连接的不同数据库。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DBIdentifier {
    /**
     * 用不同的工程编码来区分数据库
     */

    private static ThreadLocal<String> projectCode = new ThreadLocal<String>();


    public static String getProjectCode() {
        return projectCode.get();
    }


    public static void setProjectCode(String code) {
        projectCode.set(code);
    }

}

(2)从DataSource派生一个DynamicDataSource,在其中实现数据库连接的动态切换(DynamicDataSource.java)

package com.taipingyang.config.datasource2;

import java.lang.reflect.Field;


import java.sql.SQLException;


import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.pool.PoolableWrapper;
import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

/**
 * 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DynamicDataSource extends DruidDataSource {
    private static Logger log = LogManager.getLogger(DynamicDataSource.class);


    /**
     * 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。 DruidDataSource getConnection
     */
    @Override
    public DruidPooledConnection getConnection() {
        String projectCode = DBIdentifier.getProjectCode()==null?"project_001": DBIdentifier.getProjectCode();
        //1、获取数据源
        DruidDataSource dds = DDSHolder.instance().getDDS(projectCode);

        DruidDataSource ddsddn = null;
        //2、如果数据源不存在则创建
        if (dds == null) {
            try {
                DruidDataSource newDDS = initDDS(projectCode);
                DDSHolder.instance().addDDS(projectCode, newDDS);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                log.error("Init data source fail. projectCode:" + projectCode);
                return null;
            }
        }

        dds = DDSHolder.instance().getDDS(projectCode);
        try {
            return dds.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 以当前数据对象作为模板复制一份。
     *
     * @return dds
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */

    private DruidDataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {
        DruidDataSource dds = new DruidDataSource();
        String urlFormat = this.getUrl();

        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        String url  =urlFormat.replace("${IP}", ProjectDBMgr.instance().getDBIP(projectCode)).replace("${NAME}",ProjectDBMgr.instance().getDBName(projectCode));
        dataSource.setUrl(url);
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;

    }

}

(3)通过DDSTimer控制数据连接释放(DDSTimer.java)

package com.taipingyang.config.datasource2;


import com.alibaba.druid.pool.DruidDataSource;



/**
 * 动态数据源定时器管理。长时间无访问的数据库连接关闭。
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DDSTimer {
    /**
     * 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。
     */

    private static long idlePeriodTime = 10 * 60 * 1000;


    /**
     * 动态数据源
     */

    private DruidDataSource dds;


    /**
     * 上一次访问的时间
     */

    private long lastUseTime;


    public DDSTimer(DruidDataSource dds) {

        this.dds = dds;

        this.lastUseTime = System.currentTimeMillis();

    }


    /**
     * 更新最近访问时间
     */

    public void refreshTime() {

        lastUseTime = System.currentTimeMillis();

    }


    /**
     * 检测数据连接是否超时关闭。
     *
     * @return true-已超时关闭; false-未超时
     */

    public boolean checkAndClose() {
        if (System.currentTimeMillis() - lastUseTime > idlePeriodTime) {
            dds.close();
            return true;
        }
        return false;
    }

    public DruidDataSource getDds() {
        return dds;
    }

}

(4)通过DDSHolder来管理不同的数据源,提供数据源的添加、查询功能(DDSHolder.java)

package com.taipingyang.config.datasource2;

import com.alibaba.druid.pool.DruidDataSource;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;


import java.util.Map.Entry;



/**
 * 动态数据源管理器。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class DDSHolder {

    /**
     * 管理动态数据源列表。<工程编码,数据源>

     */
    private Map<String, DDSTimer> ddsMap =new HashMap<String, DDSTimer>();

    /**
     * 通过定时任务周期性清除不使用的数据源
     */

    private static Timer clearIdleTask = new Timer();

    static {
        clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000);
    };


    private DDSHolder() {

    }



    /*
     * 获取单例对象
     */
    public static DDSHolder instance() {
        return DDSHolderBuilder.instance;
    }


    /**
     * 添加动态数据源。
     * @param projectCode 项目编码
     * @param dds         dds
     */
    public synchronized void addDDS(String projectCode, DruidDataSource dds) {
        DDSTimer ddst = new DDSTimer(dds);
        ddsMap.put(projectCode, ddst);

    }


    /**
     * 查询动态数据源
     *
     * @param projectCode 项目编码
     * @return dds
     */
    public synchronized DruidDataSource getDDS(String projectCode) {

        if (ddsMap.containsKey(projectCode)) {

            DDSTimer ddst = ddsMap.get(projectCode);

            ddst.refreshTime();

            return ddst.getDds();

        }


        return null;

    }


    /**
     * 清除超时无人使用的数据源。
     */

    public synchronized void clearIdleDDS() {
        Iterator<Map.Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator();

        while(iter.hasNext()){
            Entry<String, DDSTimer> entry = iter.next();
            if (entry.getValue().checkAndClose()) {
                iter.remove();
            }
        }
    }


    /**
     * 单例构件类
     *
     * @author elon
     * @version 2018年2月26日
     */

    private static class DDSHolderBuilder {
        private static DDSHolder instance = new DDSHolder();
    }
}

(5)定时器任务ClearIdleTimerTask用于定时清除空闲的数据源(ClearIdleTimerTask.java)

package com.taipingyang.config.datasource2;

import java.util.TimerTask;


/**
 * 清除空闲连接任务。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class ClearIdleTimerTask extends TimerTask {


    @Override
    public void run() {
        DDSHolder.instance().clearIdleDDS();
    }
}

(6)管理项目编码与数据库IP和名称的映射关系(ProjectDBMgr.java)

package com.taipingyang.config.datasource2;

import java.util.HashMap;

import java.util.Map;


/**
 * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。
 *
 * @author zz
 * @version 2021/1/10 20:46
 */
public class ProjectDBMgr {
    /**
     * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;
     * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。

     */

    private Map<String, String> dbNameMap =new HashMap<String, String>();


    /**
     * 保存项目编码与数据库IP的映射关系。

     */

    private Map<String, String> dbIPMap =new HashMap<String, String>();


    private ProjectDBMgr() {
        dbNameMap.put("project_001", "annoyingly");
        dbNameMap.put("project_002", "tpy");

        dbIPMap.put("project_001", "127.0.0.1");
        dbIPMap.put("project_002", "127.0.0.1");
    }


    public static ProjectDBMgr instance() {
        return ProjectDBMgrBuilder.instance;
    }


    // 实际开发中改为从缓存获取

    public String getDBName(String projectCode) {
        if (dbNameMap.containsKey(projectCode)) {
            return dbNameMap.get(projectCode);
        }
        return "";
    }


    //实际开发中改为从缓存中获取

    public String getDBIP(String projectCode) {
        if (dbIPMap.containsKey(projectCode)) {
            return dbIPMap.get(projectCode);
        }
        return "";
    }

    private static class ProjectDBMgrBuilder {
        private static ProjectDBMgr instance = new ProjectDBMgr();
    }
}

以上就是Druid+mybatis动态数据源全部配置

下面看一下我写的测试类=》

package com.taipingyang.Mapper;

import com.taipingyang.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {

    @Select(" select * from tpy_user ")
    List<User> testuser();

}

直接上controller 


@RestController
public class userController {



    @Autowired
    private UserService userService;

    @RequestMapping("user/testuser")
    public List<User> testuser(String projectCode) {
        DBIdentifier.setProjectCode(projectCode);
        return userService.testuser();
    }

}

四、postman开始测试

查询数据库全部正常

 

 

其实非常简单,只需要在继承DruidDataSource重新getConnection建立连接就好了,至于搞并发场景下还是需要凭借其他框架的。通过这次搭建希望大家能够对DataSource有全新的认识。

 

 

 

 

代码千万行,注释第一行。格式不规范,报错两行泪!!!

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态数据源配置druid+mybatis 的相关文章

  • 当“修复表”查询在 mysql 中不起作用时该怎么办?

    我收到此错误 表的存储引擎不支持修复 当我尝试使用查询修复表时repair table tbl college master 表是 innodb 类型 但我不知道我收到此错误 See 手册 http dev mysql com doc re
  • 比较表中的行以了解字段之间的差异

    我有一个包含 20 多列的表 客户端 其中大部分是历史数据 就像是 id clientID field1 field2 etc updateDate 如果我的数据如下所示 10 12 A A 2009 03 01 11 12 A B 200
  • 如何在 phpmyadmin 中创建 MySQL 触发器

    我想在 MySQL 中创建一个触发器 我运行以下命令 mysql gt delimiter mysql gt CREATE TRIGGER before insert money BEFORE INSERT ON money gt FOR
  • 使用 JWT 创建 PostMan GET 请求

    我是 PostMan 的新手 通常我使用curl 这是获得 JTW 的一个 curl X POST H X Requested With XMLHttpRequest H Content Type application json H Ca
  • java mysql 准备好的语句

    我正在尝试使用 java 向数据库中进行简单的插入 它告诉我我的 sql 语法已关闭 但是 当我复制打印出来的字符串并将其放入 phpmyadmin 中的 sql 命令中时 它会正确执行该命令 并且我似乎无法弄清楚 java 中的字符串查询
  • Spring引导错误

    我正在使用 Spring BOOT 运行一个简单的桌面应用程序 下面是错误堆栈 org springframework beans factory BeanCreationException Error creating bean with
  • 截断 Mysql 表 Cron 作业?

    我在如何使用 cron 作业截断 Mysql 表时遇到了一些麻烦 无论我尝试什么 我似乎都无法让数据库清除表格 感谢您的帮助 mysql uderp example pexample hlocalhost Dexample e TRUNCA
  • 为什么这会返回资源 id #2? [复制]

    这个问题在这里已经有答案了 可能的重复 我如何从 PHP 中的 MySql 响应中 回显 资源 id 6 https stackoverflow com questions 4290108 how do i echo a resource
  • Spring Boot Batch - 不包括 JobLauncherCommandLineRunner

    我在 Spring Boot 中配置了一个简单的 Spring Batch 作业 类似于弹簧导轨 http spring io guides gs batch processing 在启动时 它会自动检测并调用 JobLauncherCom
  • InnoDB 因读未提交而死锁! - Java - Glassfish - EJB3(JPA/Hibernate)

    几天来 我在使用 Glassfish EJB3 和 Mysql InnoDB 的 Java 应用程序上遇到了死锁问题 配置 Mysql InnoDB Ver 14 12 Distrib 5 0 51a 适用于 debian linux gn
  • MySQL PHP邮政编码比较具体距离

    我试图找出比较一个邮政编码 用户提供的 和一大堆其他邮政编码 现在大约有 200 个邮政编码 之间的距离的最有效方法 相对于加载时间 但它会随着时间的推移而增加 我不需要任何精确的东西 只是在球场上 我下载了整个美国的邮政编码 csv 文件
  • Mysql 中 UNION 子句的替代方案

    我有两张桌子 表 a 表 b table a ID 1 2 3 4 5 7 table b ID 2 3 4 5 6 我必须得到这样的输出而无需UNION命令 ID 1 2 3 4 5 6 7 注意 我有一个联合解决方案 select fr
  • 带有 Maven Wrapper 的 Java 17 导致无法识别的 VM 选项“MaxPermSize=512m”

    I use OpenJDK 17 https jdk java net 17 使用 Maven Wrapper 3 8 2 从春季初始化 https start spring io Maven项目 JAR打包 Java 17 Spring
  • 即使使用“autoReconnect=true”,MySql JDBC 也会超时[重复]

    这个问题在这里已经有答案了 有时 我的 Java Tomcat6 Debian Squeeze 应用程序无法与 MySql 服务器通信 Tomcat 应用程序位于前端服务器上 而 MySql 位于单独的 仅限 MySql 的机器上 一个典型
  • Mysql 将 int 转换为 MAC

    我有一些数据可以转换 其中有 2 列 其中一列有 IP 它包含整数值 我在 mysql 查询中使用了以下函数 是否有一个函数可以用来转换我的 mac 列 其中包含整数和数据类型是bigint to MAC地址 SELECT INET NTO
  • MySQL 查询到 CSV [重复]

    这个问题在这里已经有答案了 有没有一种简单的方法来运行MySQL查询来自linux命令行并以csv格式输出结果 这就是我现在正在做的事情 mysql u uid ppwd D dbname lt lt EOQ sed e s g tee l
  • Java JDBC:更改表

    我希望对此表进行以下修改 添加 状态列 varchar 20 日期列 时间戳 我不确定该怎么做 String createTable Create table aircraft aircraftNumber int airLineCompa
  • 在 PHP 字符串中格式化 MySQL 代码

    是否有任何程序 IDE 可以在 PHP 字符串中格式化 MySQL 代码 例如 我使用 PHPStorm IDE 但它无法做到这一点 它对 PHP 和 MYSQL 执行此操作 但不适用于 php 字符串内的 MYSQL 我已准备好使用新的
  • 我可以使用 HSQLDB 进行 junit 测试克隆 mySQL 数据库吗

    我正在开发一个 spring webflow 项目 我想我可以使用 HSQLDB 而不是 mysql 进行 junit 测试吗 如何将我的 mysql 数据库克隆到 HSQLDB 如果您使用 spring 3 1 或更高版本 您可以使用 s
  • 在 MySQL 中存储表情符号的编码问题:如何使用 Prisma ORM 在 NodeJS 中定义字符排序规则?

    亲爱的 Nodejs 专家和数据库专家 我们在 MySQL 数据库中存储表情符号和其他特殊字符时遇到问题 我们使用 Prisma 得到一个错误 这是我们使用的 ORM 参数无法从排序规则 utf8 general ci 转换为 utf8mb

随机推荐

  • [指针五]指针做参数传递--使用详解

    void myMalloc char s 我想在函数中分配内存 再返回 s char malloc 100 void main char p NULL myMalloc p 这里的p实际还是NULL p的值没有改变 为什么 if p fre
  • STM32-ADC电压采样实验(寄存器版)

    STM32F10X系列支持三路ADC 其ADC通道及对应IO口如下表所示 其能接受的电压输入范围一般为0 3 3V VREF VIN VREF 因此 如果需要测量超出0 3 3v量程范围的电压数据 需要在外围硬件增加分压电阻 将电路转换到0
  • iOS Cookies.binarycookies(俗称63数据)存取

    iOS 系统会自动将Safari或APP中网络请求的cookie保存为文件 APP的cookie保存路径为APP的沙盒路径 var mobile Containers Data Application x x x Library Cooki
  • 软件测试的策略和计划

    软件测试是保证软件质量的关键步骤 在进行软件测试之前 需要制定一份测试策略和计划来确保测试的有效性和可操作性 下面 我们将介绍一些关于软件测试策略和计划的信息 1 测试策略 测试策略是指测试团队为达到测试目标而采取的方法和技术 下面是一些测
  • PolarDB-X 私有协议2.0

    本文主要介绍私有协议2 0 也即XRPC的背景 总体设计 相关技术实现细节和性能测试结果 私有协议作为解决 PolarDB X 中计算节点和存储节点复杂通信需求的技术手段 在 PolarDB X 2 0 公共云版上线初期就作为重要的功能一起
  • maven 报错 Failed to execute goal on project ...: Could not resolve dependencies for project ...

    网上看了很多博客 都是说在根工程那里clean install一下就可以了 根本原因还是要看Could not resolve dependencies for project 我这边是common工程打包的时候打成了war包 其他工程都是
  • 最全IO流解析——IO流的骚操作

    Java中是通过流的方式对数据进行操作 用于操作流的类都在IO包中 IO流用来处理设备之间的数据传输 IO流按照流向分为输入流和输出流 按照操作的数据分为字节流和字符流 字节流可以操作任何数据 因为在计算机中任何数据都是以字节的形式存储的
  • Centos7的iso everything与DVD以及Live的区别

    DVD ISO 可以用安装程序安装的所有安装包 推荐镜像 Netinstall iso 从网络安装或者救援系统 Everything iso 包含centos7的一套完整的软件包 可以用来安装系统或者本地镜像 GnomeLive iso G
  • Mac下github的基本使用(有详细过程)

    一 github准备 1 注册github账号 https github com 按照提示进行注册 2 查看git版本 由于macOS默认安装了git 在终端输入 git v 3 设置username和email username随便输入一
  • 如何防止uniswap和pancakeswap夹子机器人

    被机器人夹是通俗说法 实际就是 front running 抢先提前交易 具体就是机器人在链上嗅探到你有买入行为的时候 他立刻买 gas给的比你高 快你一步确认 这样你成交价就高了 因为交易所有滑点 所以你依旧会以高一点的价格成交并且再将价
  • 区块链简单实现之p2p网络多节点同步

    区块链简单实现之p2p网络多节点同步 将区块保存为json文件 节点 不确定性 区块里保存节点信息 并未向所有节点广播 简单模拟 广播的代码 实现效果 完整的代码 承接上文 区块链的简单实现 我们已经实现了一个简单的区块链数据结构 现状 区
  • wasm + ffmpeg实现前端截取视频帧功能

    有没有那么一种可能 在前端页面处理音视频 例如用户选择一个视频 然后支持他设置视频的任意一帧作为封面 就不用把整一个视频上传到后端处理了 经过笔者的一番摸索 基本实现了这个功能 一个完整的demo ffmpeg wasm截取视频帧功能 支持
  • paddle-pytorch API对应表

    PyTorch API名称 对应Paddle API torch set default dtype paddle set default dtype torch get default dtype paddle get default d
  • linux下mysql-connector-c++连接远程服务器失败

    最近在将windows项目移植到linux下 碰到诸多问题 先谈mysql connector c 连接远程服务器失败问题 在windows下 sql Driver driver sql mysql get driver instance
  • 因果推理相关的图神经网络研究

    本文介绍两篇因果推理相关的图神经网络研究工作 一 OOD推荐系统下的因果表征学习 本文介绍了什么是推荐系统中的Out of Distribution OOD 问题 并从因果的角度提出了一种解决OOD问题的表示学习方式 文章链接 https
  • 关于xinput1_3.dll丢失的详细解决方法

    xinput1 3 dll是电脑文件中的dll文件 动态链接库文件 如果计算机中丢失了某个dll文件 可能会导致某些软件和游戏等程序无法正常启动运行 并且导致电脑系统弹窗报错 在我们打开软件或者游戏的时候 电脑提示xinput1 3 dll
  • Windows10下安装Linux子系统

    Windows10下安装Linux子系统 版本说明 版本 作者 日期 备注 0 1 ZY 2019 7 9 初稿 目录 文章目录 Windows10下安装Linux子系统 版本说明 目录 一 初衷 二 资料收集 三 官方安装说明 1 准备
  • 5.0结构型模式—概述

    结构型模式描述如何将类或对象按某种布局组成更大的结构 它分为类结构型模式和对象结构型模式 前者采用继承机制来组织接口和类 后者釆用组合或聚合来组合对象 由于组合关系或聚合关系比继承关系耦合度低 满足 合成复用原则 所以对象结构型模式比类结构
  • 国庆假期将至,拓世AI智能规划行程,让您轻松游遍全球热门景点!

    卡夫卡曾说 人不是活几年 几月 几天 几小时 而只活几个瞬间 亲赴一场与美景的邂逅 便是去找寻人生里的瞬间之美 转眼已是九月 正是人间好时节 挥别工作和生活的烦闷 奔向辽阔的天地中 即将到来的国庆长假 你需要来一场说走就走的旅行 将所有烦恼
  • 动态数据源配置druid+mybatis

    本方案不限数据库数量完全动态配置 支持不同的数据库部署在不同的服务器上 mybatis plus没测试 下个版本用oracle配的时候尝试plus 一 这次我们使用Mysql 本地现在有两个个数据库用于测试 如图 二 下一步我们看一下Dru