目的:
需要能够动态切换数据源,实现对不同数据库的增删查改操作
- 需要引入的jar包,pom.xml如下:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lan</groupId>
<artifactId>springboot-druid</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>springboot-druid</name>
<description>Demo project for Spring Boot</description>
<properties>
<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-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--druid 连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 在项目启动类上配置,取消spring自动配置数据源操作
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringbootDruidApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDruidApplication.class, args);
}
}
- 在配置文件application.yml中配置数据库信息等
server:
port: 9696
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
map-underscore-to-camel-case: true
# 控制台打印sql
logging:
level:
com.lan.springbootdruid.mapper: debug
spring:
datasource:
druid:
master:
url: jdbc:mysql://localhost:3306/springbootdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 12345
initialSize: 5
other:
url: jdbc:mysql://localhost:3306/springbootdb_cluster?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 12345
driver-class-name: com.mysql.jdbc.Driver
# 初始化数量
initial-size: 5
# 最大活跃数
max-active: 20
# 最大连接等待超时时间
max-wait: 60000
# 最小
min-idle: 5
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打开PSCache
pool-prepared-statements: true
# 指定每个连接上PSCache的大小
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,‘wall’用于防火墙
filters: stat,wall,log4j
# 通过conncetProperties属性来打开mergeSql功能,慢sql记录
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
- 创建数据源枚举类
public enum DataSourceKey {
DB_MASTER,
DB_OTHER;
}
- 创建DynamicDataSourceContextHolder类,来解决多线程访问全局变量的问题
package com.lan.springbootdruid.datasource;
import com.lan.springbootdruid.enums.DataSourceKey;
import lombok.extern.slf4j.Slf4j;
/**
* @author: Lan
* @date: 2019/5/5 17:52
* @description:解决多线程访问全局变量的问题
*/
@Slf4j
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<DataSourceKey> currentDateSource = new ThreadLocal<>();
/**
* 清除当前数据源
*/
public static void clear() {
log.info("清除当前数据源");
currentDateSource.remove();
}
/**
* 获取当前使用的数据源
*
* @return
*/
public static DataSourceKey get() {
log.info("获取当前使用的数据源:{}", currentDateSource.get());
return currentDateSource.get();
}
/**
* 设置当前使用数据源
*
* @param key 需要设置的数据源
*/
public static void set(DataSourceKey key) {
log.info("设置当前使用数据源:{}", key);
currentDateSource.set(key);
}
}
- 设置数据源
package com.lan.springbootdruid.datasource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author: Lan
* @date: 2019/5/5 17:57
* @description:
*/
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
/**
* 设置数据源
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
log.info("当前数据源:{}", DynamicDataSourceContextHolder.get());
return DynamicDataSourceContextHolder.get();
}
}
- 配置数据源
package com.lan.springbootdruid.datasource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.lan.springbootdruid.enums.DataSourceKey;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author: Lan
* @date: 2019/5/5 18:01
* @description:配置数据源
*/
@MapperScan(basePackages = "com.lan.springbootdruid.mapper")
@Configuration
public class DynamicDataSourceConfiguration {
/**
* 数据源
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DataSource dbMaster() {
return DruidDataSourceBuilder.create().build();
}
/**
* 数据源
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.other")
public DataSource dbOther() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置项(数据库与实体类映射,驼峰命名)
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration configuration() {
return new org.apache.ibatis.session.Configuration();
}
/**
* 核心动态数据源
*
* @return
*/
@Bean
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
dynamicRoutingDataSource.setDefaultTargetDataSource(dbMaster());
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceKey.DB_MASTER, dbMaster());
dataSourceMap.put(DataSourceKey.DB_OTHER, dbOther());
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//开启数据库与实体类映射
sqlSessionFactoryBean.setConfiguration(configuration());
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
//此处设置为了解决找不到mapper文件的问题
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
/**
* 事务管理
*
* @return
*/
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
- 通过aop来动态切换数据源
package com.lan.springbootdruid.datasource;
import com.lan.springbootdruid.enums.DataSourceKey;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author: Lan
* @date: 2019/5/5 18:22
* @description:数据源切面
*/
@Aspect
@Order(-1)
@Component
@Slf4j
public class DynamicDataSourceAspect {
// @Pointcut("execution(* com.lan.springbootdruid.controller..*.*(..))")
// public void pointCut() {
//
// }
@Pointcut("@annotation(TargetDataSource)")
public void pointCut() {
}
/**
* 执行前更换数据源
*
* @param joinPoint
*/
@Before("@annotation(TargetDataSource)")
public void doBefore(JoinPoint joinPoint) {
log.info("执行前");
//获取类上的注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
if (targetDataSource == null) {
log.info("使用默认数据源:{}", DataSourceKey.DB_MASTER);
DynamicDataSourceContextHolder.set(DataSourceKey.DB_MASTER);
} else {
log.info("设置数据源:{}", targetDataSource.dataSourceKey());
DynamicDataSourceContextHolder.set(targetDataSource.dataSourceKey());
}
}
/**
* 执行后清除数据源
*
* @param joinPoint
*/
@After("@annotation(TargetDataSource)")
public void doAfter(JoinPoint joinPoint) {
log.info("执行后");
//获取类上的注解
TargetDataSource targetDataSource = joinPoint.getTarget().getClass().getAnnotation(TargetDataSource.class);
if (targetDataSource == null) {
log.info("当前数据源:{}", DataSourceKey.DB_MASTER);
} else {
log.info("当前数据源:{}", targetDataSource.dataSourceKey());
}
DynamicDataSourceContextHolder.clear();
}
}
- mapper
package com.lan.springbootdruid.mapper;
import com.lan.springbootdruid.datasource.TargetDataSource;
import com.lan.springbootdruid.entity.User;
import com.lan.springbootdruid.enums.DataSourceKey;
import org.springframework.stereotype.Repository;
/**
* @author: Lan
* @date: 2019/5/5 18:26
* @description:
*/
@Repository
public interface UserMapper {
/**
* 通过ID获取用户对象
*
* @param id
* @return
*/
@TargetDataSource(dataSourceKey = DataSourceKey.DB_OTHER)
User getUserById(int id);
/**
* 通过id修改用户名
*
* @param id
* @return
*/
@TargetDataSource(dataSourceKey = DataSourceKey.DB_OTHER)
int updateUserName(int id);
}
- controller
package com.lan.springbootdruid.controller;
import com.lan.springbootdruid.entity.City;
import com.lan.springbootdruid.entity.User;
import com.lan.springbootdruid.service.CityService;
import com.lan.springbootdruid.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @author: Lan
* @date: 2019/5/5 18:42
* @description:
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private CityService cityService;
@GetMapping("/{id}")
public Map<String, Object> getUserById(@PathVariable("id") int id) {
User user = userService.getUserById(id);
City city = cityService.getCityById(id);
Map<String, Object> map = new HashMap<>(2);
map.put("user", user);
map.put("city", city);
return map;
}
}
git地址:https://gitee.com/lanran1/multiple_datasources