服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Spring AOP实现多数据源动态切换

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

2022-09-19 17:13Carson-Zhao Java教程

本文主要介绍了Spring AOP实现多数据源动态切换,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

需求背景

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

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

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

思考

  • 如何让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 AOP实现多数据源动态切换

本地数据库

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

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

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

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

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

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

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

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

@Data
@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<Object, Object> 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<HikariDataSource> DBNames;

}

优化后的DataSourceConfig

@Slf4j
@Configuration
public class DataSourceConfig {

  @Autowired
  private DBProperties dbProperties;


  @Bean(name = "multiDataSource")
  public MultiDataSource multiDataSource(){
      MultiDataSource multiDataSource = new MultiDataSource();
      
      List<HikariDataSource> names = dbProperties.getNames();
      if (CollectionUtils.isEmpty(names)){
          throw new RuntimeException(" please configure the data source! ");
      }

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

      HashMap<Object, Object> 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<String> 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();
  }
}

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

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

。。。。

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

一套代码直接打完:

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();
      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();

}

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

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

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

结语

到此这篇关于Spring AOP实现多数据源动态切换的文章就介绍到这了,更多相关Spring AOP多数据源动态切换内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/zhaorongbiao/p/15998940.html

延伸 · 阅读

精彩推荐
  • Java教程手把手教你用Java实现计算BMI值、HashSet集合

    手把手教你用Java实现计算BMI值、HashSet集合

    本文主要介绍了Java实现计算BMI值、HashSet集合、如何把Student对象存入HashSet集合中。这个BMI值主要是在国际上是用来衡量人体的胖瘦程度以及是否健康的一个...

    Java进阶学习交流5302021-09-02
  • Java教程基于Spring中的线程池和定时任务功能解析

    基于Spring中的线程池和定时任务功能解析

    下面小编就为大家带来一篇基于Spring中的线程池和定时任务功能解析。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看...

    HelloWorld199211722021-01-02
  • Java教程Java中使用阻塞队列控制线程集实例

    Java中使用阻塞队列控制线程集实例

    这篇文章主要介绍了Java控制阻塞队列线程集实例,本文用一个程序展示了如何使用阻塞队列来控制线程集,程序功能是在一个目录及它的所有子目录下搜索所...

    junjie3992019-12-08
  • Java教程java发送email一般步骤(实例讲解)

    java发送email一般步骤(实例讲解)

    下面小编就为大家带来一篇java发送email一般步骤(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    lsy-blogs7232021-01-08
  • Java教程springboot 获取访问接口的请求的IP地址的实现

    springboot 获取访问接口的请求的IP地址的实现

    本文主要介绍了springboot获取访问接口的请求的IP地址的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    小目标青年11412021-10-25
  • Java教程java查找图中两点之间所有路径

    java查找图中两点之间所有路径

    这篇文章主要为大家详细介绍了java查找图中两点之间所有路径,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Coder_py6802021-07-09
  • Java教程Java集合基础知识 List/Set/Map详解

    Java集合基础知识 List/Set/Map详解

    这篇文章主要介绍了Java集合基础知识 List/Set/Map,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面...

    唯美唯好10862021-07-24
  • Java教程jenkins安装及其配置笔记

    jenkins安装及其配置笔记

    这篇文章主要介绍了jenkins安装及其配置笔记,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    KaliArch8572021-03-15