栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java > SpringBoot

如何解决使用mybatis-plus提供的多租户插件出现Column ‘tenant_id‘ specified twice问题

SpringBoot 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

如何解决使用mybatis-plus提供的多租户插件出现Column ‘tenant_id‘ specified twice问题

前言

本文案例来源于业务开发部门进行多租户开发时发生的案例。用过mybatis-plus多租户插件的朋友,可能会知道,该插件的租户id值基本都是从上下文得来,这个上下文可以是cookie、session、threadlocal等。据业务部门反馈,在某次插入时,他们发现获取不到租户id值,于是他们在他们的代码层面上做了这么一层操作,在保存的时候,设置租户id。保存的时候,很成功的出现了Column ‘tenant_id’ specified twice

问题来源

在mybatis-plus 3.4版本之前,mybatis-plus进行多租户插入时是不会对已经存在的tenant_id进行过滤的,这就导致出现Column ‘tenant_id’ specified twice问题。其3.4版本之前多租户sql解析器处理insert语句源码如下

  @Override
    public void processInsert(Insert insert) {
 if (tenantHandler.doTableFilter(insert.getTable().getName())) {
     // 过滤退出执行
     return;
 }
 insert.getColumns().add(new Column(tenantHandler.getTenantIdColumn()));
 if (insert.getSelect() != null) {
     processPlainSelect((PlainSelect) insert.getSelect().getSelectBody(), true);
 } else if (insert.getItemsList() != null) {
     // fixed github pull/295
     ItemsList itemsList = insert.getItemsList();
     if (itemsList instanceof MultiexpressionList) {
  ((MultiexpressionList) itemsList).getExprList().forEach(el -> el.getexpressions().add(tenantHandler.getTenantId(false)));
     } else {
  ((expressionList) insert.getItemsList()).getexpressions().add(tenantHandler.getTenantId(false));
     }
 } else {
     throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
 }
    }

问题解决方案

1、方案一:在业务代码插入时,实体不要设置租户id值,统一由多租户插件进行设值

2、方案二:升级mybatis-plus版本为3.4.1或者之后的版本

不过此时的多租户插件的写法就不要按之前那种方式写,虽然之前写法3.4.1也兼容,不过官方已经打了@Deprecated标注,说明官方已经不推荐之前那种写法了,因此采用官方最新提供租户插件拦截器。其示例代码如下

  
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
 interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
     @Override
     public expression getTenantId() {
  return new LongValue(1);
     }

     // 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
     @Override
     public boolean ignoreTable(String tableName) {
  return !"user".equalsIgnoreCase(tableName);
     }
 }));
 // 如果用了分页插件注意先 add TenantLineInnerInterceptor 再 add PaginationInnerInterceptor
 // 用了分页插件必须设置 MybatisConfiguration#useDeprecatedExecutor = false
// interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
 return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
 return configuration -> configuration.setUseDeprecatedExecutor(false);
    }

TenantLineInnerInterceptor这个拦截器的包在com.baomidou.mybatisplus.extension.plugins.inner这个包下

3、方案三:如果是使用mybatis-plus3.4.1之前的版本,可以通过自定义一个TenantSqlParser解析器并重写processInsert方法,其核心代码如下

  */
    @Override
    public void processInsert(Insert insert) {
 if (getTenantHandler().doTableFilter(insert.getTable().getName())) {
     // 过滤退出执行
     return;
 }
 if (isAleadyExistTenantColumn(insert)) {
     return;
 }
 insert.getColumns().add(new Column(getTenantHandler().getTenantIdColumn()));
 if (insert.getSelect() != null) {
     processPlainSelect((PlainSelect) insert.getSelect().getSelectBody(), true);
 } else if (insert.getItemsList() != null) {
     // fixed github pull/295
     ItemsList itemsList = insert.getItemsList();
     if (itemsList instanceof MultiexpressionList) {
  ((MultiexpressionList) itemsList).getExprList().forEach(el -> el.getexpressions().add(getTenantHandler().getTenantId()));
     } else {
  ((expressionList) insert.getItemsList()).getexpressions().add(getTenantHandler().getTenantId());
     }
 } else {
     throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
 }
    }

    
    private boolean isAleadyExistTenantColumn(Insert insert) {
 List columns = insert.getColumns();
 if(CollectionUtils.isEmpty(columns)){
     return false;
 }
 String tenantIdColumn = getTenantHandler().getTenantIdColumn();
 return columns.stream().map(Column::getColumnName).anyMatch(tenantId -> tenantId.equals(tenantIdColumn));
    }

总结

以上三种方案如何选择?如果是项目初期阶段,推荐使用方案一,就是不要在业务层面直接去设置租户id,由租户插件统一处理。如果是全新项目,mybatis-plus推荐使用最新版。如果项目已经业务层面已经多处地方设置了租户id且mybatis-plus版本是3.4之前版本,推荐方案三直接扩展mybatis-plus的租户插件功能,就不推荐方案一了,避免漏改

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号