通过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:数据库访问接口。

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<String> projectCode = new ThreadLocal<String>();
 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<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用于定时清除空闲的数据源

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<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

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.       定义查询对象模型

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<User> 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的数据,正常返回

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

总结

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

您可能感兴趣的文章:

  • Spring Boot + Mybatis多数据源和动态数据源配置方法
  • Spring Boot 动态数据源示例(多数据源自动切换)
  • 解决spring boot 1.5.4 配置多数据源的问题
  • springboot下配置多数据源的方法
  • Spring Boot多数据源及其事务管理配置方法
  • 详解Spring Boot整合Mybatis实现 Druid多数据源配置
  • Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例
  • springboot + mybatis配置多数据源示例
时间: 2018-02-27

解决spring boot 1.5.4 配置多数据源的问题

spring boot 已经支持多数据源配置了,无需网上好多那些编写什么类的,特别麻烦,看看如下解决方案,官方的,放心! 1.首先定义数据源配置 #=====================multiple database config============================ #ds1 first.datasource.url=jdbc:mysql://localhost/test?characterEncoding=utf8&useSSL=true first.datasou

Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

本文将介绍使用Spring Boot集成Mybatis并实现主从库分离的实现(同样适用于多数据源).延续之前的Spring Boot 集成MyBatis.项目还将集成分页插件PageHelper.通用Mapper以及Druid. 新建一个Maven项目,最终项目结构如下: 多数据源注入到sqlSessionFactory POM增加如下依赖: <!--JSON--> <dependency> <groupId>com.fasterxml.jackson.core<

详解Spring Boot整合Mybatis实现 Druid多数据源配置

一.多数据源的应用场景 目前,业界流行的数据操作框架是 Mybatis,那 Druid 是什么呢? Druid 是 Java 的数据库连接池组件.Druid 能够提供强大的监控和扩展功能.比如可以监控 SQL ,在监控业务可以查询慢查询 SQL 列表等.Druid 核心主要包括三部分: 1. DruidDriver 代理 Driver,能够提供基于 Filter-Chain 模式的插件体系. 2. DruidDataSource 高效可管理的数据库连接池 3. SQLParser 当业务数据量达

Spring Boot多数据源及其事务管理配置方法

准备工作 先给我们的项目添加Spring-JDBC依赖和需要访问数据库的驱动依赖. 配置文件 spring.datasource.prod.driverClassName=com.mysql.jdbc.Driver spring.datasource.prod.url=jdbc:mysql://127.0.0.1:3306/prod spring.datasource.prod.username=root spring.datasource.prod.password=123456 spring

Spring Boot 动态数据源示例(多数据源自动切换)

本文实现案例场景: 某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便. 一配置二使用 1. 启动类注册动态数据源 2. 配置文件中配置多个数据源 3. 在需要的方法上使用注解指定数据源 1.在启动类添加 @Import({Dyna

Spring Boot + Mybatis多数据源和动态数据源配置方法

网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种方式,记录一下配置方法供大家参考. 应用场景 项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库. 多数据源 首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源.在@SpringBootApplication注解中添加ex

springboot + mybatis配置多数据源示例

在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)DatabaseType列出所有的数据源的key---key 2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法 3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用Da

springboot下配置多数据源的方法

一.springboot 简介 SpringBoot使开发独立的,产品级别的基于Spring的应用变得非常简单,你只需"just run". 我们为Spring平台及第三方库提 供开箱即用的设置,这样你就可以有条不紊地开始.多数Spring Boot应用需要很少的Spring配置. 你可以使用SpringBoot创建Java应用,并使用 java -jar 启动它或采用传统的war部署方式.我们也提供了一个运行"spring 脚本"的命令行工具. 二.传统的Dat

SpringBoot整合MyBatisPlus配置动态数据源的方法

MybatisPlus特性 •无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 •损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作 •强大的 CRUD 操作:内置通用 Mapper.通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求 •支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错 •支持多种数据库:支持 MySQL.MariaDB.Ora

springboot 配置DRUID数据源的方法实例分析

本文实例讲述了springboot 配置DRUID数据源的方法.分享给大家供大家参考,具体如下: druid 是阿里开源的数据库连接池. 开发时整合 druid 数据源过程. 1.修改pom.xml <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> &l

SpringBoot下使用MyBatis-Puls代码生成器的方法

1.官方地址: http://mybatis.plus/guide/generator.html#%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B 2.数据库结构: 3.依赖导入 <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> <

Ubuntu samba下配置共享文件夹的方法

文件放在ubuntu下,通过samba共享到windows,避免windows下不支持软链接的问题. 虚拟机安装的ubuntu系统,联网方式为NAT # 更新源 sudo apt-get update # 安装samba sudo apt-get install samba samba-common sudo apt-get install cifs-utils<strong> </strong> # 创建共享的目录 sudo mkdir /home/share sudo chmo

nginx下配置thinkphp文件的方法

在上篇文章给大家介绍了在Nginx上部署ThinkPHP项目教程,今天给大家介绍nginx下thinkphp的配置,具体详解如下: ## domain redirect #if ($host != "my.ruanzhuangyun.cn"){ # rewrite ^/(.*)$ http://my.ruanzhuangyun.cn/$1 permanent; #} ## domain redirect ## tp pathinfo location /data/www/tp.360r

CentOS 7下配置ntp服务的方法教程

前言 对于校园网/企业用户,如果您网内所有计算机都通过互联网同步时间,在速度和精度上都有一定的折扣,并且对互联网出口带宽也有一定的影响,对于这类用户,我们建议通过自己搭建ntp服务为内部用户提供时间同步服务. 本文介绍的是在CentOS 7下配置ntp服务的方法教程,分享出来供大家参考学习,下面来看看详细的介绍吧. 步骤如下: 安装ntp yum -y install ntp 同步时间 ntpdate pool.ntp.org 将ntp服务设为开机启动 chkconfig ntpd on 重启n

vm下centos7 mini版 NAT模式下配置静态IP的方法

1.查看虚拟机的默认网关和子网掩码 a.vm菜单栏点击编辑->虚拟网络编辑器 b.选择VMnet8,点击NAT设置,查看子网掩码.网关IP 2. 修改服务器的网络配置 a. 修改主机名 hostnamectl set-hostname node01(可选) b. 编辑网卡配置 vi /etc/sysconfig/network-scripts/ifcfg-ens33 其中主要的几处参数修改为: BOOTPROTO=static 设置为静态ip ONBOOT=yes 设置开机启动网卡 IPADDR

PyCharm 2020.2下配置Anaconda环境的方法步骤

最近在学习关于Python数据分析与挖掘方面的知识,在学习到Python数据分析工具方面时,需要安装一些第三方扩展库来增强Python的数据分析能力,刚开始我就按照最一般的方法,使用pip包管理工具来安装,但是总是遇到各种错误,失败了很多次,耐心都快被耗尽了. 初次接触这方面的内容,不是太了解,就请教了老师,老师就给我推荐直接安装Anaconda来使用.Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项(我也不是很懂).废话不多说,

Springboot mybais配置多数据源过程解析

一.分包方式实现: 1.在application.properties中配置两个数据库: #druid连接池 #dataSoureOne(这里是我本地的数据源) spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.one.driver-class-name=com.mysql.jdbc.Driver spring.datasource.one.jdbc-url=jdbc:mysql