简介:这是一款面向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只给varchar,COLUMN_TYPE才给varchar(64)。你要判断字段长度、要不要加@Size校验,必须读后者。主键判断也别只看COLUMN_KEY='PRI',联合主键、自增标记EXTRA='auto_increment'都得一起看,否则生成的主键策略会翻车。
2.2 用一段 JDBC 代码把表结构读成内存对象
不依赖任何 ORM,先用原生 JDBC 把元数据捞出来,这样你能看清每一步。
// 读取单表元数据,返回列描述列表 public List<ColumnMeta> readColumns(Connection conn, String tableName) throws SQLException { List<ColumnMeta> 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 类型 | 备注 |
|---|---|---|
| bigint | Long | 主键常用 |
| int / integer | Integer | |
| tinyint(1) | Boolean | 布尔语义,别用 Byte |
| tinyint | Byte | 非 (1) 时 |
| varchar / char / text | String | |
| datetime / timestamp | LocalDateTime | 新项目别用 Date |
| date | LocalDate | |
| decimal | BigDecimal | 金额必须 |
| double / float | Double / Float |
映射逻辑写成Map<String, String>,tinyint单独判断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.ftl:
package ${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_KEY='PRI',没看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 分的业务逻辑永远要手写。把生成器定位成「省掉重复劳动」,而不是「替代思考」,心态就对了。我见过有人花两周把模板打磨到能生成复杂关联查询,结果业务一变,模板全废。骨架够用就行,把精力留给真正的业务。希望帮到你。
本文还有配套的精品资源,点击获取