首页 > 其他分享 >Mybatis 之 自定义别名处理插件

Mybatis 之 自定义别名处理插件

时间:2022-10-28 23:23:50浏览次数:82  
标签:插件 自定义 Expression void 别名 Table Mybatis table expression

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

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

 

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

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

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

/**
 * 别名插件处理(只处理主表的别名)
 *
 * @author Alay
 * @date 2022-05-23 13:23
 * @see {MP 官方: https://gitee.com/baomidou/mybatis-plus/pulls/137}
 */
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);

...

标签:插件,自定义,Expression,void,别名,Table,Mybatis,table,expression
From: https://www.cnblogs.com/Alay/p/16837804.html

相关文章

  • Mybatis 之 SQL 监控插件
    请结合上一篇>>> MP插件原理  <<<  以及 >>>  Mybatis插件原理 <<< 进行查看查看 使用场景:开发过程中监控每一条SQL语句的执行时长,已经顺便将SQL......
  • Mybatis 之 Mybatis-Plus 插件
    请结合上一边 >>> Mybatis插件原理<<< 进行查看Mybatis中自己定义了一个自己的插件类接口 InnerInterceptor 其内部实现了一些现成的插件,如:PaginationInn......
  • VsCode中一些可以让工作“事半功倍”的插件
    1.GitLens—Gitsupercharged这个插件可以查看代码修改的消息,比如是谁修改的以及修改时间2.Chinese(Simplified)(简体中文)简体中文,这个可以说是装的最多的一款插......
  • MyBatis
    MyBatisMybatis简介原始jdbc操作的分析原始jdbc开发存在的问题如下:数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能sql语句在代码中硬编码,造成代码不易......
  • Mybatis 之 插件原理
    核心代码简介MyBatis插件原理:责任链模式  + JDK动态代理 ( 接口、代理对象、代理类:实现 jdk的 InvocationHandler)Mybatis 插件核心接口:Interceptor/***......
  • 自定义对象属性操作
    最基本点运算letperson={name:'henry',age:18,run:function(){console.log('running');}}person.run();属性的读取:letperson={name:'......
  • 利用Kong 的 request-transformer 插件重写 URL
    1.背景介绍需求是将URL:www.abc.com/api/item/111 重写成 www.xyz.com/open/item/itemdetail?id=111。且域名不变,不能发生302跳转。2.request-transformerrequest-transf......
  • 【SpringBoot】引入mybatis及连接Mysql数据库
    创建一个SpringBoot项目其他不赘叙了,引入MyBaties、MySql依赖编辑 创建mysql表CREATETABLEsp_users(`id`INTPRIMARYKEY,`username`VARCHAR(30),`age`INT);刚......
  • 企业级自定义表单引擎解决方案(十五)--前端开源说明
    一直做后端开发,前端还真不是强项,半桶水的样子,好在现在前端框架和组件层出不穷,基本上勉强可以上路。自定义表单对前端要求非常高,技术上的难度不亚于后端,而且要考虑扩展性......
  • vs.net 效率提升-自定义快捷键
    工欲善其事必先利其器,记录一下自己开发时常用的几个自定义的快捷键。做了这么多年了用着还是比较顺手的分享下~~~~设置时有时设置不成功,非得一项一......