目录
- 我的springboot web项目工程目录如下:
- POM文件如下:
- YAML配置文件如下:
- 在annotation包中创建自定义注解DataSource:
- 在enums包中创建枚举类DataSourcesType:
- 在core.aop包中创建切面类DynamicDataSourceAspect:
- 在core.datasource包中创建动态数据源类DynamicDataSource和数据源切换处理类DynamicDataSourceContextHolder:
- 在core.druid包中创建数据源配置类DataSourceConfiguration和数据源配置文件类DataSourceProperties:
- 在启动类StudyApplication加入注解,解决循环依赖:
- 添加Controller测试类:
- 自己在mysql中建立一个user表,进行测试。
- 在浏览器输入地址,查看结果:
- 后台程序打印日志:
我的springboot web项目工程目录如下:
POM文件如下:
<?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>
<groupId>cn.example</groupId>
<artifactId>SpringBootStudy</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>SpringBootStudy Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!--自定义配置-->
<druid.starter.version>1.1.16</druid.starter.version>
</properties>
<!-- Inherit defaults from Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- Add typical dependencies for a web application -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 配置注解执行器配置完成后,当执行类中已经定义了对象和该对象的字段后,在配置文件中对该类赋值时,便会非常方便的弹出提示信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- jdbcTemplate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--引入druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.starter.version}</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 切面 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
YAML配置文件如下:
spring:
profiles:
active: dev
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #修改数据源为Druid
druid:
master:
url: jdbc:mysql://localhost:3306/study?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
username: root
password: root
slave:
enable: true
url: jdbc:mysql://localhost:3306/study?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull
username: root
password: root
initialSize: 3 #修改Druid的默认连接配置
minIdle: 10 # 最小连接池数量
maxActive: 20 # 最大连接池数量
maxWait: 60000 # 配置获取连接等待超时的时间
timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000 # 配置一个连接在池中最大生存的时间,单位是毫秒
在annotation包中创建自定义注解DataSource:
package cn.xhh.study.common.annotation;
import cn.xhh.study.common.enums.DataSourcesType;
import java.lang.annotation.*;
/**
* 数据源自定义注解
* @author Administrator
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
DataSourcesType name() default DataSourcesType.MASTER;
}
在enums包中创建枚举类DataSourcesType:
package cn.xhh.study.common.enums;
/**
* 数据源类型
* @author Administrator
*/
public enum DataSourcesType {
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE
}
在core.aop包中创建切面类DynamicDataSourceAspect:
package cn.xhh.study.core.aop;
import cn.xhh.study.common.annotation.DataSource;
import cn.xhh.study.core.datasource.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 1.@Order(-1)保证该AOP在@Transactional之前执行
* @author Administrator
*/
@Aspect
@Component
@Order(-1)
public class DynamicDataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(cn.xhh.study.common.annotation.DataSource)"
+ "|| @within(cn.xhh.study.common.annotation.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method targetMethod = this.getTargetMethod(point);
//获取要切换的数据源
DataSource dataSource = targetMethod.getAnnotation(DataSource.class);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.name().name());
}
try {
return point.proceed();
}
finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.removeDataSourceType();
}
}
/**
* 获取目标方法
*/
private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method agentMethod = methodSignature.getMethod();
return pjp.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes());
}
}
在core.datasource包中创建动态数据源类DynamicDataSource和数据源切换处理类DynamicDataSourceContextHolder:
package cn.xhh.study.core.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 获取数据源(依赖于 spring)
* 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换
* @author Administrator
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public static DynamicDataSource build() {
return new DynamicDataSource();
}
/**
* 获取与数据源相关的key
* 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
* 在通过determineTargetDataSource获取目标数据源时使用
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
package cn.xhh.study.core.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
* @author Administrator
*/
public class DynamicDataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
*此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 设置当前线程的数据源变量
*/
public static void setDataSourceType(String dataSourceType) {
log.info("已切换到{}数据源", dataSourceType);
contextHolder.set(dataSourceType);
}
/**
* 获取当前线程的数据源变量
*/
public static String getDataSourceType() {
return contextHolder.get();
}
/**
* 删除与当前线程绑定的数据源变量
*/
public static void removeDataSourceType() {
contextHolder.remove();
}
}
在core.druid包中创建数据源配置类DataSourceConfiguration和数据源配置文件类DataSourceProperties:
package cn.xhh.study.core.druid;
import cn.xhh.study.common.enums.DataSourcesType;
import cn.xhh.study.core.datasource.DynamicDataSource;
import cn.xhh.study.core.druid.DataSourceProperties;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 数据源配置类
* @author Administrator
*/
@Configuration
public class DataSourceConfiguration {
/**
* 主库
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());
}
/**
* 从库
* @param dataSourceProperties
* @return DataSource
*/
@Bean
@ConditionalOnProperty( prefix = "spring.datasource.druid.slave", name = "enable", havingValue = "true")//是否开启数据源开关---若不开启 默认适用默认数据源
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());
}
/**
* 1.@DependsOn({"masterDataSource","slaveDataSource"})配合Springboot启动类,解决Druid多数据源循环依赖的问题
* 设置数据源
*/
@Bean(name = "dynamicDataSource")
@Primary
@DependsOn({"masterDataSource","slaveDataSource"})
public DynamicDataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
DynamicDataSource dynamicDataSource = DynamicDataSource.build();
targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource);
//默认数据源配置 DefaultTargetDataSource
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
//额外数据源配置 TargetDataSources
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
}
}
package cn.xhh.study.core.druid;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 数据源配置文件
* @author Administrator
*/
@Setter
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DataSourceProperties {
private int initialSize;
private int minIdle;
private int maxActive;
private int maxWait;
private int timeBetweenEvictionRunsMillis;
private int minEvictableIdleTimeMillis;
private int maxEvictableIdleTimeMillis;
private String validationQuery;
private boolean testWhileIdle;
private boolean testOnBorrow;
private boolean testOnReturn;
public DruidDataSource setDataSource(DruidDataSource datasource) {
// 配置初始化大小、最小、最大
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
// 配置获取连接等待超时的时间
datasource.setMaxWait(maxWait);
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 配置一个连接在池中最小、最大生存的时间,单位是毫秒
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
}
在启动类StudyApplication加入注解,解决循环依赖:
package cn.xhh.study;
import cn.xhh.study.servlet.listener.MyListener;
import cn.xhh.study.servlet.MyServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
/**
* 1.@ServletComponentScan:启动对自定义servlet的扫描
* 2.添加(exclude = {DataSourceAutoConfiguration.class}),解决在配置Druid多数据源时,出现循环依赖的问题
* @author Administrator
*/
@ServletComponentScan
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class StudyApplication {
public static void main(String[] args) {
SpringApplication.run(StudyApplication.class, args);
}
}
添加Controller测试类:
package cn.xhh.study.controller;
import cn.xhh.study.common.annotation.DataSource;
import cn.xhh.study.common.enums.DataSourcesType;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;
/**
* @author Administrator
*/
@Controller
public class JdbcTemplateController {
private JdbcTemplate jdbcTemplate;
public JdbcTemplateController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@ResponseBody
@RequestMapping("queryUserForList")
@DataSource(name = DataSourcesType.SLAVE)
public List<Map<String, Object>> getData() {
String sql = "select * from user";
List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
return maps;
}
}
自己在mysql中建立一个user表,进行测试。
在浏览器输入地址,查看结果:
http://localhost:8081/queryUserForList
后台程序打印日志:
2021-12-08 10:58:13.400 INFO 7284 --- [nio-8081-exec-1] c.x.s.c.d.DynamicDataSourceContextHolder : 已切换到SLAVE数据源
Druid多数据源配置成功!