热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

之前写过一篇博客《SpringMybatisMysql搭建分布式数据库访问框架》描述如何通过SpringMybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客

之前写过一篇博客《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配置动态数据源访问多个数据库的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!



推荐阅读
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 2018深入java目标计划及学习内容
    本文介绍了作者在2018年的深入java目标计划,包括学习计划和工作中要用到的内容。作者计划学习的内容包括kafka、zookeeper、hbase、hdoop、spark、elasticsearch、solr、spring cloud、mysql、mybatis等。其中,作者对jvm的学习有一定了解,并计划通读《jvm》一书。此外,作者还提到了《HotSpot实战》和《高性能MySQL》等书籍。 ... [详细]
  • 分享css中提升优先级属性!important的用法总结
    web前端|css教程css!importantweb前端-css教程本文分享css中提升优先级属性!important的用法总结微信门店展示源码,vscode如何管理站点,ubu ... [详细]
  • 本文介绍了关系型数据库和NoSQL数据库的概念和特点,列举了主流的关系型数据库和NoSQL数据库,同时描述了它们在新闻、电商抢购信息和微博热点信息等场景中的应用。此外,还提供了MySQL配置文件的相关内容。 ... [详细]
  • 微信官方授权及获取OpenId的方法,服务器通过SpringBoot实现
    主要步骤:前端获取到code(wx.login),传入服务器服务器通过参数AppID和AppSecret访问官方接口,获取到OpenId ... [详细]
  • Spring框架《一》简介
    Spring框架《一》1.Spring概述1.1简介1.2Spring模板二、IOC容器和Bean1.IOC和DI简介2.三种通过类型获取bean3.给bean的属性赋值3.1依赖 ... [详细]
  • 本文整理了Java中java.lang.NoSuchMethodError.getMessage()方法的一些代码示例,展示了NoSuchMethodErr ... [详细]
  • 面试经验分享:华为面试四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试
    最近有朋友去华为面试,面试经历包括四轮电话面试、一轮笔试、一轮主管视频面试、一轮hr视频面试。80%的人都在第一轮电话面试中失败,因为缺乏基础知识。面试问题涉及 ... [详细]
  • 旁路|发生_Day749.旁路缓存:Redis是如何工作的Redis 核心技术与实战
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Day749.旁路缓存:Redis是如何工作的-Redis核心技术与实战相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
mobiledu2502925687
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有