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

发布时间:2024年01月24日

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

  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当中;

 

文章来源:https://blog.csdn.net/qq_42526963/article/details/135822181
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。