前言
提示:这里可以添加本文要记录的大概内容:
Spring Boot实现自定义注解切换多数据源,在前几天项目的升级的过程中,由于业务原因扩展了三个新的数据库,则编写了 一个自定义注解切换数据源.
提示:以下是本篇文章正文内容,下面案例可供参考
一、pom依赖导入
<!-- 专用数据库连接池 引入 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mysql-->
二、YML文件配置
hikari:
master:
jdbc-url: jdbc:mysql://192.168.1.180:3306/syb?useUnicode=true&characterEncoding=utf8&useSSL=true&allowMultiQueries=true&verifyServerCertificate=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
username: leader
password: leader
maximum-pool-size: 20
pool-name: master
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1765000
database:
jdbc-url: jdbc:mysql://192.168.1.211:3306/database_db?useUnicode=true&characterEncoding=utf8&useSSL=true&allowMultiQueries=true&verifyServerCertificate=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
username: leader
password: leader
maximum-pool-size: 80
pool-name: database
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1765000
qht:
jdbc-url: jdbc:mysql://192.168.1.11:3306/qihaotong?useUnicode=true&characterEncoding=utf8&useSSL=true&allowMultiQueries=true&verifyServerCertificate=false&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
username: qihaotong
password: 123456
maximum-pool-size: 80
pool-name: qht
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1765000
具体实现
DataSourcesEnum 枚举类
这个类中存放的需要转换的数据源枚举, 列:切换数据源的时候可以直接 DataSourcesEnum.QHT
public enum DataSourcesEnum {
MASTER,DATABASE,QHT;
}
Properties 数据源名称对应的实体类
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 数据源配置的实体类
* @author zzz11
* @version 1.0
* @date 2023/11/20 16:05
* 博客:<a href="https://blog.csdn.net/qq_49354230?type=blog">...</a>
*/
@Component
@Data
@ConfigurationProperties(prefix = "hikari")
public class Properties {
private HikariDataSource master;
private HikariDataSource database;
private HikariDataSource qht;
}
DataSourceConfig 数据源配置
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.PlatformTransactionManager;
import com.example.publicdemo.Enum.DataSourcesEnum;
import com.example.publicdemo.dbs.DynamicDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
*数据源配置
* @author zzz11
* @version 1.0
* @date 2023/11/20 16:10
* 博客:<a href="https://blog.csdn.net/qq_49354230?type=blog">...</a>
*/
@Configuration
@EnableScheduling
@Slf4j
public class DataSourceConfig {
@Autowired
private Properties properties;
@Bean(name = "dataSource")
public DataSource dataSource() {
//按照目标数据源名称和目标数据源对象的映射存放在Map中
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourcesEnum.MASTER, properties.getMaster());
targetDataSources.put(DataSourcesEnum.DATABASE, properties.getDatabase());
targetDataSources.put(DataSourcesEnum.QHT, properties.getQht());
//采用是想AbstractRoutingDataSource的对象包装多数据源
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
//设置默认的数据源,当拿不到数据源时,使用此配置
//指定目标数据源的映射,以查找键为键。映射的值可以是相应的 DataSource 实例,也可以是数据源名称 String(通过 DataSourceLookup进行解析)。
dataSource.setDefaultTargetDataSource(properties.getMaster());
log.info("条件默认数据源为主数据源");
return dataSource;
}
@Bean
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dataSource());
}
MyDataSource 注解类
import com.example.publicdemo.Enum.DataSourcesEnum;
import java.lang.annotation.*;
/**
* @author zzz11
* @version 1.0
* @date 2023/11/20 16:21
* 博客:<a href="https://blog.csdn.net/qq_49354230?type=blog">...</a>
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyDataSource {
//要切换的动态数据库名称
DataSourcesEnum value();
}
MyDataSourceAspect 环绕通知切换数据源
package com.example.publicdemo.dbproperties;
import com.example.publicdemo.Enum.DataSourcesEnum;
import com.example.publicdemo.annottation.MyDataSource;
import com.example.publicdemo.dbs.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Date;
/**
* @author zzz11
* @version 1.0
* @date 2023/11/20 16:22
* 博客:<a href="https://blog.csdn.net/qq_49354230?type=blog">...</a>
*/
@Slf4j
@Component
@Aspect
public class MyDataSourceAspect {
/**
* 定义连接点
*
* @within 用于匹配标注有特定注解的类的目标对象
* @annotation 用于匹配目标方法上标注有特定注解的方法
*/
@Pointcut("@annotation(com.example.publicdemo.annottation.MyDataSource) || @within(com.example.publicdemo.annottation.MyDataSource)")
public void myPointcut() {
}
@Around(value = "myPointcut()")
public void myAspect(ProceedingJoinPoint joinPoint) {
DataSourcesEnum value = null;
try {
//通过 getClass() 方法获取目标对象的类,返回的是一个 Class 对象,该对象包含了目标对象的类信息,例如类的名称、方法、字段
Class<?> aClass = joinPoint.getTarget().getClass();
//判断这个是否存在MyDataSource注解 如果存在返回true 否则返回false
//检查类上是否有MyDataSource注解
if (aClass.isAnnotationPresent(MyDataSource.class)) {
MyDataSource annotation = aClass.getAnnotation(MyDataSource.class);
value = annotation.value();
}
//用于获取该类中所有声明的方法,不包括继承自父类的方法。返回的是一个 Method 对象数组,每个元素代表一个类中声明的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method method : declaredMethods) {
//检查方法上是否存在MyDataSoruce注解 如果存在就切换数据源
if (method.isAnnotationPresent(MyDataSource.class)) {
MyDataSource annotation = method.getAnnotation(MyDataSource.class);
value = annotation.value();
}
}
//切换数据源
log.info("切换数据源,目标数据源-------------"+value+"-------------");
DynamicDataSourceHolder.putDataSource(value);
joinPoint.proceed();
} catch (Throwable e) {
log.error("当前线程 " + Thread.currentThread().getName() + "向ThreadLocal添加数据发生异常", e);
e.printStackTrace();
} finally {
log.info("切换会默认数据源"+"-----时间:"+new Date()+"-----");
log.info("切换会默认数据源"+"-----"+DataSourcesEnum.MASTER+"-----");
DynamicDataSourceHolder.putDataSource(DataSourcesEnum.MASTER);
}
}
}
具体调用
@MyDataSource(DataSourcesEnum.QHT)
public void timingPullTittle() throws Exception {
List<DouYinLittleProcedure> douYinLittleProcedures = procedureMapper.selectTittleInfo();
}