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

目录

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

    需求背景 去年底,公司项目有一个需求中有个接口需要用到平台、算法、大数据等三个不同数据库的数据进行计算、组装以及最后的展示,当时这个需求是另一个老同事在做,我只是负责自己的部分。
    直到今年回来了,这个项目也做得差不多了,这会儿才有时间区仔细看同事的代码,是怎么去实现多数据源动态切换的。
    扩展:当业务也来越复杂,数据量越来越庞大时,就可能会对数据库进行分库分表、读写分离等设计来减轻压力、提高系统性能,那么多数据源动态切换势必是必不可少!
    经过了一星期零零碎碎的下班时间,从了解原理、实现、优化的过程,自己终于总算是弄出来了,接下来一起看看!
    思考
    • 如何让Spring知道我们配置了多个数据源?
    • 配置了多个数据源后,Spring是如何决定使用哪一个数据源?
    • Spring是如何动态切换数据源?

    分析及实现
    配置多数据源信息
    spring:datasource:local:database: localusername: rootpassword: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Driverserver:database: serverusername: rootpassword: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Driver

    这是我的两个数据库:本地数据库+个人服务器数据库
    服务器数据库
    Spring|Spring AOP实现多数据源动态切换
    文章图片

    本地数据库
    Spring|Spring AOP实现多数据源动态切换
    文章图片


    Spring如何获取配置好的多个数据源信息?
    Spring提供了三种方式进行获取
    @Value注解获取(实体类需配合@Component),最简单,但当配置信息较多时,写起来比较繁琐
    @ConfigurationProperties注解获取,需要定义前缀,可大批量获取配置信息
    【Spring|Spring AOP实现多数据源动态切换】@Environment注解从Spring环境中获取,实现较为复杂,本人很少用
    同事使用的方式是第一种方式,但是我个人觉得这样侵入性较大,每增加一个数据源,就要重新定义变量然后用@Value去重新配置,很麻烦,所以我就选择了第二种方式
    通过@ConfigurationProperties注解获取,需要定义前缀,可大批量获取配置信息
    @Data@Component@ConfigurationProperties(prefix = "spring.datasource")public class DBProperties { private HikariDataSource server; private HikariDataSource local; }

    将所有的数据源加载到Spring中,可供其选择使用
    @Slf4j@Configurationpublic class DataSourceConfig { @Autowiredprivate 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: dataSource0username: rootpassword: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Driver- database: dataSource1username: rootpassword: jdbc-url: jdbc:mysql://ip:port/test_user?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTCdriver-class-name: com.mysql.cj.jdbc.Driver

    优化后的DBProperties
    @Data@Component@ConfigurationProperties(prefix = "spring.datasource")public class DBProperties { private List DBNames; }

    优化后的DataSourceConfig
    @Slf4j@Configurationpublic class DataSourceConfig { @Autowiredprivate 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方法,选择具体的数据源
    @Slf4jpublic class MultiDataSource extends AbstractRoutingDataSource {@Overrideprotected 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)@Documentedpublic @interface MultiDataSource { String DBName(); }

    AOP切面
    @Slf4j@Aspect@Componentpublic class DataSourceAspect { @Pointcut(value = "https://www.it610.com/article/@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|Spring AOP实现多数据源动态切换
    文章图片

    。。。。
    如果我工作中也这样,估计要被测试打死!为了敷衍一下,来进行一下测试
    一套代码直接打完:
    Controller+Service+Dao
    @RestController@RequestMapping("user")public class UserController { @Autowiredprivate 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 { @Autowiredprivate UserMapper userMapper; @Autowiredprivate InfoMapper infoMapper; @Overridepublic 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; }} @Mapperpublic interface InfoMapper { @Select("select id,user_name as userName,password,phone,address from test_user")UserVO getUserInfo(); } @Mapperpublic interface UserMapper { @Select("select id,user_name as userName,password,phone from user")UserVO getUserInfoMapper(); }

    测试结果:红框数据来自于服务器数据库,绿框数据来自于本地数据库
    Spring|Spring AOP实现多数据源动态切换
    文章图片

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

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

      推荐阅读