Browse Source

动态添加数据源,动态切换数据源

pull/1/head
Yangkai.Shen 5 years ago
parent
commit
ce4fb28c01
26 changed files with 1029 additions and 0 deletions
  1. +1
    -0
      pom.xml
  2. +31
    -0
      spring-boot-demo-dynamic-datasource/.gitignore
  3. +16
    -0
      spring-boot-demo-dynamic-datasource/db/init.sql
  4. +25
    -0
      spring-boot-demo-dynamic-datasource/db/user.sql
  5. +71
    -0
      spring-boot-demo-dynamic-datasource/pom.xml
  6. +39
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java
  7. +17
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java
  8. +70
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java
  9. +29
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java
  10. +17
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java
  11. +37
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java
  12. +44
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java
  13. +33
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java
  14. +58
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java
  15. +40
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java
  16. +91
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java
  17. +57
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java
  18. +44
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java
  19. +61
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java
  20. +17
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java
  21. +15
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java
  22. +69
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java
  23. +35
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java
  24. +86
    -0
      spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java
  25. +10
    -0
      spring-boot-demo-dynamic-datasource/src/main/resources/application.yml
  26. +16
    -0
      spring-boot-demo-dynamic-datasource/src/test/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplicationTests.java

+ 1
- 0
pom.xml View File

@@ -63,6 +63,7 @@
<module>spring-boot-demo-codegen</module>
<module>spring-boot-demo-graylog</module>
<module>spring-boot-demo-ldap</module>
<module>spring-boot-demo-dynamic-datasource</module>
</modules>
<packaging>pom</packaging>



+ 31
- 0
spring-boot-demo-dynamic-datasource/.gitignore View File

@@ -0,0 +1,31 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/

### VS Code ###
.vscode/

+ 16
- 0
spring-boot-demo-dynamic-datasource/db/init.sql View File

@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS `datasource_config`
(
`id` bigint(13) NOT NULL AUTO_INCREMENT COMMENT '主键',
`host` varchar(255) NOT NULL COMMENT '数据库地址',
`port` int(6) NOT NULL COMMENT '数据库端口',
`username` varchar(100) NOT NULL COMMENT '数据库用户名',
`password` varchar(100) NOT NULL COMMENT '数据库密码',
`database` varchar(100) DEFAULT 0 COMMENT '数据库名称',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='数据源配置表';

INSERT INTO `datasource_config`(`id`, `host`, `port`, `username`, `password`, `database`)
VALUES (1, '127.0.01', 3306, 'root', 'root', 'test');
INSERT INTO `datasource_config`(`id`, `host`, `port`, `username`, `password`, `database`)
VALUES (2, '192.168.239.4', 3306, 'dmcp', 'Dmcp321!', 'test');

+ 25
- 0
spring-boot-demo-dynamic-datasource/db/user.sql View File

@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS `test_user`
(
`id` bigint(13) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(255) NOT NULL COMMENT '姓名',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8 COMMENT ='用户表';

-- 默认数据库插入如下 SQL
INSERT INTO `test_user`(`id`, `name`)
values (1, '默认数据库用户1');
INSERT INTO `test_user`(`id`, `name`)
values (2, '默认数据库用户2');

-- 测试库1插入如下SQL
INSERT INTO `test_user`(`id`, `name`)
values (1, '测试库1用户1');
INSERT INTO `test_user`(`id`, `name`)
values (2, '测试库1用户2');

-- 测试库2插入如下SQL
INSERT INTO `test_user`(`id`, `name`)
values (1, '测试库2用户1');
INSERT INTO `test_user`(`id`, `name`)
values (2, '测试库2用户2');

+ 71
- 0
spring-boot-demo-dynamic-datasource/pom.xml View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-boot-demo-dynamic-datasource</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-boot-demo-dynamic-datasource</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>com.xkcoding</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<finalName>spring-boot-demo-dynamic-datasource</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

+ 39
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplication.java View File

@@ -0,0 +1,39 @@
package com.xkcoding.dynamic.datasource;

import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigCache;
import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigContextHolder;
import com.xkcoding.dynamic.datasource.mapper.DatasourceConfigMapper;
import com.xkcoding.dynamic.datasource.model.DatasourceConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.List;

/**
* <p>
* 启动器
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 17:57
*/
@SpringBootApplication
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class SpringBootDemoDynamicDatasourceApplication implements CommandLineRunner {
private final DatasourceConfigMapper configMapper;

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoDynamicDatasourceApplication.class, args);
}

@Override
public void run(String... args) {
DatasourceConfigContextHolder.setDefaultDatasource();
List<DatasourceConfig> datasourceConfigs = configMapper.selectAll();
System.out.println("加载其余数据源配置列表: " + datasourceConfigs);
datasourceConfigs.forEach(config -> DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config));
}
}

+ 17
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/annotation/DefaultDatasource.java View File

@@ -0,0 +1,17 @@
package com.xkcoding.dynamic.datasource.annotation;

import java.lang.annotation.*;

/**
* <p>
* 默认数据源
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 17:37
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DefaultDatasource {
}

+ 70
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/aspect/DatasourceSelectorAspect.java View File

@@ -0,0 +1,70 @@
package com.xkcoding.dynamic.datasource.aspect;

import com.xkcoding.dynamic.datasource.annotation.DefaultDatasource;
import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigContextHolder;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
* <p>
* 数据源选择器切面
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:52
*/
@Aspect
@Component
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class DatasourceSelectorAspect {
@Pointcut("execution(public * com.xkcoding.dynamic.datasource.controller.*.*(..))")
public void datasourcePointcut() {
}

/**
* 前置通知 用于拦截操作
*/
@Before("datasourcePointcut()")
public void doBefore(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();

DefaultDatasource annotation = method.getAnnotation(DefaultDatasource.class);
if (null != annotation) {
DatasourceConfigContextHolder.setDefaultDatasource();
} else {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = attributes.getRequest();
String configIdInHeader = request.getHeader("Datasource-Config-Id");
if (StringUtils.hasText(configIdInHeader)) {
long configId = Long.parseLong(configIdInHeader);
DatasourceConfigContextHolder.setCurrentDatasourceConfig(configId);
} else {
DatasourceConfigContextHolder.setDefaultDatasource();
}
}
}

@AfterReturning("datasourcePointcut()")
public void doAfter() {
DatasourceConfigContextHolder.setDefaultDatasource();
}

}

+ 29
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/DatasourceConfiguration.java View File

@@ -0,0 +1,29 @@
package com.xkcoding.dynamic.datasource.config;

import com.xkcoding.dynamic.datasource.datasource.DynamicDataSource;
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;

import javax.sql.DataSource;

/**
* <p>
* 数据源配置
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 10:27
*/
@Configuration
public class DatasourceConfiguration {

@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.type(DynamicDataSource.class);
return dataSourceBuilder.build();
}
}

+ 17
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MyMapper.java View File

@@ -0,0 +1,17 @@
package com.xkcoding.dynamic.datasource.config;

import tk.mybatis.mapper.annotation.RegisterMapper;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;

/**
* <p>
* 通用 mapper 自定义 mapper 文件
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:23
*/
@RegisterMapper
public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
}

+ 37
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/config/MybatisConfiguration.java View File

@@ -0,0 +1,37 @@
package com.xkcoding.dynamic.datasource.config;

import lombok.SneakyThrows;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tk.mybatis.spring.annotation.MapperScan;

import javax.sql.DataSource;

/**
* <p>
* mybatis配置
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:20
*/
@Configuration
@MapperScan(basePackages = "com.xkcoding.dynamicdatasource.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisConfiguration {
/**
* 创建会话工厂。
*
* @param dataSource 数据源
* @return 会话工厂
*/
@Bean(name = "sqlSessionFactory")
@SneakyThrows
public SqlSessionFactory getSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
}

+ 44
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/DatasourceConfigController.java View File

@@ -0,0 +1,44 @@
package com.xkcoding.dynamic.datasource.controller;

import com.xkcoding.dynamic.datasource.annotation.DefaultDatasource;
import com.xkcoding.dynamic.datasource.datasource.DatasourceConfigCache;
import com.xkcoding.dynamic.datasource.mapper.DatasourceConfigMapper;
import com.xkcoding.dynamic.datasource.model.DatasourceConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
* <p>
* 数据源配置 Controller
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 17:31
*/
@RestController
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class DatasourceConfigController {
private final DatasourceConfigMapper configMapper;

/**
* 保存
*/
@PostMapping("/config")
@DefaultDatasource
public DatasourceConfig insertConfig(@RequestBody DatasourceConfig config) {
configMapper.insertUseGeneratedKeys(config);
DatasourceConfigCache.INSTANCE.addConfig(config.getId(), config);
return config;
}

/**
* 保存
*/
@DeleteMapping("/config/{id}")
@DefaultDatasource
public void removeConfig(@PathVariable Long id) {
configMapper.deleteByPrimaryKey(id);
DatasourceConfigCache.INSTANCE.removeConfig(id);
}
}

+ 33
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/controller/UserController.java View File

@@ -0,0 +1,33 @@
package com.xkcoding.dynamic.datasource.controller;

import com.xkcoding.dynamic.datasource.mapper.UserMapper;
import com.xkcoding.dynamic.datasource.model.User;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* <p>
* 用户 Controller
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:40
*/
@RestController
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UserController {
private final UserMapper userMapper;

/**
* 获取用户列表
*/
@GetMapping("/user")
public List<User> getUserList() {
return userMapper.selectAll();
}

}

+ 58
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigCache.java View File

@@ -0,0 +1,58 @@
package com.xkcoding.dynamic.datasource.datasource;

import com.xkcoding.dynamic.datasource.model.DatasourceConfig;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* <p>
* 数据源配置缓存
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 17:13
*/
public enum DatasourceConfigCache {
/**
* 当前实例
*/
INSTANCE;

/**
* 管理动态数据源列表。
*/
private static final Map<Long, DatasourceConfig> CONFIG_CACHE = new ConcurrentHashMap<>();

/**
* 添加数据源配置
*
* @param id 数据源配置id
* @param config 数据源配置
*/
public synchronized void addConfig(Long id, DatasourceConfig config) {
CONFIG_CACHE.put(id, config);
}

/**
* 查询数据源配置
*
* @param id 数据源配置id
* @return 数据源配置
*/
public synchronized DatasourceConfig getConfig(Long id) {
if (CONFIG_CACHE.containsKey(id)) {
return CONFIG_CACHE.get(id);
}
return null;
}

/**
* 清除数据源配置
*/
public synchronized void removeConfig(Long id) {
CONFIG_CACHE.remove(id);
// 同步清除 DatasourceHolder 对应的数据源
DatasourceHolder.INSTANCE.removeDatasource(id);
}
}

+ 40
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceConfigContextHolder.java View File

@@ -0,0 +1,40 @@
package com.xkcoding.dynamic.datasource.datasource;

/**
* <p>
* 数据源标识管理
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 14:16
*/
public class DatasourceConfigContextHolder {
private static final ThreadLocal<Long> DATASOURCE_HOLDER = ThreadLocal.withInitial(() -> DatasourceHolder.DEFAULT_ID);

/**
* 设置默认数据源
*/
public static void setDefaultDatasource() {
DATASOURCE_HOLDER.remove();
setCurrentDatasourceConfig(DatasourceHolder.DEFAULT_ID);
}

/**
* 获取当前数据源配置id
*
* @return 数据源配置id
*/
public static Long getCurrentDatasourceConfig() {
return DATASOURCE_HOLDER.get();
}

/**
* 设置当前数据源配置id
*
* @param id 数据源配置id
*/
public static void setCurrentDatasourceConfig(Long id) {
DATASOURCE_HOLDER.set(id);
}

}

+ 91
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceHolder.java View File

@@ -0,0 +1,91 @@
package com.xkcoding.dynamic.datasource.datasource;

import com.zaxxer.hikari.HikariDataSource;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
* <p>
* 数据源管理
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 14:23
*/
public enum DatasourceHolder {
/**
* 当前实例
*/
INSTANCE;

/**
* 启动执行,定时5分钟清理一次
*/
DatasourceHolder() {
DatasourceScheduler.INSTANCE.schedule(this::clearExpiredDatasource, 5 * 60 * 1000);
}

/**
* 默认数据源的id
*/
public static final Long DEFAULT_ID = -1L;

/**
* 管理动态数据源列表。
*/
private static final Map<Long, DatasourceManager> DATASOURCE_CACHE = new ConcurrentHashMap<>();

/**
* 添加动态数据源
*
* @param id 数据源id
* @param dataSource 数据源
*/
public synchronized void addDatasource(Long id, HikariDataSource dataSource) {
DatasourceManager datasourceManager = new DatasourceManager(dataSource);
DATASOURCE_CACHE.put(id, datasourceManager);
}

/**
* 查询动态数据源
*
* @param id 数据源id
* @return 数据源
*/
public synchronized HikariDataSource getDatasource(Long id) {
if (DATASOURCE_CACHE.containsKey(id)) {
DatasourceManager datasourceManager = DATASOURCE_CACHE.get(id);
datasourceManager.refreshTime();
return datasourceManager.getDataSource();
}
return null;
}

/**
* 清除超时的数据源
*/
public synchronized void clearExpiredDatasource() {
DATASOURCE_CACHE.forEach((k, v) -> {
// 排除默认数据源
if (!DEFAULT_ID.equals(k)) {
if (v.isExpired()) {
DATASOURCE_CACHE.remove(k);
}
}
});
}

/**
* 清除动态数据源
* @param id 数据源id
*/
public synchronized void removeDatasource(Long id) {
if (DATASOURCE_CACHE.containsKey(id)) {
// 关闭数据源
DATASOURCE_CACHE.get(id).getDataSource().close();
// 移除缓存
DATASOURCE_CACHE.remove(id);
}
}
}

+ 57
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceManager.java View File

@@ -0,0 +1,57 @@
package com.xkcoding.dynamic.datasource.datasource;

import com.zaxxer.hikari.HikariDataSource;
import lombok.Getter;

import java.time.LocalDateTime;

/**
* <p>
* 数据源管理类
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 14:27
*/
public class DatasourceManager {
/**
* 默认释放时间
*/
private static final Long DEFAULT_RELEASE = 10L;

/**
* 数据源
*/
@Getter
private HikariDataSource dataSource;

/**
* 上一次使用时间
*/
private LocalDateTime lastUseTime;

public DatasourceManager(HikariDataSource dataSource) {
this.dataSource = dataSource;
this.lastUseTime = LocalDateTime.now();
}

/**
* 是否已过期,如果过期则关闭数据源
*
* @return 是否过期,{@code true} 过期,{@code false} 未过期
*/
public boolean isExpired() {
if (LocalDateTime.now().isBefore(this.lastUseTime.plusMinutes(DEFAULT_RELEASE))) {
return false;
}
this.dataSource.close();
return true;
}

/**
* 刷新上次使用时间
*/
public void refreshTime() {
this.lastUseTime = LocalDateTime.now();
}
}

+ 44
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DatasourceScheduler.java View File

@@ -0,0 +1,44 @@
package com.xkcoding.dynamic.datasource.datasource;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
* <p>
* 数据源缓存释放调度器
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 14:42
*/
public enum DatasourceScheduler {
/**
* 当前实例
*/
INSTANCE;

private AtomicInteger cacheTaskNumber = new AtomicInteger(1);
private ScheduledExecutorService scheduler;

DatasourceScheduler() {
create();
}

private void create() {
this.shutdown();
this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("Datasource-Release-Task-%s", cacheTaskNumber.getAndIncrement())));
}

private void shutdown() {
if (null != this.scheduler) {
this.scheduler.shutdown();
}
}

public void schedule(Runnable task,long delay){
this.scheduler.scheduleAtFixedRate(task, delay, delay, TimeUnit.MILLISECONDS);
}

}

+ 61
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/datasource/DynamicDataSource.java View File

@@ -0,0 +1,61 @@
package com.xkcoding.dynamic.datasource.datasource;

import com.xkcoding.dynamic.datasource.model.DatasourceConfig;
import com.xkcoding.dynamic.datasource.utils.SpringUtil;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;

import java.sql.Connection;
import java.sql.SQLException;

/**
* <p>
* 动态数据源
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 10:41
*/
@Slf4j
public class DynamicDataSource extends HikariDataSource {
@Override
public Connection getConnection() throws SQLException {
// 获取当前数据源 id
Long id = DatasourceConfigContextHolder.getCurrentDatasourceConfig();
// 根据当前id获取数据源
HikariDataSource datasource = DatasourceHolder.INSTANCE.getDatasource(id);

if (null == datasource) {
datasource = initDatasource(id);
}

return datasource.getConnection();
}

private HikariDataSource initDatasource(Long id) {
HikariDataSource dataSource = new HikariDataSource();

if (DatasourceHolder.DEFAULT_ID.equals(id)) {
DataSourceProperties properties = SpringUtil.getBean(DataSourceProperties.class);
dataSource.setJdbcUrl(properties.getUrl());
dataSource.setUsername(properties.getUsername());
dataSource.setPassword(properties.getPassword());
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
} else {
// 获取数据库的配置
DatasourceConfig datasourceConfig = DatasourceConfigCache.INSTANCE.getConfig(id);

if (datasourceConfig == null) {
throw new RuntimeException("无此数据源");
}

dataSource.setJdbcUrl(datasourceConfig.buildJdbcUrl());
dataSource.setUsername(datasourceConfig.getUsername());
dataSource.setPassword(datasourceConfig.getPassword());
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
}
DatasourceHolder.INSTANCE.addDatasource(id, dataSource);
return dataSource;
}
}

+ 17
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/DatasourceConfigMapper.java View File

@@ -0,0 +1,17 @@
package com.xkcoding.dynamic.datasource.mapper;

import com.xkcoding.dynamic.datasource.config.MyMapper;
import com.xkcoding.dynamic.datasource.model.DatasourceConfig;
import org.apache.ibatis.annotations.Mapper;

/**
* <p>
* 数据源配置 Mapper
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:20
*/
@Mapper
public interface DatasourceConfigMapper extends MyMapper<DatasourceConfig> {
}

+ 15
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/mapper/UserMapper.java View File

@@ -0,0 +1,15 @@
package com.xkcoding.dynamic.datasource.mapper;

import com.xkcoding.dynamic.datasource.config.MyMapper;
import com.xkcoding.dynamic.datasource.model.User;

/**
* <p>
* 用户 Mapper
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:49
*/
public interface UserMapper extends MyMapper<User> {
}

+ 69
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/DatasourceConfig.java View File

@@ -0,0 +1,69 @@
package com.xkcoding.dynamic.datasource.model;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

/**
* <p>
* 数据源配置表
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 10:58
*/
@Data
@Table(name = "datasource_config")
public class DatasourceConfig implements Serializable {
/**
* 主键
*/
@Id
@Column(name = "`id`")
@GeneratedValue(generator = "JDBC")
private Long id;

/**
* 数据库地址
*/
@Column(name = "`host`")
private String host;

/**
* 数据库端口
*/
@Column(name = "`port`")
private Integer port;

/**
* 数据库用户名
*/
@Column(name = "`username`")
private String username;

/**
* 数据库密码
*/
@Column(name = "`password`")
private String password;

/**
* 数据库名称
*/
@Column(name = "`database`")
private String database;

/**
* 构造JDBC URL
*
* @return JDBC URL
*/
public String buildJdbcUrl() {
return String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=utf-8&useSSL=false", this.host, this.port, this.database);
}

}

+ 35
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/model/User.java View File

@@ -0,0 +1,35 @@
package com.xkcoding.dynamic.datasource.model;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;

/**
* <p>
* 用户
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:41
*/
@Data
@Table(name = "test_user")
public class User implements Serializable {
/**
* 主键
*/
@Id
@Column(name = "`id`")
@GeneratedValue(generator = "JDBC")
private Long id;

/**
* 姓名
*/
@Column(name = "`name`")
private String name;
}

+ 86
- 0
spring-boot-demo-dynamic-datasource/src/main/java/com/xkcoding/dynamic/datasource/utils/SpringUtil.java View File

@@ -0,0 +1,86 @@
package com.xkcoding.dynamic.datasource.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

/**
* <p>
* Spring 工具类
* </p>
*
* @author yangkai.shen
* @date Created in 2019/9/4 16:16
*/
@Slf4j
@Service
@Lazy(false)
public class SpringUtil implements ApplicationContextAware, DisposableBean {

private static ApplicationContext applicationContext = null;

/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}

/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringUtil.applicationContext = applicationContext;
}

/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
return (T) applicationContext.getBean(name);
}

/**
* 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
return applicationContext.getBean(requiredType);
}

/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
if (log.isDebugEnabled()) {
log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
}
applicationContext = null;
}

/**
* 发布事件
*
* @param event 事件
*/
public static void publishEvent(ApplicationEvent event) {
if (applicationContext == null) {
return;
}
applicationContext.publishEvent(event);
}

/**
* 实现DisposableBean接口, 在Context关闭时清理静态变量.
*/
@Override
public void destroy() {
SpringUtil.clearHolder();
}

}

+ 10
- 0
spring-boot-demo-dynamic-datasource/src/main/resources/application.yml View File

@@ -0,0 +1,10 @@
server:
port: 8080
servlet:
context-path: /demo
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver

+ 16
- 0
spring-boot-demo-dynamic-datasource/src/test/java/com/xkcoding/dynamic/datasource/SpringBootDemoDynamicDatasourceApplicationTests.java View File

@@ -0,0 +1,16 @@
package com.xkcoding.dynamic.datasource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootDemoDynamicDatasourceApplicationTests {

@Test
public void contextLoads() {
}

}

Loading…
Cancel
Save