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

 更新时间:2022年03月14日 09:58:43   作者:Carson-Zhao  
本文主要介绍了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如何获取配置好的多个数据源信息?

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

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

。。。。

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

一套代码直接打完:

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

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

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

结语

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

相关文章

  • JAVA面向对象之继承 super入门解析

    JAVA面向对象之继承 super入门解析

    在JAVA类中使用super来引用父类的成分,用this来引用当前对象,如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。怎么引用里面的父类对象呢?用super来引用,this指当前对象的引用,super是当前对象里面的父对象的引用
    2022-01-01
  • 使用java判断输入年份是否为闰年完整代码

    使用java判断输入年份是否为闰年完整代码

    闰年的引入确保了我们的日历与地球运行轨道的对齐,使得时间的计算更加准确,在编程中判断给定年份是否为闰年是一项常见的任务,这篇文章主要给大家介绍了关于使用java判断输入年份是否为闰年的相关资料,需要的朋友可以参考下
    2023-10-10
  • Mybatis多数据源切换实现代码

    Mybatis多数据源切换实现代码

    这篇文章主要介绍了Mybatis多数据源切换实现代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Spring cloud 限流的多种方式

    Spring cloud 限流的多种方式

    在频繁的网络请求时,服务有时候也会受到很大的压力,尤其是那种网络攻击,非法的。这样的情形有时候需要作一些限制。本文主要介绍了两种限流方法,感兴趣的可以了解一下
    2021-06-06
  • 给新来的同事讲where 1=1是什么意思

    给新来的同事讲where 1=1是什么意思

    当遇到多个查询条件,使用where 1=1 可以很方便的解决我们的问题,但这究竟有什么意思呢?所以下面这篇文章主要给大家介绍了关于where 1=1是什么意思,需要的朋友可以参考下
    2021-12-12
  • 美化java代码,从合理注释开始

    美化java代码,从合理注释开始

    在Java的编写过程中我们需要对一些程序进行注释,除了自己方便阅读,更为别人更好理解自己的程序,可以是编程思路或者是程序的作用,总而言之就是方便自己他人更好的阅读。下面我们来一起学习一下吧
    2019-06-06
  • java 实现音乐播放器的简单实例

    java 实现音乐播放器的简单实例

    这篇文章主要介绍了java 实现音乐播放器的简单实例的相关资料,希望通过本文能帮助到大家,实现这样的功能,需要的朋友可以参考下
    2017-09-09
  • 深入了解Java中Volatile关键字

    深入了解Java中Volatile关键字

    这篇文章主要介绍了Java中Volatile关键字的相关知识,文章讲解非常详细,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • IDEA 显示Run Dashboard窗口的2种方式(推荐)

    IDEA 显示Run Dashboard窗口的2种方式(推荐)

    这篇文章主要介绍了IDEA 显示Run Dashboard窗口的2种方式,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-08-08
  • Java中设置JAVA_HOME无效的解决方法

    Java中设置JAVA_HOME无效的解决方法

    最近遇到一个问题,就是配置JAVA_HOME无效,不管怎么改,运行Java -version始终是最初的那个java版本,所以这篇文章主要给大家介绍了关于Java中设置JAVA_HOME无效的解决方法,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-09-09

最新评论