diff --git a/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java b/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java index 9774a23..76c4ed7 100644 --- a/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java +++ b/src/main/java/com/iqudoo/framework/mybatis/TapeMybatisGeneratorPlugin.java @@ -2,6 +2,7 @@ package com.iqudoo.framework.mybatis; import com.iqudoo.framework.mybatis.utils.ElementTools; import com.iqudoo.framework.mybatis.utils.FormatTools; +import org.mybatis.generator.api.IntrospectedColumn; import org.mybatis.generator.api.IntrospectedTable; import org.mybatis.generator.api.PluginAdapter; import org.mybatis.generator.api.dom.java.*; @@ -339,16 +340,160 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { element.addElement(ifLimitNotNullElement); } + // ============================================ batchInsert ========================================== + + private void addBatchInsertClientMethod(Interface interfaze, IntrospectedTable introspectedTable) { + if (introspectedTable.getAllColumns().isEmpty()) { + return; + } + FullyQualifiedJavaType rowType = ElementTools.hasBLOBColumns(introspectedTable) + ? ElementTools.getModelTypeWithBLOBs(introspectedTable) + : ElementTools.getModelTypeWithoutBLOBs(introspectedTable); + FullyQualifiedJavaType listType = FullyQualifiedJavaType.getNewListInstance(); + listType.addTypeArgument(rowType); + Parameter recordsParam = new Parameter(listType, "records"); + recordsParam.addAnnotation("@Param(\"records\")"); + + Method batchInsert = new Method("batchInsert"); + batchInsert.setAbstract(true); + batchInsert.setVisibility(JavaVisibility.PUBLIC); + batchInsert.setReturnType(FullyQualifiedJavaType.getIntInstance()); + batchInsert.addParameter(recordsParam); + + interfaze.addImportedType(new FullyQualifiedJavaType("java.util.List")); + interfaze.addImportedType(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Param")); + interfaze.addMethod(batchInsert); + } + + private void addBatchInsertXmlElement(Document document, IntrospectedTable introspectedTable) { + if (introspectedTable.getAllColumns().isEmpty()) { + return; + } + String tableName = introspectedTable.getFullyQualifiedTableNameAtRuntime(); + StringBuilder columnList = new StringBuilder(); + StringBuilder foreachBody = new StringBuilder(); + foreachBody.append("("); + boolean first = true; + for (IntrospectedColumn column : introspectedTable.getAllColumns()) { + if (!first) { + columnList.append(", "); + foreachBody.append(", "); + } + first = false; + columnList.append(column.getActualColumnName()); + foreachBody.append("#{item.").append(column.getJavaProperty()).append("}"); + } + foreachBody.append(")"); + + XmlElement foreach = new XmlElement("foreach"); + foreach.addAttribute(new Attribute("collection", "records")); + foreach.addAttribute(new Attribute("item", "item")); + foreach.addAttribute(new Attribute("separator", ",")); + foreach.addElement(new TextElement(foreachBody.toString())); + + XmlElement insert = new XmlElement("insert"); + insert.addAttribute(new Attribute("id", "batchInsert")); + insert.addElement(new TextElement( + "insert into " + tableName + " (" + columnList + ") values")); + insert.addElement(foreach); + + document.getRootElement().addElement(insert); + } + + // ============================================ batchUpdate ========================================== + + private static String actualColumnByJavaProperty(IntrospectedTable introspectedTable, String javaProperty) { + for (IntrospectedColumn column : introspectedTable.getAllColumns()) { + if (javaProperty.equals(column.getJavaProperty())) { + return column.getActualColumnName(); + } + } + return null; + } + + private void addBatchUpdateClientMethod(Interface interfaze, IntrospectedTable introspectedTable) { + if (introspectedTable.getAllColumns().isEmpty()) { + return; + } + if (actualColumnByJavaProperty(introspectedTable, "guid") == null + || actualColumnByJavaProperty(introspectedTable, "dataVersion") == null) { + return; + } + FullyQualifiedJavaType rowType = ElementTools.hasBLOBColumns(introspectedTable) + ? ElementTools.getModelTypeWithBLOBs(introspectedTable) + : ElementTools.getModelTypeWithoutBLOBs(introspectedTable); + FullyQualifiedJavaType listType = FullyQualifiedJavaType.getNewListInstance(); + listType.addTypeArgument(rowType); + Parameter recordsParam = new Parameter(listType, "records"); + recordsParam.addAnnotation("@Param(\"records\")"); + + Method batchUpdate = new Method("batchUpdate"); + batchUpdate.setAbstract(true); + batchUpdate.setVisibility(JavaVisibility.PUBLIC); + batchUpdate.setReturnType(FullyQualifiedJavaType.getIntInstance()); + batchUpdate.addParameter(recordsParam); + + interfaze.addImportedType(new FullyQualifiedJavaType("java.util.List")); + interfaze.addImportedType(new FullyQualifiedJavaType("org.apache.ibatis.annotations.Param")); + interfaze.addMethod(batchUpdate); + } + + private void addBatchUpdateXmlElement(Document document, IntrospectedTable introspectedTable) { + if (introspectedTable.getAllColumns().isEmpty()) { + return; + } + String guidCol = actualColumnByJavaProperty(introspectedTable, "guid"); + String dataVersionCol = actualColumnByJavaProperty(introspectedTable, "dataVersion"); + if (guidCol == null || dataVersionCol == null) { + return; + } + String isDeleteCol = actualColumnByJavaProperty(introspectedTable, "isDelete"); + String isHiddenCol = actualColumnByJavaProperty(introspectedTable, "isHidden"); + + String tableName = introspectedTable.getFullyQualifiedTableNameAtRuntime(); + + XmlElement setElement = new XmlElement("set"); + for (IntrospectedColumn column : introspectedTable.getAllColumns()) { + setElement.addElement(new TextElement( + column.getActualColumnName() + " = #{item." + column.getJavaProperty() + "},")); + } + + StringBuilder where = new StringBuilder(); + where.append("where ").append(guidCol).append(" = #{item.guid}"); + if (isDeleteCol != null) { + where.append(" and ").append(isDeleteCol).append(" = 0"); + } + if (isHiddenCol != null) { + where.append(" and ").append(isHiddenCol).append(" = 0"); + } + where.append(" and ").append(dataVersionCol).append(" = #{item.dataVersion} - 1"); + + XmlElement foreach = new XmlElement("foreach"); + foreach.addAttribute(new Attribute("collection", "records")); + foreach.addAttribute(new Attribute("item", "item")); + foreach.addAttribute(new Attribute("separator", ";")); + foreach.addElement(new TextElement("update " + tableName)); + foreach.addElement(setElement); + foreach.addElement(new TextElement(where.toString())); + + XmlElement update = new XmlElement("update"); + update.addAttribute(new Attribute("id", "batchUpdate")); + update.addElement(foreach); + + document.getRootElement().addElement(update); + } + // ============================================ selectPrimaryKeyByExample ========================================== @Override public boolean clientGenerated(Interface interfaze, IntrospectedTable introspectedTable) { if (introspectedTable.getTargetRuntime() != IntrospectedTable.TargetRuntime.MYBATIS3) { - return true; + return super.clientGenerated(interfaze, introspectedTable); } - // 添加接口方法 + addBatchInsertClientMethod(interfaze, introspectedTable); + addBatchUpdateClientMethod(interfaze, introspectedTable); if (introspectedTable.getPrimaryKeyColumns().size() <= 0) { - return true; + return super.clientGenerated(interfaze, introspectedTable); } // 获取主键列类型 FullyQualifiedJavaType primaryType = introspectedTable.getPrimaryKeyColumns().get(0) @@ -373,10 +518,12 @@ public class TapeMybatisGeneratorPlugin extends PluginAdapter { @Override public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) { if (introspectedTable.getTargetRuntime() != IntrospectedTable.TargetRuntime.MYBATIS3) { - return true; + return super.sqlMapDocumentGenerated(document, introspectedTable); } + addBatchInsertXmlElement(document, introspectedTable); + addBatchUpdateXmlElement(document, introspectedTable); if (introspectedTable.getPrimaryKeyColumns().size() <= 0) { - return true; + return super.sqlMapDocumentGenerated(document, introspectedTable); } // 获取主键列名 String primaryKeyColumn = introspectedTable.getPrimaryKeyColumns().get(0) diff --git a/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java b/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java index ca3ac23..3118be4 100644 --- a/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java +++ b/src/main/java/com/iqudoo/framework/mybatis/TapeRepositoryGeneratorPlugin.java @@ -99,9 +99,10 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { String repositoryInterfaceName = "I" + domainObjectName + "Repository"; String repositoryImplName = domainObjectName + "RepositoryImpl"; boolean hasBLOBColumns = ElementTools.hasBLOBColumns(introspectedTable); + boolean supportsBatchUpdate = supportsTapeBatchUpdate(introspectedTable); // 生成Repository接口(核心修改:手动添加所有方法,不再继承父接口) - Interface repositoryInterface = generateRepositoryInterface(repositoryInterfaceName, domainObjectName, exampleClassName); + Interface repositoryInterface = generateRepositoryInterface(repositoryInterfaceName, domainObjectName, exampleClassName, supportsBatchUpdate); GeneratedJavaFile interfaceFile = new GeneratedJavaFile( repositoryInterface, targetProject, @@ -118,7 +119,8 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { exampleClassName, mapperClassName, introspectedTable, - hasBLOBColumns + hasBLOBColumns, + supportsBatchUpdate ); GeneratedJavaFile implFile = new GeneratedJavaFile( repositoryImpl, @@ -138,6 +140,23 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { /** * 判断是否为视图表(支持多关键字匹配) */ + /** + * 与 {@link TapeMybatisGeneratorPlugin} 中 batchUpdate 生成条件一致:需存在 guid、dataVersion 列。 + */ + private static boolean supportsTapeBatchUpdate(IntrospectedTable introspectedTable) { + boolean hasGuid = false; + boolean hasDataVersion = false; + for (IntrospectedColumn column : introspectedTable.getAllColumns()) { + if ("guid".equals(column.getJavaProperty())) { + hasGuid = true; + } + if ("dataVersion".equals(column.getJavaProperty())) { + hasDataVersion = true; + } + } + return hasGuid && hasDataVersion; + } + private boolean isViewTable(String tableName) { if (StringUtility.stringHasValue(viewKeyWords)) { String[] keywords = viewKeyWords.split(","); @@ -153,7 +172,7 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { /** * 核心修改:生成Repository接口(手动添加所有方法,无继承,匹配指定格式) */ - private Interface generateRepositoryInterface(String interfaceName, String modelClassName, String exampleClassName) { + private Interface generateRepositoryInterface(String interfaceName, String modelClassName, String exampleClassName, boolean supportsBatchUpdate) { Interface repositoryInterface = new Interface(facadeRepositoryPackage + "." + interfaceName); repositoryInterface.setVisibility(JavaVisibility.PUBLIC); @@ -326,7 +345,16 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { insertMethod.setAbstract(true); repositoryInterface.addMethod(insertMethod); - // 19. update + // 19. batchInsert + Method batchInsertMethod = new Method("batchInsert"); + batchInsertMethod.setVisibility(JavaVisibility.PUBLIC); + batchInsertMethod.setReturnType(new FullyQualifiedJavaType("List<" + modelClassName + ">")); + batchInsertMethod.addParameter(new Parameter(new FullyQualifiedJavaType("List<" + modelClassName + ">"), "records")); + batchInsertMethod.addException(new FullyQualifiedJavaType("Throwable")); + batchInsertMethod.setAbstract(true); + repositoryInterface.addMethod(batchInsertMethod); + + // 20. update Method updateMethod = new Method("update"); updateMethod.setVisibility(JavaVisibility.PUBLIC); updateMethod.setReturnType(new FullyQualifiedJavaType("int")); @@ -335,7 +363,18 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { updateMethod.setAbstract(true); repositoryInterface.addMethod(updateMethod); - // 20. updateByExampleSelective + // 20b. batchUpdate(与 Mapper 一致,仅 guid + dataVersion 齐备时生成) + if (supportsBatchUpdate) { + Method batchUpdateMethod = new Method("batchUpdate"); + batchUpdateMethod.setVisibility(JavaVisibility.PUBLIC); + batchUpdateMethod.setReturnType(new FullyQualifiedJavaType("int")); + batchUpdateMethod.addParameter(new Parameter(new FullyQualifiedJavaType("List<" + modelClassName + ">"), "records")); + batchUpdateMethod.addException(new FullyQualifiedJavaType("Throwable")); + batchUpdateMethod.setAbstract(true); + repositoryInterface.addMethod(batchUpdateMethod); + } + + // 21. updateByExampleSelective Method updateByExampleSelectiveMethod = new Method("updateByExampleSelective"); updateByExampleSelectiveMethod.setVisibility(JavaVisibility.PUBLIC); updateByExampleSelectiveMethod.setReturnType(new FullyQualifiedJavaType("int")); @@ -358,7 +397,8 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { String exampleClassName, String mapperClassName, IntrospectedTable introspectedTable, - boolean hasBLOBColumns) { + boolean hasBLOBColumns, + boolean supportsBatchUpdate) { TopLevelClass implClass = new TopLevelClass(domainRepositoryPackage + "." + implClassName); implClass.setVisibility(JavaVisibility.PUBLIC); @@ -389,7 +429,11 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { generateFindValidByIdMethod(implClass, modelClassName, mapperFieldName, exampleClassName); generateFindTrashByIdMethod(implClass, modelClassName, mapperFieldName, exampleClassName); generateInsertMethod(implClass, modelClassName, mapperFieldName, introspectedTable); + generateBatchInsertMethod(implClass, modelClassName, mapperFieldName, introspectedTable); generateUpdateMethod(implClass, modelClassName, exampleClassName, mapperFieldName, introspectedTable, hasBLOBColumns); + if (supportsBatchUpdate) { + generateBatchUpdateMethod(implClass, modelClassName, mapperFieldName, introspectedTable); + } generateUpdateByExampleSelectiveMethod(implClass, modelClassName, exampleClassName, mapperFieldName); generateDeleteByIdMethod(implClass, modelClassName, mapperFieldName); generateDeleteAllMethod(implClass, modelClassName, exampleClassName, mapperFieldName); @@ -634,6 +678,58 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { implClass.addMethod(method); } + private void generateBatchInsertMethod(TopLevelClass implClass, String modelClassName, String mapperFieldName, IntrospectedTable introspectedTable) { + Method method = new Method("batchInsert"); + method.addAnnotation("@Override"); + method.setVisibility(JavaVisibility.PUBLIC); + method.setReturnType(new FullyQualifiedJavaType("List<" + modelClassName + ">")); + method.addParameter(new Parameter(new FullyQualifiedJavaType("List<" + modelClassName + ">"), "records")); + method.addException(new FullyQualifiedJavaType("Throwable")); + + method.addBodyLine("if (records == null || records.isEmpty()) {"); + method.addBodyLine("return new ArrayList<>();"); + method.addBodyLine("}"); + method.addBodyLine("List<" + modelClassName + "> batch = new ArrayList<>();"); + method.addBodyLine("for (" + modelClassName + " record : records) {"); + method.addBodyLine(modelClassName + " aDo = new " + modelClassName + "();"); + method.addBodyLine("if (record.getGuid() != null) {"); + method.addBodyLine("aDo.setGuid(record.getGuid());"); + method.addBodyLine("} else {"); + method.addBodyLine("Long guid = " + snowflakeUtilGenId + ";"); + method.addBodyLine("aDo.setGuid(guid);"); + method.addBodyLine("}"); + + for (IntrospectedColumn column : introspectedTable.getAllColumns()) { + String fieldName = column.getJavaProperty(); + String setterMethod = "set" + upperFirst(fieldName); + String getterMethod = "get" + upperFirst(fieldName); + + if ("guid".equals(fieldName) || "isDelete".equals(fieldName) || "isHidden".equals(fieldName) + || "deleteToken".equals(fieldName) || "dataVersion".equals(fieldName) + || "createTime".equals(fieldName) || "updateTime".equals(fieldName)) { + continue; + } + method.addBodyLine("aDo." + setterMethod + "(record." + getterMethod + "());"); + } + + method.addBodyLine("aDo.setIsDelete(0);"); + method.addBodyLine("aDo.setIsHidden(0);"); + method.addBodyLine("aDo.setDeleteToken(\"VALID\");"); + method.addBodyLine("aDo.setDataVersion(1);"); + method.addBodyLine("aDo.setCreateTime(new Date());"); + method.addBodyLine("aDo.setUpdateTime(new Date());"); + method.addBodyLine("batch.add(aDo);"); + method.addBodyLine("}"); + + method.addBodyLine("int count = " + mapperFieldName + ".batchInsert(batch);"); + method.addBodyLine("if (count == batch.size()) {"); + method.addBodyLine("return batch;"); + method.addBodyLine("}"); + method.addBodyLine("throw new Throwable(\"Database batchInsert failed, " + modelClassName + " affected: \" + count + \", expected: \" + batch.size());"); + + implClass.addMethod(method); + } + private void generateUpdateMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName, IntrospectedTable introspectedTable, boolean hasBLOBColumns) { Method method = new Method("update"); @@ -683,6 +779,55 @@ public class TapeRepositoryGeneratorPlugin extends PluginAdapter { implClass.addMethod(method); } + private void generateBatchUpdateMethod(TopLevelClass implClass, String modelClassName, String mapperFieldName, IntrospectedTable introspectedTable) { + Method method = new Method("batchUpdate"); + method.addAnnotation("@Override"); + method.setVisibility(JavaVisibility.PUBLIC); + method.setReturnType(new FullyQualifiedJavaType("int")); + method.addParameter(new Parameter(new FullyQualifiedJavaType("List<" + modelClassName + ">"), "records")); + method.addException(new FullyQualifiedJavaType("Throwable")); + + method.addBodyLine("if (records == null || records.isEmpty()) {"); + method.addBodyLine("return 0;"); + method.addBodyLine("}"); + method.addBodyLine("List<" + modelClassName + "> batch = new ArrayList<>();"); + method.addBodyLine("for (" + modelClassName + " record : records) {"); + method.addBodyLine(modelClassName + " aDo = findValidById(record.getGuid());"); + method.addBodyLine("if (aDo == null) {"); + method.addBodyLine("throw new Throwable(\"Database record not found, " + modelClassName + " GUID:\" + record.getGuid());"); + method.addBodyLine("}"); + + for (IntrospectedColumn column : introspectedTable.getAllColumns()) { + String fieldName = column.getJavaProperty(); + String setterMethod = "set" + upperFirst(fieldName); + String getterMethod = "get" + upperFirst(fieldName); + + if ("guid".equals(fieldName) || "isDelete".equals(fieldName) || "isHidden".equals(fieldName) + || "deleteToken".equals(fieldName) || "dataVersion".equals(fieldName) + || "createTime".equals(fieldName) || "updateTime".equals(fieldName)) { + continue; + } + method.addBodyLine("if (record." + getterMethod + "() != null) {"); + method.addBodyLine("aDo." + setterMethod + "(record." + getterMethod + "());"); + method.addBodyLine("}"); + } + + method.addBodyLine("aDo.setDataVersion(aDo.getDataVersion() + 1);"); + method.addBodyLine("aDo.setUpdateTime(new Date());"); + method.addBodyLine("record.setDataVersion(aDo.getDataVersion());"); + method.addBodyLine("record.setUpdateTime(aDo.getUpdateTime());"); + method.addBodyLine("batch.add(aDo);"); + method.addBodyLine("}"); + + method.addBodyLine("int count = " + mapperFieldName + ".batchUpdate(batch);"); + method.addBodyLine("if (count == batch.size()) {"); + method.addBodyLine("return count;"); + method.addBodyLine("}"); + method.addBodyLine("throw new Throwable(\"Database batchUpdate failed, " + modelClassName + " affected: \" + count + \", expected: \" + batch.size());"); + + implClass.addMethod(method); + } + private void generateUpdateByExampleSelectiveMethod(TopLevelClass implClass, String modelClassName, String exampleClassName, String mapperFieldName) { Method method = new Method("updateByExampleSelective"); method.addAnnotation("@Override");