如何通过Spring Boot配置动态数据源访问多个数据库
之前写过一篇博客《Spring+Mybatis+Mysql搭建分布式数据库访问框架》描述如何通过Spring+Mybatis配置动态数据源访问多个数据库。但是之前的方案有一些限制(原博客中也描述了):只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力。
下面讲的方案能支持数据库动态增删,数量不限。
数据库环境准备
下面以Mysql为例,先在本地建3个数据库用于测试。需要说明的是本方案不限数据库数量,支持不同的数据库部署在不同的服务器上。如图所示db_project_001、db_project_002、db_project_003。

搭建Java后台微服务项目
创建一个Spring Boot的maven项目:

config:数据源配置。
datasource:自己实现的动态数据源相关类。
dbmgr:管理项目编码与数据库IP、名称的映射关系(实际项目中这部分数据保存在redis缓存中,可动态增删)。
mapper:mybatis的数据库访问接口。
model:映射模型。
rest:微服务对外发布的restful接口,这里用来测试。
application.yml:配置数据库JDBC参数。
详细的代码实现
1. 数据源配置管理类(DataSourceConfig.java)
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) 首先增加一个数据库标识类,用于区分不同的数据库(DBIdentifier.java)
由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。而微服务支持多线程并发的,采用线程变量。
package com.elon.dds.datasource;
/**
* 数据库标识管理类。用于区分数据源连接的不同数据库。
*
* @author elon
* @version 2018-02-25
*/
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.elon.dds.datasource;
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)
{
//有一些static final的属性不能修改。忽略。
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控制数据连接释放(DDSTimer.java)
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来管理不同的数据源,提供数据源的添加、查询功能(DDSHolder.java)
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<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, 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<Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator();
for (; 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.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和名称的映射关系(ProjectDBMgr.java)
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<String, String> dbNameMap = new HashMap<String, String>();
/**
* 保存项目编码与数据库IP的映射关系。
*/
private Map<String, String> dbIPMap = new HashMap<String, String>();
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(UserMapper.java)
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<User> getUsers();
}
5. 定义查询对象模型(User.java)
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接口(WSUser.java)
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<User> queryUser(@RequestParam(value="projectCode", required=true) String projectCode)
{
DBIdentifier.setProjectCode(projectCode);
return userMapper.getUsers();
}
}
要求每次查询都要带上projectCode参数。
7. 编写Spring Boot App的启动代码(App.java)
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的数据,正常返回

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

如何通过Spring Boot配置动态数据源访问多个数据库的更多相关文章
- Spring Boot配置多数据源并实现Druid自动切换
原文:https://blog.csdn.net/acquaintanceship/article/details/75350653 Spring Boot配置多数据源配置yml文件主数据源配置从数据 ...
- spring boot 配置双数据源mysql、sqlServer
背景:原来一直都是使用mysql数据库,在application.properties 中配置数据库信息 spring.datasource.url=jdbc:mysql://xxxx/test sp ...
- spring boot 配置多数据源
https://www.jianshu.com/p/b2e53a2521fc
- spring boot 配置虚拟静态资源文件
我们实现的目的是:通过spring boot 配置静态资源访问的虚拟路径,可实现在服务器,或者在本地通过:http://ip地址:端口/资源路径/文件名 ,可直接访问文件 比如:我们本地电脑的:E: ...
- Spring配置动态数据源-读写分离和多数据源
在现在互联网系统中,随着用户量的增长,单数据源通常无法满足系统的负载要求.因此为了解决用户量增长带来的压力,在数据库层面会采用读写分离技术和数据库拆分等技术.读写分离就是就是一个Master数据库,多 ...
- Spring Boot2.x 动态数据源配置
原文链接: Spring Boot2.x 动态数据源配置 基于 Spring Boot 2.x.Spring Data JPA.druid.mysql 的动态数据源配置Demo,适合用于数据库的读写分 ...
- Spring boot配置多个Redis数据源操作实例
原文:https://www.jianshu.com/p/c79b65b253fa Spring boot配置多个Redis数据源操作实例 在SpringBoot是项目中整合了两个Redis的操作实例 ...
- spring boot 配置访问其他模块包中的mapper和xml
maven项目结构如下,这里只是简单测试demo,使用的springboot版本为2.1.3.RELEASE 1.comm模块主要是一些mybatis的mapper接口和对应的xml文件,以及数据库表 ...
- spring boot 开静态资源访问,配置视图解析器
配置视图解析器spring.mvc.view.prefix=/pages/spring.mvc.view.suffiix= spring boot 开静态资源访问application.proerti ...
随机推荐
- angularjs 指令详解
一.指令定义 对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素的功能. 首先来看个完整的参数示例再来详细的介绍各个参数的作用及用法: <div my-direct ...
- 【学习笔记】Struts2 类型转换
为什么需要类型转换 在基于HTTP协议的Web应用中 客户端请求的所有内容(表单中提交的内容等)都以文本编码的方式传输到服务器端但服务器端的编程语言(如Java)有着丰富的数据类型 如 int boo ...
- 【学习笔记】Hibernate HQL连接查询和数据批处理 (Y2-1-7)
HQL连接查询 和SQL查询一样 hql也支持各种链接查询 如内连接 外连接 具体如下 左外连接 left (outer) join 迫切左外连接 left (outer) join fetch 右外 ...
- SpringMvc笔记-对RESTFUL风格的配置
1.@RequestMapping注解可以使用如下参数: 1,params:例如params={'username',"age!=100"}表示需要usernmame并且age 属 ...
- CentOS Crontab(定时任务)
安装crontab: yum install crontabs 说明: service crond start //启动服务 service crond stop //关闭服务 service cro ...
- 1.12 dict 字典表
dict 字典表属于映射分类 dict的声明 >>> #dict类型 是 {}中包含若干个键值对 >>> d = dict() >>> d = { ...
- RGB与HSV之间的转换公式及颜色表
RGB & HSV 英文全称 RGB - Red, Green, Blue HSV - Hue, Saturation, Value HSV --> RGB 转换公式 HSV --> ...
- 【开源项目】智能电视及电视盒子的控制应用TVRemoteIME的接口说明
一.APP项目介绍: APP名称:TVRemoteIME 功能说明:安卓智能电视或者安卓盒子的控制应用,可跨屏远程输入.远程遥控(代替遥控器)盒子.盒子应用及文件管理.HTTP/RTMP/MMS网络视 ...
- Git 用户名和邮箱
用户名邮箱的作用 用户名和邮箱地址是本地git客户端的一个变量,不随git库而改变. 每次commit都会用用户名和邮箱纪录. github的contributions统计就是按邮箱来统计的. 查看用 ...
- js函数之四大调用模式
一.方法调用模式 当一个函数调用保存为一个对象的属性时我们称之为方法调用. var myObject = { value:0, increment:function(inc){ this.value ...