请结合上一篇 >>> 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