Mybatis 之 自定义别名处理插件

请结合上一篇 >>> MP 插件原理  <<<   

以及   >>>  Mybatis 插件原理 <<<  进行查看查看

 

MP 开发中,遇到的问题,动态的 Wrapper 产生的 SQL 中,主要出现在组合查询中,会有别名的问题困扰,

实战中问题场景这里就不便于战士了,可以参考  >>> MP 官方 <<<

插件代码如下: (未完全编写,仅仅编写了 SELECT 的一部分场景,未遇到的场景未编写,使用到的朋友们请自行编写,增、删、改 相关逻辑也没有编写)

/**
 * 别名插件处理(只处理主表的别名)
 *
 * @author Alay
 * @date 2022-05-23 13:23
 * @see <a href="https://gitee.com/baomidou/mybatis-plus/pulls/137">MP官方issue</a>
 */
public class AliasInterceptor extends JsqlParserSupport implements InnerInterceptor {

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 是否需要忽略处理别名,这里对方法级进行了增强,定义执行的函数忽略别名处理(不需要的朋友可移除此行)
        boolean ignore = AliasContextHolder.ignore();
        if (ignore) return;
        try {
            Statement parse = CCJSqlParserUtil.parse(boundSql.getSql());
            Select select = (Select) parse;
            PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
            FromItem fromItem = plainSelect.getFromItem();
            // from 语句的 别名
            Alias alias = fromItem.getAlias();
            // 没有别名,无需处理
            if (null == alias) return;

            PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
            mpBs.sql(parserSingle(mpBs.sql(), null));
        } catch (JSQLParserException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        // 增  使用到的时候再进行实现
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        // 删  使用到的时候再进行实现
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        // 改  使用到的时候再进行实现
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        this.processSelectBody(select.getSelectBody());
        List<WithItem> withItems = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItems)) {
            withItems.forEach(this::processSelectBody);
        }
    }

    private void processSelectBody(SelectBody selectBody) {
        if (null == selectBody) return;

        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;
            this.processPlainSelect(plainSelect);
        }
    }

    /**
     * 处理 PlainSelect
     */
    protected void processPlainSelect(PlainSelect plainSelect) {
        FromItem fromItem = plainSelect.getFromItem();
        Alias alias = fromItem.getAlias();
        if (null == alias) return;
        // 不处理子查询,如果需要处理子查询,请自行编写逻辑
        if (!(fromItem instanceof Table)) return;

        Expression where = plainSelect.getWhere();
        this.processExpression(where, (Table) fromItem);
        plainSelect.setWhere(where);
    }

    /**
     * 主解析表达式函数
     *
     * @param expression
     * @param table
     */
    private void processExpression(Expression expression, Table table) {
        // 递归挑出条件
        if (null == expression) return;
        // Parenthesis 实现类
        if (expression instanceof Parenthesis) {
            this.processParenthesis(expression, table);
        }
        // AndExpression 实现类处理
        else if (expression instanceof AndExpression) {
            this.processAndAndExpression(expression, table);
        }
        // `column_name` IN (?,?,?)
        else if (expression instanceof InExpression) {
            this.processInExpression(expression, table);
        }
        // (xxx =#{xxx})
        else if (expression instanceof EqualsTo) {
            this.processEqualsTo(expression, table);
        }
    }

    private void processParenthesis(Expression where, Table table) {
        Parenthesis parenthesis = (Parenthesis) where;
        // 表达式
        this.processExpression(parenthesis.getExpression(), table);
    }

    /**
     * 处理And 表达式
     *
     * @param expression
     * @param table
     */
    private void processAndAndExpression(Expression expression, Table table) {
        AndExpression andExpression = (AndExpression) expression;
        // 左表达式
        Expression leftExpression = andExpression.getLeftExpression();
        if (leftExpression instanceof EqualsTo) {
            this.processEqualsTo(leftExpression, table);
        } else {
            this.processExpression(leftExpression, table);
        }

        // 右表达式
        Expression rightExpression = andExpression.getRightExpression();
        if (rightExpression instanceof EqualsTo) {
            this.processEqualsTo(rightExpression, table);
        } else {
            this.processExpression(rightExpression, table);
        }
    }

    private void processBinaryExpression(Expression expression, Table table) {
        BinaryExpression binaryExpression = (BinaryExpression) expression;
        // 自行处理逻辑
    }

    private void processInExpression(Expression expression, Table table) {
        InExpression inExpression = (InExpression) expression;
        // `column_name` IN (?,?) (括号里边的子查询不做 别名处理),只处理主表的别名
        Expression leftExpression = inExpression.getLeftExpression();
        if (leftExpression instanceof Column) {
            this.processColumn(leftExpression, table);
        }
        // 其他情况没遇到过,暂时不做处理,使用者自行扩展处理
    }

    private void processExistsExpression(Expression expression, Table table) {
        ExistsExpression existsExpression = (ExistsExpression) expression;
        // 自行处理逻辑
        // this.processExpression(existsExpression.getRightExpression(), table);
    }

    private void processNotExpression(Expression expression, Table table) {
        NotExpression notExpression = (NotExpression) expression;
        // 自行处理逻辑
        // this.processExpression(notExpression.getExpression(), table);
    }

    /**
     * 给 SQL 字段加别名
     *
     * @param expression
     * @param table
     */
    private void processEqualsTo(Expression expression, Table table) {
        EqualsTo equalsTo = (EqualsTo) expression;
        Expression leftExpression = equalsTo.getLeftExpression();
        if (leftExpression instanceof Column) {
            this.processColumn(leftExpression, table);
        }
        // 其他情况没有遇到过,暂不做处理,使用者自行扩展处理
    }

    /**
     * 字段别名处理的最终函数
     *
     * @param expression
     * @param table
     */
    private void processColumn(Expression expression, Table table) {
        Column column = (Column) expression;
        // 表别名
        Table columnTable = column.getTable();
        // SQL 语句中已经定义了 别名
        if (null != columnTable) return;
        // 给 SQL 添加别名
        columnTable = new Table(table.getAlias().getName());
        column.setTable(columnTable);
    }

}

 


扩展:

关于方法级的忽略别名插件处理逻辑:

思路,通过 AOP 将执行的 SQL 的函数进行切面处理,通过线程变量将数据传递到插件中,最后记得清除线程变量

1、忽略别名的注解

/**
 * 忽略别名处理,搭配 AliasInterceptor 使用
 *
 * @author Alay
 * @date 2022-06-17 12:46
 */
@Documented
@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface IgnoreAlias {

}

2、定义注解 @IgnoreAlias  的切面

/**
 * 方法级忽略租户处理
 *
 * @author Alay
 * @date  2022-06-17 12:46
 */
@Aspect
public class IgnoreAliasAspect {

    @Around("@annotation(ignoreAlias)")
    public Object around(ProceedingJoinPoint joinPoint, IgnoreAlias ignoreAlias) throws Throwable {
        // 设置为忽略别名
        AliasContextHolder.ignore(true);
        Object result;
        try {
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            // 清除线程数据
            AliasContextHolder.clear();
        }
        return result;
    }
}

3、编写一个线程变量类:

/**
 * 本地线程存储谨慎使用
 *
 * @author Alay
 * @date 2022-06-17 12:46
 */
public class AliasContextHolder {

    private final static ThreadLocal<Boolean> THREAD_LOCAL_ALIAS = new ThreadLocal<>();
    /**
     * 是否忽略别名
     *
     * @return
     */
    public static boolean ignore() {
        Boolean ignore = THREAD_LOCAL_ALIAS.get();
        return Optional.ofNullable(ignore).orElse(false);
    }
public static void ignore(boolean ignore) { THREAD_LOCAL_ALIAS.set(ignore); } /** * 只会清除当前线程的 */ public static void clear() { THREAD_LOCAL_ALIAS.remove(); } }

 

默认全部执行,如果需要手动控制忽略 SQL 中别名处理,则直接使用 注解  @IgnoreAlias

忽略使用:XxxMapper.java 接口中使用如下(也可以将注解加到 Service 以及 Controller 大方法上,只是 DAO 层使用到 Mapper.java 类中更符合)

如: XxxMapper.java

    /**
     * 查询表全部列信息
     *
     * @param dsId      数据源Id
     * @param tableName 表名称
     * @return
     */
    @IgnoreAlias
    @DS("#last")
    List<TableColumn> listTableColumn(@Param("tableName") String tableName, String dsId);

...

posted @ 2022-10-28 23:14  Vermeer  阅读(356)  评论(0)    收藏  举报