【低代码】手写模板根据数据库表自动生成后端Controller Service Dao层增删改查相关代码

目录

  • 背景
  • 方案
    • MyBatis Generator:
    • Spring Boot:
    • Ruby on Rails:
    • Django:
    • JHipster:
  • 原理:
    • 反射(Reflection):
    • 模板引擎(Template Engine):
    • 元数据解析(Metadata Parsing):
    • 代码生成策略:
    • 数据库连接和操作:
    • 模型映射(Model Mapping):
    • 代码模块化:
  • 步骤:
    • 1.连接idea自带的database
    • 2、在项目结构底部找到这个文件,双击打开,然后将里边的内容进行替换。
    • 展示结果:
  • 结果

背景

随着科技不断演进,我们正逐渐迈向一个前所未有的时代。代码的自动化正成为现实,让人们不得不重新思考其在创造和创新中的角色。过去,编写代码是实现数字化愿景的关键,但随着人工智能和自动化工具的崛起,代码编写变得更加智能、高效,甚至开始自我完善。现在要求对一个绩效平台-福分按照这种思路进行重构。

方案

以下是一些可以实现这种功能的工具:

MyBatis Generator:

MyBatis Generator 可以根据数据库表结构生成基本的 CRUD(增删改查)操作,包括实体类、Mapper 接口和 XML 配置文件。你可以配置生成的方式以及自定义查询方法。

Spring Boot:

Spring Boot 是一个用于快速创建 Spring 应用程序的框架,它提供了许多自动化配置和代码生成的功能。你可以使用 Spring Initializr 创建一个新的 Spring Boot 项目,然后使用 Spring Data JPA 或 MyBatis 等框架来自动生成基本的 CRUD 操作。

Ruby on Rails:

Ruby on Rails 的 ActiveRecord 功能可以根据数据库表生成模型类以及默认的增删改查方法。你只需要在模型类中定义好数据库关系,Rails 就会自动生成相应的方法。

Django:

Django 的 ORM 也可以根据数据库表生成模型类以及基本的 CRUD 操作方法。你可以通过定义模型类来指定数据库结构,Django 会自动处理数据库操作。

JHipster:

JHipster 是一个用于生成现代 Java Web 应用程序的生成器,它可以根据数据库表生成 Spring Boot 后端代码,包括实体类、Repository 接口和 REST 控制器。它支持多种数据库,并提供了一些自定义选项。

Jooby: Jooby 是一个用 Java 编写的轻量级 Web 框架,它可以根据数据库表结构生成实体类和基本的 CRUD 操作。

原理:

这么多工具,但是无外乎是根据模板去匹配每个表,我们将共性的代码抽象到模板里边,差异性的东西可以通过传参或者其他形式传进去,然后生成相关的文件。涉及到相关的技术如下:

反射(Reflection):

许多工具会使用反射来分析数据库表结构和字段信息,然后自动生成对应的实体类和属性。反射允许程序在运行时获取和操作类的结构和属性。

模板引擎(Template Engine):

这些工具通常使用模板引擎来根据预定义的模板生成代码。模板引擎允许将动态数据(如数据库表名、字段名等)插入到静态模板中,生成最终的代码。

元数据解析(Metadata Parsing):

工具可能会通过连接到数据库,解析数据库的元数据(如表结构、字段类型等),从而理解数据库的结构。

代码生成策略:

这些工具通常会基于一些预定义的代码生成策略来生成代码。这些策略可能是根据约定的命名规则,例如根据表名生成实体类名,根据字段名生成属性名等。

数据库连接和操作:

这些工具需要连接到数据库来获取元数据,并执行生成的代码所需的数据库操作。它们可能使用 JDBC 或其他数据库连接库来实现与数据库的交互。

模型映射(Model Mapping):

许多工具使用模型映射技术,将数据库表映射到对应的模型类。这使得数据库表的结构可以直接反映在生成的代码中。

代码模块化:

为了生成更具可维护性的代码,这些工具通常会将生成的代码模块化,将不同的功能块分成不同的文件和类。

步骤:

现在我们就开始写自己的Generator吧,let‘s go!

1.连接idea自带的database

2、在项目结构底部找到这个文件,双击打开,然后将里边的内容进行替换。

【低代码】手写模板根据数据库表自动生成后端Controller Service Dao层增删改查相关代码_第1张图片

3、定义模板

import com.intellij.database.model.DasTable
import com.intellij.database.model.ObjectKind
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil

import java.text.SimpleDateFormat

config = [
        impSerializable  : true,
        extendBaseEntity : false,
        extendBaseService: false,
        useLombok        : true, // 不使用会生成get、set方法

        // 不生成哪个就注释哪个
        generateItem     : [
                // "Entity",
                "Service",
                "Controller",//第一步
                "Repository",
//                "RepositoryCustom",
//                "RepositoryImpl",
                "ServiceImpl",
        ]
]

baseEntityPackage = "com.dz.demo.framework.base.BaseEntity"
baseServicePackage = "com.dz.demo.framework.base.BaseService"
baseEntityProperties = ["id", "createDate", "lastModifiedDate", "version"]

typeMapping = [
        (~/(?i)bool|boolean|tinyint/)     : "Boolean",
        (~/(?i)bigint/)                   : "Long",
        (~/int/)                          : "Integer",
        (~/(?i)float|double|decimal|real/): "Double",
        (~/(?i)datetime|timestamp/)       : "java.util.Date",
        (~/(?i)date/)                     : "java.sql.Date",
        (~/(?i)time/)                     : "java.sql.Time",
        (~/(?i)/)                         : "String"
]

FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
    SELECTION.filter {
        it instanceof DasTable && it.getKind() == ObjectKind.TABLE
    }.each {
        generate(it, dir)
    }
}

// 生成对应的文件              开始起飞
def generate(table, dir) {

    def entityPath = "${dir.toString()}\\entity",
        servicePath = "${dir.toString()}\\service",
        repPath = "${dir.toString()}\\repository",
//      repImpPath = "${dir.toString()}\\repository\\impl",
        serviceImplPath = "${dir.toString()}\\service\\impl",
        controllerPath = "${dir.toString()}\\controller"

    mkdirs([entityPath, servicePath, repPath, serviceImplPath, controllerPath])

    System.out.println(table.getName())
    def entityName = javaName(table.getName(), true)
    def fields = calcFields(table)
    def basePackage = clacBasePackage(dir)

    if (isGenerate("Entity")) {
        genUTF8File(entityPath, "${entityName}.java").withPrintWriter { out -> genEntity(out, table, entityName, fields, basePackage) }
    }
    if (isGenerate("Service")) {
        genUTF8File(servicePath, "${entityName}Service.java").withPrintWriter { out -> genService(out, table, entityName, fields, basePackage) }
    }
    //第二步 记得改后面的方法
    if (isGenerate("Controller")) {
        genUTF8File(controllerPath, "${entityName}Controller.java").withPrintWriter { out -> genController(out, table, entityName, fields, basePackage) }
    }
    if (isGenerate("Repository")) {
        genUTF8File(repPath, "${entityName}Repository.java").withPrintWriter { out -> genRepository(out, table, entityName, fields, basePackage) }
    }
    if (isGenerate("RepositoryCustom")) {
        genUTF8File(repPath, "${entityName}RepositoryCustom.java").withPrintWriter { out -> genRepositoryCustom(out, entityName, basePackage) }
    }
    if (isGenerate("RepositoryImpl")) {
        genUTF8File(repImpPath, "${entityName}RepositoryImpl.java").withPrintWriter { out -> genRepositoryImpl(out, table, entityName, fields, basePackage) }
    }
    if (isGenerate("ServiceImpl")) {
        genUTF8File(serviceImplPath, "${entityName}ServiceImpl.java").withPrintWriter { out -> genServiceImpl(out, table, entityName, fields, basePackage) }
    }

}

// 判断是否需要被生成
def isGenerate(itemName) {
    config.generateItem.contains(itemName)
}

// 首字母小写转换
def theFirstToLowerCase(str) {
    StringBuilder sb = new StringBuilder();
    sb.append(str.substring(0, 1).toLowerCase())
    sb.append(str.substring(1))

    return sb.toString();
}

// 指定文件编码方式,防止中文注释乱码
def genUTF8File(dir, fileName) {
    new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(dir, fileName)), "utf-8"))
}

// 生成每个字段
def genProperty(out, field) {

    out.println ""
    out.println "\t/**"
    out.println "\t * ${field.comment.toString()}"
//  out.println "\t * default value: ${field.default}"
    out.println "\t */"
    // 默认表的第一个字段为主键
    if (field.position == 1) {
        out.println "\t@Id"
        //自增主键需要
        out.println "\t@GeneratedValue(strategy = GenerationType.IDENTITY)"
    }
    // 枚举不需要长度
//  out.println "\t@Column(name = \"${field.colum}\", nullable = ${!field.isNotNull}${field.dataType == "enum" ? "" : ", length = $field.len"})"
    out.println "\t@Column(name = \"${field.colum}\", nullable = ${!field.isNotNull}${field.dataType == "enum" ? "" : ""})"
    out.println "\tprivate ${field.type} ${field.name};"
}

// 生成get、get方法
def genGetSet(out, field) {

    // get
    out.println "\t"
    out.println "\tpublic ${field.type} get${field.name.substring(0, 1).toUpperCase()}${field.name.substring(1)}() {"
    out.println "\t\treturn this.${field.name};"
    out.println "\t}"

    // set
    out.println "\t"
    out.println "\tpublic void set${field.name.substring(0, 1).toUpperCase()}${field.name.substring(1)}(${field.type} ${field.name}) {"
    out.println "\t\tthis.${field.name} = ${field.name};"
    out.println "\t}"
}

// 生成实体类
def genEntity(out, table, entityName, fields, basePackage) {
    out.println "package ${basePackage}.entity;"
    out.println ""
    if (config.extendBaseEntity) {
//    out.println "import $baseEntityPackage;"
    }
    if (config.useLombok) {
        out.println "import lombok.Data;"
    }
    if (config.impSerializable) {
        out.println "import java.io.Serializable;"
    }
    out.println "import javax.persistence.*;"
    out.println ""
    out.println "/**\n" +
            " * @Description  \n" +
            " * @Author  xwd\n" + //1. 修改idea为自己名字
            " * @Date " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " \n" +
            " */"
    if (config.useLombok) {
        out.println "@Data"
    }
    out.println "@Entity"
    out.println "@Table(name = \"${table.getName()}\")"
    out.println "public class $entityName${config.extendBaseEntity ? " extends BaseEntity" : ""}${config.impSerializable ? " implements Serializable" : ""} {"

    if (config.extendBaseEntity) {
        fields = fields.findAll { it ->
            !baseEntityProperties.any { it1 -> it1 == it.name }
        }
    }

    fields.each() {
        genProperty(out, it)
    }

    if (!config.useLombok) {
        fields.each() {
            genGetSet(out, it)
        }
    }
    out.println "}"
}

// 生成Service
def genService(out, table, entityName, fields, basePackage) {
    out.println "package ${basePackage}.service;"
    out.println ""
//  out.println "import ${basePackage}.repository.${entityName}Repository;"
    if (config.extendBaseService) {
//    out.println "import $baseServicePackage;"
        out.println "import ${basePackage}.entity.$entityName;"
    }
        out.println "import ${basePackage}.entity.$entityName;"

    out.println "import org.springframework.stereotype.Service;"
//  out.println "import javax.annotation.Resource;"
    out.println ""
    out.println "@Service"
    out.println "public interface ${entityName}Service${config.extendBaseService ? " extends BaseService<$entityName, ${fields[0].type}>" : ""}  {"
    out.println ""
    out.println "public Object findAllBySomething( ${entityName}  ${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}) ;"
//  out.println "\t@Resource"
//  out.println "\tprivate ${entityName}Repository repository;"
    out.println("public Object modifyBySomething( ${entityName}  ${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}) ;");

    out.println "}"
}

// 生成Controller 第三步
def genController(out, table, entityName, fields, basePackage) {
    out.println "package ${basePackage}.controller;"
    out.println ""
//  out.println "import ${basePackage}.repository.${entityName}Repository;"
    if (config.extendBaseService) {
//    out.println "import $baseServicePackage;"
        out.println "import ${basePackage}.entity.$entityName;"
    }
    out.println "import lombok.extern.slf4j.Slf4j;"
    out.println "import io.swagger.annotations.ApiOperation;"
    out.println "import ${basePackage}.entity.$entityName;"
    out.println "import org.springframework.web.bind.annotation.RequestBody;"
    out.println "import org.springframework.web.bind.annotation.PostMapping;"
    out.println "import org.springframework.web.bind.annotation.RequestMapping;"
    out.println "import org.springframework.web.bind.annotation.RestController;"
    out.println "import ${basePackage}.service.${entityName}Service;"
    out.println "import javax.annotation.Resource;"
    out.println ""
    out.println "@Slf4j"
    out.println "@RestController"
    out.println "@RequestMapping(\"/${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}\")"//首字母小写
    out.println "public class ${entityName}Controller${config.extendBaseService ? " extends BaseService<$entityName, ${fields[0].type}>" : ""}  {"
    out.println ""
    out.println "\t@Resource"
    out.println "\tprivate ${entityName}Service "+theFirstToLowerCase("${entityName}")+"Service;"


    out.println "\t@ApiOperation(\"找东西\")";
    out.println "\t@PostMapping(\"/findBySomeThing\")";
    out.println "\tpublic Object findBySomeThing(@RequestBody ${entityName}  ${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}) {";
        
        // Insert your provided text here

    out.println ""
    out.println "\t\tObject allBySomething = "+theFirstToLowerCase(entityName)+"Service.findAllBySomething(${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)});";
    out.println "\t\treturn allBySomething;";
    out.println "\t}";
     out.println ""
    out.println ""
 out.println("@ApiOperation(\"设置东西\")");
out.println("@PostMapping(\"/modifyBySomething\")");
out.println("public Object modifyBySomething(@RequestBody ${entityName} ${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}) {");
out.println("\tObject allBySomething = "+theFirstToLowerCase(entityName)+"Service.modifyBySomething(${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)});");
out.println("\treturn allBySomething;");
    out.println("}");


    
    out.println "}"
}

// 生成Repository
def genRepository(out, table, entityName, fields, basePackage) {
    out.println "package ${basePackage}.repository;"
    out.println ""
    out.println "import ${basePackage}.entity.$entityName;"
    out.println "import org.springframework.data.jpa.repository.JpaRepository;"
    out.println "import org.springframework.data.jpa.repository.JpaSpecificationExecutor;"
    out.println ""
    out.println "public interface ${entityName}Repository extends JpaRepository<$entityName, ${fields[0].type}>, JpaSpecificationExecutor<$entityName>{"
    out.println ""
    out.println "}"
}

// 生成RepositoryCustom
def genRepositoryCustom(out, entityName, basePackage) {
    out.println "package ${basePackage}.repository;"
    out.println ""
    out.println "public interface ${entityName}RepositoryCustom {"
    out.println ""
    out.println "}"
}

// 生成RepositoryImpl
def genRepositoryImpl(out, table, entityName, fields, basePackage) {
    out.println "package ${basePackage}.repository.impl;"
    out.println ""
    out.println "import ${basePackage}.repository.${entityName}RepositoryCustom;"
    out.println "import org.springframework.stereotype.Repository;"
    out.println ""
    out.println "import javax.persistence.EntityManager;"
    out.println "import javax.persistence.PersistenceContext;"
    out.println ""
    out.println "@Repository"
    out.println "public class ${entityName}RepositoryImpl implements ${entityName}RepositoryCustom {"
    out.println ""
    out.println "\t@PersistenceContext"
    out.println "\tprivate EntityManager em;"
    out.println "}"
}
// 生成ServiceImpl
def genServiceImpl(out, table, entityName, fields, basePackage) {
    out.println "package ${basePackage}.service.impl;"
    out.println ""
     out.println "import java.util.List;"
     out.println "import org.springframework.data.domain.Example;"
    out.println "import ${basePackage}.entity.$entityName;"
    out.println "import ${basePackage}.service.${entityName}Service;"
    out.println "import ${basePackage}.repository.${entityName}Repository;"
    out.println "import javax.annotation.Resource;"
//  out.println "import ${basePackage}.repository.${entityName}RepositoryCustom;"
    out.println "import org.springframework.stereotype.Service;"
    out.println ""
//  out.println "import javax.persistence.EntityManager;"
//  out.println "import javax.persistence.PersistenceContext;"
    out.println ""
    out.println "@Service"
    out.println "public class ${entityName}ServiceImpl implements ${entityName}Service {"
    out.println ""
    out.println "\t@Resource"
    out.println "\tprivate ${entityName}Repository repository;"
//  out.println "\t@PersistenceContext"
//  out.println "\tprivate EntityManager em;"
    out.println ""
    out.println "\tpublic Object findAllBySomething(${entityName} ${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}) {"
    out.println "\t\tExample<${entityName}> example = Example.of(${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)});"
    out.println "\t\tList<${entityName}> all = repository.findAll(example);"
    out.println "\t\treturn all;"
    out.println "\t}"
out.println ""
out.println ""
    out.println("public Object modifyBySomething(${entityName}  ${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)}) {");
out.println("\t${entityName} save = repository.save(${entityName.substring(0,1).toLowerCase()}${entityName.substring(1)});");
out.println("\treturn save;");
out.println("}");


    out.println "}"
}

// 生成文件夹
def mkdirs(dirs) {
    dirs.forEach {
        def f = new File(it)
        if (!f.exists()) {
            f.mkdirs()
        }
    }
}

def clacBasePackage(dir) {
    dir.toString()
            .replaceAll("^.+\\\\src\\\\main\\\\java\\\\", "")
            .replaceAll("\\\\", ".")
}

def isBaseEntityProperty(property) {
    baseEntityProperties.find { it == property } != null
}

// 转换类型
def calcFields(table) {
    DasUtil.getColumns(table).reduce([]) { fields, col ->

        def spec = Case.LOWER.apply(col.getDataType().getSpecification())
        def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
        fields += [[
                           name     : javaName(col.getName(), false),
                           colum    : col.getName(),
                           type     : typeStr,
                           dataType : col.getDataType().toString().replaceAll(/\(.*\)/, "").toLowerCase(),
                           len      : col.getDataType().toString().replaceAll(/[^\d]/, ""),
                           default  : col.getDefault(),
                           comment  : col.getComment(),
                           isNotNull: col.isNotNull(),
                           position : col.getPosition(),
//                           getDefault            : col.getDefault(),
//                           getParent             : col.getParent(),
//                           getTable              : col.getTable(),
//                           getDataType           : col.getDataType(),
//                           isNotNull             : col.isNotNull(),
//                           getWeight             : col.getWeight(),
//                           getDocumentation      : col.getDocumentation(),
//                           getTableName          : col.getTableName(),
//                           getName               : col.getName(),
//                           getLanguage           : col.getLanguage(),
//                           getTypeName           : col.getTypeName(),
//                           isDirectory           : col.isDirectory(),
//                           isValid               : col.isValid(),
//                           getComment            : col.getComment(),
//                           getText               : col.getText(),
//                           getDeclaration        : col.getDeclaration(),
//                           getPosition           : col.getPosition(),
//                           canNavigate           : col.canNavigate(),
//                           isWritable            : col.isWritable(),
//                           getIcon               : col.getIcon(),
//                           getManager            : col.getManager(),
//                           getDelegate           : col.getDelegate(),
//                           getChildren           : col.getChildren(),
//                           getKind               : col.getKind(),
//                           isCaseSensitive       : col.isCaseSensitive(),
//                           getProject            : col.getProject(),
//                           getDataSource         : col.getDataSource(),
//                           getVirtualFile        : col.getVirtualFile(),
//                           getMetaData           : col.getMetaData(),
//                           canNavigateToSource   : col.canNavigateToSource(),
//                           getDisplayOrder       : col.getDisplayOrder(),
//                           getDasParent          : col.getDasParent(),
//                           getLocationString     : col.getLocationString(),
//                           getDependences        : col.getDependences(),
//                           getBaseIcon           : col.getBaseIcon(),
//                           getNode               : col.getNode(),
//                           getTextLength         : col.getTextLength(),
//                           getFirstChild         : col.getFirstChild(),
//                           getLastChild          : col.getLastChild(),
//                           getNextSibling        : col.getNextSibling(),
//                           getTextOffset         : col.getTextOffset(),
//                           getPrevSibling        : col.getPrevSibling(),
//                           getPresentation       : col.getPresentation(),
//                           isPhysical            : col.isPhysical(),
//                           getTextRange          : col.getTextRange(),
//                           getPresentableText    : col.getPresentableText(),
//                           textToCharArray       : col.textToCharArray(),
//                           getStartOffsetInParent: col.getStartOffsetInParent(),
//                           getContext            : col.getContext(),
//                           getUseScope           : col.getUseScope(),
//                           getResolveScope       : col.getResolveScope(),
//                           getReferences         : col.getReferences(),
//                           getReference          : col.getReference(),
//                           getContainingFile     : col.getContainingFile(),
//                           getOriginalElement    : col.getOriginalElement(),
//                           getNavigationElement  : col.getNavigationElement(),
//                           getUserDataString     : col.getUserDataString(),
//                           isUserDataEmpty       : col.isUserDataEmpty(),
//                           getDbParent           : col.getDbParent(),
                   ]]

    }
}

def javaName(str, capitalize) {
    def s = str.split(/(?<=[^\p{IsLetter}])/).collect { Case.LOWER.apply(it).capitalize() }
            .join("").replaceAll(/[^\p{javaJavaIdentifierPart}]/, "_").replaceAll(/_/, "")
    capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}

4、确定生成三层代码的位置
【低代码】手写模板根据数据库表自动生成后端Controller Service Dao层增删改查相关代码_第2张图片
然后选择位置
【低代码】手写模板根据数据库表自动生成后端Controller Service Dao层增删改查相关代码_第3张图片

展示结果:

【低代码】手写模板根据数据库表自动生成后端Controller Service Dao层增删改查相关代码_第4张图片

备注:我误删了Generate POJOs.groovy (回收站都找不到)
然后右击文件夹,本地历史记录,显示历史记录就找回了。
【低代码】手写模板根据数据库表自动生成后端Controller Service Dao层增删改查相关代码_第5张图片

结果

手写根据数据库表自动生成代码的好处有以下几点:

提高效率: 自动生成代码可以大大减少开发人员编写重复性、模板性代码的时间。特别是在大型项目中,生成代码可以迅速创建基础的代码框架,使开发人员可以专注于更具创造性和复杂性的任务。

减少错误: 手动编写代码时容易出现拼写错误、语法错误等问题,而自动生成的代码通常会减少这些潜在错误,因为代码生成工具通常会根据规则自动生成正确的代码。

保持一致性: 自动生成的代码可以确保在不同部分之间保持一致的代码风格和结构,避免了由于不同开发人员编写代码而引入的不一致性。

快速迭代: 在需要进行迭代和变更时,自动生成的代码可以快速重新生成,从而在项目需求变更时提供更高的灵活性。

降低学习成本: 对于团队中新加入的开发人员,通过自动生成的代码,他们可以更快地理解项目的结构和流程,降低了学习成本。

保持最新: 当数据库表结构变化时,自动生成的代码可以更容易地进行更新,确保代码始终与数据库表保持同步。

可维护性: 自动生成的代码通常会遵循一定的模板和规范,这有助于提高代码的可读性和可维护性,使项目更易于管理。

减少重复工作: 自动生成代码可以避免开发人员在不同的项目或模块中重复编写类似的代码,从而节省时间和精力。

需要注意的是,虽然代码生成工具能够带来很多好处,但在使用时也需要谨慎考虑。在一些特殊情况下,手动编写代码可能更适合,因为自动生成的代码可能无法满足特定的需求或复杂的业务逻辑。另外,生成的代码可能会增加项目的复杂性,因此在选择代码生成工具和模板时需要仔细评估。

你可能感兴趣的:(中间件,自动生成增删改查,手写代码)