本方案不限数据库数量完全动态配置,支持不同的数据库部署在不同的服务器上。(mybatis-plus没测试,下个版本用oracle配的时候尝试plus)
一 、这次我们使用Mysql,本地现在有两个个数据库用于测试。
二、下一步我们看一下Druid继承关系
我们可以看到想要配置DataSource其实非常简单,继承DruidDataSource就可以调用
getConnection方法了
三、下面直接开始上配置(简单的一个小例子其他的自己扩展吧)
#连接池 使用阿里的druid
spring:
application:
name: mall_user
datasource:
url: jdbc:mysql://${IP}:3306/${NAME}?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2b8&useSSL=false
username:
password:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
四、开始上代码
package com.taipingyang.config.datasource2;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
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.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 数据源配置管理。
* @author zz
* @date 2021/1/10 20:46
*/
@MapperScan(basePackages="com.taipingyang.Mapper", value="sqlSessionFactory")
@Configuration
public class DataSourceConfig {
/**
* 根据配置参数创建数据源。使用派生的子类。
*
* @return 数据源
*/
@Bean(name="dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DruidDataSource getDataSource() {DataSourceBuilder.create().type(DynamicDataSource.class).build();
return 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;
}
}
}
(1)首先增加一个数据库标识类,用于区分不同的数据库(DBIdentifier.java)
由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。而微服务支持多线程并发的,采用线程变量。
package com.taipingyang.config.datasource2;
/**
* 数据库标识管理类。用于区分数据源连接的不同数据库。
*
* @author zz
* @version 2021/1/10 20:46
*/
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,在其中实现数据库连接的动态切换(DynamicDataSource.java)
package com.taipingyang.config.datasource2;
import java.lang.reflect.Field;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.pool.PoolableWrapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
* 定义动态数据源派生类。从基础的DataSource派生,动态性自己实现。
*
* @author zz
* @version 2021/1/10 20:46
*/
public class DynamicDataSource extends DruidDataSource {
private static Logger log = LogManager.getLogger(DynamicDataSource.class);
/**
* 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。 DruidDataSource getConnection
*/
@Override
public DruidPooledConnection getConnection() {
String projectCode = DBIdentifier.getProjectCode()==null?"project_001": DBIdentifier.getProjectCode();
//1、获取数据源
DruidDataSource dds = DDSHolder.instance().getDDS(projectCode);
DruidDataSource ddsddn = null;
//2、如果数据源不存在则创建
if (dds == null) {
try {
DruidDataSource 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 DruidDataSource initDDS(String projectCode) throws IllegalArgumentException, IllegalAccessException {
DruidDataSource dds = new DruidDataSource();
String urlFormat = this.getUrl();
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("root");
String url =urlFormat.replace("${IP}", ProjectDBMgr.instance().getDBIP(projectCode)).replace("${NAME}",ProjectDBMgr.instance().getDBName(projectCode));
dataSource.setUrl(url);
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
return dataSource;
}
}
(3)通过DDSTimer控制数据连接释放(DDSTimer.java)
package com.taipingyang.config.datasource2;
import com.alibaba.druid.pool.DruidDataSource;
/**
* 动态数据源定时器管理。长时间无访问的数据库连接关闭。
* @author zz
* @version 2021/1/10 20:46
*/
public class DDSTimer {
/**
* 空闲时间周期。超过这个时长没有访问的数据库连接将被释放。默认为10分钟。
*/
private static long idlePeriodTime = 10 * 60 * 1000;
/**
* 动态数据源
*/
private DruidDataSource dds;
/**
* 上一次访问的时间
*/
private long lastUseTime;
public DDSTimer(DruidDataSource 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 DruidDataSource getDds() {
return dds;
}
}
(4)通过DDSHolder来管理不同的数据源,提供数据源的添加、查询功能(DDSHolder.java)
package com.taipingyang.config.datasource2;
import com.alibaba.druid.pool.DruidDataSource;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.Map.Entry;
/**
* 动态数据源管理器。
*
* @author zz
* @version 2021/1/10 20:46
*/
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, DruidDataSource dds) {
DDSTimer ddst = new DDSTimer(dds);
ddsMap.put(projectCode, ddst);
}
/**
* 查询动态数据源
*
* @param projectCode 项目编码
* @return dds
*/
public synchronized DruidDataSource 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();
while(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用于定时清除空闲的数据源(ClearIdleTimerTask.java)
package com.taipingyang.config.datasource2;
import java.util.TimerTask;
/**
* 清除空闲连接任务。
*
* @author zz
* @version 2021/1/10 20:46
*/
public class ClearIdleTimerTask extends TimerTask {
@Override
public void run() {
DDSHolder.instance().clearIdleDDS();
}
}
(6)管理项目编码与数据库IP和名称的映射关系(ProjectDBMgr.java)
package com.taipingyang.config.datasource2;
import java.util.HashMap;
import java.util.Map;
/**
* 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。
*
* @author zz
* @version 2021/1/10 20:46
*/
public class ProjectDBMgr {
/**
* 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;
* 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。
*/
private Map dbNameMap =new HashMap();
/**
* 保存项目编码与数据库IP的映射关系。
*/
private Map dbIPMap =new HashMap();
private ProjectDBMgr() {
dbNameMap.put("project_001", "annoyingly");
dbNameMap.put("project_002", "tpy");
dbIPMap.put("project_001", "127.0.0.1");
dbIPMap.put("project_002", "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();
}
}
以上就是Druid+mybatis动态数据源全部配置
下面看一下我写的测试类=》
package com.taipingyang.Mapper;
import com.taipingyang.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select(" select * from tpy_user ")
List testuser();
}
直接上controller
@RestController
public class userController {
@Autowired
private UserService userService;
@RequestMapping("user/testuser")
public List testuser(String projectCode) {
DBIdentifier.setProjectCode(projectCode);
return userService.testuser();
}
}
查询数据库全部正常
其实非常简单,只需要在继承DruidDataSource重新getConnection建立连接就好了,至于搞并发场景下还是需要凭借其他框架的。通过这次搭建希望大家能够对DataSource有全新的认识。
代码千万行,注释第一行。格式不规范,报错两行泪!!!