SpringBoot框架:通过AOP和自定义注解完成druid连接池的动态数据源切换(三)

一、引入依赖

  引入数据库连接池的依赖——druid和面向切面编程的依赖——aop,如下所示:

<!-- druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.21</version> </dependency> <!-- aop --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

二、创建数据库

  1、主数据库

  使用前文中已经创建的名为spring_boot_demo的数据库。

  spring_boot_demo中t_user数据如下:

  

  2、辅数据库

  数据库名为other_data,库中建立数据表t_user,表结构与spring_boot_demo中的t_user一致。

  实际项目中,大多是跨数据库的数据源切换,常用在同公司的多个不同系统中共用一个用户数据库,或者二次开发项目在原有数据库基础上做拓展,保留原有的数据连接。

  这里为了方便操作,就都在mysql下部署数据库并且使表结构一致,方便形成数据对比。

  other_data中插入数据如下:

  

三、修改数据库连接配置信息

  在application.yml中,修改数据库连接配置如下:

spring: application: name: spring-boot-demo datasource: type: com.alibaba.druid.pool.DruidDataSource druid: primary: url: jdbc:mysql://localhost:3306/spring_boot_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: root driverClassName: com.mysql.jdbc.Driver second: url: jdbc:mysql://localhost:3306/other_data?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2B8 username: root password: root driverClassName: com.mysql.jdbc.Driver

  

四、编写代码

  结构如下:

  

  1、枚举类DataSourceName:

  该类用来存放数据源的名称,定义两个数据源名称分别为PRIMARYSECOND

package com.example.demo.enums; /** * DataSource的name常量 * 便于切换 * @author 我命倾尘 */ public enum DataSourceName { /** * 主数据源 spring_boot_demo */ PRIMARY("PRIMARY"), /** * 副数据源other_data */ SECOND("SECOND"); private String dataSourceName; private DataSourceName(String dataSourceName){ this.dataSourceName=dataSourceName; } DataSourceName(){ } public String getDataSourceName(){ return this.dataSourceName; } }

  2、配置类DynamicDataSourceConfig:

  通过@ConfigurationProperties读取配置文件中的数据源配置信息,并通过DruidDataSourceBuilder.create().build()创建数据连接,将多个数据源放入map,注入到IoC中:

package com.example.demo.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.example.demo.bean.DynamicDataSource; import com.example.demo.enums.DataSourceName; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author 我命倾尘 */ @Configuration public class DynamicDataSourceConfig { /** * 创建DataSource Bean,将数据源配置从配置文件中读出 */ @Bean @ConfigurationProperties("spring.datasource.druid.primary") public DataSource oneDataSource(){ return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.druid.second") public DataSource twoDataSource(){ return DruidDataSourceBuilder.create().build(); } /** * 将数据源放入到 这个map中,注入到IoC */ @Bean @Primary public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource){ Map<Object,Object> targetDataSources=new HashMap<>(2); targetDataSources.put(DataSourceName.PRIMARY.getDataSourceName(),oneDataSource); targetDataSources.put(DataSourceName.SECOND.getDataSourceName(),twoDataSource); return new DynamicDataSource(oneDataSource,targetDataSources); } }

  3、动态数据源DynamicDataSource:

  通过继承AbstractRoutingDataSource类,在构造函数中调用父类的方法,将配置类中放入map的数据源集合定为备选数据源,将传来的oneDataSource作为默认数据源

package com.example.demo.bean; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * @author 我命倾尘 */ public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder=new ThreadLocal<>(); /** * 配置DataSource * 设置defaultTargetDataSource为主数据库 */ public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object,Object> targetDataSources){ super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } public static String getDataSource(){ return contextHolder.get(); } public static void setDataSource(String dataSource){ contextHolder.set(dataSource); } public static void clearDataSource(){ contextHolder.remove(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } }

  setTargetDataSources设置备选的数据源集合,

  setDefaultTargetDataSource设置默认数据源,

  determineCurrentLookupKey决定当前数据源的对应的key

  4、自定义注释类DataSource:

package com.example.demo.annotation; import com.example.demo.enums.DataSourceName; import java.lang.annotation.*; /** * @author 我命倾尘 */ @Documented @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { DataSourceName value() default DataSourceName.PRIMARY; }

  @Documented指定被标注的注解会包含在javadoc中,

  @Target指定注释可能出现在Java程序中的语法位置(ElementType.METHOD则说明注解可能出现在方法上),

  @Retention指定注释的保留时间(RetentionPolicy.RUNTIME则是在java文件编译成class类时也依旧保存该注释)。

  5、切面类DataSourceAspect:

package com.example.demo.aspect; import com.example.demo.annotation.DataSource; import com.example.demo.bean.DynamicDataSource; 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.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author 我命倾尘 */ @Aspect @Component public class DataSourceAspect implements Ordered { private Logger log= LoggerFactory.getLogger(DataSourceAspect.class); /** * 切点:所有配置DataSource注解的方法 */ @Pointcut("@annotation(com.example.demo.annotation.DataSource)") public void dataSourcePointCut(){ } @Around(value = "dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable{ Object result; MethodSignature signature=(MethodSignature)point.getSignature(); Method method=signature.getMethod(); DataSource ds=method.getAnnotation(DataSource.class); /** * 判断DataSource的值 * 获取当前方法应用的数据源 */ DynamicDataSource.setDataSource(ds.value().getDataSourceName()); try{ result=point.proceed(); }finally { DynamicDataSource.clearDataSource(); } return result; } @Override public int getOrder() { return 1; } }

  Spring框架有很多相同接口的实现类,提供了Ordered接口来处理相同接口实现类之间的优先级问题。

  通过环绕切面,对方法上的注释进行了检验,如果获取到有DataSource注释,则会进行数据源的切换,否则按默认数据源进行处理。

  6、引入配置类:

  既然手动配置了动态切换数据连接池,就要在入口类中排除自动引入,并引入数据源的配置类,以及开启AOP:

package com.example.demo; import com.example.demo.config.DynamicDataSourceConfig; 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.EnableAspectJAutoProxy; import org.springframework.context.annotation.Import; @MapperScan("com.example.demo.mapper") @Import({DynamicDataSourceConfig.class}) @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

  通过@Import引入配置类,在@SpringBootApplication后进行自动引入的排除。

  @EnableAspectJAutoProxy用来开启AOP。

五、简单测试

  1、不使用注解:

  UserController中的方法如下:

  @RequestMapping("/user/age")   public int getAgeOfUser(){ return userService.getAgeByUsername("springbootdemo");   }

  所得到的结果如下:

  

  这个结果是从主数据源spring_boot_demo数据库的表中得到的数据。

  2、在方法前添加注解@DataSource(DataSourceName.SECOND)

  UserController中的方法如下:

  @RequestMapping("/user/age")   @DataSource(DataSourceName.SECOND)   public int getAgeOfUser(){ return userService.getAgeByUsername("springbootdemo");   }

  结果如下:

  

  这个结果则是从辅数据源other_data中得到的数据。

 

  前言:SpringBoot框架:使用mybatis连接mysql数据库完成数据访问(二)

 


__EOF__

本文作者我命倾尘
本文链接https://www.cnblogs.com/guobin-/p/13696900.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   我命倾尘  阅读(2851)  评论(7编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
历史上的今天:
2019-09-21 Oracle数据库:安装Oracle 11g时遇到“【INS-13001】此环境不满足最低配置”的问题解决
2019-09-21 Oracle数据库:Oracle 11g安装过程
点击右上角即可分享
微信分享提示