SpringBoot实现多数据源启动时不加载数据库
目录
SpringBoot实现多数据源(启动时不加载数据库)
1.前言
本篇文章主要是完成springboot整合多数据源,一般都用来解决那些比较复杂需要连接不同的数据库来支持业务,可以做到不同的接口连接不同的数据库来请求数据,在配置方面不同于其他人的文章在application.yml文件里面配置两三个数据库连接,当然这种只适用于数据库数量不多且固定的情况。针对数据库动态增加的情况无能为力,当你有很多数据库需要连接时,这篇文章就为你提供了解决方法。
2.项目结构
这里主要的功能实现是在config文件里面
3.application.yml
yml配置文件针对数据库连接的配置贴出来。
这里就不复制pom.xml 因为不需要另类的jar。
%s是占位符
第一个%s代替ip地址 第二个%s代替数据库名字,密码如实填写
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
url: jdbc:mysql://%s:3306/%s?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
username: root
#password: ********
password: ********
4.详细的代码实现
1.数据源配置管理类(DataSourceConfig.java)**
package com.example.demo.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;
/**
* Created by Administrator on 2019/8/5.
*/
@Configuration
//"com.example.demo.mapper" 这里写mapper的地址
@MapperScan(basePackages="com.example.demo.mapper", value="sqlSessionFactory")
public class DataSourceConfig {
/**
* 根据配置参数创建数据源
*
* @return 数据源
*/
@Bean(name="dataSource")
//prefix 这里写yml文件里面的spring.datasource.druid
@ConfigurationProperties(prefix="spring.datasource.druid")
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、定义动态数据源**
首先增加一个数据库标识类,用于区分不同的数据库(DBIdentifier.java)
package com.example.demo.config;
/**
* Created by Administrator on 2019/8/5.
* 首先增加一个数据库标识类,用于区分不同的数据库访问。
*由于我们为不同的project创建了单独的数据库,所以使用项目编码作为数据库的索引。
* 而微服务支持多线程并发的,采用线程变量。
*/
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);
}
}
3.从DataSource派生了一个DynamicDataSource,在其中实现数据库连接的动态切换(DynamicDataSource.java)
package com.example.demo.config;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.apache.tomcat.jdbc.pool.PoolProperties;
/**
* Created by Administrator on 2019/8/5.
*/
public class DynamicDataSource extends DataSource {
//private static Logger log = LogManager.getLogger(DynamicDataSource.class);
/**
* 改写本方法是为了在请求不同工程的数据时去连接不同的数据库。
*/
@Override
public Connection getConnection() {
String projectCode = DBIdentifier.getProjectCode();
System.out.println(projectCode+"projectCode");
//1、获取数据源
DataSource dds = DDSHolder.instance().getDDS(projectCode);
System.out.println(dds+"dds");
//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 username=this.getUsername();
String password= this.getPassword();
System.out.println("urlFormat"+urlFormat);
System.out.println("getUsername"+username);
System.out.println("getPassword"+password);
String url = String.format(urlFormat, ProjectDBMgr.instance().getDBIP(projectCode),
ProjectDBMgr.instance().getDBName(projectCode));
System.out.println("url"+url);
dds.setUrl(url);
dds.setUsername(ProjectDBMgr.instance().getUsername(projectCode));
dds.setPassword(ProjectDBMgr.instance().getPassword(projectCode));
return dds;
}
}
3.通过DDSTimer控制数据连接释放(DDSTimer.java)
package com.example.demo.config;
import org.apache.tomcat.jdbc.pool.DataSource;
/**
* Created by Administrator on 2019/8/5.
*/
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.example.demo.config;
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;
/**
* Created by Administrator on 2019/8/5.
*/
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.example.demo.config;
import java.util.TimerTask;
/**
* 清除空闲连接任务。
*
* @author wu
* @version 2019年8月5日
*/
public class ClearIdleTimerTask extends TimerTask {
@Override
public void run() {
DDSHolder.instance().clearIdleDDS();
}
}
6.管理项目编码与数据库IP和名称的映射关系(ProjectDBMgr.java)
package com.example.demo.config;
import com.example.demo.util.DBData;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* * 项目数据库管理。提供根据项目编码查询数据库名称和IP的接口。
* * @author wu
* * @version 2 2019/8/5.
* 格式化 ctrl+ALT+;
*/
public class ProjectDBMgr {
/**
* * 保存项目编码与数据名称的映射关系。这里是硬编码,实际开发中这个关系数据可以保存到redis缓存中;
* * 新增一个项目或者删除一个项目只需要更新缓存。到时这个类的接口只需要修改为从缓存拿数据。
*
*/
private Map<String, DBData> depDBMap = new HashMap<>();
//private Map<String, String> dbNameMap = new HashMap<String, String>();
/**
* * 保存项目编码与数据库IP的映射关系。
*
*/
//private Map<String, String> dbIPMap = new HashMap<String, String>();
private ProjectDBMgr() {
//注意你的项目用到的数据库不同,如果表结果也不同的话 那你得写不同的bean
depDBMap.put("5642",new DBData("5642","青和居西区","10.10.10.10","3306","数据库名字","账号","密码");
depDBMap.put("5455",new DBData("5455","八大家花西园","10.10.10.10","3306","数据库名字","账号","密码"));
depDBMap.put("5643",new DBData("5643","青和居西区","10.10.10.10","3306","数据库名字","账号","密码"));
depDBMap.put("5454",new DBData("5454","八大家花东园","10.10.10.10","3306","数据库名字","账号","密码"));
.
.
.
}
public static ProjectDBMgr instance() {
return ProjectDBMgrBuilder.instance;
}
// 实际开发中改为从缓存获取
public String getDBName(String projectCode) {
if (depDBMap.containsKey(projectCode)) {
return depDBMap.get(projectCode).getDbName();
}
return "";
}
//实际开发中改为从缓存中获取
public String getDBIP(String projectCode) {
if (depDBMap.containsKey(projectCode)) {
return depDBMap.get(projectCode).getIp();
}
return "";
}
//动态获取密码
public String getPassword(String projectCode) {
if (depDBMap.containsKey(projectCode)) {
return depDBMap.get(projectCode).getPassword();
}
return "";
}
//动态获取username
public String getUsername(String projectCode) {
if (depDBMap.containsKey(projectCode)) {
return depDBMap.get(projectCode).getUser();
}
return "";
}
private static class ProjectDBMgrBuilder {
private static ProjectDBMgr instance = new ProjectDBMgr();
}
}
7.编写数据库访问的mapper(PersonSnapshotHistoryMapper .java)
package com.example.demo.mapper;
import com.example.demo.bean.PersonSnapshotHistory;
import com.example.demo.bean.PersonSnapshotHistoryExample;
import java.util.List;
import org.apache.ibatis.annotations.*;
import org.springframework.data.jpa.repository.Query;
@Mapper
public interface PersonSnapshotHistoryMapper {
@Select(value = "SELECT uuid,cardid,cardno FROM PERSON_SNAPSHOT_HISTORY ORDER BY createdAt DESC LIMIT 0,3")
List<PersonSnapshotHistory> selectThreeSnapshot();
}
8.定义查询对象模型(PersonSnapshotHistory .java)
package com.example.demo.bean;
public class PersonSnapshotHistory {
private String uuid;
private Integer cardid;
private String cardno;
…get set省略
}
9.定义查询数据的restful接口(PersonSnapshotHistoryController .java)
package com.example.demo.controller;
import com.example.demo.bean.PersonSnapshotHistory;
import com.example.demo.config.DBIdentifier;
import com.example.demo.mapper.PersonSnapshotHistoryMapper;
import com.example.demo.util.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Created by Administrator on 2019/8/5.
*/
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "*", maxAge = 3600)
public class PersonSnapshotHistoryController {
@Autowired
PersonSnapshotHistoryMapper personSnapshotHistoryMapper;
/**
* 显示前三条数据
* @return
*/
@RequestMapping("threeSnapshot")
public R selectThreeSnapshot(){
//不同的接口需要请求哪个数据库数据 直接填定义好的数据库编号
DBIdentifier.setProjectCode("5642");
List<PersonSnapshotHistory> list=personSnapshotHistoryMapper.selectThreeSnapshot();
return R.ok("").put("list",list);
}
@RequestMapping("threeSnapshot1")
public R selectThreeSnapshot1(){
DBIdentifier.setProjectCode("5455");
List<PersonSnapshotHistory> list=personSnapshotHistoryMapper.selectThreeSnapshot();
DBIdentifier.setProjectCode("5642");
List<PersonSnapshotHistory> list1=personSnapshotHistoryMapper.selectThreeSnapshot();
list.addAll(list1);
return R.ok("").put("list",list);
}
}
5.贴出整个项目
6. 启动类配置重点
这样启动springboot项目的时候就不会去连接数据库 ,不然会启动报错
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class,HibernateJpaAutoConfiguration.class,JpaRepositoriesAutoConfiguration.class})
@MapperScan(basePackages = {"com.example.demo.*","com.example.demo.mapper"})
public class SmartCommunityBootApplication {
public static void main(String[] args) {
//DBIdentifier.setProjectCode("5642");
SpringApplication.run(SmartCommunityBootApplication.class, args);
}
}