时间:2021-05-19
之前写过一篇博客《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. 添加数据源配置
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
5. 定义查询对象模型
6. 定义查询用户数据的restful接口
要求每次查询都要带上projectCode参数。
7. 编写Spring Boot App的启动代码
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.Driverlogging: config: classpath:log4j2.xml测试方案
1. 查询project_001的数据,正常返回
2. 查询project_002的数据,正常返回
总结
以上所述是小编给大家介绍的通过Spring Boot配置动态数据源访问多个数据库的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
什么是多数据源支持?简单的说,就是一个项目里,同时可以访问多个不同的数据库。实现原理单个数据源在配置时会绑定一套mybatis配置,多个数据源时,不同的数据源绑
你在使用MyBatis的过程中,是否有想过多个数据源应该如何配置,如何去实现?出于这个好奇心,我在DruidWiki的数据库多数据源中知晓Spring提供了对多
需求:一条SQL语句从多个数据库查询出相关联的结果,输出到客户端调查:spring框架可以配置多数据源;sql也可以将多个数据库附加到一个主数据库下解决办法:用
前言Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方案就会不同于常见的单
1.概述项目中经常会遇到一个应用需要访问多个数据源的情况,本文介绍在SpringBoot项目中利用SpringDataJpa技术如何支持多个数据库的数据源。具体