栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

通过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;

@Configuration
@MapperScan(basePackages="com.elon.dds.mapper", value="sqlSessionFactory")
public class DataSourceConfig {
 
 @Bean(name="dataSource")
 @ConfigurationProperties(prefix="spring.datasource")
 public DataSource getDataSource() {
 DataSourceBuilder builder = DataSourceBuilder.create();
 builder.type(DynamicDataSource.class);
 return builder.build();
 }
 
 @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;

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;

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

public class DDSTimer {
 
 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();
 }
 
 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;

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;
 }
 
 public synchronized void addDDS(String projectCode, DataSource dds) {
 DDSTimer ddst = new DDSTimer(dds);
 ddsMap.put(projectCode, ddst);
 }
 
 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();
  }
 }
 }
 
 private static class DDSHolderBuilder {
 private static DDSHolder instance = new DDSHolder();
 }
}

5)      定时器任务ClearIdleTimerTask用于定时清除空闲的数据源

package com.elon.dds.datasource;
import java.util.TimerTask;

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;

public class ProjectDBMgr {
 
 private Map dbNameMap = new HashMap();
 
 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;

@Mapper
public interface UserMapper
{
 
 @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;

@RestController
@RequestMapping(value="/user")
public class WSUser {
 @Autowired
 private UserMapper userMapper;
 
 @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;

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/141518.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号