TenantLineInnerInterceptor源码解读

本文最后更新于 2025年4月14日

本文未完待续………..

一、引言

TenantLineInnerInterceptor是MyBatis-Plus中的一个拦截器类,位于com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor,通过MyBatis-Plus的插件机制调用,用于实现表级的多租户功能。

本文基于MyBatis-Plus的3.5.9版本的源码,并fork了代码: https://github.com/changelzj/mybatis-plus/tree/lzj-3.5.9

public class TenantLineInnerInterceptor 
extends BaseMultiTableInnerInterceptor implements InnerInterceptor {

    private TenantLineHandler tenantLineHandler;

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {...}

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {...}

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {...}

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {...}

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {...}

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {...}

    protected void processInsertSelect(Select selectBody, final String whereSegment) {...}

    protected void appendSelectItem(List<SelectItem<?>> selectItems) {...}

    protected Column getAliasColumn(Table table) {...}

    @Override
    public void setProperties(Properties properties) {...}

    @Override
    public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {...}
}

多租户和数据权限DataPermissionInterceptor的实现原理是类似的,租户本质上也是一种特殊的数据权限,不同于数据权限的是对于涉及租户的表的增、删、改、查四种操作,都需要对SQL语句进行处理,实现原理是执行SQL前进行拦截,并获取要执行的SQL,然后解析SQL语句中的表,遇到需要租户隔离的表就要进行处理,对于查询、删除和更新的场景,就在现有的SQL条件中追加一个tenant_id = ?的条件,获取当前操作的用户或要执行的某种任务所属的租户ID赋值给tenant_id,对于添加操作,则是将tenant_id字段加入到INSERT列表中并赋值。

TenantLineInnerInterceptor类也像数据权限插件一样继承了用于解析和追加条件的BaseMultiTableInnerInterceptor类,但是BaseMultiTableInnerInterceptor主要是提供了对查询SQL的解析重写能力供插件类使用,本类对于添加数据的场景采用自己实现的解析和重写INSERT SQL的逻辑。

二、源码解读

2.1 beforeQuery/beforePrepare


@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
        return;
    }
    PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
    mpBs.sql(parserSingle(mpBs.sql(), null));
}
@Override
public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
    PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
    MappedStatement ms = mpSh.mappedStatement();
    SqlCommandType sct = ms.getSqlCommandType();
    if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
        if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
            return;
        }
        PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
        mpBs.sql(parserMulti(mpBs.sql(), null));
    }
}

2.2 processSelect

@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
    final String whereSegment = (String) obj;
    processSelectBody(select, whereSegment);
    List<WithItem> withItemsList = select.getWithItemsList();
    if (!CollectionUtils.isEmpty(withItemsList)) {
        withItemsList.forEach(withItem -> processSelectBody(withItem, whereSegment));
    }
}

2.3 processInsert

@Override
protected void processInsert(Insert insert, int index, String sql, Object obj) {
    if (tenantLineHandler.ignoreTable(insert.getTable().getName())) {
        // 过滤退出执行
        return;
    }
    List<Column> columns = insert.getColumns();
    if (CollectionUtils.isEmpty(columns)) {
        // 针对不给列名的insert 不处理
        return;
    }
    String tenantIdColumn = tenantLineHandler.getTenantIdColumn();
    if (tenantLineHandler.ignoreInsert(columns, tenantIdColumn)) {
        // 针对已给出租户列的insert 不处理
        return;
    }
    columns.add(new Column(tenantIdColumn));
    Expression tenantId = tenantLineHandler.getTenantId();
    // fixed gitee pulls/141 duplicate update
    List<UpdateSet> duplicateUpdateColumns = insert.getDuplicateUpdateSets();
    if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
        EqualsTo equalsTo = new EqualsTo();
        equalsTo.setLeftExpression(new StringValue(tenantIdColumn));
        equalsTo.setRightExpression(tenantId);
        duplicateUpdateColumns.add(new UpdateSet(new Column(tenantIdColumn), tenantId));
    }

    Select select = insert.getSelect();
    if (select instanceof PlainSelect) { //fix github issue 4998  修复升级到4.5版本的问题
        this.processInsertSelect(select, (String) obj);
    } else if (insert.getValues() != null) {
        // fixed github pull/295
        Values values = insert.getValues();
        ExpressionList<Expression> expressions = (ExpressionList<Expression>) values.getExpressions();
        if (expressions instanceof ParenthesedExpressionList) {
            expressions.addExpression(tenantId);
        } else {
            if (CollectionUtils.isNotEmpty(expressions)) {//fix github issue 4998 jsqlparse 4.5 批量insert ItemsList不是MultiExpressionList 了,需要特殊处理
                int len = expressions.size();
                for (int i = 0; i < len; i++) {
                    Expression expression = expressions.get(i);
                    if (expression instanceof Parenthesis) {
                        ExpressionList rowConstructor = new RowConstructor<>()
                            .withExpressions(new ExpressionList<>(((Parenthesis) expression).getExpression(), tenantId));
                        expressions.set(i, rowConstructor);
                    } else if (expression instanceof ParenthesedExpressionList) {
                        ((ParenthesedExpressionList) expression).addExpression(tenantId);
                    } else {
                        expressions.add(tenantId);
                    }
                }
            } else {
                expressions.add(tenantId);
            }
        }
    } else {
        throw ExceptionUtils.mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
    }
}

2.4 processUpdate

/**
 * update 语句处理
 */
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
    final Table table = update.getTable();
    if (tenantLineHandler.ignoreTable(table.getName())) {
        // 过滤退出执行
        return;
    }
    List<UpdateSet> sets = update.getUpdateSets();
    if (!CollectionUtils.isEmpty(sets)) {
        sets.forEach(us -> us.getValues().forEach(ex -> {
            if (ex instanceof Select) {
                processSelectBody(((Select) ex), (String) obj);
            }
        }));
    }
    update.setWhere(this.andExpression(table, update.getWhere(), (String) obj));
}

2.5 processDelete


/**
 * delete 语句处理
 */
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
    if (tenantLineHandler.ignoreTable(delete.getTable().getName())) {
        // 过滤退出执行
        return;
    }
    delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere(), (String) obj));
}

2.6 processInsertSelect


/**
 * 处理 insert into select
 * <p>
 * 进入这里表示需要 insert 的表启用了多租户,则 select 的表都启动了
 *
 * @param selectBody SelectBody
 */
protected void processInsertSelect(Select selectBody, final String whereSegment) {
    if(selectBody instanceof PlainSelect){
        PlainSelect plainSelect = (PlainSelect) selectBody;
        FromItem fromItem = plainSelect.getFromItem();
        if (fromItem instanceof Table) {
            // fixed gitee pulls/141 duplicate update
            processPlainSelect(plainSelect, whereSegment);
            appendSelectItem(plainSelect.getSelectItems());
        } else if (fromItem instanceof Select) {
            Select subSelect = (Select) fromItem;
            appendSelectItem(plainSelect.getSelectItems());
            processInsertSelect(subSelect, whereSegment);
        }
    } else if(selectBody instanceof ParenthesedSelect){
        ParenthesedSelect parenthesedSelect = (ParenthesedSelect) selectBody;
        processInsertSelect(parenthesedSelect.getSelect(), whereSegment);

    }
}

2.7 appendSelectItem

/**
 * 追加 SelectItem
 *
 * @param selectItems SelectItem
 */
protected void appendSelectItem(List<SelectItem<?>> selectItems) {
    if (CollectionUtils.isEmpty(selectItems)) {
        return;
    }
    if (selectItems.size() == 1) {
        SelectItem item = selectItems.get(0);
        Expression expression = item.getExpression();
        if (expression instanceof AllColumns) {
            return;
        }
    }
    selectItems.add(new SelectItem<>(new Column(tenantLineHandler.getTenantIdColumn())));
}

2.8 getAliasColumn

/**
 * 租户字段别名设置
 * <p>tenantId 或 tableAlias.tenantId</p>
 *
 * @param table 表对象
 * @return 字段
 */
protected Column getAliasColumn(Table table) {
    StringBuilder column = new StringBuilder();
    // todo 该起别名就要起别名,禁止修改此处逻辑
    if (table.getAlias() != null) {
        column.append(table.getAlias().getName()).append(StringPool.DOT);
    }
    column.append(tenantLineHandler.getTenantIdColumn());
    return new Column(column.toString());
}

2.9 setProperties

@Override
public void setProperties(Properties properties) {
    PropertyMapper.newInstance(properties).whenNotBlank("tenantLineHandler",
        ClassUtils::newInstance, this::setTenantLineHandler);
}

2.10 buildTableExpression

/**
 * 构建租户条件表达式
 *
 * @param table        表对象
 * @param where        当前where条件
 * @param whereSegment 所属Mapper对象全路径(在原租户拦截器功能中,这个参数并不需要参与相关判断)
 * @return 租户条件表达式
 * @see BaseMultiTableInnerInterceptor#buildTableExpression(Table, Expression, String)
 */
@Override
public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
    if (tenantLineHandler.ignoreTable(table.getName())) {
        return null;
    }
    return new EqualsTo(getAliasColumn(table), tenantLineHandler.getTenantId());
}


TenantLineInnerInterceptor源码解读
https://blog.liuzijian.com/post/mybatis-plus-source-tenant-line-inner-interceptor.html
作者
Liu Zijian
发布于
2025年3月31日
更新于
2025年4月14日
许可协议