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:
该类用来存放数据源的名称,定义两个数据源名称分别为PRIMARY和SECOND。
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数据库完成数据访问(二)
SpringBoot框架:通过AOP和自定义注解完成druid连接池的动态数据源切换(三)的更多相关文章
- SSM框架、Druid连接池实现多数据源配置(已上线使用)
总体大概流程: 1.配置数据源.账密(账密一致,文章不多阐述) driverClassName = com.mysql.jdbc.Driver validationQuery = SELECT 1 ...
- 【开发笔记】- AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换
AbstractRoutingDataSource动态数据源切换 上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求.一般的情况下我们都是使 ...
- AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u012881904/article/de ...
- Java注解--实现动态数据源切换
当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换. 实现原理 在Spring 2.0.1中引入了Abstract ...
- springboot整合druid连接池、mybatis实现多数据源动态切换
demo环境: JDK 1.8 ,Spring boot 1.5.14 一 整合durid 1.添加druid连接池maven依赖 <dependency> <groupId> ...
- springboot集成druid连接池
使用druid连接池主要有几步: 1.添加jar和依赖 <groupId>org.mybatis.spring.boot</groupId> <artifactId> ...
- SpringBoot学习笔记:动态数据源切换
SpringBoot学习笔记:动态数据源切换 数据源 Java的javax.sql.DataSource接口提供了一种处理数据库连接的标准方法.通常,DataSource使用URL和一些凭据来建立数据 ...
- Druid连接池和springJDbc框架-Java(新手)
Druid连接池: Druid 由阿里提供 安装步骤: 导包 durid1.0.9 jar包 定义配置文件 properties文件 名字任意位置也任意 加载文件 获得数据库连接池对象 通过Durid ...
- 用AOP拦截自定义注解并获取注解属性与上下文参数(基于Springboot框架)
目录 自定义注解 定义切面 获取上下文信息JoinPoint ProceedingJoinPoint 定义测试方法 测试结果 小结 AOP可以用于日志的设计,这样话就少不了要获取上下文的信息,博主在设 ...
随机推荐
- 个人项目作业WC(JAVA)
GitHub地址:https://github.com/1666403186/WC 一.题目描述 Word Count1. 实现一个简单而完整的软件工具(源程序特征统计程序).2. 进行单元测试.回归 ...
- ISO8601
日期和时间的组合表示法 合并表示时,要在时间前面加一大写字母T,如要表示东八区时间2004年5月3日下午5点30分8秒,可以写成2004-05-03T17:30:08+08:00或20040503T1 ...
- 分享一个登录页面(前端框架layui)-20200318
效果图 对该页面的总结: 1.前端框架layui layui官网:https://www.layui.com/,下载之后,简单配置就可使用 2.layui模块引用与使用的方式 <script&g ...
- Appium框架
1.通过TestNG单元测试框架管理/执行测试用例 2.TestNG参数化,App启动的四个配置存到Testng.xml中 3.通过page object (PO)设计模式,测试用例和页面元素分离,页 ...
- openCV - 5~7 图像混合、调整图像亮度与对比度、绘制形状与文字
5. 图像混合 理论-线性混合操作.相关API(addWeighted) 理论-线性混合操作 用到的公式 (其中 α 的取值范围为0~1之间) 相关API(addWeighted) 参数1:输入图像M ...
- 深入学习redis 的线程模型
一.redis 的线程模型 redis 内部使用文件事件处理器 file event handler,它是单线程的,所以redis才叫做单线程模型.它采用IO多路复用机制同时监听多个 socket,将 ...
- 特性预览:Apache 顶级项目 Apache Pulsar 2.6.1 版本
在正式分享 2.6.1 版本更新细节之前,冉小龙首先为我们分享了两个相关 PIP 的内容. 一个是 PIP-47 中关于「基于时间来进行版本更新」的计划.该 PIP 提出后,从 2.5.0 版本到目前 ...
- shader之间的数据传递
shader之间传递数据实在是太常用了. 下面我们总结几种shader之间传递数据的方法. Name based matching 最简单,也是最常用的一种传递方式是依靠名字进行匹配. 例如我们从ve ...
- 小程序开发-组件navigator导航篇
navigator 页面链接 navigator的open-type属性 可选值 navigate.redirect.switchTab,对应于wx.navigateTo.wx.redirectTo. ...
- Redux异步解决方案之Redux-Thunk原理及源码解析
前段时间,我们写了一篇Redux源码分析的文章,也分析了跟React连接的库React-Redux的源码实现.但是在Redux的生态中还有一个很重要的部分没有涉及到,那就是Redux的异步解决方案.本 ...