Spring AOP实现多数据源动态切换

网友投稿 307 2022-08-21


Spring AOP实现多数据源动态切换

目录需求背景分析及实现配置多数据源信息Spring如何获取配置好的多个数据源信息?Spring如何选择使用数据源?结语

需求背景

去年底,公司项目有一个需求中有个接口需要用到平台、算法、大数据等三个不同数据库的数据进行计算、组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分。直到今年回来了,这个项目也做得差不多了,这会儿才有时间区仔细看同事的代码,是怎么去实现多数据源动态切换的。

扩展:当业务也来越复杂,数据量越来越庞大时,就可能会对数据库进行分库分表、读写分离等设计来减轻压力、提高系统性能,那么多数据源动态切换势必是必不可少!

经过了一星期零零碎碎的下班时间,从了解原理、实现、优化的过程,自己终于总算是弄出来了,接下来一起看看!

思考

如何让Spring知道我们配置了多个数据源?配置了多个数据源后,Spring是如何决定使用哪一个数据源?Spring是如何动态切换数据源?

分析及实现

配置多数据源信息

spring:

datasource:

local:

database: local

username: root

password:

jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC

driver-class-name: com.mysql.cj.jdbc.Driver

server:

database: server

username: root

password:

jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC

driver-class-name: com.mysql.cj.jdbc.Driver

这是我的两个数据库:本地数据库+个人服务器数据库

服务器数据库

本地数据库

Spring如何获取配置好的多个数据源信息?

Spring提供了三种方式进行获取

@Value注解获取(实体类需配合@Component),最简单,但当配置信息较多时,写起来比较繁琐

@ConfigurationProperties注解获取,需要定义前缀,可大批量获取配置信息

@Environment注解从Spring环境中获取,实现较为复杂,本人很少用

同事使用的方式是第一种方式,但是我个人觉得这样侵入性较大,每增加一个数据源,就要重新定义变量然后用@Value去重新配置,很麻烦,所以我就选择了第二种方式

通过@ConfigurationProperties注解获取,需要定义前缀,可大批量获取配置信息

@AeIkdPaData

@Component

@ConfigurationProperties(prefix = "spring.datasource")

public class DBProperties {

private HikariDataSource server;

private HikariDataSource local;

}

将所有的数据源加载到Spring中,可供其选择使用

@Slf4j

@Configuration

public class DataSourceConfig {

@Autowired

private DBProperties dbProperties;

@Bean(name = "multiDataSource")

public MultiDataSource multiDataSource(){

MultiDataSource multiDataSource = new MultiDataSource();

//1.设置默认数据源

multiDataSource .setDefaultTargetDataSource(dbProperties.getLocal());

//2.配置多数据源

HashMap dataSourceMap = Maps.newHashMap();

dataSourceMap.put("local", dbProperties.getLocal());

dataSourceMap.put("server", dbProperties.getServer());

//3.存放数据源集

multiDataSource.setTargetDataSources(dataSourceMap);

return multiDataSource;

}

}

如此之后,确实是可以读取YML中的数据源信息,但是总觉得怪怪的。果然!当我实现了整个功能后,我发现,如果我想要再加一个数据源,我还是得去求改DBProperties和DataSourceConfig这两类的内容,就很烦,我这个人比较懒,所以我就将这部分内容优化了一下:

优化后的YML

spring:

datasource:

names:

- database: dataSource0

username: root

password:

jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC

driver-class-name: com.mysql.cj.jdbc.Driver

- database: dataSource1

username: root

password:

jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC

driver-class-name: com.mysql.cj.jdbc.Driver

优化后的DBProperties

@Data

@Component

@ConfigurationProperties(prefix = "spring.datasource")

public class DBProperties {

private List DBNames;

}

优化后的DataSourceConfig

@Slf4j

@Configuration

public class DataSourceConfig {

@Autowired

private DBProperties dbProperties;

@Bean(name = "multiDataSource")

public MultiDataSource multiDataSource(){

MultiDataSource multiDataSource = new MultiDataSource();

List names = dbProperties.getNames();

if (CollectionUtils.isEmpty(names)){

throw new RuntimeException(" please configure the data source! ");

}

multiDataSource.setDefaultTargetDataSource(names.get(0));

HashMap dataSourceMap = Maps.newHashMap();

int i = 0;

for (HikariDataSource name : names) {

dataSourceMap.put("dataSource"+(i++),name);

}

multiDataSource.setTargetDataSources(dataSourceMap);

return multiDataSource;

}

}

这样子,我之后无论配置了多少个数据源信息,我都不需要再去修改配置代码

Spring如何选择使用数据源?

选择一个数据源

通过继承AbstractRoutingDataSource接口,重写determineCurrentLookupKey方法,选择具体的数据源

@Slf4j

public class MultiDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

return MultiDataSourceHolder.getDatasource();

}

}

利用ThreadLocal实现数据源线程隔离

public class MultiDataSourceHolder {

private static final ThreadLocal threadLocal =new ThreadLocal<>();

public static void setDatasource(String datasource){

threadLocal.set(datasource);

}

public static String getDatasource(){

return threadLocal.get();

}

public static void clearDataSource(){

threadLocal.remove();

}

}

准备工作做好,下面开始将动态切换操作串联起来

利用AOP切面+自定义注解

自定义注解

@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface MultiDataSource {

String DBName();

}

AOP切面

@Slf4j

@Aspect

@Component

public class DataSourceAspect {

@Pointcut(value = "@within(com.xiaozhao.base.aop.annotation.MultiDataSource) || @annotation(com.xiaozhao.base.aop.annotation.MultiDataSource)")

public void dataSourcePointCut(){}

@Before("dataSourcePointCut() && @annotation(multiDataSource)")

public void before(MultiDataSource multiDataSource){

String dbName = multiDataSource.DBName();

if (StringUtils.hasLength(dbName)){

MultiDataSourceHolder.setDatasource(multiDataSource.DBName());

log.info("current dataSourceName ====== "+dbName);

}else {

log.info("switch datasource fail, use default, or please configure the data source for the annotations,");

}

}

@After("dataSourcePointCut()")

public void after(){

MultiDataSourceHolder.clearDataSource();

}

}

好了!功能已然实现,打完收工!

。。。。

如果我工作中也这样,估计要被测试打死!为了敷衍一下,来进行一下测试

一套代码直接打完:

Controller+Service+Dao

@RestController

@RequestMapping("user")

public class UserController {

@Autowired

private UserService userService;

@GetMapping("/info")

public UserVO getUser(){

return userService.creatUser();

}

}

public interface UserService {

UserVO creatUser();

UserVO setUserInfo(String phone);

}

@Service

@EnableAspectJAutoProxy(exposeProxy = true)

public class UserServiceImpl implements UserService {

@Autowired

private UserMapper userMapper;

@Autowired

private InfoMapper infoMapper;

@Override

public UserVO creatUser() {

UserVO userVO = userMapper.getUserInfoMapper();

return ((UserService) AopContext.currentProxy()).setUserInfo(userVO.getPhone());

}

@MultiDataSource(DBName = "dataSource1")

public UserVO setUserInfo(String phone) {

UserVO userInfo = infoMapper.getUserInfo();

UserVO user = new UserVO();AeIkdPa

user.setUserName(userInfo.getUserName());

user.setPassword(userInfo.getPassword());

user.setAddress(userInfo.getAddress());

user.setPhone(phone);

return user;

}

}

@Mapper

public interface InfoMapper {

@Select("select id,user_name as userName,password,phone,address from test_user")

UserVO getUserInfo();

}

@Mapper

public interface UserMapper {

@Select("select id,user_name as userName,password,phone from user")

UserVO getUserInfoMapper();

}

测试结果:红框数据来自于服务器数据库,绿框数据来自于本地数据库

遇到的问题同一个类中,A方法调用B方法用AopContext.currentProxy()报错问题:在类上加@EnableAspectJAutoProxy(exposeProxy = true)————解决!配置多数据源时,注意将url修改成jdbc-url切面时,用JoinPoint获取方法,判断是否被注解修饰(虽然纯属多余)结果为false————有待考究!

结语


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:使用Mybatis接收Integer参数的问题
下一篇:SpringMVC中RequestParam注解的简单理解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~