SpringBoot项目多数据源配置与MyBatis拦截器生效问题解析

在日常项目开发中,由于某些原因,一个服务的数据源可能来自不同的库,比如:

  1. 对接提供的中间库,需要查询需要的数据
  2. 同步数据,需要将一个库的数据同步到另一个库,做为同步工具的服务
  3. 对接第三方系统,由于时间等原因不方便提供接口,开放数据库提供一些查询视图

在实际项目中,多数据源的配置是常见需求。本博客将详细介绍如何在Spring Boot项目中配置多个数据源,并使用MyBatis进行整合。同时,我们将解决在多数据源配置下自定义拦截器不生效的问题。

1、配置多数据源方式

我所在项目使用的SpringBoot+Mybatis,首先需要添加不同数据源的配置,一般为了区分数据源,会是在单数据源的基础上再datasource节点和具体配置节点之间自定义数据源的名称,比如default表示默认数据源。

spring:
  thymeleaf:
    cache: false
  datasource:
  # 默认数据源配置
    default:
      name: dataSource
      url: jdbc:log4jdbc:Postgresql://ip:port/db?currentSchema=db&ApplicationName=test
      username: ENCRYPT#wwWQEj+qg=
      password: ENCRYPT#Jb0N1GHFwD+MWQRiSfHg==
      driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        validation-query: select 1
        min-idle: 20
        initial-size: 20
        max-active: 50
   # 第二数据源
    secondary:
      name: dataSource
      url: jdbc:log4jdbc:Postgresql://ip:port/db?currentSchema=db&ApplicationName=test
      username: ENCRYPT#qdTt8x5ss=
      password: ENCRYPT#YvZIPJii8D+MWQRiSfHg==
      driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
      type: com.alibaba.druid.pool.DruidDataSource
      druid:
        validation-query: select 1
        min-idle: 20
        initial-size: 20
        max-active: 50

因为有了多个数据源,需要手动配置创建数据源实例, 默认数据源添加@Primary注解进行标注。

@Configuration
public class DataSourceConfig {
​
    /**
     * 构建主数据源
     * @return 数据源对象
     */
    @Primary
    @Bean(name = "defaultDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.default")
    public DataSource defaultDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        return dataSource;
    }
​
    /**
     * 第二数据源
     * @return 数据源对象
     */
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return new DruidDataSource();
    }
}

2、Mybatis配置

使用单数据源时,Mapper文件的扫描一般是在启动类上使用@MapperScan配置扫描路径的,但是多数据源则需要进行区分,扫描不同的路径;

SqlSessionFactory 在 MyBatis 中充当着重要的角色,它通过将配置信息加载到内存中,创建和管理 SqlSession 实例,以及配置和创建 Mapper 对象,为开发者提供了便捷的数据库操作接口。通常情况下,一个应用程序只需要创建一个 SqlSessionFactory 实例,并在整个应用程序的生命周期中共享该实例。多数据源场景下,不同的数据源也需要创建自己的SqlSessionFactory 实例。

@MapperScan(basePackages = {"com.xx.xx.*.mapper", "com.xx.xx.**.mapper",
    "com.xx.xx.report.provider.report.db"},
    sqlSessionFactoryRef = "defaultSqlSessionFactory", sqlSessionTemplateRef = "defaultSqlSessionTemplate")
@Configuration
public class DefaultMybatisConfig {
​
    @Autowired
    @Qualifier("dataSource")
    private DataSource defaultDataSource;
​
    @Resource
    private DictGroupTagInterceptor dictGroupTagInterceptor;
​
    @Resource
    private PageInterceptor pageInterceptor;
​
    @Resource
    private DefaultMybatisInterceptor defaultMyBatisInterceptor;
​
    @Bean(name = "defaultTransactionManager")
    @Primary
    public DataSourceTransactionManager defaultTransactionManager() {
        return new DataSourceTransactionManager(defaultDataSource);
    }
​
    @Primary
    @Bean(name = "defaultSqlSessionFactory")
    public SqlSessionFactory defaultSqlSessionFactory() throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(defaultDataSource);
        // Artery的分页插件,数据字典插件(用于动态替换模式名称)
        sessionFactory.setPlugins(pageInterceptor, dictGroupTagInterceptor, defaultMyBatisInterceptor);
        return sessionFactory.getObject();
    }
​
​
    @Bean(name = "defaultSqlSessionTemplate")
    public SqlSessionTemplate defaultSqlSessionTemplate(
        @Qualifier("defaultSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

对于其他数据源的Mybatis配置也是一样,创建配置类进行配置即可

3、过程中遇到的问题

服务正常启动,对应数据源的访问也没有问题;一个业务场景需要用到业务表的创建时间,创建时间为null导致系统异常。

排查发现,业务表最近的业务数据的创建人,创建时间,修改人,修改时间字段值都是空的。

这些基础字段并不是通过业务数据的创建和修改赋值的,都是实现Mybatis的拦截器Interceptor进行实现的,说明拦截器并未生效。

判断当前操作是新增或者修改,根据当前登录人信息对基础字段赋值

@Override
public Object intercept(Invocation invocation) throws Throwable {
​
    Object[] args = invocation.getArgs();
    IUser currentUser = securityService.getCurrUserInfo();
    if(Objects.isNull(currentUser)){
        log.warn("没有登录的用户在操作数据库,请关注,参数:{},线程:{}",args,Thread.currentThread().getName());
        currentUser = new User();
        currentUser.setCorpId("unkown");
        currentUser.setId("unkown");
    }
    if (args.length > 1) {
        MappedStatement ms = (MappedStatement)args[0];
        try {
            if (SqlCommandType.INSERT.equals(ms.getSqlCommandType())) {
                preInsert(args[1], currentUser);
            } else if (SqlCommandType.UPDATE.equals(ms.getSqlCommandType())) {
                preUpdate(args[1], currentUser);
            }
        } catch (Exception e) {
            log.warn("intercept exception : " + e.getMessage(), e);
        }
    }
​
    return invocation.proceed();
}

1、那么系统单数据源时,自定义的拦截器是如何生效的呢?

原因就在于MybatisAutoConfiguration类 ,作用是自动配置 MyBatis 相关的组件,包括创建SqlSessionFactoryMybatisAutoConfiguration实现了InitializingBean进行初始化操作,在实例化时注入了Interceptor对象。

通过跟踪代码发现,SqlSessionFactory会在MybatisAutoConfiguration创建中创建实例,而这些自定义的拦截器会在创建SqlSessionFactory实例的时候进行设置。

其中注解@ConditionalOnMissingBean表示容器中不存在某个指定类型或名称的 Bean 时,才会生效。所以在SqlSessionFactory已经存在的实例情况之下并不会创建实例,也就解释了我们配置多数据源情况下自定义拦截器不生效的原因。

所以解决办法就很简单了,在我们自定义的Mybatis配置类中注入自定义的拦截器,设置到SqlSessionFactory当中;

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/347646.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

离零售业智能体时代的真正开启还有多远?

AIGC(生成式人工智能)当道的2023年,将LLM(大语言模型)的各类生成式能力发挥到淋漓尽致、精彩纷呈的程度。各行各业一边在观望大语言模型不断扩宽的商业运用可能,一边在继续探寻能够不断拓宽企业往纵深发展的…

C/C++ - Auto Reference

目录 auto Reference auto 当使用auto​​关键字声明变量时,C编译器会根据变量的初始化表达式推断出变量的类型。 自动类型推断:auto​​关键字用于自动推断变量的类型,使得变量的类型可以根据初始化表达式进行推导。 初始化表达式&#x…

Redis的五种常用数据类型详解及相关面试问题

目录 Redis的五种常用数据类型详解 简述 Redis五种基本数据类型 String字符串 常用命令 应用场景 Hash散列表 常用命令 使用场景 List链表 常用命令 应用场景 Set( 集合) 常用命令 应用场景 SortedSet( 有序集合) zset 常用命令介绍 应用场景 面试题常问的数…

【驱动】TI AM437x(内核调试-07):devmem2直接读写内存、寄存器,devkmem读取内核变量

1、/dev/mem 和 /dev/kmem 1)/dev/mem: 物理内存的全镜像。可以用来访问物理内存 2)/dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。kernel部分内存用户空间本不可访问。但是因为所有进程共享内核空间的页表。所以内核虚拟地址对应物理地址是确定的…

【码农新闻】 CSS 即将支持嵌套,SASS/LESS 等预处理器已无用武之地?常见的Web攻击手段,拿捏了!......

目录 【码农新闻】 CSS 即将支持嵌套,SASS/LESS 等预处理器已无用武之地?常见的Web攻击手段,拿捏了!...... 流行框架与库的源码分析与最简实现CSS 即将支持嵌套,SASS/LESS 等预处理器已无用武之地?常见的W…

低代码开发平台与可组合业务:实现高效应用的完美结合

如今,有很多产品已经走在了模块化的道路上,例如一款吸尘器,它可以经由不同配件组合来实现不同的功能,来满足消费者的需求。这种类似于“一站式”的产品解决方案,正在成为一种可见的趋势。 今年年初,Gartne…

函数递归知识点与经典例题

目录 递归的概念 (什么是递归) 递归举例 举例1:求n的阶乘 举例2:顺序打印一个整数的每一位 递归与迭代 举例3:求第n个斐波那契数 递归的概念 (什么是递归) 递归是学习C语言函数绕不开的⼀…

第13章_泛型(集合中使用泛型,比较器中使用泛型,自定义泛型结构,泛型在继承上的体现,通配符的使用)

文章目录 第13章_泛型(Generic)本章专题与脉络1. 泛型概述1.1 生活中的例子1.2 泛型的引入 2. 使用泛型举例2.1 集合中使用泛型2.1.1 举例2.1.2 练习 2.2 比较器中使用泛型2.2.1 举例2.2.2 练习 2.3 相关使用说明 3. 自定义泛型结构3.1 泛型的基础说明3.2 自定义泛型类或泛型接…

一文学习Thrift RPC

Thrift RPC引言 Thrift RPC的特点 Thrift 是一个RPC的框架,和Hessian RPC有什么区别,最重要的区别是Thrift可以做异构系统开发。 什么是异构系统,服务的提供者和服务的调用者是用不同语言开发的。 为什么会当前系统会有异构系统的调用&…

Flume介绍

一、介绍 Apache Flume 是一种分布式、可靠且可用的系统,用于有效地收集、汇总大量日志数据,并将其从多个不同来源转移到集中式数据存储区。 Apache Flume 的使用不仅限于日志数据聚合。由于数据源是可定制的,Flume 可用于传输大量事件数据&a…

C++学习| QT快速入门

QT简单入门 QT Creater创建QT项目选择项目类型——不同项目类型的区别输入项目名字和路径选择合适的构建系统——不同构建系统的却别选择合适的类——QT基本类之间的关系Translation File选择构建套件——MinGW和MSVC的区别 简单案例:加法器设计界面——构建加法器界…

红帽认证有啥用?初级红帽认证证书值得考吗?

大家好,这里是G-LAB IT实验室。 今天我们来了解一下Linux红帽认证。 红帽认证已成为企业和个人竞相追逐的热门资质。 红帽认证认可度究竟如何?红帽RHCSA认证含金量又有多高? 下面G-LAB将为你一一解答。 1 、红帽认证认可度怎么样? 事实上&#xff0…

git:git reset 和 git revert

在使用 git 进行项目开发的过程中,有时会出现错误提交的情况,这时就需要能够撤销错误的提交,将代码恢复到提交之前的样子。根据不同情况,可以使用 git reset 或 git revert 命令。 一. git reset git reset 的原理是修改 HEAD 的…

php怎么输入一个变量,http常用的两种请求方式getpost(ctf基础)

php是网页脚本语言,网页一般支持两种提交变量的方式,即get和post get方式传参 直接在网页URL的后面写上【?a1027】,如果有多个参数则用&符号连接, 如【?a10&b27】 post方式传参 需要借助插件,ctfer必备插…

羊奶与牛奶,谁更好?

羊奶与牛奶,谁更好? 羊奶和牛奶是我们日常饮食中常见的乳制品,但究竟哪种更好呢?今天就让小编羊大师带大家一起来探讨一下,看看羊奶和牛奶在各方面的优势和劣势,帮助你作出更明智的选择。 让我们从营养价…

仅使用 Python 创建的 Web 应用程序(前端版本)第07章_商品列表

在本章中,我们将实现一个产品列表页面。 完成后的图像如下 创建过程与User相同,流程如下。 No分类内容1Model创建继承BaseDataModel的数据类Item2MockDB创建产品表并生成/添加虚拟数据3Service创建一个 ItemAPIClient4Page定义PageId并创建继承自BasePage的页面类5Applicati…

常见算法思想:迭代法

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 学习必须往深处挖&…

vue3 实现百分比进度条,文件上传进度条,包更新进度条

包更新&#xff0c; 文件上传&#xff0c;进度条 template 里面 <van-button click"changerun">{{ run?暂停:播放 }}</van-button>{{ percentage }}%<div class"line" :style"{height:2px, width:percentage%, backgroundColor:perc…

Linux中的LVM理论

Linux LVM&#xff1a;Logical Volume Manager逻辑卷管理 无需在停机的情况下&#xff0c;动态调整分区的大小 PV里面的存在很多小方块PE&#xff08;物理扩展&#xff09;&#xff0c;一个PV继承了pp的100G&#xff0c;只不过被分开分配了 划分小的PE再存放在VG里面&#…

两相步进电机驱动原理

两相步进电机驱动 前言什么是步进电机驱动器细分控制电机内部结构图片步进电机驱动原理&#xff08;重要&#xff09;步进电机参数&#xff11;、步距角&#xff1a;收到一个脉冲转动的角度&#xff12;、细分数 &#xff1a;&#xff11;&#xff0f;&#xff12;&#xff0c…
最新文章