项目结构图
application.yml
server:
port: 8081
servlet:
context-path: /data
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
mysql-datasource1: ## 声明第一个数据源所需的数据
url: jdbc:mysql://localhost:3306/t1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
username: root
password: jsdayth@2022
driver-class-name: com.mysql.cj.jdbc.Driver
mysql-datasource2: ## 声明第二个数据源所需的数据
url: jdbc:mysql://localhost:3306/t2?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true&failOverReadOnly=false&maxReconnects=10
username: root
password: jsdayth@2022
driver-class-name: com.mysql.cj.jdbc.Driver
druid: ## druid数据库连接池的基本初始化属性
initial-size: 5
min-idle: 1
max-active: 20
mybatis:
mapper-locations: classpath*:com/**/xml/**/*.xml
数据源注入
package com.example.data_sourse.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
@Bean(name = "mysqlDataSource1")
@ConfigurationProperties(prefix = "spring.datasource.mysql-datasource1")
public DataSource dataSource1(){
DruidDataSource build = DruidDataSourceBuilder.create().build();
return build;
}
@Bean(name = "mysqlDataSource2")
@ConfigurationProperties(prefix = "spring.datasource.mysql-datasource2")
public DataSource dataSource2(){
DruidDataSource build = DruidDataSourceBuilder.create().build();
return build;
}
}
导入依赖
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>data_sourse</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>data_sourse</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
<mysql.jdbc.version>8.0.11</mysql.jdbc.version>
</properties>
<dependencies>
<!--web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.jdbc.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<!--修改maven-compiler-plugin版本-->
<!--修改maven-compiler-plugin版本-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
</plugin>
</plugins>
<resources>
<!--idea默认不编译 src\main\java下的xml文件-->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
<resource>
<directory>lib</directory>
<targetPath>/BOOT-INF/lib/</targetPath>
<includes>
<include>**/*.jar</include>
</includes>
</resource>
</resources>
</build>
</project>
开启AOP支持
package com.example.data_sourse;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableAspectJAutoProxy //开启Spring Boot对AOP的支持
//@ComponentScan({"com.example.data_sourse"})
@ComponentScan(basePackages = {
"com.example.data_sourse.**.controller",
"com.example.data_sourse.**.model",
"com.example.data_sourse.**.service",
"com.example.data_sourse.*",
})
@MapperScan({"com.example.data_sourse.mapper"})
//@MapperScan({"com.example.*.mapper","com.example.*.*.mapper"})
public class DataSourseApplication {
public static void main(String[] args) {
SpringApplication.run(DataSourseApplication.class, args);
}
}
定义枚举来表示数据源的标识
package com.example.data_sourse.transfer.enums;
public enum DataSourceType {
MYSQL_DATASOURCE1,
MYSQL_DATASOURCE2,
}
继承AbstractRoutingDataSource类
package com.example.data_sourse.config;
import cn.hutool.json.JSONUtil;
import com.example.data_sourse.transfer.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Primary
@Component
public class DataSourceManagement extends AbstractRoutingDataSource {
public static ThreadLocal<String> flag = new ThreadLocal<>();
@Resource
private DataSource mysqlDataSource1;
@Resource
private DataSource mysqlDataSource2;
public DataSourceManagement(){
flag.set(DataSourceType.MYSQL_DATASOURCE1.name());
}
@Override
protected Object determineCurrentLookupKey() {
log.info("determineCurrentLookupKey====>flag:{}",flag);
String f_lag = flag.get();
log.info("determineCurrentLookupKey====>f_lag:{}",f_lag);
return f_lag;
}
@Override
public void afterPropertiesSet() {
log.info("afterPropertiesSet====>:{}",JSONUtil.toJsonStr(mysqlDataSource1));
log.info("afterPropertiesSet====>:{}",JSONUtil.toJsonStr(mysqlDataSource2));
Map<Object,Object> targetDataSource = new ConcurrentHashMap<>();
targetDataSource.put(DataSourceType.MYSQL_DATASOURCE1.name(),mysqlDataSource1);
targetDataSource.put(DataSourceType.MYSQL_DATASOURCE2.name(),mysqlDataSource2);
// 将第一个数据源设置为默认数据源
super.setDefaultTargetDataSource(mysqlDataSource1);
// 将map对象赋值给AbstrictRoutingDataSource内部的Map对象中。
super.setTargetDataSources(targetDataSource);
super.afterPropertiesSet();
}
}
自定义注解
package com.example.data_sourse.anno;
import com.example.data_sourse.transfer.enums.DataSourceType;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
DataSourceType value() default DataSourceType.MYSQL_DATASOURCE1;
}
定义注解的实现类
package com.example.data_sourse.aop;
import cn.hutool.json.JSONUtil;
import com.example.data_sourse.anno.TargetDataSource;
import com.example.data_sourse.config.DataSourceManagement;
import com.example.data_sourse.transfer.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
@Aspect
@Slf4j
public class TargetDataSourceAspect {
@Before("@within(com.example.data_sourse.anno.TargetDataSource) || @annotation(com.example.data_sourse.anno.TargetDataSource)")
public void beforeNoticeUpdateDataSource(JoinPoint joinPoint){
TargetDataSource annotation = null;
Class<?> target = joinPoint.getTarget().getClass();
// 判断类上是否标注着注解
if (target.isAnnotationPresent(TargetDataSource.class)){
annotation = target.getAnnotation(TargetDataSource.class);
log.info("类上标注了注解:{}", annotation);
log.info("类上标注了注解");
}else {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// 判断方法上是否存在注解,如果方法上都没有则报错
if (method.isAnnotationPresent(TargetDataSource.class)){
annotation = method.getAnnotation(TargetDataSource.class);
log.info("方法上标注了注解:{}", annotation);
log.info("方法上标注了注解");
}else {
throw new RuntimeException("@TargetDataSource注解只能用于类或者方法上, 错误出现在:【" +
target.toString() +" " + method.toString() + "】;");
}
}
log.info("获取注解数据:{}", JSONUtil.toJsonStr(annotation.value().name()));
// 切换数据源
DataSourceManagement.flag.set(annotation.value().name());
}
}
在有的博客中也会使用@Around环绕通知的方式,但是环绕通知需要执行joinPoint.process()方法来调用目标对象的方法,最后返回执行的值,不然得不到所需要的数据。
我这里使用了@Before前置通知,效果是一样的,因为@Around就会包含@Before。
@Around("@within(TargetDataSource) || @annotation(TargetDataSource)")
public Object beforeNoticeUpdateDataSource(ProceedingJoinPoint joinPoint){
// 省略逻辑代码
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
使用
package com.example.data_sourse.controller;
import com.example.data_sourse.anno.TargetDataSource;
import com.example.data_sourse.service.UserService;
import com.example.data_sourse.transfer.enums.DataSourceType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.*;
@Slf4j
@RestController
// 将注解标注在类上,表示本类中所有的方法都是使用数据源1
//@TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE1)
public class UserController {
@Autowired
private UserService userService;
/**
* 数据源1
*/
@GetMapping(value = "/user_list_a")
@TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE1)
public List<Map<String,Object>> showUserLista(){
log.info("user_list_a数据源:{}",DataSourceType.MYSQL_DATASOURCE1.name());
List<Map<String,Object>> list = userService.list();
return list;
}
/**
* 数据源2
*/
@GetMapping(value = "/user_list_b")
@TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE2)
public List<Map<String,Object>> showUserListb(){
log.info("user_list_b数据源:{}",DataSourceType.MYSQL_DATASOURCE2.name());
List<Map<String,Object>> list = userService.list();
return list;
}
/**
* 默认数据源
*/
@GetMapping(value = "/user_list_c")
public List<Map<String,Object>> showUserListc(){
List<Map<String,Object>> list = userService.list();
return list;
}
}