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注解获取,需要定义前缀,可大批量获取配置信息

@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多数据源动态切换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring AOP如何实现注解式的Mybatis多数据源切换详解

    一.为什么要使用多数据源切换? 多数据源切换是为了满足什么业务场景?正常情况下,一个微服务或者说一个WEB项目,在使用Mybatis作为数据库链接和操作框架的情况下通常只需要构建一个系统库,在该系统库创建业务表来满足需求,当然也有分为测试库和正式库dev/prod,不过这俩库的切换是使用配置文件进行切分的,在项目启动时或者打成maven JAR包指定environment-dev.properties或者environment-prod.properties. 那么当程序运行过程中,比如一个co

  • SpringBoot AOP方式实现多数据源切换的方法

    最近在做保证金余额查询优化,在项目启动时候需要把余额全量加载到本地缓存,因为需要全量查询所有骑手的保证金余额,为了不影响主数据库的性能,考虑把这个查询走从库.所以涉及到需要在一个项目中配置多数据源,并且能够动态切换.经过一番摸索,完美实现动态切换,记录一下配置方法供大家参考. 设计总体思路 Spring-Boot+AOP方式实现多数据源切换,继承AbstractRoutingDataSource实现数据源动态的获取,在service层使用注解指定数据源. 步骤 一.多数据源配置 在applica

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

    目录 需求背景 分析及实现 配置多数据源信息 Spring如何获取配置好的多个数据源信息? Spring如何选择使用数据源? 结语 需求背景 去年底,公司项目有一个需求中有个接口需要用到平台.算法.大数据等三个不同数据库的数据进行计算.组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分.直到今年回来了,这个项目也做得差不多了,这会儿才有时间区仔细看同事的代码,是怎么去实现多数据源动态切换的. 扩展:当业务也来越复杂,数据量越来越庞大时,就可能会对数据库进行分库分表.读写分离

  • spring boot多数据源动态切换代码实例

    这篇文章主要介绍了spring boot多数据源动态切换代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 当项目中存在多数据源时,就涉及到数据源的动态切换,通过研究,特此记录一下. 1.maven依赖 <!--数据库连接--> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> &

  • 关于Spring3 + Mybatis3整合时多数据源动态切换的问题

    以前的项目经历中,基本上都是spring + hibernate + Spring JDBC这种组合用的多.至于MyBatis,也就这个项目才开始试用,闲话不多说,进入正题. 以前的这种框架组合中,动态数据源切换可谓已经非常成熟了,网上也有非常多的博客介绍,都是继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法.具体做法就不在此废话了. 所以当项目中碰到这个问题,我几乎想都没有想,就采用了这种做法,但是一测试,一点反应都没有.当

  • mybatis多数据源动态切换的完整步骤

    笔者主要从事c#开发,近期因为项目需要,搭建了一套spring-cloud微服务框架,集成了eureka服务注册中心. gateway网关过滤.admin服务监控.auth授权体系验证,集成了redis.swagger.jwt.mybatis多数据源等各项功能. 具体搭建过程后续另写播客介绍.具体结构如下: 在搭建过程集成mybatis的时候,考虑到单一数据源无法满足实际业务需要,故结合c#的开发经验,进行多数据源动态集成. mybatis的多数据源可以采用两种方式进行,第一种是分包方式实现,这

  • SpringBoot基于AbstractRoutingDataSource实现多数据源动态切换

    目录 一.场景 二.原理 三.代码示例 一.场景 在生产业务中,有一些任务执行了耗时较长的查询操作,在实时性要求不高的时候,我们希望将这些查询sql分离出来,去从库查询,以减少应用对主数据库的压力. 一种方案是在配置文件中配置多个数据源,然后通过配置类来获取数据源以及mapper相关的扫描配置,不同的数据源配置不佟的mapper扫描位置,然后需要哪一个数据源就注入哪一个mapper接口即可,这种方法比较简单.特征是通过mapper扫描位置区分数据源. 第二种方案是配置一个默认使用的数据源,然后定

  • springboot中mybatis多数据源动态切换实现

    目录 多数据源配置引入 动态数据源路由实现 动态数据源切换使用 案例源码 在开发中,动态数据源配置还是用的比较多的,比如在多数据源使用方面,又或者是在多个DB之间切换方面.这里给出一个动态数据源的配置方案,两个DB均以mysql为例. 多数据源配置引入 mybatis和mysql在springboot中的引入这里就不在说了,不了解的可以参见springboot中mysql与mybatis的引入. 数据源配置如下: datasource: master: type: com.alibaba.dru

  • Spring AOP手动实现简单动态代理的代码

    什么是AOP我们先来看一张图 图中A就是通知,比如你要给每个方法前都加一个before()方法,目标类的每一个方法叫joinpoint(切入点),每个切入点都会用到通知,把通知和切入点连起来,点成线,线成面,这就是切面,也就是AOP,下面我们来简单写个小例子来实现一下 目标类的接口 public interface UserService { public void addUser() ; public void updateUser(); public void deleteUser(); }

  • spring boot + mybatis实现动态切换数据源实例代码

    前言 前几天有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源.我上网收集了一些资料整合了一下,虽然最后这个需求不了了之了,但是多数据源动态切换还是蛮好用的,所以记录一下,或许以后有用呢?或者自己感兴趣又想玩呢! 下面话不多说了,随着小编来一起看看详细的介绍吧 方法如下: 1.加个依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybat

  • Spring Boot + Mybatis多数据源和动态数据源配置方法

    网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种方式,记录一下配置方法供大家参考. 应用场景 项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库. 多数据源 首先要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源.在@SpringBootApplication注解中添加ex

  • druid多数据源配置+Datasurce动态切换方式

    目录 druid多数据源配置+Datasurce动态切换 AbstractRoutingDataSource 数据源动态切换 例子 配置多数据源并实现Druid自动切换 配置yml文件 主数据源配置 从数据源配置 使用dao 日志 druid多数据源配置+Datasurce动态切换 AbstractRoutingDataSource 数据源动态切换 spring 使用AbstractRoutingDataSource自定义动态数据源时的事务处理, 需要继承spring的AbstractRouti

随机推荐