Mybatis 之 自定义年月插件 yyyy-MM

请结合 >>> MP 插件原理  <<<   

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

此插件再 JsqlParser 更新后已经同步更新,以下代码仅仅适用于 4.6 版本之前的代码

 

使用场景

在写 一些与月份为单位的场景中,如: 薪资系统(月薪制)

1、Java 类中设计的 月份属性类为 : YearMonth  (2022-10) Java8后的月份类和 LocalDate... 等是一伙的,并没有到日,

2、数据库中设计的 数据类型为: date (2022-10-10),此时将会产生,数据库的数据类型与 Java 类型中的数据不匹配,

3、数据库中并不支持 yyyy-MM 格式的时间类型数据,可能有朋友会想到使用 varchar 类型来替代,可是当你使用时间范围统计查询的时候,varchar 类型显然不太合适

 

思路分析:

将 SQL  语句进行切分处理: SELECT,SET(UPDATE)、INSERT、WHERE

1、将 需要处理的 YearMonth 类型的数据统一设计为一个 统一命名的字段,如  yyyy_mm  ( yyyyMm )

2、WHERE 语句中 凡是出现条件为 YearMonth 类型的,统统处理为  WHERE DATE_FORMAT( `yyyy_mm`  , '%Y-%m' ) =  #{ yyyyMm } 

3、SET 和 INSERT 中的 YearMonth 类型 统统处理为: 以 SET 为例 SET `yyyy_mm` = CONCAT( #{ yyyyMm } , '-01')  即,给Java中的时间带到数据库中然后拼接一个日期   -01

4、另一种方案:编写一个 TypeHandler  对 YearMonth  类进行处理,能解决一部分问题,但是 WHERE  中的的条件语句无法被处理,而且 YearMonth, Mybatis 已经内置了一个 YearMonthTypeHandler  类型处理器,再写一个继承他或者替换他 显然都不太合适

 

问题:

1、少数的编码还能手动的进行 SQL 编写,当大量使用时,将带来大量工作量,且开发初期,字段变动较大,如果使用大量的手写 xml  SQL ,只要字段以及 SQL 有任意的变化,都将需要对 XML 中的  SQL 进行追踪 修改 处理,这对团队开发效率造成灾难性的打击,这是致命的,是不能容忍的

2、项目中使用的是 MP ,为了提高效率,简单的  SQL  我提倡 无 SQL  开发,开发效率高,可维护性强,字段的增减变化对SQL影响不大(MP  Wrapper 动态SQL),

3、所以我选择的解决方案是,使用插件动态 处理 SQL  ,无论是  手写自定义的 SQL  还是 MP  动态 SQL ,都将被插件 统一拦截处理(不是每一条 SQL 都别拦截处理,有条件的按需拦截处理改写 SQL)

 

解决方案: 

综上所述,我的解决方案:  编写插件,动态处理 SQL  , 事实证明,这也将大大加快了团队的开发效率,让开发人员将心思花在业务开发上,无需顾霞这些琐碎的处理上

插件代码如下:(以下仅仅为示例代码,并非生产代码,还有大量场景未考虑到未实现,权当 抛砖引玉,切勿直接使用)

1、定义 YearMonth 类 统一命名字段的接口: 

/**
 * YearMonth 处理器( yyyyMm 行级 )
 *
 * @author Alay
 * @date 2022-10-28 01:00
 */
public interface YearMonthLineHandler {

    /**
     * 取 YearMonth 字段名,默认字段名叫: yyyy_mm,可按需编写实现类自定义实现
     * @return YearMonth 字段名
     */
    String yearMonthColumn();

    /**
     * 年月转数据库年月日 值规则
     * @param yyyyMm
     * @return
     */
    default String toDateValue(YearMonth yyyyMm) {
        yyyyMm = null == yyyyMm ? YearMonth.now() : yyyyMm;
        return String.format("%s-01", yyyyMm);
    }
}

2、 插件类编写:

(仅仅实现了测试中遇到的场景)

/**
 * Java类 YearMonth ( yyyy-MM )与数据库 date ( yyyy-MM-dd ) 映射处理
 *
 * @author Alay
 * @date 2022-10-28 00:40
 */
public class YearMonthInterceptor extends JsqlParserSupport implements InnerInterceptor {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * 可自定义实现,用于定义项目中的 YearMonth 类统一处理的字段(我使用了 匿名内部类,也可以使用 IOC 注入的形式进行注入使用 )
     */
    private final YearMonthLineHandler yearMonthLineHandler;
  // 时间 YearMonth 属性字段数据库中的统一 字段名
private final String yearMonthColumn; public YearMonthInterceptor() {
     // 定义 时间 YearMonth 属性字段数据库中的统一 字段名(这里使用的是 匿名内部类处理了)
this.yearMonthLineHandler = () -> "yyyy_mm"; this.yearMonthColumn = yearMonthLineHandler.yearMonthColumn(); } @Override public void beforePrepare(StatementHandler statementHandler, Connection connection, Integer transactionTimeout) { BoundSql boundSql = statementHandler.getBoundSql(); // statementHandler 转 MetaObject MetaObject metaObject = SystemMetaObject.forObject(statementHandler); // 上下文对象 MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement"); try { Statement parse = CCJSqlParserUtil.parse(boundSql.getSql()); SqlCommandType sqlType = mappedStatement.getSqlCommandType(); Expression where = null; boolean hasYearMonth = false; // 更新或插入 switch (sqlType) { case INSERT: Insert insert = (Insert) parse; hasYearMonth = this.inInsert(insert); break; case DELETE: Delete delete = (Delete) parse; this.inDelete(delete); where = delete.getWhere(); break; case UPDATE: Update update = (Update) parse; hasYearMonth = this.inUpdate(update); if (hasYearMonth) break; where = update.getWhere(); break; case SELECT: Select select = (Select) parse; hasYearMonth = this.inSelect(select); if (hasYearMonth) break; SelectBody selectBody = select.getSelectBody(); if (null == selectBody) return; if (selectBody instanceof PlainSelect) { PlainSelect plainSelect = (PlainSelect) selectBody; where = plainSelect.getWhere(); } break; } hasYearMonth = hasYearMonth || this.inWhere(where); if (hasYearMonth) { // 需要处理 SQL PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); mpBs.sql(parserSingle(mpBs.sql(), null)); } } catch (Throwable e) { logger.error(e.getMessage()); } } private boolean inInsert(Insert insert) { if (null == insert) return false; String insertSql = insert.toString(); return insertSql.contains(yearMonthColumn); } private boolean inDelete(Delete delete) { return this.inWhere(delete.getWhere()); } private boolean inUpdate(Update update) { List<UpdateSet> updateSets = update.getUpdateSets(); for (UpdateSet updateSet : updateSets) { List<Column> columns = updateSet.getColumns(); if (ICollUtil.isEmpty(columns)) return false; for (Column column : columns) { boolean hasYyyyMm = this.isYyyyMm(column); if (hasYyyyMm) return true; } } return false; } private boolean inSelect(Select select) { if (null == select) return false; String sqlStr = select.toString(); return sqlStr.contains(yearMonthColumn); } private boolean inWhere(Expression where) { if (null == where) return false; String whereStr = where.toString(); return whereStr.contains(yearMonthColumn); } @Override protected void processInsert(Insert insert, int index, String sql, Object obj) { List<Column> columns = insert.getColumns(); if (ICollUtil.isEmpty(columns)) return; for (int i = 0; i < columns.size(); i++) { Column column = columns.get(i); boolean isYyyyMm = this.isYyyyMm(column); // 匹配到处理的字段了 if (isYyyyMm) { ItemsList itemsList = insert.getItemsList(); if (itemsList instanceof ExpressionList) { ExpressionList expressionList = (ExpressionList) itemsList; List<Expression> expressions = expressionList.getExpressions(); // 移除原来的表达式 expressions.remove(i); // 添加新的表达式 CONCAT(?,'-01') expressions.add(i, this.concatFunction()); } } } } @Override protected void processDelete(Delete delete, int index, String sql, Object obj) { Expression where = delete.getWhere(); if (where instanceof Parenthesis) { Parenthesis parentWhere = (Parenthesis) where; // 去除where 的 括号 delete.setWhere(parentWhere.getExpression()); where = delete.getWhere(); } this.processWhere(where); } @Override protected void processUpdate(Update update, int index, String sql, Object obj) { List<UpdateSet> updateSets = update.getUpdateSets(); for (UpdateSet updateSet : updateSets) { this.concatYyyyMm(updateSet); } // where 语句 Expression where = update.getWhere(); if (where instanceof Parenthesis) { Parenthesis parentWhere = (Parenthesis) where; // 去除where 的 括号 update.setWhere(parentWhere.getExpression()); where = update.getWhere(); } // where 语句处理 this.processWhere(where); } @Override protected void processSelect(Select select, int index, String sql, Object obj) { SelectBody selectBody = select.getSelectBody(); if (null == selectBody) return; if (selectBody instanceof PlainSelect) { PlainSelect plainSelect = (PlainSelect) selectBody; List<SelectItem> selectItems = plainSelect.getSelectItems(); if (ICollUtil.isNotEmpty(selectItems)) { selectItems.forEach(this::processSelectItem); } // 处理 Where 语句 Expression where = plainSelect.getWhere(); processWhereSubSelect(where); } } private void concatYyyyMm(UpdateSet updateSet) { List<Column> columns = updateSet.getColumns(); // 没有字段 if (ICollUtil.isEmpty(columns)) return; for (Column column : columns) { boolean hasYyyyMm = this.isYyyyMm(column); if (hasYyyyMm) { Function function = this.concatFunction(); ArrayList<Expression> expressions = new ArrayList<>(); expressions.add(function); updateSet.setExpressions(expressions); } } } private void processSelectItem(SelectItem selectItem) { if (selectItem instanceof SelectExpressionItem) { SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem; Expression expression = selectExpressionItem.getExpression(); // 只处理 Column 类型的,如果已经是 Function 类型了,说明SQL中已经处理了函数,不需要插件再次处理 if (expression instanceof Column) { if (this.isYyyyMm((Column) expression)) { // 适配到 yyyy_mm 字段了 selectExpressionItem.setExpression(this.dateFormatFunction()); // 添加别名 selectExpressionItem.setAlias(new Alias(yearMonthColumn)); } } // 其他类型目前我个人的使用场景中没有遇到,暂时不编写,遇到的时候再进行编写补充 } } protected void processSelectBody(SelectBody selectBody) { if (selectBody == null) { return; } if (selectBody instanceof PlainSelect) { this.processPlainSelect((PlainSelect) selectBody); } else if (selectBody instanceof WithItem) { WithItem withItem = (WithItem) selectBody; processSelectBody(withItem.getSubSelect().getSelectBody()); } else { SetOperationList operationList = (SetOperationList) selectBody; List<SelectBody> selectBodyList = operationList.getSelects(); if (CollectionUtils.isNotEmpty(selectBodyList)) { selectBodyList.forEach(this::processSelectBody); } } } /** * 处理 PlainSelect */ protected void processPlainSelect(PlainSelect plainSelect) { //#3087 github List<SelectItem> selectItems = plainSelect.getSelectItems(); if (CollectionUtils.isNotEmpty(selectItems)) { selectItems.forEach(this::processSelectItem); } // 处理 where 中的子查询 Expression where = plainSelect.getWhere(); this.processWhereSubSelect(where); // 处理 fromItem FromItem fromItem = plainSelect.getFromItem(); List<Table> list = this.processFromItem(fromItem); List<Table> mainTables = new ArrayList<>(list); // 处理 join List<Join> joins = plainSelect.getJoins(); if (CollectionUtils.isNotEmpty(joins)) { mainTables = this.processJoins(mainTables, joins); } // 当有 mainTable 时,进行 where 条件追加 if (CollectionUtils.isNotEmpty(mainTables)) { plainSelect.setWhere(this.builderExpression(where, mainTables)); } } private Expression builderExpression(Expression where, List<Table> mainTables) { return null; } private List<Table> processJoins(List<Table> mainTables, List<Join> joins) { return null; } private List<Table> processFromItem(FromItem fromItem) { return null; } private void processWhereSubSelect(Expression where) { if (where == null) { return; } if (where instanceof FromItem) { this.processOtherFromItem((FromItem) where); return; } if (where.toString().indexOf("SELECT") > 0) { // 有子查询 if (where instanceof BinaryExpression) { // 比较符号 , and , or , 等等 BinaryExpression expression = (BinaryExpression) where; processWhereSubSelect(expression.getLeftExpression()); processWhereSubSelect(expression.getRightExpression()); } else if (where instanceof InExpression) { // in InExpression expression = (InExpression) where; Expression inExpression = expression.getRightExpression(); if (inExpression instanceof SubSelect) { processSelectBody(((SubSelect) inExpression).getSelectBody()); } } else if (where instanceof ExistsExpression) { // exists ExistsExpression expression = (ExistsExpression) where; processWhereSubSelect(expression.getRightExpression()); } else if (where instanceof NotExpression) { // not exists NotExpression expression = (NotExpression) where; processWhereSubSelect(expression.getExpression()); } else if (where instanceof Parenthesis) { Parenthesis expression = (Parenthesis) where; processWhereSubSelect(expression.getExpression()); } } // 没有子查询 this.processWhere(where); } private void processOtherFromItem(FromItem fromItem) { // 去除括号 while (fromItem instanceof ParenthesisFromItem) { fromItem = ((ParenthesisFromItem) fromItem).getFromItem(); } if (fromItem instanceof SubSelect) { SubSelect subSelect = (SubSelect) fromItem; if (subSelect.getSelectBody() != null) { processSelectBody(subSelect.getSelectBody()); } } else if (fromItem instanceof ValuesList) { logger.debug("Perform a subQuery, if you do not give us feedback"); } else if (fromItem instanceof LateralSubSelect) { LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem; if (lateralSubSelect.getSubSelect() != null) { SubSelect subSelect = lateralSubSelect.getSubSelect(); if (subSelect.getSelectBody() != null) { processSelectBody(subSelect.getSelectBody()); } } } } private void processWhere(Expression where) { // 没有 Where 语句 if (null == where) return; // Parenthesis 实现类 if (where instanceof Parenthesis) { Parenthesis parentWhere = ((Parenthesis) where); // 去除括号 this.processWhere(parentWhere.getExpression()); } // AndExpression 实现类处理 else if (where instanceof AndExpression) { this.processAndExpression((AndExpression) where); } // `column_name` IN (?,?,?) else if (where instanceof InExpression) { this.processInExpression((InExpression) where); } // (xxx =#{xxx}) else if (where instanceof EqualsTo) { this.processEqualsToExpression((EqualsTo) where); } } private void processInExpression(InExpression inExpression) { // `column_name` IN (?,?) (括号里边的子查询不做 别名处理),只处理主表的别名 Expression left = inExpression.getLeftExpression(); if (left instanceof Column) { if (this.isYyyyMm((Column) left)) { inExpression.setLeftExpression(this.dateFormatFunction()); } } // 其他情况没遇到过,暂时不做处理,使用者自行扩展处理 } private void processAndExpression(AndExpression and) { Expression left = and.getLeftExpression(); if (left instanceof EqualsTo) { this.processEqualsToExpression((EqualsTo) left); } else { this.processWhere(left); } Expression right = and.getRightExpression(); this.processWhere(right); } /** * column_name = #{?} * * @param equalsTo * @return */ private void processEqualsToExpression(EqualsTo equalsTo) { Expression left = equalsTo.getLeftExpression(); if (left instanceof Column) { if (this.isYyyyMm((Column) left)) { equalsTo.setLeftExpression(this.dateFormatFunction()); } } } private boolean isYyyyMm(Column column) { return Objects.equals(column.getColumnName(), yearMonthColumn) || Objects.equals(column.getColumnName(), String.format("`%s`", yearMonthColumn)); } private Function dateFormatFunction() { Function function = new Function(); function.setName("DATE_FORMAT"); ExpressionList expressionList = new ExpressionList( new Column(String.format("`%s`", yearMonthColumn)), new StringValue("%Y-%m")); function.setParameters(expressionList); return function; } /** * 写入数据库前拼接 -01 * * @return */ private Function concatFunction() { Function function = new Function(); function.setName("CONCAT"); ExpressionList expressionList = new ExpressionList( new JdbcParameter(1, false), new StringValue("-01")); function.setParameters(expressionList); return function; } }

 

可能存在问题:

JSQLParser 版本中会存在 SQL 解析问题,如下,官方 GitHub 中我提的  issue  已经得到回答(目前已经得到回应与解决)

    public static void main(String[] args) throws JSQLParserException {
        Statement parse = CCJSqlParserUtil.parse(
                "SELECT " +
                        "table_schema," +
                        "table_name," +
                        "table_type," +
                        "engine," +
                        "row_format," +
                        "table_comment," +
                        "table_collation," +
                        "version," +
                        "create_time " +
                        "FROM " +
                        "information_schema.tables " +
                        "WHERE " +
                        "table_schema =(SELECT DATABASE()) " +
                        "ORDER BY " +
                        "create_time DESC");
        System.out.println(parse);
    }

 

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