自定义多数据源

SpringBoot利用自定义注解实现多数据源,前置知识:注解、Aop、SpringBoot整合Mybaits

1、搭建工程

创建一个SpringBoot工程,并引入依赖

<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>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency> <dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.18</version>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2、定义多数据源注解

/**
* 1、定义多数据源注解
* @author ss_419
* TODO 这个注解将来可以加在service类上或者方法上,通过value属性来指定类或者方法应该使用那个数据源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
String value() default DataSourceType.DEFAULT_DS_NAME;
}

3、创建一个多数据上下文对象

这个类用来存储当前线程所使用的数据源名称


/**
* TODO 这个类用来存储当前线程所使用的数据源名称
*
* @author ss_419
* @version 1.0
* @date 2023/5/21 09:21
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
} public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
} }

4、配置aop

  • @annotation(org.pp.dd.annotation.DataSource) 如果有@DataSource注解就给拦截下来

  • @within(org.pp.dd.annotation.DataSource) 表示类上有@DataSource注解就将类中的方法给拦截下来

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/5/21 09:42
*/
@Component
@Aspect
@Order(11)
public class DataSourceAspect { /**
* 定义切点
*
* @annotation(org.pp.dd.annotation.DataSource) 如果有@DataSource注解就给拦截下来
* @within(org.pp.dd.annotation.DataSource) 表示类上有@DataSource注解就将类中的方法给拦截下来
*/
@Pointcut("@annotation(org.pp.dd.annotation.DataSource) || @within(org.pp.dd.annotation.DataSource)")
public void pc() { }
/**
* 环绕通知
*
* @param pjp
* @return
*/
@Around("pc()")
public Object around(ProceedingJoinPoint pjp) {
// 获取方法上的有效注解
DataSource dataSource = getDataSource(pjp);
if (dataSource != null) {
// 获取注解中数据源的名称
String value = dataSource.value();
DynamicDataSourceContextHolder.setDataSourceType(value);
}
try {
return pjp.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
DynamicDataSourceContextHolder.clearDataSourceType();
} } private DataSource getDataSource(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
// 获取方法上的注解
DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); if (annotation != null) {
// 说明方法上有注解
return annotation;
} return (DataSource) AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}

5、读取参数DruidProperties


/**
* TODO 读取数据源
*
* @author ss_419
* @version 1.0
* @date 2023/5/21 10:20
*/
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String type;
private String driverClassName;
private Map<String, Map<String ,String>> ds;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait; /**
* 在这个方法中设置公共属性
* @param dataSource
* @return
*/
public DataSource dataSource(DruidDataSource dataSource){
dataSource.setInitialSize(initialSize);
dataSource.setMinIdle(minIdle);
dataSource.setMaxActive(maxActive);
dataSource.setMaxWait(maxWait);
return dataSource;
} public String getType() {
return type;
} public void setType(String type) {
this.type = type;
} public String getDriverClassName() {
return driverClassName;
} public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
} public Map<String, Map<String, String>> getDs() {
return ds;
} public void setDs(Map<String, Map<String, String>> ds) {
this.ds = ds;
} public Integer getInitialSize() {
return initialSize;
} public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
} public Integer getMinIdle() {
return minIdle;
} public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
} public Integer getMaxActive() {
return maxActive;
} public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
} public Integer getMaxWait() {
return maxWait;
} public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
}

6、加载数据源LoadDataSource

/**
* TODO 加载数据源
*
* @author ss_419
* @version 1.0
* @date 2023/5/21 10:30
*/
@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource { @Autowired
DruidProperties druidProperties; public Map<String, DataSource> loadAllDataSource() {
Map<String, DataSource> map = new HashMap<>();
Map<String, Map<String, String>> ds = druidProperties.getDs(); try {
Set<String> keySet = ds.keySet();
for (String key : keySet) {
map.put(key, druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
}
} catch (Exception e) {
throw new RuntimeException(e);
} return map;
}
}

7、定义数据源管理器

当系统需要调用数据源的时候,数据源以key-value存起来,当需要数据源时调用determineCurrentLookupKey()方法来获取数据源。

由于本人实力原因,解答不了大家这里的疑惑。大致功能 通过修改本地线程的值,来实现数据源的切换。

/**
* TODO 设置数据源
* 当系统需要调用数据源的时候,数据源以key-value存起来,当需要数据源时调用determineCurrentLookupKey()方法
* @author ss_419
* @version 1.0
* @date 2023/5/21 10:47
*/
@Component
public class DynamicDataSource extends AbstractRoutingDataSource { public DynamicDataSource(LoadDataSource loadDataSource) {
//1、设置所有的数据源
Map<String, DataSource> allDs = loadDataSource.loadAllDataSource();
super.setTargetDataSources(new HashMap<>(allDs));
//2、设置默认数据源
super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DS_NAME)); super.afterPropertiesSet();
} /**
* 这个方法用来返回数据源名称,当系统需要获取数据源的时候会自动调用该方法获取数据源名称
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}

定一个用于存储数据库类型的接口,这个接口类似于枚举类:

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/5/21 10:54
*/
public interface DataSourceType {
String DEFAULT_DS_NAME = "master";
String DS_SESSION_KEY = "ds_session_key";
}

8、测试

创建User实体:

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/5/21 11:15
*/
public class User {
private Integer id;
private String username;
private String password; @Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getPassword() {
return password;
} public void setPassword(String password) {
this.password = password;
}
}

创建UserService:

@Service
// 在类上加注解的效果,会使该类的所有方法都切入到新的数据源中
//@DataSource
public class UserService { @Autowired
UserMapper userMapper;
// 在方法上加注解的效果,只会让指定的方法切入到另一个数据源中
//@DataSource("slave")
public List<User> findUsers(){
return userMapper.findAllUsers();
}
}

创建UserMapper:

@Mapper
public interface UserMapper { @Select("SELECT * FROM user")
List<User> findAllUsers();
}

测试类:

@SpringBootTest
class DynamicDatasourcesApplicationTests { @Autowired
UserService userService; @Test
void contextLoads() { List<User> users = userService.findUsers();
users.stream()
.forEach(user -> System.out.println(user));
} }

默认选择主库的数据源:

执行结果如下:

在Service上加上注解,指定数据源为从库:



执行结果如下:

SpringBoot利用自定义注解实现多数据源的更多相关文章

  1. SpringBoot利用自定义注解实现通用的JWT校验方案

    利用注解开发一个通用的JWT前置校验功能 设计的预期: 系统中并不是所有的应用都需要JWT前置校验,这就需要额外设计一个注解Annotation来标识这个方法需要JWT前置校验.例如: @GetMap ...

  2. SpringBoot 自定义注解 实现多数据源

    SpringBoot自定义注解实现多数据源 前置学习 需要了解 注解.Aop.SpringBoot整合Mybatis的使用. 数据准备 基础项目代码:https://gitee.com/J_look/ ...

  3. java 编程基础:【注解】 提取注解信息,利用自定义注解编写测试类,注解绑定事件

    提取注解信息 使用注解修饰了类.方法.成员变量等成员之后,这些注解不会自己生效,必须由开发者提供相应工具来提取并处理注解信息.   Java使用java.lang.annotation.Annotat ...

  4. ssm+redis 如何更简洁的利用自定义注解+AOP实现redis缓存

    基于 ssm + maven + redis 使用自定义注解 利用aop基于AspectJ方式 实现redis缓存 如何能更简洁的利用aop实现redis缓存,话不多说,上demo 需求: 数据查询时 ...

  5. Springboot+Redisson自定义注解一次解决重复提交问题(含源码)

    前言   项目中经常会出现重复提交的问题,而接口幂等性也一直以来是做任何项目都要关注的疑难点,网上可以查到非常多的方案,我归纳了几点如下:   1).数据库层面,对责任字段设置唯一索引,这是最直接有效 ...

  6. Java利用自定义注解、反射实现简单BaseDao

    在常见的ORM框架中,大都提供了使用注解方式来实现entity与数据库的映射,这里简单地使用自定义注解与反射来生成可执行的sql语句. 这是整体的目录结构,本来是为复习注解建立的项目^.^ 好的,首先 ...

  7. springboot aop 自定义注解方式实现完善日志记录(完整源码)

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者.原文超链接 一:功能简介 本文主要记录如何使用aop切面的方式来实现日志记录功能. 主要记录的信息有: 操作人,方法名,参数,运行时间,操作类型 ...

  8. springboot aop 自定义注解方式实现一套完善的日志记录(完整源码)

    https://www.cnblogs.com/wenjunwei/p/9639909.html https://blog.csdn.net/tyrant_800/article/details/78 ...

  9. Springboot+Mybatis AOP注解动态切换数据源

    在开发中因需求在项目中需要实现多数据源(虽然项目框架是SpringCloud,但是因其中只是单独的查询操作,觉得没必要开发一个项目,所以采用多数据源来进行实现) 1.在配置文件中创建多个数据连接配置 ...

  10. Springboot使用自定义注解实现简单参数加密解密(注解+HandlerMethodArgumentResolver)

    前言 我黄汉三又回来了,快半年没更新博客了,这半年来的经历实属不易,疫情当头,本人实习的公司没有跟员工共患难, 直接辞掉了很多人.作为一个实习生,本人也被无情开除了.所以本人又得重新准备找工作了. 算 ...

随机推荐

  1. node node-sass sass-loader版本兼容问题

    相关概念 sass Sass是一种预处理器脚本语言,可以解释或编译成层叠样式表(CSS). Sass包含两种语法:较旧的语法使用缩进将代码块和换行符分隔为单独的规则:较新的语法SCSS使用像CSS这样 ...

  2. CSS3学习笔记-字体属性

    在CSS3中,可以使用字体属性来控制网页中文本的样式和排版.以下是常用的字体属性: font-family 该属性用于指定网页中的文本所使用的字体.我们可以通过使用通用的字体名称,或者直接使用字体名称 ...

  3. C#基于ScottPlot进行可视化

    C#基于ScottPlot进行可视化 前言 上一篇文章跟大家分享了用NumSharp实现简单的线性回归,但是没有进行可视化,可能对拟合的过程没有直观的感受,因此今天跟大家介绍一下使用C#基于Scott ...

  4. 揭秘华为云GaussDB(for Redis)丨大key治理

    本文分享自华为云社区<华为云GaussDB(for Redis)揭秘第31期:大key治理>,作者: 高斯Redis官方博客. 从DBA的视角看,大Key无疑是引起Redis线上问题的常见 ...

  5. 论文分享丨Holistic Evaluation of Language Models

    摘要:该文为大模型评估方向的综述论文. 本文分享自华为云社区<[论文分享]<Holistic Evaluation of Language Models>>,作者:DevAI. ...

  6. 五一高铁票难抢?用RPA机器人试试!

    随着信息数字化的高速发展,RPA在各行业中得到广泛应用,热度大增.这匹"技术黑马"已然成为构建业务流程自动化的重要引擎之一,助力企业组织向"智能自动化转型. 什么是RPA ...

  7. 用IoT放羊养牛,不出门也能知道它们的动向

    摘要:羊圈里几只小羊羔有点无精打采,"叮",牧民拉索划开手机,第一时间得知了这个情况.草场上有只牛一直到天黑都没回来,拉索再次通过手机软件,很快定位到那只独自流落在外的牛儿-- 本 ...

  8. 【教程】React Native 应用中的代码混淆与安全性管理

    ​ 混淆是指对源代码进行加密.重命名等操作,以增加代码的复杂度,使其难以理解和反编译. 在React Native中,混淆可以通过以下步骤实现: 1. 将JavaScript源代码转换为基于本机平台的 ...

  9. 开心档之MySQL 导出数据

    MySQL 导出数据 MySQL中你可以使用SELECT...INTO OUTFILE语句来简单的导出数据到文本文件上. 使用 SELECT ... INTO OUTFILE 语句导出数据 以下实例中 ...

  10. 9月活动回顾(免费领取PPT)|火山引擎DataLeap、ByteHouse多位专家带来DataOps、实时计算等前沿技术分享!

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群   在上月举行的火山引擎开发者社区 Meetup 第12期暨超话数据专场<数智化转型背景下的火山引擎大数据技 ...