ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SQL代码生成器实战:JDBC元数据与FreeMarker模板生成Java代码

SQL代码生成器实战:JDBC元数据与FreeMarker模板生成Java代码 简介这套MyBatis-Plus代码生成器面向Java开发人员用于根据数据库表结构快速生成controller、service、repository、entity、mapper及mapper.xml的增删改查代码。工具无需集成到项目中本地双击start.bat并配置好mybatisplus.json中的数据库连接、输出目录等信息即可批量生成带注释和Swagger注解的代码实体类还内置MyBatis-Plus注解稍作修改便能落地到业务项目大幅提升日常CRUD开发效率。压缩包共25个文件以Java源码为主18个另含XML映射、JSON配置、启动脚本、JAR包及建表SQL整体约24.51MB结构清晰便于对照使用。目前已有1934人学习下载。配置示例与建表脚本均包含在内按使用说明操作即可快速体验从数据库到代码文件的完整生成流程很适合需要规范化快速搭建数据访问层的Java团队或个人开发者。1. 从手写 CRUD 到批量生成一个 SQL 代码生成器解决什么把数据库 SQL 变成 Java 代码的代码生成器在团队里几乎是第三个工作日就会冒出来的需求。一张业务表从建表到能用至少要写 Entity、Mapper 接口、Mapper XML、Service、Controller 五层文件字段多的时候纯手工敲一个小时还容易把下划线转驼峰转错。我见过不少项目因为这个开始自己写生成器最后沉淀成公司内部通用工具。这个标题讲的正是最常见、也最可靠的做法不靠解析 SQL 文本来猜结构而是直接用 JDBC 读数据库元数据把表结构映射成 Java 类型再用 FreeMarker 模板批量渲染出整套数据访问层代码。适合手上压着一堆表要建 CRUD、又被重复劳动折磨够了的后端开发。2. SQL 到 Java 的映射原理JDBC 元数据读表类型转换与模板渲染分工2.1 为什么不该直接解析 DDL 文本新手听到“根据数据库 SQL 生成 Java 代码”的第一反应通常是写一个解析器去读 CREATE TABLE 语句。我第一次做生成器也是这么干的用正则硬解析建表语句撑了半年换了一张带联合主键、默认值和 TEXT 类型注释的表就崩了而且崩得毫无规律。不建议解析 SQL 文本的核心原因有三条。第一MySQL、SQL Server、Oracle、PostgreSQL 的 DDL 语法细节差异极大自增写法、字符串类型、注释位置全不一致解析器要为每一种方言维护一套规则成本直接失控。第二SQL 文本可以带换行、注释、多行括号正则写出来几乎没法维护今天能跑明天换个建表工具生成的格式就翻车纯属玄学。第三解析 DDL 只能拿到这一张表的结构拿不到数据库中已经存在但没体现在脚本里的表而实际项目里很多表根本没有保留建表 SQL 的习惯。所以常见做法是走 JDBC 的 DatabaseMetaData 接口。这个接口把不同数据库的方言差异屏蔽掉了驱动内部已经完成真正的 SQL 解析我们只需要拿到统一的结果集列名表名、列名、类型名、长度、是否自增、是否主键、注释。这决定了生成器的骨架不碰 SQL 文本只消费数据库连接返回的元数据既稳定又省事。2.2 数据库类型到 Java 类型的映射表核心规则和两个反例生成器之所以需要一张类型映射表是因为 JDBC 返回的 TYPE_NAME 是数据库原生类型名而 Java 侧需要的是能放进private字段声明的类型。下面是 MySQL 下我实际在用的映射规则直接决定了 Entity 长什么样数据库类型JDBC TYPE_NAMEJava 类型说明BIGINTLong最常见的主键类型BIGINT UNSIGNEDBigInteger无符号大整型超过 Long 上限才需要INT / INTEGERInteger普通状态、排序字段TINYINTInteger默认按整型处理不自动推断布尔SMALLINTInteger同上DECIMAL / NUMERICBigDecimal金额、比例字段一律用 BigDecimalFLOATFloat非精确数值业务慎用DOUBLEDouble同上DATELocalDate只存日期DATETIME / TIMESTAMPLocalDateTime日期加时间CHAR / VARCHAR / TEXT / LONGTEXTString文本类全部落 String这张表有两个常见的反例要单独说。第一个是 TINYINT(1)MySQL JDBC 在某些连接参数下会把 tinyint(1) 当成 BIT 类型返回如果生成器看到 BIT 就转 Boolean那么“deleted”字段变成 Boolean 没问题但“status”字段如果也建成了 tinyint(1)生成出来的就是 Boolean写代码时status 0这种判断直接编译报错。我的处理是默认一律按 Integer 生成Boolean 由配置项显式声明不搞自动推断。第二个反例是 DECIMAL数据库里定义成 decimal(10,2)如果图省事映射成 Double金额计算会出现精度偏差。这个没有回旋余地必须用 BigDecimal模板里还要记得引导出 import 语句。2.3 模板渲染的三个上下文表级、字段级、包与路径生成器的第二个核心设计是模板上下文。所谓上下文就是渲染模板时塞进去的数据模型它决定了 ftl 文件里能写哪些变量。我的做法是分三层。表级上下文放的是整张表的信息表名、表注释、主键列名、是否自增、生成后的类名。主要在TableName注解、XML 的 resultMap、insert 语句的主键回填处用。字段级上下文放的是每个列的信息列名、去掉下划线的 Java 属性名、Java 类型、数据库类型、列注释、是否主键。Entity 的字段声明、XML 的result映射、查询条件的拼接全靠这一层。包与路径上下文放的是输出包名、输出目录、是否生成 Service、是否生成 Controller 这类工程级配置。三个上下文分离的好处是模板只管渲染不用关心数据从哪来。后面想加一个 VO 生成器只需复用表级上下文和字段级上下文再加一个新的 vo.ftl 就行已有的读表和类型映射代码完全不用动。这也是生成器值得投入的根本原因一次元数据读取多次模板复用。3. 跑通最小生成器JDBC 读表结构 FreeMarker 渲染的完整实现3.1 工程结构与配置文件这一节直接给一套能跑的最小实现依赖只有 MySQL 驱动、FreeMarker、Lombok生成代码里用。工程结构按普通 Maven 项目组织核心目录如下gen-demo/ ├── pom.xml ├── gen.properties └── src/main/resources/templates/ ├── entity.ftl ├── mapper.ftl └── xml.ftl配置文件是生成器的入口参数所有可变的东西都放这里不要写死在代码里# 数据库连接 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseInformationSchematrueallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456 # 要生成的表逗号分隔 table.namessys_user,sys_role # 表前缀生成类名时会去掉sys_user - SysUser table.prefixsys_ # 输出根目录指向目标工程的 java 目录 output.dirD:/workspace/myapp/src/main/java # 基础包名 package.basecom.myapp # 是否生成 Service 和 Controller generate.servicetrue generate.controllertrue几个参数的解释。useInformationSchematrue是拿注释的关键少了它JDBC 的getTables和getColumns返回的 REMARKS 字段大概率是空的生成出来的代码没有任何注释等于丢了半条命。serverTimezoneAsia/Shanghai是为了避免 MySQL 8.x 驱动连接时报时区异常。allowPublicKeyRetrievaltrue是 MySQL 8.x 配合 caching_sha2_password 认证时的常见配置不写会出现连接被拒绝的公钥检索错误。table.prefix决定类名如果前缀设置错误生成的类名会变成SysUser而不是预期的User。3.2 读表结构DatabaseMetaData 取出表、主键、注释读取结构是整个生成器的数据源核心是用 JDBC 的元数据接口不是解析 SQLpublic class TableMetaReader { private final Connection connection; public TableMetaReader(Connection connection) { this.connection connection; } public ListTableMeta readTables(ListString tableNames) throws SQLException { ListTableMeta result new ArrayList(); DatabaseMetaData meta connection.getMetaData(); for (String tableName : tableNames) { try (ResultSet tableRs meta.getTables(null, null, tableName, new String[]{TABLE})) { if (!tableRs.next()) { throw new IllegalArgumentException(表不存在: tableName); } TableMeta tm new TableMeta(); tm.setName(tableName); tm.setComment(tableRs.getString(REMARKS)); try (ResultSet pkRs meta.getPrimaryKeys(null, null, tableName)) { while (pkRs.next()) { tm.setPrimaryKeyColumn(pkRs.getString(COLUMN_NAME)); } } try (ResultSet colRs meta.getColumns(null, null, tableName, %)) { while (colRs.next()) { ColumnMeta cm new ColumnMeta(); cm.setName(colRs.getString(COLUMN_NAME)); cm.setJdbcType(colRs.getString(TYPE_NAME)); cm.setComment(colRs.getString(REMARKS)); cm.setJavaType(TypeMapper.toJavaType(cm.getJdbcType())); cm.setPrimaryKey(cm.getName().equalsIgnoreCase(tm.getPrimaryKeyColumn())); cm.setAutoIncrement(YES.equals(colRs.getString(IS_AUTOINCREMENT))); tm.getColumns().add(cm); } } result.add(tm); } } return result; } }这段代码的逻辑是先通过getTables确认表存在并拿到表注释再通过getPrimaryKeys找到主键列名最后通过getColumns遍历所有列。三个 ResultSet 分别对应表、主键、列三层信息各自的 REMARKS 列就是注释来源。IS_AUTOINCREMENT在 MySQL JDBC 驱动里返回 YES 或 NO用来判断 insert 后是否需要回填自增主键。顺带说明一个容易踩的细节meta.getTables的第一个参数给 null 表示使用当前连接默认的 catalog如果你有多库连接最好在配置里显式加一个jdbc.catalog代码里用meta.getTables(catalog, null, tableName, ...)替代否则可能出现查不到表的情况。实际项目里我一般把 catalog 也写进 gen.properties。3.3 渲染入口FreeMarker 按模板批量写文件拿到 TableMeta 列表后剩下的工作就是模板渲染。核心入口代码如下public class GeneratorMain { public static void main(String[] args) throws Exception { // 1. 加载配置并建立连接 Properties props new Properties(); props.load(new FileInputStream(gen.properties)); Class.forName(props.getProperty(jdbc.driver)); try (Connection conn DriverManager.getConnection( props.getProperty(jdbc.url), props.getProperty(jdbc.username), props.getProperty(jdbc.password))) { // 2. 读表结构 ListTableMeta tables new TableMetaReader(conn) .readTables(Arrays.asList(props.getProperty(table.names).split(,))); // 3. 初始化 FreeMarker Configuration cfg new Configuration(Configuration.VERSION_2_3_32); cfg.setDirectoryForTemplateLoading(new File(src/main/resources/templates)); cfg.setDefaultEncoding(UTF-8); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // 4. 逐表渲染 for (TableMeta table : tables) { String className toUpperCamel( table.getName().replaceFirst(^ props.getProperty(table.prefix), )); MapString, Object data new HashMap(); data.put(table, table); data.put(className, className); data.put(entityPackage, props.getProperty(package.base) .entity); data.put(mapperPackage, props.getProperty(package.base) .mapper); String outputDir props.getProperty(output.dir); render(cfg, entity.ftl, data, outputDir /entity/ className .java); render(cfg, mapper.ftl, data, outputDir /mapper/ className Mapper.java); render(cfg, xml.ftl, data, outputDir /resources/mapper/ className Mapper.xml); } } } private static void render(Configuration cfg, String template, MapString, Object data, String targetFile) throws Exception { File file new File(targetFile); file.getParentFile().mkdirs(); try (Writer writer new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { cfg.getTemplate(template).process(data, writer); } System.out.println(生成完成: targetFile); } }这段代码的逻辑分四步加载配置、读表结构、初始化渲染引擎、逐表渲染。第 4 步里toUpperCamel负责把sys_user转成SysUser规则是以下划线分割单词首字母大写再拼接。渲染时每个模板都拿到同一个 data map模板内部自己决定取哪些变量这样新增一个模板不需要改主流程。render方法里先建父目录再写文件避免输出目录不存在时报 IOException。需要特别注意的一个参数是 FreeMarker 的setTemplateExceptionHandler(RETHROW_HANDLER)。生成器脚本和线上应用不一样模板出错时宁可立刻抛异常终止生成也不能输出半个文件然后静默通过。否则你得到的是一个编译不过的 Java 文件排查起来比报错本身痛苦得多。3.4 生成的代码长什么样Entity 和 Mapper 的最小可运行形态生成器跑完一次应该立刻产出可以编译进 Spring Boot 工程的代码。Entity 文件的核心内容是字段声明加 MyBatis-Plus 注解XML 文件的核心内容是 BaseResultMap 和基础 CRUD 语句。我用下面这两个模板作为最小可运行形态完整 ftl 文件在工程里可以继续扩展。Entity 模板package ${entityPackage}; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; Data TableName(${table.name}) public class ${className} { #list table.columns as col #if col.primaryKey TableId(value ${col.name}, type IdType.AUTO) #else TableField(${col.name}) /#if #if col.comment?? col.comment?length gt 0 /** ${col.comment} */ /#if private ${col.javaType} ${col.name?replace(_([a-z]), ?$1, r)?lower_case?cap_first}; /#list }这里有个 FreeMarker 处理下划线转驼峰的常见写法在模板引擎里做正则替换比较绕容易出错。我实际会提前在 Java 侧给 ColumnMeta 增加一个camelName属性由TableMetaReader里用 Guava 或手写方法完成转换模板里直接取${col.camelName}。这样模板保持简单转换逻辑也可以在单元测试里覆盖比放在模板里正则替换靠谱得多。${table.name}会被渲染成真实的表名${col.primaryKey}是布尔值FreeMarker 直接用它做条件判断。Mapper XML 模板的最小形态要处理好两件事主键回填和字段清单。insert 语句必须写useGeneratedKeystrue和keyProperty否则自增主键不会回填到实体对象里。具体模板片段放在后面第 5 章给完整版。4. 生成器踩坑实录注释丢失、类型误判与保留字翻车的现场4.1 表注释和列注释全是空的useInformationSchema 不是可选项现象生成出来的 Java 文件里没有任何注释/** 用户ID */全都不见代码看起来像机器吐出来的裸代码交给团队评审时被吐槽“还不如手写”。原因MySQL 的 JDBC 驱动默认不会去读取 information_schema 里的注释信息DatabaseMetaData.getTables()返回的 REMARKS 列是 null。这个行为在 MySQL Connector/J 5.x 和 8.x 里都存在只有显式在连接 URL 上加了useInformationSchematrue驱动才会去查询 information_schema把表和列的 COMMENT 填进 REMARKS。解决在 gen.properties 的 jdbc.url 里补上useInformationSchematrue然后重跑生成器。这个参数必须在连接 URL 上不能通过Properties的setProperty传入否则不生效。改完连接串后重启生成进程即可。提示如果你用的是阿里云 RDS 之类的托管数据库服务连接串里还要注意把useSSL和allowPublicKeyRetrieval一起配上否则连接阶段就可能失败根本走不到读注释这一步。4.2 tinyint(1) 被生成成 Boolean按显示宽度自动推断会翻车现象表里有一个status tinyint(1)字段用于表示订单状态结果生成出来是private Boolean status。代码里写if (order.getStatus() 2)直接编译报错只能手动改类型改完 Mapper XML 里对应字段的映射也跟着要动生成器的可用性大打折扣。原因MySQL 的 JDBC 驱动在元数据里把 tinyint(1) 特殊处理为 BIT 类型。很多生成器看到 BIT 就映射成 Boolean这个映射对“是否删除”这类标志位字段是对的但对用 tinyint(1) 存状态值的表就是灾难。问题根源在于驱动做了约定而生成器把这个约定扩大成了普适规则。解决类型映射规则里不要自动推断布尔TINYINT 一律先映射成 Integer。如果确实有字段要生成 Boolean在 gen.properties 里配置一个白名单例如boolean.columnsdeleted,enabled生成时判断当前列名是否在白名单中命中才用 Boolean。生成器是工具不是 AI确定性永远比智能重要。4.3 字段名叫 order、desc、group保留字导致的生成即报错现象生成好的 SQL 在插入或查询时报语法错误比如insert into sys_order (order, status) values (...),MySQL 直接报 You have an error in your SQL syntax但手写 SQL 时同样的语句却没问题。原因表名或列名命中了数据库保留字。order、desc、group、key、status在某些上下文里是函数名或关键字JDBC 元数据读取时并不会告诉你“这个字段会被 SQL 解析器理解为关键字”它只是如实返回列名。生成器如果不处理XML 里的 SQL 拼出来就是裸列名。解决在模板渲染拼接列名时统一给表名和列名加反引号。MySQL 支持用反引号包裹标识符加完之后insert into sys_order (order,status)就被正确识别为列名。需要注意不同数据库的反引号语法不一样SQL Server 用方括号PostgreSQL 用双引号。如果生成器要跨库这个转义函数必须做成配置项不能写死。我一般在 ColumnMeta 里生成一个sqlName属性专门用于 SQL 语句渲染Java 属性名用camelName两者分离。4.4 生成的 insert 拿不到自增主键useGeneratedKeys 必须显式声明现象插入一条记录后业务代码里要立刻用实体的 id 做后续操作比如生成关联子表数据。结果entity.getId()始终是 null插入期间也没有任何报错排查半天发现 id 根本没回填。原因MyBatis 的 insert 语句如果不配置useGeneratedKeystrue和keyPropertyid执行完insert后不会把数据库自增的主键值写回实体对象。这个问题在 XML 手写 SQL 时最容易出现因为生成器生成的 insert 如果不注意也会漏掉这两个属性。解决XML 模板里 insert 标签固定写成下面这种形态insert idinsert parameterTypecom.myapp.entity.User useGeneratedKeystrue keyPropertyidkeyProperty的值要和实体类的属性名一致不是数据库列名。如果主键列叫user_id而实体属性叫userId这里必须写userId。在 MyBatis-Plus 体系下如果 Entity 字段标注了TableId(type IdType.AUTO)用 BaseMapper 的 insert 方法会自动回填但用生成器自定义 XML 时还是要显式补上防止有人绕开 BaseMapper 直接调用这段 XML。4.5 外键关联关系读不到生成器不做 ER 推断现象两张表在数据库层面有外键约束但生成的 Entity 里没有关联对象也没有ManyToOne之类的注解想一次查出关联数据还是得手写 VO。原因JDBC 的getImportedKeys()接口确实能读到外键信息但大多数业务表根本不会在数据库层建外键关联关系只存在于开发者的脑子里元数据里自然没有。生成器设计上就不该试图推断一对多、多对一否则生成一堆错误关联更麻烦。解决生成器只负责单表的 CRUD 基础代码关联查询统一放 Service 层手写。这是我用了多年后确认的边界守住这个边界生成器永远不会生成出让你意想不到的坏代码。5. 把生成器接进 Spring Boot 工程模板参数、输出路径与三个常用扩展5.1 输出路径与包名src/main/java 下的目录映射生成器要真正投入团队使用第一件事是把输出目录直接指向目标工程而不是生成到临时目录再手动拷贝。常见的做法是在 gen.properties 里把output.dir指向/workspace/myapp/src/main/java然后在主流程里按照包名 文件类型拼接子路径。这样生成后刷新 IDEA 就能直接看到新代码。路径拼接有个细节容易忽略output.dir指向的是 java 源码根目录而 Mapper XML 通常放在src/main/resources/mapper/下。同一个生成器要同时输出 Java 文件和 XML 文件就得在配置里再增加一个resource.dir否则 XML 会被写进 java 目录编译时不会被当成资源文件处理。完整配置建议是这样的output.dir/workspace/myapp/src/main/java resource.dir/workspace/myapp/src/main/resources主流程里渲染 XML 时拼接resource.dir /mapper/ className Mapper.xml渲染 Java 文件时拼接output.dir /entity/ className .java。我在实际项目里遇到过把 XML 生成到 java 目录导致启动时 mapper 文件找不到的踩坑事故后来就是靠拆分两个目录参数根治的。5.2 Entity 模板扩展TableLogic 与 TableField(fill) 如何透传项目升级到 MyBatis-Plus 之后逻辑删除和自动填充几乎是标配。生成器需要支持这两类字段的注解透传否则生成完还要手工补注解效率打折。逻辑删除字段的处理方式是在 gen.properties 里指定逻辑删除列名例如logic.delete.columndeleted。模板渲染时判断当前列名是否等于配置值相等则输出TableLogic TableField(deleted) private Integer deleted;自动填充字段类似指定fill.columnscreate_time,update_time命中的字段输出带fill属性的注解TableField(value create_time, fill FieldFill.INSERT) private LocalDateTime createTime;注意FieldFill.INSERT和INSERT_UPDATE的差异一般 create_time 用 INSERTupdate_time 用 INSERT_UPDATE。生成器不负责判断这个逻辑靠配置项区分即可。这块实践经验是不要试图在模板里写复杂的判断表达式把判断逻辑提前到 Java 侧给每个列塞一个logicDelete、fillStrategy之类的属性模板只做取值渲染可读性和排查效率都好很多。5.3 Mapper XML 模板BaseResultMap、开关字段与批量插入XML 模板是生成器里最需要照顾手写 SQL 习惯的地方因为 MyBatis 的 resultMap 和 insert 语句稍有不慎就会在运行时才报错。我长期使用并验证过的模板结构分三段。第一段是 BaseResultMap结果映射分为主键id和普通字段result两组分别对应TableId和TableField。第二段是基础 insert必须带useGeneratedKeys和keyProperty。第三段是批量插入用foreach遍历集合item属性名统一叫item不给团队留记忆负担。insert idinsertBatch parameterTypejava.util.List useGeneratedKeystrue keyPropertyid insert into ${table.name} ( #list table.columns as col ${col.name}#if col_has_next,/#if /#list ) values foreach collectionlist itemitem separator, ( #list table.columns as col #{item.${col.camelName}}#if col_has_next,/#if /#list ) /foreach /insert这里col_has_next是 FreeMarker 内置变量用于判断当前列是否最后一个避免多出一个逗号导致 SQL 拼接错误。列名统一用反引号包住就是第 4 章提到的保留字处理方案。批量插入的parameterType写java.util.Listforeach 的 collection 固定为list这是 MyBatis 对 List 参数的默认别名不用额外加Param注解也能生效。参数说明keyPropertyid在这里表示批量插入后每一行数据的自增 id 都会回填到对应实体对象前提是实体类的属性名确实叫id如果主键属性叫userId这里就得改成keyPropertyuserId。Service 和 Controller 的模板相对简单Service 层主要生成接口和实现类的空壳Controller 层生成 REST 接口骨架。这两层我建议只生成空壳不生成具体业务逻辑因为业务逻辑千差万别生成得越多改动量越大反而让团队不想用。5.4 一个完整的 gen.properties 参考把前面的配置项汇总成一个可直接投入使用的版本jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/shop?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseInformationSchematrueallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456 jdbc.catalogshop table.namessys_user,sys_role table.prefixsys_ boolean.columnsdeleted,enabled logic.delete.columndeleted fill.columnscreate_time,update_time output.dir/workspace/myapp/src/main/java resource.dir/workspace/myapp/src/main/resources package.basecom.myapp generate.servicetrue generate.controllertrue这个配置跑一次sys_user 和 sys_role 两章表的 Entity、Mapper 接口、Mapper XML、Service、Controller 全部生成。字段删改、表结构变更后随时重跑生成器会覆盖同名文件。建议团队约定生成器产出的文件一律不改手需要改动时去改模板这样生成器才能持续承担团队一致性这个职责。如果重跑时发现手改的内容被覆盖那就说明流程没遵守约定应该去改模板不是去改生成结果。提示重跑生成器前先 git commit 一次。生成器是批量写文件的脚本式程序一旦模板写错可能一次覆盖几十个文件没有版本控制兜底的话只能哭。把生成器当成“批量重构操作”对待跑之前备份跑之后 diff。6. 最后一招用 diff 验证生成质量把生成器变成团队脚手架生成器写得对不对靠肉眼读生成代码效率太低我的习惯是生成后立刻用 git diff 看变更。新建表首次生成时diff 看的是新增文件内容重跑生成器时diff 看的是变更点是否符合预期。特别是改了类型映射规则之后diff 里如果出现大范围字段类型变动要逐项确认防止全局规则误伤。验证生成质量还可以写一个简单的断言脚本把类型映射的关键点做成单元测试tinyint(1) 不进白名单必须是 Integerdecimal 必须是 BigDecimaldatetime 必须是 LocalDateTime。类型映射是全局规则一个映射错了会污染所有相关表值得用测试锁住。手写 CRUD 可以靠代码评审兜底生成器没有评审环节测试就是兜底。有了稳定的生成器下一步是把它固化成 Maven 插件或独立命令行工具放进 CI 的构建脚本里。表结构变更后开发只需要在数据库里执行 ALTER TABLE然后跑一条mvn gen:generate代码就跟着同步不再出现数据库字段和实体属性不一致的脱节问题。这个方向投入产出比很高值得做。前提是守住第 4 章说的边界不推断关联、不生成业务逻辑、不自动智能识别布尔生成器只做元数据到模板的确定性映射。我自己维护生成器三年的教训就一句话别让生成器替你思考它只负责把表结构翻译成代码翻译规则要显式、要可配置要能被测试覆盖。希望帮到你。本文还有配套的精品资源点击获取
返回列表