多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

基于数据库表结构自动生成Java CRUD代码:元数据驱动与模板引擎实践

基于数据库表结构自动生成Java CRUD代码:元数据驱动与模板引擎实践 简介这是一款面向Java后端开发者的本地代码生成工具针对数据库表结构重复编写增删改查代码的痛点可一键生成controller、service、repository、entity、mapper及mapper.xml等分层代码生成结果自带注释、swagger注解与mybatisplus实体注解复制到项目后稍作修改即可满足大部分CRUD需求适合中初级开发者提升开发效率。资源包共25个文件以18个java源码、2个xml映射文件为主另含json配置、bat启动脚本、jar执行包、sql建表脚本与使用说明文档压缩包约24.51MB无需导入项目即可本地运行。目前已有1934人学习下载。通过配置mybatisplus.json中的数据库连接、包名与表名列表再设置start.bat中的配置路径双击即可在指定输出目录获取完整代码配合示例建表sql可快速验证效果帮助读者省去大量模板代码编写时间。1. 从一张表到一套 CRUD为什么我劝你先别急着写模板手写一套增删改查是什么体验一张十几列的表Entity、Mapper、Service、Controller 四层文件挨个敲字段名抄错一个字母编译能过、运行报错排查半小时。更别提数据库改了个字段类型你还得回头把四层代码全捋一遍。这就是「根据数据库 SQL 生成 Java 代码」这个方向存在的理由把表结构当成唯一事实来源让代码从 DDL 里长出来而不是靠人肉同步。这件事的本质是元数据驱动的代码生成。你连上数据库读出information_schema里的表、列、类型、注释套一层模板引擎吐出 Java 文件。它解决的不是「会不会写 CRUD」而是「几十上百张表的重复劳动」和「表结构与代码长期漂移」。适合谁做后台管理系统、中台服务、内部工具的 Java 工程师尤其是用 MyBatis-Plus 这类框架、表多且改动频繁的团队。不适合追求极致领域建模、手写聚合逻辑的复杂业务——生成器给你的是骨架不是大脑。2. 元数据怎么读把 information_schema 吃透再谈生成2.1 一张表的结构到底藏在哪几个字段里很多人一上来就写模板结果生成出来的字段类型全是 String。问题出在没把元数据读全。以 MySQL 为例information_schema.COLUMNS是核心表关键列有这些列名含义生成时的用途TABLE_NAME表名决定类名前缀、文件名COLUMN_NAME列名转驼峰成 Java 字段名DATA_TYPE数据类型映射 Java 类型COLUMN_TYPE完整类型判断长度、无符号、枚举IS_NULLABLE是否可空决定包装类型还是基本类型COLUMN_KEY索引类型PRI 标记主键COLUMN_COMMENT注释生成字段注释、Swagger 描述COLUMN_DEFAULT默认值生成初始化逻辑DATA_TYPE和COLUMN_TYPE的区别是血泪经验DATA_TYPE只给varcharCOLUMN_TYPE才给varchar(64)。你要判断字段长度、要不要加Size校验必须读后者。主键判断也别只看COLUMN_KEYPRI联合主键、自增标记EXTRAauto_increment都得一起看否则生成的主键策略会翻车。2.2 用一段 JDBC 代码把表结构读成内存对象不依赖任何 ORM先用原生 JDBC 把元数据捞出来这样你能看清每一步。// 读取单表元数据返回列描述列表 public ListColumnMeta readColumns(Connection conn, String tableName) throws SQLException { ListColumnMeta list new ArrayList(); // 只查当前库、指定表避免全库扫描 String sql SELECT COLUMN_NAME, DATA_TYPE, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, EXTRA, COLUMN_COMMENT, COLUMN_DEFAULT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA DATABASE() AND TABLE_NAME ? ORDER BY ORDINAL_POSITION; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, tableName); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { ColumnMeta c new ColumnMeta(); c.setName(rs.getString(COLUMN_NAME)); c.setDataType(rs.getString(DATA_TYPE)); c.setColumnType(rs.getString(COLUMN_TYPE)); c.setNullable(YES.equals(rs.getString(IS_NULLABLE))); c.setPrimaryKey(PRI.equals(rs.getString(COLUMN_KEY))); c.setAutoIncrement(auto_increment.equals(rs.getString(EXTRA))); c.setComment(rs.getString(COLUMN_COMMENT)); c.setDefaultValue(rs.getString(COLUMN_DEFAULT)); list.add(c); } } } return list; }逻辑说明TABLE_SCHEMA DATABASE()保证只读当前连接的库避免多库同名表串数据ORDER BY ORDINAL_POSITION让字段顺序和建表语句一致生成的代码可读性更好。参数上tableName建议做白名单校验别直接拼进 SQL——这是防注入的基本功别在这栽跟头。2.3 类型映射表别让 int 变成 String类型映射是生成器最容易偷懒的地方。下面这张表是我常用的默认映射遇到tinyint(1)要特别处理成 Boolean这是踩过坑的MySQL 类型Java 类型备注bigintLong主键常用int / integerIntegertinyint(1)Boolean布尔语义别用 BytetinyintByte非 (1) 时varchar / char / textStringdatetime / timestampLocalDateTime新项目别用 DatedateLocalDatedecimalBigDecimal金额必须double / floatDouble / Float映射逻辑写成MapString, Stringtinyint单独判断columnType.startsWith(tinyint(1))。金额字段用BigDecimal是硬规矩用double算钱迟早对不上账。3. 模板引擎选型与代码骨架Velocity、Freemarker 还是自己拼字符串3.1 三种模板方案的取舍生成 Java 代码模板引擎的选择直接决定维护成本。常见做法有三种字符串拼接StringBuilder一路 append。上手快但模板一复杂就变成意大利面改一个缩进要动十行代码不推荐超过两个文件类型。Velocity / Freemarker老牌模板引擎语法成熟#foreach、#if写循环和条件很自然。MyBatis-Plus 官方的代码生成器早期就用 Velocity。适合模板多、需要团队协作维护的场景。自己写 AST用 JavaParser 之类直接构造语法树。最灵活能保证生成代码一定合法但学习曲线陡杀鸡用牛刀。我一般选 Freemarker理由是它对空白和换行的控制比 Velocity 细生成的代码缩进不会乱。下面用 Freemarker 演示。3.2 一个 Entity 模板长什么样模板文件entity.java.ftlpackage ${packageName}.entity; import com.baomidou.mybatisplus.annotation.*; import java.io.Serializable; import java.time.LocalDateTime; import lombok.Data; /** * ${tableComment} */ Data TableName(${tableName}) public class ${className} implements Serializable { #list columns as col /** ${col.comment} */ #if col.primaryKey TableId(value ${col.name}, type IdType.${col.autoIncrement?string(AUTO,ASSIGN_ID)}) #else TableField(${col.name}) /#if private ${col.javaType} ${col.javaField}; /#list }逻辑说明#list columns as col遍历列col.primaryKey决定加TableId还是TableField。IdType用三元表达式自增用AUTO非自增用ASSIGN_ID雪花算法。参数上packageName、tableName、className、tableComment由 Java 侧组装进Map传给模板。注意col.javaField必须是驼峰后的名字模板里不做转换转换在 Java 侧完成模板只负责排版。3.3 驼峰转换和类名生成的两个细节列名user_name转userName是基本操作但有几个边界全大写列名USER_NAME、带数字field_1、单字母a。我一般用下划线切分后首字母大写拼接遇到连续下划线跳过空串。类名生成则要处理表前缀比如t_user、sys_role配置一个前缀列表生成时剥掉t_user变成User而不是TUser。这个前缀配置建议放配置文件别写死在代码里不同项目前缀不一样。4. 避坑与排查生成器上线前必须过的五道坎4.1 生成的主键策略和数据库对不上现象生成的 Entity 主键标了TableId(type IdType.AUTO)但数据库主键不是自增插入时报主键为空。原因判断自增只看了COLUMN_KEYPRI没看EXTRA。解决主键策略必须同时判断primaryKey autoIncrement只有两者都真才用AUTO否则用ASSIGN_ID或INPUT。4.2 字段注释里的换行把模板搞崩现象某张表字段注释里带了换行符生成的 Javadoc 变成两行*/提前闭合编译报错。原因数据库注释没做转义。解决写入模板前把\r\n、*/替换掉注释统一压成单行。这个坑不常见但一旦出现很难定位。4.3 生成的 Service 覆盖了手写业务逻辑现象重新生成后之前手写的业务方法没了。原因生成器直接覆盖整个文件。解决生成策略分两种——首次生成全量后续只生成Entity和Mapper接口Service实现类用「不存在才生成」的策略。MyBatis-Plus 生成器有fileOverride开关默认关掉别图省事全开。4.4 分页失效查出来还是全量现象生成的 Mapper 用了 MyBatis-Plus 的selectPage但返回的还是全部数据。原因没配分页插件PaginationInnerInterceptor。解决在配置类里注册拦截器并指定数据库类型。这是 MyBatis-Plus 分页失效最常见的原因跟生成器无关但生成的分页代码会让人误以为是生成的问题。4.5 表名带保留字生成的 SQL 报语法错误现象表名叫order、user生成的查询 SQL 直接报错。原因保留字没加反引号。解决模板里表名和列名统一用反引号包裹TableName(\order)。MySQL 用反引号SQL Server 用方括号跨库时这个转义符要按方言配置。5. 进阶把生成器接进构建流程让它自己跑起来生成器写完不是终点手动点一下才生成用不了两周就没人维护了。我现在的习惯是把它接进 Maven 或 Gradle 的构建生命周期或者做成一个独立的 CLI 工具配合 CI 在表结构变更后自动生成并提交。具体做法写一个main方法参数从命令行读——--tables、--package、--output。用picocli或干脆手写args解析都行。然后在pom.xml里挂一个exec-maven-plugin绑定到generate-sources阶段。这样每次mvn compile前代码先按最新表结构刷新一遍。注意别让它每次全量覆盖配合 4.3 的策略只刷新 Entity 和 Mapper。验证生成结果是否可靠我有个笨办法但很管用生成完立刻跑一次mvn compile编译不过说明模板有问题比人工肉眼检查快得多。再进一步写一个对比脚本把生成前后的文件做 diff只提交真正变化的文件避免无意义的 git 噪音。一个具体技巧是给生成器加「干跑」模式--dry-run只打印将要生成的文件路径和内容摘要不落盘。第一次接入新库时先干跑确认表名、字段、类型都读对了再真正生成。这个开关救过我一次——某次连错了测试库干跑时发现表名全是test_前缀及时刹住。最后说个我自己的教训别追求一次生成完美代码。生成器给你的是 80 分的骨架剩下 20 分的业务逻辑永远要手写。把生成器定位成「省掉重复劳动」而不是「替代思考」心态就对了。我见过有人花两周把模板打磨到能生成复杂关联查询结果业务一变模板全废。骨架够用就行把精力留给真正的业务。希望帮到你。本文还有配套的精品资源点击获取
返回列表