mysql 多数据源访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码

之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力。

下面讲的方案能支持数据库动态增删,数量不限。

数据库环境准备

下面一Mysql为例,先在本地建3个数据库用于测试。需要说明的是本方案不限数据库数量,支持不同的数据库部署在不同的服务器上。如图所示db_project_001、db_project_002、db_project_003。

471a2422016011635b24b3864774ad76.png

搭建Java后台微服务项目

创建一个Spring Boot的maven项目:

94d3385f74618d36cff0a77ba18ee7cc.png

config:数据源配置管理类。

datasource:自己实现的数据源管理逻辑。

dbmgr:管理了项目编码与数据库IP、名称的映射关系(实际项目中这部分数据保存在redis缓存中,可动态增删)。

mapper:数据库访问接口。

model:映射模型。

rest:微服务对外发布的restful接口,这里用来测试。

application.yml:配置了数据库的JDBC参数。

详细的代码实现

1. 添加数据源配置

package com.elon.dds.config;

import javax.sql.DataSource;

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.autoconfigure.jdbc.DataSourceBuilder;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import com.elon.dds.datasource.DynamicDataSource;

/**

* 数据源配置管理。

*

* @author elon

* @version 2018年2月26日

*/

@Configuration

@MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory")

public class DataSourceConfig {

/**

* 根据配置参数创建数据源。使用派生的子类。

*

* @return 数据源

*/

@Bean(name="dataSource")

@ConfigurationProperties(prefix="spring.datasource")

public DataSource getDataSource() {

DataSourceBuilder builder = DataSourceBuilder.create();

builder.type(DynamicDataSource.class);

return builder.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)  首先增加一个数据库标识类,用于区分不同的数据库访问。

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

package com.elon.dds.datasource;

/**

* 数据库标识管理类。用于区分数据源连接的不同数据库。

*

* @author elon

* @version 2018-02-25

*/

public class DBIdentifier {

/**

* 用不同的工程编码来区分数据库

*/

private static ThreadLocal projectCode = new ThreadLocal();

public static String getProjectCode() {

return projectCode.get();

}

public static void setProjectCode(String code) {

projectCode.set(code);

}

}

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

import java.lang.reflect.Field;

import java.sql.Connection;

import java.sql.SQLException;

import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

import org.apache.tomcat.jdbc.pool.DataSource;

import org.apache.tomcat.jdbc.pool.PoolProperties;

import com.elon.dds.dbmgr.ProjectDBMgr;

/**

* 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。

*

* @author elon

* @version 2018-02-25

*/

public class DynamicDataSource extends DataSource {

private static Logger log = LogManager.getLogger(DynamicDataSource.class);

/**

* 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。

*/

@Override

public Connection getConnection(){

String projectCode = DBIdentifier.getProjectCode();

//1、获取数据源

DataSource dds = DDSHolder.instance().getDDS(projectCode);

//2、如果数据源不存在则创建

if (dds == null) {

try {

DataSource 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 DataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {

DataSource dds = new DataSource();

// 2、复制PoolConfiguration的属性

PoolProperties property = new PoolProperties();

Field[] pfields = PoolProperties.class.getDeclaredFields();

for (Field f : pfields) {

f.setAccessible(true);

Object value = f.get(this.getPoolProperties());

try

{

f.set(property, value);

}

catch (Exception e)

{

log.info("Set value fail. attr name:" + f.getName());

continue;

}

}

dds.setPoolProperties(property);

// 3、设置数据库名称和IP(一般来说,端口和用户名、密码都是统一固定的)

String urlFormat = this.getUrl();

String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),

ProjectDBMgr.instance().getDBName(projectCode));

dds.setUrl(url);

return dds;

}

}

3)  通过DDSTimer控制数据连接释放(超过指定时间未使用的数据源释放)

package com.elon.dds.datasource;

import org.apache.tomcat.jdbc.pool.DataSource;

/**

* 动态数据源定时器管理。长时间无访问的数据库连接关闭。

*

* @author elon

* @version 2018年2月25日

*/

public class DDSTimer {

/**

* 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。

*/

private static long idlePeriodTime = 10 * 60 * 1000;

/**

* 动态数据源

*/

private DataSource dds;

/**

* 上一次访问的时间

*/

private long lastUseTime;

public DDSTimer(DataSource 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 DataSource getDds() {

return dds;

}

}

4)      增加DDSHolder来管理不同的数据源,提供数据源的添加、查询功能

package com.elon.dds.datasource;

import java.util.HashMap;

import java.util.Iterator;

import java.util.Map;

import java.util.Map.Entry;

import java.util.Timer;

import org.apache.tomcat.jdbc.pool.DataSource;

/**

* 动态数据源管理器。

*

* @author elon

* @version 2018年2月25日

*/

public class DDSHolder {

/**

* 管理动态数据源列表。

*/

private Map ddsMap = new HashMap();

/**

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

*/

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, DataSource dds) {

DDSTimer ddst = new DDSTimer(dds);

ddsMap.put(projectCode, ddst);

}

/**

* 查询动态数据源

*

* @param projectCode 项目编码

* @return dds

*/

public synchronized DataSource getDDS(String projectCode) {

if (ddsMap.containsKey(projectCode)) {

DDSTimer ddst = ddsMap.get(projectCode);

ddst.refreshTime();

return ddst.getDds();

}

return null;

}

/**

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

*/

public synchronized void clearIdleDDS() {

Iterator> iter = ddsMap.entrySet().iterator();

for (; iter.hasNext(); ) {

Entry 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用于定时清除空闲的数据源

package com.elon.dds.datasource;

import java.util.TimerTask;

/**

* 清除空闲连接任务。

*

* @author elon

* @version 2018年2月26日

*/

public class ClearIdleTimerTask extends TimerTask {

@Override

public void run() {

DDSHolder.instance().clearIdleDDS();

}

}

3.       管理项目编码与数据库IP和名称的映射关系

package com.elon.dds.dbmgr;

import java.util.HashMap;

import java.util.Map;

/**

* 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。

* @author elon

* @version 2018年2月25日

*/

public class ProjectDBMgr {

/**

* 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;

* 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。

*/

private Map dbNameMap = new HashMap();

/**

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

*/

private Map dbIPMap = new HashMap();

private ProjectDBMgr() {

dbNameMap.put("project_001", "db_project_001");

dbNameMap.put("project_002", "db_project_002");

dbNameMap.put("project_003", "db_project_003");

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

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

dbIPMap.put("project_003", "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();

}

}

4.       定义数据库访问的mapper

package com.elon.dds.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import org.apache.ibatis.annotations.Result;

import org.apache.ibatis.annotations.Results;

import org.apache.ibatis.annotations.Select;

import com.elon.dds.model.User;

/**

* Mybatis映射接口定义。

*

* @author elon

* @version 2018年2月26日

*/

@Mapper

public interface UserMapper

{

/**

* 查询所有用户数据

* @return 用户数据列表

*/

@Results(value= {

@Result(property="userId", column="id"),

@Result(property="name", column="name"),

@Result(property="age", column="age")

})

@Select("select id, name, age from tbl_user")

List getUsers();

}

5.       定义查询对象模型

package com.elon.dds.model;

public class User

{

private int userId = -1;

private String name = "";

private int age = -1;

@Override

public String toString()

{

return "name:" + name + "|age:" + age;

}

public int getUserId()

{

return userId;

}

public void setUserId(int userId)

{

this.userId = userId;

}

public String getName()

{

return name;

}

public void setName(String name)

{

this.name = name;

}

public int getAge()

{

return age;

}

public void setAge(int age)

{

this.age = age;

}

}

6.       定义查询用户数据的restful接口

package com.elon.dds.rest;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.bind.annotation.RestController;

import com.elon.dds.datasource.DBIdentifier;

import com.elon.dds.mapper.UserMapper;

import com.elon.dds.model.User;

/**

* 用户数据访问接口。

*

* @author elon

* @version 2018年2月26日

*/

@RestController

@RequestMapping(value="/user")

public class WSUser {

@Autowired

private UserMapper userMapper;

/**

* 查询项目中所有用户信息

*

* @param projectCode 项目编码

* @return 用户列表

*/

@RequestMapping(value="/v1/users", method=RequestMethod.GET)

public List queryUser(@RequestParam(value="projectCode", required=true) String projectCode)

{

DBIdentifier.setProjectCode(projectCode);

return userMapper.getUsers();

}

}

要求每次查询都要带上projectCode参数。

7.       编写Spring Boot App的启动代码

package com.elon.dds;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

/**

* Hello world!

*

*/

@SpringBootApplication

public class App

{

public static void main( String[] args )

{

System.out.println( "Hello World!" );

SpringApplication.run(App.class, args);

}

}

8.       在application.yml中配置数据源

其中的数据库IP和数据库名称使用%s。在查询用户数据中动态切换。

spring:

datasource:

url: jdbc:mysql://%s:3306/%s?useUnicode=true&characterEncoding=utf-8

username: root

password:

driver-class-name: com.mysql.jdbc.Driver

logging:

config: classpath:log4j2.xml

测试方案

1.       查询project_001的数据,正常返回

3393b556794e5eb7e588474c3cb9de8f.png

2.       查询project_002的数据,正常返回

529d10427f70b920543d877a3d30a644.png

总结

以上所述是小编给大家介绍的通过Spring Boot配置动态数据源访问多个数据库的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/394278.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我如何将Google I / O 2018的兴奋带给尼日利亚沃里的115个人

Google Developer Group Warri的第一个I / O扩展事件的故事 (A tale of Google Developer Group Warri’s first I/O Extended event) Google I/O is one of the largest developer festivals in the tech ecosystem. I am the lead organizer for the Google Developer Group …

菜鸟postman接口测试_postman 接口测试(转)

本文转载自testerhome;作者:xinxi1990 ;原文链接:https://testerhome.com/topics/18719;转载以分享知识为目的,著作权归原作者所有,如有侵权,请联系删除。postman使用创建用例集启动…

求绝对值最小的数

题目 有一个升序排列的数组&#xff0c;数组中可能有正数&#xff0c;负数或0. 求数组中元素的绝对值最小的数. 例如 数组{-10&#xff0c; 05&#xff0c; 02 &#xff0c;7&#xff0c;15&#xff0c;50} 绝对值最小的是-2 解答 #include <bits/stdc.h> using namespac…

leetcode面试题 04.02. 最小高度树(深度优先搜索)

给定一个有序整数数组&#xff0c;元素各不相同且按升序排列&#xff0c;编写一个算法&#xff0c;创建一棵高度最小的二叉搜索树。 public TreeNode sortedArrayToBST(int[] nums) {if(nums.length0) return null;return BST(nums,0,nums.length-1);}public TreeNode BST(int[…

IT团队如何赢得尊重?

本文讲的是IT团队如何赢得尊重,在传统观念中&#xff0c;作为企业的IT人&#xff0c;似乎都有一种挥之不去的消极情绪&#xff1a;能够为企业带来直接利益的业务部门才是企业核心&#xff0c;而作为技术支撑的IT部门&#xff0c;则是作为附属而存在。 我们经常也会听到一些企业…

mysql 官方镜像_运行官方mysql 镜像

//目前最新的为mysql 8sudo docker run -itd --restart unless-stopped --nethost --name mysql -p3306:3306 -e MYSQL_ROOT_PASSWORDroot mysqlmysql 官方docker 需要重新设置密码&#xff0c;否则无法远程连接step1 : docker exec -it [容器id] /bin/bashstep2 : 登陆mysql &…

我如何使用React,Redux-Saga和Styled Components构建NBA球员资料获取器

by Jonathan Puc乔纳森普克(Jonathan Puc) 我如何使用React&#xff0c;Redux-Saga和Styled Components构建NBA球员资料获取器 (How I built an NBA player profile fetcher with React, Redux-Saga, and Styled Components) Hello, all! It’s been a while since I built so…

vb 数组属性_VB中菜单编辑器的使用讲解及实际应用

大家好&#xff0c;今天我们共同来学习VB中菜单方面的知识。VB中菜单的基本作用有两个&#xff1a;1、提供人机对话的界面&#xff0c;以便让使用者选择应用系统的各种功能&#xff1b;2、管理应用系统&#xff0c;控制各种功能模块的运行。在实际应用中&#xff0c;菜单可分为…

《JAVA程序设计》_第七周学习总结

一、学习内容 1.String类——8,1知识 Java专门提供了用来处理字符序列的String类。String类在java.lang包中&#xff0c;由于java.lang包中的类被默认引入&#xff0c;因此程序可以直接使用String类。需要注意的是Java把String类声明为final类&#xff0c;因此用户不能扩展Stri…

leetcode109. 有序链表转换二叉搜索树(深度优先搜索/快慢指针)

给定一个单链表&#xff0c;其中的元素按升序排序&#xff0c;将其转换为高度平衡的二叉搜索树。 本题中&#xff0c;一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 解题思路 先将链表转换成数组&#xff0c;再构造二叉搜索树 代码 …

NeHe OpenGL教程 第三十七课:卡通映射

转自【翻译】NeHe OpenGL 教程 前言 声明&#xff0c;此 NeHe OpenGL教程系列文章由51博客yarin翻译&#xff08;2010-08-19&#xff09;&#xff0c;本博客为转载并稍加整理与修改。对NeHe的OpenGL管线教程的编写&#xff0c;以及yarn的翻译整理表示感谢。 NeHe OpenGL第三十七…

SDN交换机在云计算网络中的应用场景

SDN的技术已经发展了好几年了&#xff0c;而云计算的历史更长&#xff0c;两者的结合更是作为SDN的一个杀手级应用在近两年炒得火热&#xff0c;一些知名咨询公司的关于SDN逐年增加的市场份额的论断&#xff0c;也主要是指SDN在云计算网络中的应用。 关于SDN在云计算网络中的应…

sql server 里面怎么支持数字使用双引号_国查:用中文编写SQL

这两天被 文言(wenyan-lang)刷屏了&#xff0c;这个项目在于使用文言文进行编程&#xff0c;我打算蹭个热度&#xff0c;把年初的作品再捞一捞&#xff0c;即中文SQL。1. 文言Wenyan&#xff1a;吾有一數。曰三。名之曰「甲」。為是「甲」遍。吾有一言。曰「「問天地好在。」」…

七日掌握设计配色基础_掌握正确的基础知识:如何设计网站的导航,搜索和首页...

七日掌握设计配色基础by Anant Jain通过Anant Jain 掌握正确的基础知识&#xff1a;如何设计网站的导航&#xff0c;搜索和首页 (Get the basics right: how to design your site’s navigation, search, and homepage) 一个7分钟的指南&#xff0c;使这三个基础组件正确无误。…

python渲染光线_python模板渲染配置文件

python的mako、jinja2模板库&#xff0c;确实好用&#xff01;这里做个笔记&#xff0c;好记性不如烂笔头。#!/usr/bin/env python#encodingutf-8import sys,yaml # 配置文件使用yaml格式from mako.template import Template # 加载mako库的Templat…

leetcode114. 二叉树展开为链表(深度优先搜索)

给定一个二叉树&#xff0c;原地将它展开为一个单链表。例如&#xff0c;给定二叉树1/ \2 5/ \ \ 3 4 6 将其展开为&#xff1a;1\2\3\4\5\6代码 class Solution {public void flatten(TreeNode root) {flat(root);}public TreeNode flat(TreeNode root) {if(rootnull)…

eclipse新建web项目

需要点击File—>New—>Other…在Web文件夹下找到Dynamic Web Project—>Next修改server端口可以在启动项目后访问地址是端口号项目名转载于:https://juejin.im/post/5cb4999df265da037b610545

idea tips

AltInsert 自动出现generate ,,里面有构造方法&#xff0c;getter,setter... CtrlO,重写方法 CtrlI...自动出现接口的方法 转载于:https://www.cnblogs.com/bin-lin/p/6247538.html

革新以太网交换机架构 全光网络的风刮进园区

全光网络的风正在刮进园区网&#xff0c;众所周知&#xff0c;光纤入户发展迅速&#xff0c;随着PON&#xff08;无源光纤网络&#xff09;技术在运营商通信网络的大规模使用&#xff0c;PON相关产业链逐步成熟&#xff0c;这也使得PON技术逐步在企业园区网得到应用。 基于铜线…

mysql loop循环实例_MySql CURSOR+LOOP循环-使用小实例

转载自https://blog.csdn.net/starinbrook/article/details/77078126转载自https://blog.csdn.net/makang456/article/details/53896346/【简介】游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。游标充当指针的作用。尽管游标能遍历结果中的所有行&am…