From a2e1fdd8e3a61c1616309d097855d25877933c26 Mon Sep 17 00:00:00 2001 From: iqudoo Date: Sun, 12 Apr 2026 13:08:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 61 ++-- ...-mybatis-generator-plugin-1.0-SNAPSHOT.jar | Bin 31646 -> 33976 bytes .../mybatis/TapeMybatisGeneratorPlugin.java | 160 +++++----- .../TapeRepositoryGeneratorPlugin.java | 290 +++++++++++------- .../mybatis/TapeRepoviewGeneratorPlugin.java | 137 ++++++--- .../framework/mybatis/utils/UtilTools.java | 15 + 6 files changed, 399 insertions(+), 264 deletions(-) create mode 100644 src/main/java/com/iqudoo/framework/mybatis/utils/UtilTools.java diff --git a/README.md b/README.md index 074a1a3..8428852 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ - `getTrashList({Example} example)` - 获取回收站记录列表(支持分页) - `countByValid({Example} example)` - 统计有效记录数 - `countByTrash({Example} example)` - 统计回收站记录数 +- `countByValidWithPage({Example} example)` - 统计有效记录数 +- `countByTrashWithPage({Example} example)` - 统计回收站记录数 - `insert({Model} record)` - 插入记录(自动生成 GUID、设置默认值) - `updateByExampleSelective({Model} record, {Example} example)` - 按条件更新记录 - `update({Model} record)` - 更新记录(支持乐观锁) @@ -60,9 +62,8 @@ **添加的字段**: - `offset` - 偏移量 - `rows` - 每页数量 -- `minPageNum` - 最小页码(默认 1) +- `startPageNum` - 最小页码(默认 1) - `ignorePageSize` - 忽略分页数量(默认 10000)每页数量大于10000时,忽略分页 -- `defaultPageSize` - 默认每页数量(默认 20) - `maxPageSize` - 最大每页数量(默认 100) - `withBLOBs` - 是否返回BLOBs列的数据 @@ -70,10 +71,12 @@ - `limit(int rows)` - 设置每页数量 - `limit(int offset, int rows)` - 设置偏移量和每页数量 - `usePage(int pageNum, int pageSize)` - 使用页码和每页数量(自动计算 offset) -- `getPageNum()` - 获取当前页码 -- `getPageSize()` - 获取当前每页数量 - `setWithBLOBs(boolean withBLOBs)` - 设置是否返回BLOBs列的数据 - `isWithBLOBs()` - 是否返回BLOBs列的数据 +- `getPageNum()` - 获取当前页码 +- `getPageSize()` - 获取当前每页数量 +- `getOffset()` - 获取当前分页limit的offset +- `getRows()` - 获取当前分页limit的rows ## 使用方法 ### 1. 在 `pom.xml` 中配置插件 @@ -138,9 +141,11 @@ + + - - + + @@ -153,21 +158,23 @@ ### 3. 配置参数说明 -| 参数名 | 说明 | 默认值 | 必需 | -|--------|---------------------|---------------------------------------------------------|------| -| `viewKeyWords` | 视图表关键字(逗号分隔,不区分大小写) | `VIEW_,V_` | 否 | -| `targetProject` | 生成代码的目标项目路径 | `src/main/java` | 否 | -| `modelPackage` | Model 类的包路径 | `com.iqudoo.platform.application.database.model` | 是 | -| `mapperPackage` | Mapper 接口的包路径 | `com.iqudoo.platform.application.database.mapper` | 是 | -| `facadeRepositoryPackage` | Repository 接口的包路径 | `com.iqudoo.platform.application.facade.repository` | 否 | -| `domainRepositoryPackage` | Repository 实现类的包路径 | `com.iqudoo.platform.application.domain.repository` | 否 | -| `facadeRepoviewPackage` | RepoView 接口的包路径 | `com.iqudoo.platform.application.facade.repoview` | 否 | -| `snowflakeUtilClass` | 雪花算法ID生成工具类 | `com.iqudoo.framework.tape.modules.utils.SnowflakeUtil` | 否 | -| `snowflakeUtilGenId` | 雪花算法ID生成方法 | `SnowflakeUtil.nextId()` | 否 | -| `slowQueryLoggerTime` | 慢查询日志时间阈值 | `200` | 否 | -| `ignorePageSize` | 忽略分页阈值 | `10000` | 否 | -| `defaultPageSize` | 默认分页数量 | `20` | 否 | -| `startPage` | 开始页码 | `1` | 否 | +| 参数名 | 说明 | 默认值 | 必需 | +|----------------------------|-------------------------------|-------------------------------------------------------|------| +| `viewKeyWords` | 视图表关键字(逗号分隔,不区分大小写) | `VIEW_,V_` | 否 | +| `targetProject` | 生成代码的目标项目路径 | `src/main/java` | 否 | +| `modelPackage` | Model 类的包路径 | `com.iqudoo.platform.application.database.model` | 是 | +| `mapperPackage` | Mapper 接口的包路径 | `com.iqudoo.platform.application.database.mapper` | 是 | +| `facadeRepositoryPackage` | Repository 接口的包路径 | `com.iqudoo.platform.application.facade.repository` | 否 | +| `domainRepositoryPackage` | Repository 实现类的包路径 | `com.iqudoo.platform.application.domain.repository` | 否 | +| `facadeRepoviewPackage` | RepoView 接口的包路径 | `com.iqudoo.platform.application.facade.repoview` | 否 | +| `snowflakeUtilClass` | 雪花算法ID生成工具类 | `com.iqudoo.framework.tape.modules.utils.SnowflakeUtil` | 否 | +| `snowflakeUtilGenId` | 雪花算法ID生成方法 | `SnowflakeUtil.nextId()` | 否 | +| `slowQueryLoggerTime` | 慢查询日志时间阈值 | `300` | 否 | +| `slowQueryLoggerLevel` | 慢查询日志类型:error,warn,debug,info | `error` | 否 | +| `priorityPrimaryKeyOffset` | 优先查询主键偏移阈值 | `0` | 否 | +| `ignorePageSize` | 忽略分页阈值 | `10000` | 否 | +| `startPageNum` | 分页开始页码 | `1` | 否 | +| `maxPageSize` | 最大每页数量 | `100` | 否 | **视图表识别规则**: - 表名包含 `viewKeyWords` 中任一关键字的表将被识别为视图表,大小写不敏感 @@ -180,7 +187,7 @@ 为了使用完整的 Repository 功能(软删除、回收站等),表结构需要包含以下标准字段: -```sql +```mysql DROP TABLE IF EXISTS `your_table_name`; CREATE TABLE `your_table_name` ( `guid` bigint(0) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'GUID', @@ -193,7 +200,15 @@ CREATE TABLE `your_table_name` ( `data_version` int(0) NOT NULL DEFAULT 0 COMMENT '数据版本', `create_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间', `update_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '更新时间', - PRIMARY KEY (`guid`) USING BTREE + PRIMARY KEY (`guid`) USING BTREE, + -- UNIQUE KEY `idx_unique_key` (`you unique key`,`delete_token`) USING BTREE, + -- KEY `idx_common_query` ( + -- 等值查询字段, + -- `is_hidden`,`is_delete`, + -- 其他参与排序字段, + -- `create_time` desc, + -- `guid`) USING BTREE + KEY `idx_common_query` (`is_hidden`,`is_delete`,`create_time` desc,`guid`) USING BTREE ) ENGINE = InnoDB COMMENT = '你的表格备注' ROW_FORMAT = Dynamic; ``` diff --git a/releases/tape-mybatis-generator-plugin-1.0-SNAPSHOT.jar b/releases/tape-mybatis-generator-plugin-1.0-SNAPSHOT.jar index a8ff2ebf6cbde366d32bc73f2dcf197df504a9f7..966cf0dd8c22de89214f16ee1f9c8aedfe79ebd8 100644 GIT binary patch delta 25625 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 delta 23368 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 LF2IOmj7{Qp*|%(mjV+@kyp?YvkzT2P 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 Zl^=NjnPW^uj6saijfH_R;QjB>e*wetsMY`g diff --git a/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java b/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java index e7214c0..0b69364 100644 --- a/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java +++ b/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java @@ -9,61 +9,60 @@ import org.mybatis.generator.api.dom.xml.Attribute; import org.mybatis.generator.api.dom.xml.Document; import org.mybatis.generator.api.dom.xml.TextElement; import org.mybatis.generator.api.dom.xml.XmlElement; +import org.mybatis.generator.config.Context; import org.mybatis.generator.internal.util.StringUtility; import java.util.List; -import java.util.Properties; @SuppressWarnings("unused") public class TapeMybatisGeneratorPlugin extends PluginAdapter { - private final static int DEFAULT_START_PAGE = 1; - private final static int DEFAULT_PAGE_SIZE = 20; - private final static int DEFAULT_IGNORE_PAGE_SIZE = 10000; - private int startPage = DEFAULT_START_PAGE; - private int defaultPageSize = DEFAULT_PAGE_SIZE; - private int ignorePageSize = DEFAULT_IGNORE_PAGE_SIZE; - - @Override - public void setProperties(Properties properties) { - super.setProperties(properties); - if (StringUtility.stringHasValue(properties.getProperty("startPage"))) { - try { - startPage = Integer.parseInt(properties.getProperty("startPage")); - } catch (Throwable ignored) { - startPage = DEFAULT_START_PAGE; - } - } - if (StringUtility.stringHasValue(properties.getProperty("defaultPageSize"))) { - try { - defaultPageSize = Integer.parseInt(properties.getProperty("defaultPageSize")); - } catch (Throwable ignored) { - defaultPageSize = DEFAULT_PAGE_SIZE; - } - } - if (StringUtility.stringHasValue(properties.getProperty("ignorePageSize"))) { - try { - ignorePageSize = Integer.parseInt(properties.getProperty("ignorePageSize")); - } catch (Throwable ignored) { - ignorePageSize = DEFAULT_IGNORE_PAGE_SIZE; - } - } - } + private int startPageNum = 1; + private int maxPageSize = 100; + private int ignorePageSize = 10000; @Override public boolean validate(List list) { return true; } + @Override + public void setContext(Context context) { + super.setContext(context); + resolveConfiguration(); + } + + private void resolveConfiguration() { + startPageNum = intConfig("startPageNum", startPageNum); + ignorePageSize = intConfig("ignorePageSize", ignorePageSize); + maxPageSize = intConfig("maxPageSize", maxPageSize); + } + + private int intConfig(String key, int defaultValue) { + String v = properties.getProperty(key); + if (!StringUtility.stringHasValue(v) && context != null) { + v = context.getProperty(key); + } + if (!StringUtility.stringHasValue(v)) { + return defaultValue; + } + try { + return Integer.parseInt(v.trim()); + } catch (Throwable ignored) { + return defaultValue; + } + } + + @SuppressWarnings("DuplicatedCode") @Override public boolean modelExampleClassGenerated(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) { PrimitiveTypeWrapper integerWrapper = FullyQualifiedJavaType.getIntInstance().getPrimitiveTypeWrapper(); - // 添加 minPageNum、defaultPageSize、maxPageSize、ignorePageSize 字段 + // 添加 startPageNum、maxPageSize、ignorePageSize 字段 Field maxPageSizeField = ElementTools.generateField( "maxPageSize", JavaVisibility.PROTECTED, integerWrapper, - "100" + this.maxPageSize + "" ); topLevelClass.addField(maxPageSizeField); @@ -75,21 +74,13 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { ); topLevelClass.addField(ignorePageSizeField); - Field defaultPageSizeField = ElementTools.generateField( - "defaultPageSize", + Field startPageNumField = ElementTools.generateField( + "startPageNum", JavaVisibility.PROTECTED, integerWrapper, - defaultPageSize + "" + startPageNum + "" ); - topLevelClass.addField(defaultPageSizeField); - - Field minPageNumField = ElementTools.generateField( - "minPageNum", - JavaVisibility.PROTECTED, - integerWrapper, - startPage + "" - ); - topLevelClass.addField(minPageNumField); + topLevelClass.addField(startPageNumField); // 添加offset和rows字段 Field offsetField = ElementTools.generateField( @@ -127,7 +118,6 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { FormatTools.addMethodWithBestPosition(topLevelClass, isWithBLOBsMethod); } - // 增加getter && setter 方法 Method mSetMaxPageSize = ElementTools.generateSetterMethod(maxPageSizeField); FormatTools.addMethodWithBestPosition(topLevelClass, mSetMaxPageSize); @@ -141,59 +131,41 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { Method mGetIgnorePageSize = ElementTools.generateGetterMethod(ignorePageSizeField); FormatTools.addMethodWithBestPosition(topLevelClass, mGetIgnorePageSize); - Method mSetDefaultPageSize = ElementTools.generateSetterMethod(defaultPageSizeField); - FormatTools.addMethodWithBestPosition(topLevelClass, mSetDefaultPageSize); + Method mSetStartPageNum = ElementTools.generateSetterMethod(startPageNumField); + FormatTools.addMethodWithBestPosition(topLevelClass, mSetStartPageNum); - Method mGetDefaultPageSize = ElementTools.generateGetterMethod(defaultPageSizeField); - FormatTools.addMethodWithBestPosition(topLevelClass, mGetDefaultPageSize); - - Method mSetMinPageNum = ElementTools.generateSetterMethod(minPageNumField); - FormatTools.addMethodWithBestPosition(topLevelClass, mSetMinPageNum); - - Method mGetMinPageNum = ElementTools.generateGetterMethod(minPageNumField); - FormatTools.addMethodWithBestPosition(topLevelClass, mGetMinPageNum); - - Method mSetOffset = ElementTools.generateSetterMethod(offsetField); - FormatTools.addMethodWithBestPosition(topLevelClass, mSetOffset); - - Method mGetOffset = ElementTools.generateGetterMethod(offsetField); - FormatTools.addMethodWithBestPosition(topLevelClass, mGetOffset); - - Method mSetRows = ElementTools.generateSetterMethod(rowsField); - FormatTools.addMethodWithBestPosition(topLevelClass, mSetRows); - - Method mGetRows = ElementTools.generateGetterMethod(rowsField); - FormatTools.addMethodWithBestPosition(topLevelClass, mGetRows); + Method mGetStartPageNum = ElementTools.generateGetterMethod(startPageNumField); + FormatTools.addMethodWithBestPosition(topLevelClass, mGetStartPageNum); // 提供几个快捷方法 - Method setLimit = ElementTools.generateMethod( + Method setLimitByRows = ElementTools.generateMethod( "limit", JavaVisibility.PUBLIC, topLevelClass.getType(), new Parameter(integerWrapper, "rows") ); - setLimit = ElementTools.generateMethodBody( - setLimit, + setLimitByRows = ElementTools.generateMethodBody( + setLimitByRows, "this.offset = null;", "this.rows = rows;", "return this;" ); - FormatTools.addMethodWithBestPosition(topLevelClass, setLimit); + FormatTools.addMethodWithBestPosition(topLevelClass, setLimitByRows); - Method setLimit2 = ElementTools.generateMethod( + Method setLimitByOffsetRows = ElementTools.generateMethod( "limit", JavaVisibility.PUBLIC, topLevelClass.getType(), new Parameter(integerWrapper, "offset"), new Parameter(integerWrapper, "rows") ); - setLimit2 = ElementTools.generateMethodBody( - setLimit2, + setLimitByOffsetRows = ElementTools.generateMethodBody( + setLimitByOffsetRows, "this.offset = offset;", "this.rows = rows;", "return this;" ); - FormatTools.addMethodWithBestPosition(topLevelClass, setLimit2); + FormatTools.addMethodWithBestPosition(topLevelClass, setLimitByOffsetRows); Method usePage = ElementTools.generateMethod( "usePage", @@ -204,15 +176,15 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { ); usePage = ElementTools.generateMethodBody( usePage, - "pageSize = pageSize == null || pageSize <= 0 ? this.defaultPageSize : pageSize;", - "pageNum = pageNum == null || pageNum < this.minPageNum ? this.minPageNum : pageNum;", + "pageSize = pageSize == null || pageSize <= 0 ? 1 : pageSize;", + "pageNum = pageNum == null || pageNum < this.startPageNum ? this.startPageNum : pageNum;", "if (pageSize >= this.ignorePageSize) {", "this.rows = null;", "this.offset = null;", "return this;", "}", "int cPageSize = pageSize > this.maxPageSize ? this.maxPageSize: pageSize;", - "this.offset = (pageNum - this.minPageNum) * cPageSize;", + "this.offset = (pageNum - this.startPageNum) * cPageSize;", "this.rows = cPageSize;", "return this;" ); @@ -227,9 +199,9 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { getPageNum = ElementTools.generateMethodBody( getPageNum, "if (this.rows == null || this.offset == null || this.rows == 0) {", - "return this.minPageNum;", + "return this.startPageNum;", "}", - "return this.offset / this.rows + this.minPageNum;" + "return this.offset / this.rows + this.startPageNum;" ); FormatTools.addMethodWithBestPosition(topLevelClass, getPageNum); // 计算获取当前每页数量 @@ -247,6 +219,28 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { ); FormatTools.addMethodWithBestPosition(topLevelClass, getPageSize); + Method getRows = ElementTools.generateMethod( + "getRows", + JavaVisibility.PUBLIC, + integerWrapper + ); + getRows = ElementTools.generateMethodBody( + getRows, + "return this.rows;" + ); + FormatTools.addMethodWithBestPosition(topLevelClass, getRows); + + Method getOffset = ElementTools.generateMethod( + "getOffset", + JavaVisibility.PUBLIC, + integerWrapper + ); + getOffset = ElementTools.generateMethodBody( + getOffset, + "return this.offset;" + ); + FormatTools.addMethodWithBestPosition(topLevelClass, getOffset); + // !!! clear 方法增加 offset 和 rows的清理 List methodList = topLevelClass.getMethods(); for (Method method : methodList) { diff --git a/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java b/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java index fed2138..77f5541 100644 --- a/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java +++ b/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java @@ -1,6 +1,7 @@ package com.iqudoo.framework.mybatis; import com.iqudoo.framework.mybatis.utils.ElementTools; +import com.iqudoo.framework.mybatis.utils.UtilTools; import org.mybatis.generator.api.*; import org.mybatis.generator.api.dom.DefaultJavaFormatter; import org.mybatis.generator.api.dom.java.*; @@ -11,13 +12,14 @@ import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Properties; @SuppressWarnings({"DuplicatedCode", "unused", "SpellCheckingInspection", "ExtractMethodRecommender"}) public class TapeRepositoryGeneratorPlugin extends PluginAdapter { // 固定配置项 private String slowQueryLoggerTime = "300"; + private String priorityPrimaryKeyOffset = "0"; + private String slowQueryLoggerLevel = "error"; private String snowflakeUtilClass = "com.iqudoo.framework.tape.modules.utils.SnowflakeUtil"; private String snowflakeUtilGenId = "SnowflakeUtil.nextId()"; private String facadeRepositoryPackage = "com.iqudoo.platform.application.facade.repository"; @@ -30,49 +32,51 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { // 1.4.1版本专用:Java格式化器 private JavaFormatter javaFormatter; - @Override - public void setContext(Context context) { - super.setContext(context); - this.javaFormatter = new DefaultJavaFormatter(); - this.javaFormatter.setContext(context); - } - @Override public boolean validate(List warnings) { return true; } @Override - public void setProperties(Properties properties) { - super.setProperties(properties); - // 读取自定义配置 - if (StringUtility.stringHasValue(properties.getProperty("slowQueryLoggerTime"))) { - slowQueryLoggerTime = properties.getProperty("slowQueryLoggerTime"); + public void setContext(Context context) { + super.setContext(context); + this.javaFormatter = new DefaultJavaFormatter(); + this.javaFormatter.setContext(context); + resolveConfiguration(); + } + + private void resolveConfiguration() { + slowQueryLoggerTime = stringConfig("slowQueryLoggerTime", slowQueryLoggerTime); + slowQueryLoggerLevel = stringConfig("slowQueryLoggerLevel", slowQueryLoggerLevel); + priorityPrimaryKeyOffset = stringConfig("priorityPrimaryKeyOffset", priorityPrimaryKeyOffset); + if (!UtilTools.inArray(new String[]{"error", "warn", "debug", "info"}, slowQueryLoggerLevel)) { + slowQueryLoggerLevel = "error"; } - if (StringUtility.stringHasValue(properties.getProperty("snowflakeUtilClass"))) { - snowflakeUtilClass = properties.getProperty("snowflakeUtilClass"); + snowflakeUtilClass = stringConfig("snowflakeUtilClass", snowflakeUtilClass); + snowflakeUtilGenId = stringConfig("snowflakeUtilGenId", snowflakeUtilGenId); + facadeRepositoryPackage = stringConfig("facadeRepositoryPackage", facadeRepositoryPackage); + domainRepositoryPackage = stringConfig("domainRepositoryPackage", domainRepositoryPackage); + modelPackage = stringConfig("modelPackage", modelPackage); + mapperPackage = stringConfig("mapperPackage", mapperPackage); + targetProject = stringConfig("targetProject", targetProject); + viewKeyWords = stringConfig("viewKeyWords", viewKeyWords); + if (StringUtility.stringHasValue(viewKeyWords)) { + viewKeyWords = viewKeyWords.toUpperCase(); } - if (StringUtility.stringHasValue(properties.getProperty("snowflakeUtilGenId"))) { - snowflakeUtilGenId = properties.getProperty("snowflakeUtilGenId"); + } + + private String stringConfig(String key, String defaultValue) { + String v = properties.getProperty(key); + if (StringUtility.stringHasValue(v)) { + return v; } - if (StringUtility.stringHasValue(properties.getProperty("facadeRepositoryPackage"))) { - facadeRepositoryPackage = properties.getProperty("facadeRepositoryPackage"); - } - if (StringUtility.stringHasValue(properties.getProperty("domainRepositoryPackage"))) { - domainRepositoryPackage = properties.getProperty("domainRepositoryPackage"); - } - if (StringUtility.stringHasValue(properties.getProperty("modelPackage"))) { - modelPackage = properties.getProperty("modelPackage"); - } - if (StringUtility.stringHasValue(properties.getProperty("mapperPackage"))) { - mapperPackage = properties.getProperty("mapperPackage"); - } - if (StringUtility.stringHasValue(properties.getProperty("targetProject"))) { - targetProject = properties.getProperty("targetProject"); - } - if (StringUtility.stringHasValue(properties.getProperty("viewKeyWords"))) { - viewKeyWords = properties.getProperty("viewKeyWords").toUpperCase(); + if (context != null) { + v = context.getProperty(key); + if (StringUtility.stringHasValue(v)) { + return v; + } } + return defaultValue; } /** @@ -286,7 +290,16 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { countByValidMethod.setAbstract(true); repositoryInterface.addMethod(countByValidMethod); - // 15. countByTrash + // 15. countByValidWithPage + Method countByValidWithPageMethod = new Method("countByValidWithPage"); + countByValidWithPageMethod.setVisibility(JavaVisibility.PUBLIC); + countByValidWithPageMethod.setReturnType(new FullyQualifiedJavaType("long")); + countByValidWithPageMethod.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); + countByValidWithPageMethod.addException(new FullyQualifiedJavaType("Throwable")); + countByValidWithPageMethod.setAbstract(true); + repositoryInterface.addMethod(countByValidWithPageMethod); + + // 16. countByTrash Method countByTrashMethod = new Method("countByTrash"); countByTrashMethod.setVisibility(JavaVisibility.PUBLIC); countByTrashMethod.setReturnType(new FullyQualifiedJavaType("long")); @@ -295,7 +308,16 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { countByTrashMethod.setAbstract(true); repositoryInterface.addMethod(countByTrashMethod); - // 16. insert + // 17. countByTrashWithPage + Method countByTrashWithPageMethod = new Method("countByTrashWithPage"); + countByTrashWithPageMethod.setVisibility(JavaVisibility.PUBLIC); + countByTrashWithPageMethod.setReturnType(new FullyQualifiedJavaType("long")); + countByTrashWithPageMethod.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); + countByTrashWithPageMethod.addException(new FullyQualifiedJavaType("Throwable")); + countByTrashWithPageMethod.setAbstract(true); + repositoryInterface.addMethod(countByTrashWithPageMethod); + + // 18. insert Method insertMethod = new Method("insert"); insertMethod.setVisibility(JavaVisibility.PUBLIC); insertMethod.setReturnType(new FullyQualifiedJavaType(modelClassName)); @@ -304,7 +326,7 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { insertMethod.setAbstract(true); repositoryInterface.addMethod(insertMethod); - // 17. update + // 19. update Method updateMethod = new Method("update"); updateMethod.setVisibility(JavaVisibility.PUBLIC); updateMethod.setReturnType(new FullyQualifiedJavaType("int")); @@ -313,7 +335,7 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { updateMethod.setAbstract(true); repositoryInterface.addMethod(updateMethod); - // 17. updateByExampleSelective + // 20. updateByExampleSelective Method updateByExampleSelectiveMethod = new Method("updateByExampleSelective"); updateByExampleSelectiveMethod.setVisibility(JavaVisibility.PUBLIC); updateByExampleSelectiveMethod.setReturnType(new FullyQualifiedJavaType("int")); @@ -379,8 +401,10 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { generateFindTrashOneMethod(implClass, modelClassName, exampleClassName); generateGetValidListMethod(implClass, modelClassName, exampleClassName, mapperFieldName, hasBLOBColumns); generateGetTrashListMethod(implClass, modelClassName, exampleClassName, mapperFieldName, hasBLOBColumns); - generateCountByValidMethod(implClass, exampleClassName, mapperFieldName); - generateCountByTrashMethod(implClass, exampleClassName, mapperFieldName); + generateCountByValidMethod(implClass, modelClassName, exampleClassName, mapperFieldName); + generateCountByValidWithPageMethod(implClass, modelClassName, exampleClassName, mapperFieldName); + generateCountByTrashMethod(implClass, modelClassName, exampleClassName, mapperFieldName); + generateCountByTrashWithPageMethod(implClass, modelClassName, exampleClassName, mapperFieldName); return implClass; } @@ -798,43 +822,48 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { method.addBodyLine("for (" + exampleClassName + ".Criteria criteria : example.getOredCriteria()) {"); method.addBodyLine("criteria.andIsDeleteEqualTo(0).andIsHiddenEqualTo(0);"); method.addBodyLine("}"); + method.addBodyLine("List<" + modelClassName + "> result = null;"); method.addBodyLine("long startTime = new Date().getTime();"); - method.addBodyLine("try {"); - method.addBodyLine("if (example.getRows() != null && example.getOffset() != null) {"); - method.addBodyLine("List primaryKeyList = " + mapperFieldName + ".selectPrimaryKeyByExample(example);"); - method.addBodyLine("if (primaryKeyList == null || primaryKeyList.isEmpty()) {"); - method.addBodyLine("return new ArrayList<>();"); - method.addBodyLine("}"); - method.addBodyLine("long findPrimaryKeyTime = new Date().getTime() - startTime;"); - method.addBodyLine("if (findPrimaryKeyTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"find valid list primary key use long time: \" + findPrimaryKeyTime + \"ms\");"); - method.addBodyLine("}"); - method.addBodyLine("String oldOrderByClause = example.getOrderByClause();"); if (hasBLOBColumns) { - method.addBodyLine("Boolean withBLOBsFlag = example.isWithBLOBs();"); - } - method.addBodyLine("example = new " + exampleClassName + "();"); - method.addBodyLine("example.createCriteria().andGuidIn(primaryKeyList);"); - method.addBodyLine("example.setOrderByClause(oldOrderByClause);"); - if (hasBLOBColumns) { - method.addBodyLine("example.setWithBLOBs(withBLOBsFlag);"); - } - method.addBodyLine("}"); - - if (hasBLOBColumns) { - method.addBodyLine("if (example.isWithBLOBs()) {"); - method.addBodyLine("return " + mapperFieldName + ".selectByExampleWithBLOBs(example);"); + method.addBodyLine("if (example.getRows() != null && example.getOffset() != null && example.getOffset() > " + priorityPrimaryKeyOffset + ") {"); + method.addBodyLine("List primaryKeyList = " + mapperFieldName + ".selectPrimaryKeyByExample(example);"); + method.addBodyLine("if (primaryKeyList == null || primaryKeyList.isEmpty()) {"); + method.addBodyLine("return new ArrayList<>();"); + method.addBodyLine("}"); + method.addBodyLine("long findPrimaryKeyTime = new Date().getTime() - startTime;"); + method.addBodyLine("if (findPrimaryKeyTime > " + slowQueryLoggerTime + ") {"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " valid list primary key use long time: \" + findPrimaryKeyTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); + method.addBodyLine("}"); + method.addBodyLine("// reset start time"); + method.addBodyLine("startTime = new Date().getTime();"); + method.addBodyLine("String oldOrderByClause = example.getOrderByClause();"); + method.addBodyLine("Boolean withBLOBsFlag = example.isWithBLOBs();"); + method.addBodyLine("example = new " + exampleClassName + "();"); + method.addBodyLine("example.createCriteria().andGuidIn(primaryKeyList);"); + method.addBodyLine("example.setOrderByClause(oldOrderByClause);"); + method.addBodyLine("example.setWithBLOBs(withBLOBsFlag);"); + method.addBodyLine("}"); + method.addBodyLine("if (example.isWithBLOBs()) {"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExampleWithBLOBs(example);"); + method.addBodyLine("} else {"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExample(example);"); method.addBodyLine("}"); - method.addBodyLine("return " + mapperFieldName + ".selectByExample(example);"); } else { - method.addBodyLine("return " + mapperFieldName + ".selectByExample(example);"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExample(example);"); } - method.addBodyLine("} finally {"); method.addBodyLine("long useTime = new Date().getTime() - startTime;"); method.addBodyLine("if (useTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"get valid list use long time: \" + useTime + \"ms\");"); - method.addBodyLine("}"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " valid list use long time: \" + useTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); method.addBodyLine("}"); + method.addBodyLine("return result;"); implClass.addMethod(method); } @@ -850,47 +879,52 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { method.addBodyLine("for (" + exampleClassName + ".Criteria criteria : example.getOredCriteria()) {"); method.addBodyLine("criteria.andIsDeleteEqualTo(0).andIsHiddenEqualTo(1);"); method.addBodyLine("}"); + method.addBodyLine("List<" + modelClassName + "> result = null;"); method.addBodyLine("long startTime = new Date().getTime();"); - method.addBodyLine("try {"); - method.addBodyLine("if (example.getRows() != null && example.getOffset() != null) {"); - method.addBodyLine("List primaryKeyList = " + mapperFieldName + ".selectPrimaryKeyByExample(example);"); - method.addBodyLine("if (primaryKeyList == null || primaryKeyList.isEmpty()) {"); - method.addBodyLine("return new ArrayList<>();"); - method.addBodyLine("}"); - method.addBodyLine("long findPrimaryKeyTime = new Date().getTime() - startTime;"); - method.addBodyLine("if (findPrimaryKeyTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"find trash list primary key use long time: \" + findPrimaryKeyTime + \"ms\");"); - method.addBodyLine("}"); - method.addBodyLine("String oldOrderByClause = example.getOrderByClause();"); if (hasBLOBColumns) { - method.addBodyLine("Boolean withBLOBsFlag = example.isWithBLOBs();"); - } - method.addBodyLine("example = new " + exampleClassName + "();"); - method.addBodyLine("example.createCriteria().andGuidIn(primaryKeyList);"); - method.addBodyLine("example.setOrderByClause(oldOrderByClause);"); - if (hasBLOBColumns) { - method.addBodyLine("example.setWithBLOBs(withBLOBsFlag);"); - } - method.addBodyLine("}"); - - if (hasBLOBColumns) { - method.addBodyLine("if (example.isWithBLOBs()) {"); - method.addBodyLine("return " + mapperFieldName + ".selectByExampleWithBLOBs(example);"); + method.addBodyLine("if (example.getRows() != null && example.getOffset() != null && example.getOffset() > " + priorityPrimaryKeyOffset + ") {"); + method.addBodyLine("List primaryKeyList = " + mapperFieldName + ".selectPrimaryKeyByExample(example);"); + method.addBodyLine("if (primaryKeyList == null || primaryKeyList.isEmpty()) {"); + method.addBodyLine("return new ArrayList<>();"); + method.addBodyLine("}"); + method.addBodyLine("long findPrimaryKeyTime = new Date().getTime() - startTime;"); + method.addBodyLine("if (findPrimaryKeyTime > " + slowQueryLoggerTime + ") {"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " trash list primary key use long time: \" + findPrimaryKeyTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); + method.addBodyLine("}"); + method.addBodyLine("// reset start time"); + method.addBodyLine("startTime = new Date().getTime();"); + method.addBodyLine("String oldOrderByClause = example.getOrderByClause();"); + method.addBodyLine("Boolean withBLOBsFlag = example.isWithBLOBs();"); + method.addBodyLine("example = new " + exampleClassName + "();"); + method.addBodyLine("example.createCriteria().andGuidIn(primaryKeyList);"); + method.addBodyLine("example.setOrderByClause(oldOrderByClause);"); + method.addBodyLine("example.setWithBLOBs(withBLOBsFlag);"); + method.addBodyLine("}"); + method.addBodyLine("if (example.isWithBLOBs()) {"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExampleWithBLOBs(example);"); + method.addBodyLine("} else {"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExample(example);"); method.addBodyLine("}"); - method.addBodyLine("return " + mapperFieldName + ".selectByExample(example);"); } else { - method.addBodyLine("return " + mapperFieldName + ".selectByExample(example);"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExample(example);"); } - method.addBodyLine("} finally {"); method.addBodyLine("long useTime = new Date().getTime() - startTime;"); method.addBodyLine("if (useTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"get trash list use long time: \" + useTime + \"ms\");"); - method.addBodyLine("}"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " trash list use long time: \" + useTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); method.addBodyLine("}"); + method.addBodyLine("return result;"); implClass.addMethod(method); } - private void generateCountByValidMethod(TopLevelClass implClass, String exampleClassName, String mapperFieldName) { + private void generateCountByValidMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { Method method = new Method("countByValid"); method.addAnnotation("@Override"); method.setVisibility(JavaVisibility.PUBLIC); @@ -902,19 +936,38 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { method.addBodyLine("criteria.andIsDeleteEqualTo(0).andIsHiddenEqualTo(0);"); method.addBodyLine("}"); method.addBodyLine("long startTime = new Date().getTime();"); - method.addBodyLine("try {"); - method.addBodyLine("return " + mapperFieldName + ".countByExample(example);"); - method.addBodyLine("} finally {"); + method.addBodyLine("long count = " + mapperFieldName + ".countByExample(example);"); method.addBodyLine("long useTime = new Date().getTime() - startTime;"); method.addBodyLine("if (useTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"count by valid use long time: \" + useTime + \"ms\");"); - method.addBodyLine("}"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " valid count use long time: \" + useTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); method.addBodyLine("}"); + method.addBodyLine("return count;"); implClass.addMethod(method); } - private void generateCountByTrashMethod(TopLevelClass implClass, String exampleClassName, String mapperFieldName) { + private void generateCountByValidWithPageMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { + Method method = new Method("countByValidWithPage"); + method.addAnnotation("@Override"); + method.setVisibility(JavaVisibility.PUBLIC); + method.setReturnType(new FullyQualifiedJavaType("long")); + method.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); + method.addException(new FullyQualifiedJavaType("Throwable")); + + method.addBodyLine("// When not paginated, the count query returns 0 to avoid unnecessary queries"); + method.addBodyLine("if (example.getRows() != null && example.getOffset() != null) {"); + method.addBodyLine("return countByValid(example);"); + method.addBodyLine("}"); + method.addBodyLine("return 0L;"); + + implClass.addMethod(method); + } + + private void generateCountByTrashMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { Method method = new Method("countByTrash"); method.addAnnotation("@Override"); method.setVisibility(JavaVisibility.PUBLIC); @@ -926,14 +979,33 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { method.addBodyLine("criteria.andIsDeleteEqualTo(0).andIsHiddenEqualTo(1);"); method.addBodyLine("}"); method.addBodyLine("long startTime = new Date().getTime();"); - method.addBodyLine("try {"); - method.addBodyLine("return " + mapperFieldName + ".countByExample(example);"); - method.addBodyLine("} finally {"); + method.addBodyLine("long count = " + mapperFieldName + ".countByExample(example);"); method.addBodyLine("long useTime = new Date().getTime() - startTime;"); method.addBodyLine("if (useTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"count by trash use long time: \" + useTime + \"ms\");"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " trash count use long time: \" + useTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); method.addBodyLine("}"); + method.addBodyLine("return count;"); + + implClass.addMethod(method); + } + + private void generateCountByTrashWithPageMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { + Method method = new Method("countByTrashWithPage"); + method.addAnnotation("@Override"); + method.setVisibility(JavaVisibility.PUBLIC); + method.setReturnType(new FullyQualifiedJavaType("long")); + method.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); + method.addException(new FullyQualifiedJavaType("Throwable")); + + method.addBodyLine("// When not paginated, the count query returns 0 to avoid unnecessary queries"); + method.addBodyLine("if (example.getRows() != null && example.getOffset() != null) {"); + method.addBodyLine("return countByTrash(example);"); method.addBodyLine("}"); + method.addBodyLine("return 0L;"); implClass.addMethod(method); } diff --git a/src/main/java/com/iqudoo/framework/mybatis/TapeRepoviewGeneratorPlugin.java b/src/main/java/com/iqudoo/framework/mybatis/TapeRepoviewGeneratorPlugin.java index 2384dda..48a901e 100644 --- a/src/main/java/com/iqudoo/framework/mybatis/TapeRepoviewGeneratorPlugin.java +++ b/src/main/java/com/iqudoo/framework/mybatis/TapeRepoviewGeneratorPlugin.java @@ -1,6 +1,7 @@ package com.iqudoo.framework.mybatis; import com.iqudoo.framework.mybatis.utils.ElementTools; +import com.iqudoo.framework.mybatis.utils.UtilTools; import org.mybatis.generator.api.GeneratedJavaFile; import org.mybatis.generator.api.IntrospectedTable; import org.mybatis.generator.api.JavaFormatter; @@ -14,62 +15,65 @@ import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import java.util.Properties; @SuppressWarnings({"DuplicatedCode", "SpellCheckingInspection", "ExtractMethodRecommender"}) public class TapeRepoviewGeneratorPlugin extends PluginAdapter { // 视图Repo包配置(可通过配置文件自定义) private String slowQueryLoggerTime = "300"; + private String slowQueryLoggerLevel = "error"; private String facadeRepoviewPackage = "com.iqudoo.platform.application.facade.repoview"; private String domainRepoviewPackage = "com.iqudoo.platform.application.domain.repoview"; private String modelPackage = "com.iqudoo.platform.application.database.model"; private String mapperPackage = "com.iqudoo.platform.application.database.mapper"; private String targetProject = "src/main/java"; - - // 视图名称关键字(可配置) private String viewKeyWords = "VIEW_,V_"; // 1.4.1版本专用格式化器 private JavaFormatter javaFormatter; - @Override - public void setContext(Context context) { - super.setContext(context); - this.javaFormatter = new DefaultJavaFormatter(); - this.javaFormatter.setContext(context); - } - @Override public boolean validate(List warnings) { return true; } @Override - public void setProperties(Properties properties) { - super.setProperties(properties); - // 读取自定义配置 - if (StringUtility.stringHasValue(properties.getProperty("slowQueryLoggerTime"))) { - slowQueryLoggerTime = properties.getProperty("slowQueryLoggerTime"); + public void setContext(Context context) { + super.setContext(context); + this.javaFormatter = new DefaultJavaFormatter(); + this.javaFormatter.setContext(context); + resolveConfiguration(); + } + + private void resolveConfiguration() { + slowQueryLoggerTime = stringConfig("slowQueryLoggerTime", slowQueryLoggerTime); + slowQueryLoggerLevel = stringConfig("slowQueryLoggerLevel", slowQueryLoggerLevel); + if (!UtilTools.inArray(new String[]{"error", "warn", "debug", "info"}, slowQueryLoggerLevel)) { + slowQueryLoggerLevel = "error"; } - if (StringUtility.stringHasValue(properties.getProperty("facadeRepoviewPackage"))) { - facadeRepoviewPackage = properties.getProperty("facadeRepoviewPackage"); + facadeRepoviewPackage = stringConfig("facadeRepoviewPackage", facadeRepoviewPackage); + domainRepoviewPackage = stringConfig("domainRepoviewPackage", domainRepoviewPackage); + modelPackage = stringConfig("modelPackage", modelPackage); + mapperPackage = stringConfig("mapperPackage", mapperPackage); + targetProject = stringConfig("targetProject", targetProject); + viewKeyWords = stringConfig("viewKeyWords", viewKeyWords); + if (StringUtility.stringHasValue(viewKeyWords)) { + viewKeyWords = viewKeyWords.toUpperCase(); } - if (StringUtility.stringHasValue(properties.getProperty("domainRepoviewPackage"))) { - domainRepoviewPackage = properties.getProperty("domainRepoviewPackage"); + } + + private String stringConfig(String key, String defaultValue) { + String v = properties.getProperty(key); + if (StringUtility.stringHasValue(v)) { + return v; } - if (StringUtility.stringHasValue(properties.getProperty("modelPackage"))) { - modelPackage = properties.getProperty("modelPackage"); - } - if (StringUtility.stringHasValue(properties.getProperty("mapperPackage"))) { - mapperPackage = properties.getProperty("mapperPackage"); - } - if (StringUtility.stringHasValue(properties.getProperty("targetProject"))) { - targetProject = properties.getProperty("targetProject"); - } - if (StringUtility.stringHasValue(properties.getProperty("viewKeyWords"))) { - viewKeyWords = properties.getProperty("viewKeyWords").toUpperCase(); + if (context != null) { + v = context.getProperty(key); + if (StringUtility.stringHasValue(v)) { + return v; + } } + return defaultValue; } /** @@ -183,6 +187,15 @@ public class TapeRepoviewGeneratorPlugin extends PluginAdapter { countMethod.setAbstract(true); repoInterface.addMethod(countMethod); + // 4. 添加countWithPage方法 + Method countWithPageMethod = new Method("countWithPage"); + countWithPageMethod.setVisibility(JavaVisibility.PUBLIC); + countWithPageMethod.setReturnType(new FullyQualifiedJavaType("long")); + countWithPageMethod.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); + countWithPageMethod.addException(new FullyQualifiedJavaType("Throwable")); + countWithPageMethod.setAbstract(true); + repoInterface.addMethod(countWithPageMethod); + return repoInterface; } @@ -225,12 +238,10 @@ public class TapeRepoviewGeneratorPlugin extends PluginAdapter { mapperField.addAnnotation("@Resource"); implClass.addField(mapperField); - // 生成findOne方法 - generateFindOneMethod(implClass, modelClassName, exampleClassName, mapperFieldName); - // 生成getList方法 + generateFindOneMethod(implClass, modelClassName, exampleClassName); generateGetListMethod(implClass, modelClassName, exampleClassName, mapperFieldName, hasBLOBColumns); - // 生成count方法 - generateCountMethod(implClass, exampleClassName, mapperFieldName); + generateCountMethod(implClass, modelClassName, exampleClassName, mapperFieldName); + generateCountWithPageMethod(implClass, modelClassName, exampleClassName, mapperFieldName); return implClass; } @@ -251,14 +262,14 @@ public class TapeRepoviewGeneratorPlugin extends PluginAdapter { implClass.addImportedType(new FullyQualifiedJavaType("javax.annotation.Resource")); implClass.addImportedType(new FullyQualifiedJavaType("org.slf4j.Logger")); implClass.addImportedType(new FullyQualifiedJavaType("org.slf4j.LoggerFactory")); - implClass.addImportedType(new FullyQualifiedJavaType("java.util.ArrayList")); + implClass.addImportedType(new FullyQualifiedJavaType("java.util.Date")); implClass.addImportedType(new FullyQualifiedJavaType("java.util.List")); } /** * 生成findOne方法 */ - private void generateFindOneMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { + private void generateFindOneMethod(TopLevelClass implClass, String modelClassName, String exampleClassName) { Method method = new Method("findOne"); method.addAnnotation("@Override"); method.setVisibility(JavaVisibility.PUBLIC); @@ -289,29 +300,33 @@ public class TapeRepoviewGeneratorPlugin extends PluginAdapter { // 参数名匹配示例(首字母小写) method.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); method.addException(new FullyQualifiedJavaType("Throwable")); + method.addBodyLine("List<" + modelClassName + "> result = null;"); method.addBodyLine("long startTime = new Date().getTime();"); - method.addBodyLine("try {"); if (hasBLOBColumns) { method.addBodyLine("if (example.isWithBLOBs()) {"); - method.addBodyLine("return " + mapperFieldName + ".selectByExampleWithBLOBs(example);"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExampleWithBLOBs(example);"); + method.addBodyLine("} else {"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExample(example);"); method.addBodyLine("}"); - method.addBodyLine("return " + mapperFieldName + ".selectByExample(example);"); } else { - method.addBodyLine("return " + mapperFieldName + ".selectByExample(example);"); + method.addBodyLine("result = " + mapperFieldName + ".selectByExample(example);"); } - method.addBodyLine("} finally {"); method.addBodyLine("long useTime = new Date().getTime() - startTime;"); method.addBodyLine("if (useTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"get view list use long time: \" + useTime + \"ms\");"); - method.addBodyLine("}"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " view list use long time: \" + useTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); method.addBodyLine("}"); + method.addBodyLine("return result;"); implClass.addMethod(method); } /** * 生成count方法 */ - private void generateCountMethod(TopLevelClass implClass, String exampleClassName, String mapperFieldName) { + private void generateCountMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { Method method = new Method("count"); method.addAnnotation("@Override"); method.setVisibility(JavaVisibility.PUBLIC); @@ -322,14 +337,38 @@ public class TapeRepoviewGeneratorPlugin extends PluginAdapter { // 方法体 method.addBodyLine("long startTime = new Date().getTime();"); - method.addBodyLine("try {"); - method.addBodyLine("return " + mapperFieldName + ".countByExample(example);"); - method.addBodyLine("} finally {"); + method.addBodyLine("long count = " + mapperFieldName + ".countByExample(example);"); method.addBodyLine("long useTime = new Date().getTime() - startTime;"); method.addBodyLine("if (useTime > " + slowQueryLoggerTime + ") {"); - method.addBodyLine("LOGGER.error(\"count view use long time: \" + useTime + \"ms\");"); + method.addBodyLine("LOGGER." + slowQueryLoggerLevel + "(\"Select " + modelClassName + " view count use long time: \" + useTime + \"ms\" +"); + method.addBodyLine(" \"\\n\\t|-> criteria: \" + example.getOredCriteria() +"); + method.addBodyLine(" \"\\n\\t|-> order by: \" + example.getOrderByClause() +"); + method.addBodyLine(" \"\\n\\t|-----------------------------------\""); + method.addBodyLine(");"); method.addBodyLine("}"); + method.addBodyLine("return count;"); + + implClass.addMethod(method); + } + + /** + * 生成count方法 + */ + private void generateCountWithPageMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { + Method method = new Method("countWithPage"); + method.addAnnotation("@Override"); + method.setVisibility(JavaVisibility.PUBLIC); + method.setReturnType(new FullyQualifiedJavaType("long")); + // 参数名匹配示例(首字母小写) + method.addParameter(new Parameter(new FullyQualifiedJavaType(exampleClassName), "example")); + method.addException(new FullyQualifiedJavaType("Throwable")); + + // 方法体 + method.addBodyLine("// When not paginated, the count query returns 0 to avoid unnecessary queries"); + method.addBodyLine("if (example.getRows() != null && example.getOffset() != null) {"); + method.addBodyLine("return count(example);"); method.addBodyLine("}"); + method.addBodyLine("return 0L;"); implClass.addMethod(method); } diff --git a/src/main/java/com/iqudoo/framework/mybatis/utils/UtilTools.java b/src/main/java/com/iqudoo/framework/mybatis/utils/UtilTools.java new file mode 100644 index 0000000..ab60741 --- /dev/null +++ b/src/main/java/com/iqudoo/framework/mybatis/utils/UtilTools.java @@ -0,0 +1,15 @@ +package com.iqudoo.framework.mybatis.utils; + +@SuppressWarnings("BooleanMethodIsAlwaysInverted") +public class UtilTools { + + public static boolean inArray(T[] array, T target) { + for (T val : array) { + if (val != null && val.equals(target)) { + return true; + } + } + return false; + } + +}