news 2026/10/13 7:40:43

用Groovy脚本定制IDEA生成Spring Boot JPA实体类,告别重复手动补注解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Groovy脚本定制IDEA生成Spring Boot JPA实体类,告别重复手动补注解

项目里但凡要接一张新表,最快的方式就是用 IDEA 连接数据库之后,直接把表结构反向生成实体类。以前我一直靠自带的一键生成功能,生成完再手动补 Spring Boot JPA 注解、加 Lombok、调整数据库类型映射,后来发现只要表一多,这种“半自动”操作就会变成纯粹的手工流水线,既重复又容易漏注解。干脆花点时间研究了一下用 Groovy 脚本定制生成 Spring Boot JPA 实体类的方案,把注解、注释、类型转换这些重复劳动全部在生成阶段一次性解决。这篇文章我就把从数据库连接配置、脚本编写到实际生成的完整思路和可复用脚本分享出来,适合正在用 JPA、Hibernate,又每天被建表改表折腾的同学参考。

1. 直接用 IDEA 自带功能生成实体类,到底差在哪

1.1 自带生成器的输出模式

IDEA 的数据库工具面板确实带了一套“Generate POJOs.groovy”和“Generate JPA entities.groovy”脚本,操作路径很直观:在 Database 面板选中表,右键 -> Generate,然后选对应脚本就行。第一次用的时候你会觉得:这不就能生成实体类了吗,还要折腾什么?

但实际生成出来的东西,离项目能用还差得远。默认脚本生成的 Java 类:

  • 只输出私有字段和 Getter/Setter 方法,不会自动加入 Lombok 注解;如果你的团队习惯用 Lombok,每个类都得手工再删掉全套 Getter/Setter,加上@Getter @Setter。
  • 类上没有@Entity和@Table注解,或者有@Entity但表名策略不一定符合你的命名习惯,比如数据库里明明是sys_user,生成出来类名可能叫SysUser但注解里的name没有正确映射。
  • 数据库字段上的注释几乎不会自动转换成代码里的 JavaDoc 注释,你得到的就是一堆裸字段。
  • 类型映射过于保守,PostgreSQL 的timestamptz可能直接映射成Object或者Timestamp,numeric可能变成BigDecimal还好,int8可能映射成BigInteger,这些都不是 Spring Boot JPA 项目里最顺手的类型。

另一个问题是,自带脚本默认不读取主键信息生成@Id和@GeneratedValue。生成出来的类里所有字段都一样,主键字段也需要你手工标注。一次两次还好,表一多就特别容易漏。

1.2 Spring Boot JPA 项目对实体类的真实需求

Spring Boot 项目里的实体类,早就不是一个简单的 POJO 了。对照我们日常项目的代码规范,一个合格的 JPA 实体类至少要有下面这些东西。

需求点说明
类注解@Entity+@Table(name = "xxx"),必要时指定 schema
主键策略@Id+@GeneratedValue(strategy = GenerationType.IDENTITY)或SEQUENCE
字段注解@Column(name = "xxx"),字段名与数据库列名严格对应
Lombok 注解类上@Getter @Setter,彻底简化 Getter/Setter
注释保留表注释和列注释要变成 JavaDoc,方便后面同事接手
时间类型timestamp/timestamptz映射为LocalDateTime,而非Date
数值类型numeric映射为BigDecimal,避免精度丢失
序列化支持实现Serializable并定义serialVersionUID

这些需求里,除了前两项,其余几乎都要靠手工补。而 Groovy 生成脚本最大的价值,就是把这些“规范化动作”固化成模板,每次生成都按同一个标准执行,不会因为人的状态不同而出现遗漏。

2. 准备阶段:正确的数据库连接与脚本加载方式

2.1 在 IDEA 中配置数据库数据源

要生成实体类,第一步一定是把数据源拉通。IDEA 的 Database 面板本身就是完整的数据源客户端,支持 PostgreSQL、MySQL、SQL Server、Oracle 等主流数据库。以 PostgreSQL 为例,配置步骤如下:

  1. 打开右侧 Database 面板,点击+,选择 Data Source -> PostgreSQL。
  2. 填写 Host、Port、Database、User、Password。
  3. 点 Test Connection 测试连接。
  4. 如果提示缺少驱动,让 IDEA 自动下载即可。不过我建议有条件的话使用官方 JDBC 驱动版本,部分场景下 IDEA 内置驱动的版本策略可能和你本地的数据库版本有一定的兼容差异。
  5. 连接成功后,展开对应 schema 就能看到表。

这里有两个很容易踩的坑。第一个是 schema 选择,PostgreSQL 默认连的是publicschema,如果你的业务表放在ods、app之类的 schema 下,连接配置里的 Schema 标签页一定要手动勾选,否则列表里看不到表。第二个是时区参数,MySQL 连接时经常遇到“serverTimezone”相关的报错,需要在连接 URL 上追加useSSL=false&serverTimezone=Asia/Shanghai参数;PostgreSQL 一般不用额外处理,但如果你所在网络环境有代理或防火墙,驱动下载失败时也可以手动把 jar 包放到 IDEA 的驱动目录里。

配置数据源这件事,看起来简单,其实决定后面脚本能不能顺利读取列信息和注释。IDEA 生成脚本时,需要数据源保持在线状态,并已同步过表结构元数据。如果数据源处于离线状态或者刷新失败,脚本拿到的列信息可能是空的,生成结果自然残缺。

2.2 Groovy 脚本存放位置与调用入口

很多人不知道 IDEA 的脚本自定义能力在哪里配置。IDEA 默认自带几个 Groovy 脚本模板,但它们存放在 IDEA 全局扩展目录里,不推荐直接改,更好的做法是把你的自定义脚本放到一个专门目录。

设置路径在Settings -> Tools -> Database -> Groovy Scripts Directory,点开之后可以配置脚本目录。我习惯把目录指向项目仓库里的tools/db-codegen文件夹,同时也放入 Git 管理,这样团队里其他成员同步代码后,就能在各自的 IDEA 里使用同一套生成脚本。

脚本放好后,调用方式有两种:

  • 在 Database 面板选中表,点击右键,选择Generate POJOs.groovy(或你自定义的脚本文件名)。
  • 如果脚本不在默认命名范围内,可以在右键菜单里选Scripted Extensions -> Go to Scripted Extensions,直接检查当前生效的脚本列表。

需要提醒的是:自定义脚本必须写在 IDEA 认可的目录里,并且脚本名后缀是.groovy,IDEA 才会在右键菜单里识别到。脚本本质上是一段运行在 JetBrains 脚本引擎上的 Groovy 代码,它可以直接拿到当前选中表对象、数据库列对象、数据类型对象等上下文,所以在右键菜单里选中表运行脚本是前提条件,直接当作普通 Groovy 文件运行是没有意义的。

3. 定制 Groovy 脚本的三处关键代码

3.1 表名/字段名与 Java 命名规则的转换

数据库里绝大多数表都是下划线风格,比如sys_user_info、create_time,而 Java 类名应该是SysUserInfo,字段名应该是createTime。Groovy 脚本里的命名转换函数就要解决这个问题。

实现思路很简单,按下划线拆开,第一个单词首字母小写,后续单词首字母大写,拼接起来就是驼峰格式。类名再额外把第一个单词首字母大写。

def toCamelCase(String name) { def parts = name.split("_") def result = parts[0].toLowerCase() for (int i = 1; i < parts.length; i++) { result += parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1).toLowerCase() } return result } def toPascalCase(String name) { def camel = toCamelCase(name) return camel.substring(0, 1).toUpperCase() + camel.substring(1) }

这段代码处理常规场景完全够用。要注意两个例外情况。一是表名里如果没有下划线且全是小写,toCamelCase会原样返回,类名能正常首字母大写;二是表名里带有特殊前缀(比如t_sys_user),你会发现生成出来的类名变成了tSysUser,这对很多项目来说不是想要的。可以在脚本里增加替换数组,把t_、tb_、tab_等固定前缀在转换前去掉。

def cleanTableName(String name) { def lower = name.toLowerCase() if (lower.startsWith("t_")) return name.substring(2) if (lower.startsWith("tb_")) return name.substring(3) return name }

3.2 数据库类型到 Java 类型的映射表

这一块是整个脚本的重点。Spring Boot JPA 项目里,类型映射直接影响后续仓库接口的定义和业务代码的写法。PostgreSQL 数据库里我用过的类型基本都可以归类成下面这张表。

数据库类型Java 类型理由
int2 / int4Integer常规整型
int8 / bigserialLong主键 ID 基本都是 Long
varchar / char / text / citextString字符串场景最多
numeric / decimalBigDecimal金额、精度计算必须用 BigDecimal
float4Float单精度浮点
float8Double双精度浮点
bool / booleanBoolean布尔字段
dateLocalDateJava 8 时间类型
timestamp / timestamptzLocalDateTime业务时间字段首选
time / timetzLocalTime时分秒场景
byteabyte[]二进制文件存储
uuidUUIDPG 原生 uuid 类型
json / jsonbString文本方式存取,复杂转换交给应用层

为什么numeric要固定映射成BigDecimal?因为浮点数会有精度问题,金额字段一旦出现精度偏差,后面查问题特别痛苦。为什么timestamp用LocalDateTime而不是Date?Hibernate 5.2 之后对 Java 8 时间类型支持已经很成熟,LocalDateTime更符合现在的开发习惯,并且不存在时区混淆问题。jsonb字段在这张表里映射成String是一个兜底方案,因为 JPA 标准里没有直接对应 JSON 类型的字段注解,实际项目里如果需要完整支持 JSON 查询,一般会在 Hibernate 6 里通过@JdbcTypeCode(SqlTypes.JSON)来实现,这就属于实体类生成后的定制优化了。

映射函数的代码可以这样写:

def mapType(DataType dataType) { def typeName = dataType.name?.toLowerCase() switch (typeName) { case ["int", "int2", "int4"]: return "Integer" case ["int8", "bigint", "bigserial"]: return "Long" case ["varchar", "char", "bpchar", "text", "citext"]: return "String" case ["numeric", "decimal"]: return "BigDecimal" case ["float4", "real"]: return "Float" case ["float8", "double precision"]: return "Double" case ["bool", "boolean"]: return "Boolean" case ["date"]: return "LocalDate" case ["timestamp", "timestamptz", "datetime"]: return "LocalDateTime" case ["time", "timetz"]: return "LocalTime" case ["bytea"]: return "byte[]" case ["uuid"]: return "UUID" default: return "String" } }

如果你用的是 MySQL,那么需要注意把datetime对应到LocalDateTime,tinyint(1)对应到Boolean,decimal对应到BigDecimal,longtext对应到String。映射表完全可以按团队实际规范继续扩充。

3.3 注解模板与文件头生成逻辑

脚本的核心输出是注解。项目里实体类的注解有两层,一层是类注解,一层是字段注解。

类注解固定输出:

@Getter @Setter @Entity @Table(name = "sys_user") public class SysUser implements Serializable { private static final long serialVersionUID = 1L; }

字段注解按字段类型区分:主键字段输出@Id和@GeneratedValue(strategy = GenerationType.IDENTITY);普通字段输出@Column(name = "xxx");字段有注释时,注释放在 JavaDoc 里。

这块脚本逻辑可以抽象成下面几行:

def appendFieldAnnotations(StringBuilder sb, DasColumn column, boolean isPk) { if (column.comment) { sb.append(" /**\n") sb.append(" * ").append(column.comment).append("\n") sb.append(" */\n") } if (isPk) { sb.append(" @Id\n") sb.append(" @GeneratedValue(strategy = GenerationType.IDENTITY)\n") } sb.append(" @Column(name = \"").append(column.name).append("\")\n") }

关于主键生成策略,这里有个点要特别说明。PostgreSQL 里如果主键用的是identity自增列,IDENTITY是合理的;但如果你的表用了序列(sequence),比如nextval('sys_user_id_seq'),那实体类里应该改写成:

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sys_user_id_seq") @SequenceGenerator(name = "sys_user_id_seq", sequenceName = "sys_user_id_seq", allocationSize = 1)

所以脚本里如果把主键策略写死成IDENTITY,就会和某些项目的数据库设计冲突。我的建议是把主键策略作为脚本顶部的常量,生成前按项目改一行就行。

字段注解里的@Column(name = "xxx")还有一个细节:如果数据库列名和 Java 字段名的驼峰转换关系是严格可逆的,比如列create_time转成createTime,Hibernate 开启命名策略后其实可以不用写name。但我觉得显式写上更安全,因为有些历史表字段命名不规律,比如dbsj(更新时间缩写)、bz(备注缩写),如果不写name,Hibernate 默认就会去找bz对应的驼峰字段,结果就是字段映射错乱。

4. 完整脚本:可直接复制使用的 Groovy 生成器

4.1 脚本全文与配置项说明

下面这个脚本是我在 IDEA 2023.3 版本上验证过的框架,你使用时可能需要根据 IDEA 版本对 API 导入做微调,但整体逻辑是完整可参考的。脚本文件可以命名为GenerateJpaEntityForSpringBoot.groovy,放入前面配置的 Groovy Scripts Directory 后,在表上右键就能调用。

import com.intellij.database.model.DasTable import com.intellij.database.model.DataType import com.intellij.database.util.DasUtil import com.intellij.database.psi.DasColumn import java.io.File import java.time.LocalDateTime import java.time.format.DateTimeFormatter // ================= 可配置项 ================= String PACKAGE_NAME = "com.example.jpa.entity" String AUTHOR = "your-name" boolean USE_LOMBOK = true boolean ENABLE_SERIALIZABLE = true String ID_STRATEGY = "IDENTITY" // 可选 IDENTITY / SEQUENCE String SEQ_NAME = "" // ID_STRATEGY == SEQUENCE 时必填 // ========================================== def targetDir = FILES.chooseDirectory("选择实体类保存目录", "src/main/java") if (targetDir == null) { println "未选择目录,已取消" return } def selectedTables = SELECTION.getTables() if (selectedTables == null || selectedTables.isEmpty()) { selectedTables = [table] } if (selectedTables == null || selectedTables.isEmpty()) { println "未获取到表,请先在 Database 面板选中一张或多张表" return } selectedTables.each { dasTable -> def className = toPascalCase(dasTable.name) def imports = new LinkedHashSet<String>() def fields = new StringBuilder() if (ENABLE_SERIALIZABLE) { imports.add("java.io.Serializable") } imports.add("javax.persistence.*") if (USE_LOMBOK) { imports.add("lombok.Getter") imports.add("lombok.Setter") } DasUtil.getColumns(dasTable).each { col -> def fieldName = toCamelCase(col.name) def javaType = mapType(col.dataType) def isPk = isPrimaryKey(dasTable, col) if (javaType.startsWith("java.")) { imports.add(javaType) } if (javaType == "BigDecimal") { imports.add("java.math.BigDecimal") } if (javaType == "LocalDate") { imports.add("java.time.LocalDate") } if (javaType == "LocalDateTime") { imports.add("java.time.LocalDateTime") } if (javaType == "LocalTime") { imports.add("java.time.LocalTime") } if (javaType == "UUID") { imports.add("java.util.UUID") } if (col.comment) { fields.append(" /**\n") fields.append(" * ").append(col.comment).append("\n") fields.append(" */\n") } if (isPk) { fields.append(" @Id\n") if (ID_STRATEGY == "SEQUENCE") { fields.append(" @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = \"").append(SEQ_NAME).append("\")\n") imports.add("javax.persistence.SequenceGenerator") fields.append(" @SequenceGenerator(name = \"").append(SEQ_NAME).append("\", sequenceName = \"").append(SEQ_NAME).append("\", allocationSize = 1)\n") } else { fields.append(" @GeneratedValue(strategy = GenerationType.IDENTITY)\n") } } fields.append(" @Column(name = \"").append(col.name).append("\")\n") fields.append(" private ").append(javaType).append(" ").append(fieldName).append(";\n\n") } def packagePath = PACKAGE_NAME.replace('.', '/') def saveDir = new File(targetDir, packagePath) saveDir.mkdirs() def file = new File(saveDir, className + ".java") file.write(buildClassContent(className, dasTable, fields.toString(), imports)) println "已生成: ${file.path}" } def buildClassContent(String className, DasTable table, String fields, Set<String> imports) { def sb = new StringBuilder() sb.append("package ").append(PACKAGE_NAME).append(";\n\n") imports.each { imp -> sb.append("import ").append(imp).append(";\n") } sb.append("\n") if (table.comment) { sb.append("/**\n") sb.append(" * ").append(table.comment).append("\n") sb.append(" * @author ").append(AUTHOR).append("\n") sb.append(" */\n") } if (USE_LOMBOK) { sb.append("@Getter\n") sb.append("@Setter\n") } sb.append("@Entity\n") sb.append("@Table(name = \"").append(table.name).append("\")\n") sb.append("public class ").append(className) if (ENABLE_SERIALIZABLE) { sb.append(" implements Serializable") } sb.append(" {\n\n") if (ENABLE_SERIALIZABLE) { sb.append(" private static final long serialVersionUID = 1L;\n\n") } sb.append(fields) sb.append("}\n") return sb.toString() } def isPrimaryKey(DasTable table, DasColumn col) { try { def pks = DasUtil.getPrimaryKeys(table) if (pks != null && pks.size() > 0) { return pks.any { pk -> pk.name == col.name } } } catch (Exception e) { // 部分 IDEA 版本没有 getPrimaryKeys,退化为列名判断 } return col.name.equalsIgnoreCase("id") } def toCamelCase(String name) { def cleanName = name def lower = name.toLowerCase() if (lower.startsWith("t_")) { cleanName = name.substring(2) } else if (lower.startsWith("tb_")) { cleanName = name.substring(3) } def parts = cleanName.split("_") def result = parts[0].toLowerCase() for (int i = 1; i < parts.length; i++) { result += parts[i].substring(0, 1).toUpperCase() + parts[i].substring(1).toLowerCase() } return result } def toPascalCase(String name) { def camel = toCamelCase(name) return camel.substring(0, 1).toUpperCase() + camel.substring(1) } def mapType(DataType dataType) { def typeName = dataType.name?.toLowerCase() switch (typeName) { case ["int", "int2", "int4"]: return "Integer" case ["int8", "bigint", "bigserial"]: return "Long" case ["varchar", "char", "bpchar", "text", "citext"]: return "String" case ["numeric", "decimal"]: return "BigDecimal" case ["float4", "real"]: return "Float" case ["float8", "double precision"]: return "Double" case ["bool", "boolean"]: return "Boolean" case ["date"]: return "LocalDate" case ["timestamp", "timestamptz", "datetime"]: return "LocalDateTime" case ["time", "timetz"]: return "LocalTime" case ["bytea"]: return "byte[]" case ["uuid"]: return "UUID" default: return "String" } }

这个脚本几个值得注意的设计点:

  • 文件写入统一用UTF-8是 Groovy 默认行为,IDEA 底层的File.write会正确处理中文字符,因此注释出现中文不会乱码。
  • import 集合用的是LinkedHashSet,这样能保证 import 顺序稳定,不会出现每次生成结果顺序都不一样的情况。
  • 脚本顶部特意放了ID_STRATEGY配置项,适配不同项目主键策略。
  • isPrimaryKey方法里做了兼容处理,优先读取数据库主键元数据,读不到就按列名id兜底。实际项目里大部分业务表主键确实就叫id,兜底策略能解决很多兼容问题。

4.2 实际生成的效果预览

假设数据库有一张sys_user表,结构如下:

列名类型注释
idint8主键
usernamevarchar(64)用户名
password_hashvarchar(128)密码哈希
emailvarchar(128)邮箱
statusint2状态:1启用 0停用
created_attimestamptz创建时间
updated_attimestamptz更新时间
versionint4乐观锁版本号

脚本生成出来的类大概是这样:

package com.example.jpa.entity; import java.io.Serializable; import java.time.LocalDateTime; import javax.persistence.*; import lombok.Getter; import lombok.Setter; /** * 系统用户表 * @author your-name */ @Getter @Setter @Entity @Table(name = "sys_user") public class SysUser implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; /** * 用户名 */ @Column(name = "username") private String username; /** * 密码哈希 */ @Column(name = "password_hash") private String passwordHash; /** * 邮箱 */ @Column(name = "email") private String email; /** * 状态:1启用 0停用 */ @Column(name = "status") private Integer status; /** * 创建时间 */ @Column(name = "created_at") private LocalDateTime createdAt; /** * 更新时间 */ @Column(name = "updated_at") private LocalDateTime updatedAt; /** * 乐观锁版本号 */ @Column(name = "version") private Integer version; }

这个结果和我手工写实体类的习惯已经很接近了。唯一可能还要手工补的是version字段的@Version注解,以及如果需要乐观锁,可以给脚本增加一个约定:当字段名为version时自动加上@Version。这种约定式生成在团队内部非常实用,强烈建议加上。

4.3 生成之后常见的微调事项

就算脚本再完善,生成完也不可能 100% 不需要动。下面几个调整我几乎每次都遇到。

第一个是主键策略。PostgreSQL 里如果表的主键是使用序列维护的,脚本生成的IDENTITY就要手工改成SEQUENCE,同时补上@SequenceGenerator。这步如果嫌麻烦,可以直接在脚本顶部把ID_STRATEGY改成SEQUENCE并填好序列名。

第二个是关联字段。JPA 实体类里的@ManyToOne、@OneToMany关联,我不建议写在自动生成脚本里。原因很简单:数据库外键关系复杂,自动生成关联注解很可能把只读关联和多表查询搞混。我见过有人试过自动生成关联字段,结果一张主表关联十几个子表,实体关系变得又乱又难维护。关联关系自己手写更稳妥。

第三个是通用字段。比如create_time、update_time这种审计字段,如果你项目里集成了 Hibernate Envers 或者审计框架,生成的LocalDateTime字段往往还要手工加上@CreatedDate、@LastModifiedDate这类注解。这些属于业务规则,脚本不建议过度自动化,生成后按团队规范补一下反而更清晰。

5. 实战中的坑与排查实录

5.1 脚本跑不起来:日志往哪里看

第一次写完 Groovy 脚本,运行的时候几乎不可能一次通过。IDEA 执行数据库脚本时,println 输出会打印在控制台的 Script 输出面板里,一般是在 Run 工具窗口下面,信息很全。

最常见的两个报错:

  • unable to resolve class com.intellij.database.util.DasUtil:这是 IDEA 版本之间 API 的差异问题。早期版本里的包名是com.intellij.database.model.DasUtil,新版本移动到了com.intellij.database.util.DasUtil。出现这个报错时,把 import 改一下就行。
  • FILES变量未定义:脚本必须通过在数据库面板右键选中表来触发,不是随便建一个 Groovy 文件运行。如果右键菜单里看不到你写的脚本,检查一下 Groovy Scripts Directory 是否配置正确,另外脚本文件名不要用中文。

还有一次我把脚本写得很长,运行时 IDEA 提示语法错误,排查半天发现是switch分支里的中文字符全角逗号混进去了。Groovy 对代码风格比较宽容,但对符号异常仍然零容忍,写脚本时最好打开 IDEA 自带的语法提示。

5.2 类型映射不满足业务:jsonb 字段怎么处理

PostgreSQL 的jsonb类型在 IDA 的DataType里返回的是别名jsonb,按照前面映射表会走default返回String。这在大多数情况下没问题,但如果你要执行 JPA 的Specification查询,直接在 JSONB 字段上用String类型的相等条件会很别扭,因为底层 SQL 需要对 jsonb 做?或者->>操作。

更规范的做法是引入 Hibernate 6 的类型注解:

@JdbcTypeCode(SqlTypes.JSON) @Column(name = "extra_attrs", columnDefinition = "jsonb") private String extraAttrs;

或者为 JSONB 字段定义一个简单 Json 转换器,在实体类里用@Convert注解标注。这个逻辑如果在生成脚本里做,需要项目里预先定义好转换器类,然后把脚本里 jsonb 分支从返回String改成返回自定义类型并补上对应 import。我建议接手老项目时先看看团队有没有已经封装好的 JSON 类型处理器,再去改脚本映射。

5.3 连接不上数据库或看不到表

脚本虽然和数据库驱动不直接相关,但脚本读取表结构依赖数据源元数据同步。如果数据库面板里刷新失败,脚本就拿不到列。

常见原因:

  • PostgreSQL 驱动版本与数据库版本不匹配,连接测试通过但 schema 列表为空。解决方法是去官方仓库下载对应大版本的 JDBC 驱动,在数据源驱动设置里替换。
  • 没有勾选正确 schema。一个数据库里有多个 schema 时,默认只加载当前选中 schema 下的表,需要到数据源的高级配置里把业务 schema 加上。
  • ID 连接的是只读账号,IDEA 不会因此拒绝读元数据,但某些云数据库实例的只读账号看不到注释信息。这时候生成出来的实体类就会缺 JavaDoc,只能换账号或者手动补注释。

5.4 如何让脚本成为团队资产

脚本写好后,不要只留在本地,我建议把它作为项目的代码生成工具放在仓库里。目录结构这样划分:

tools/ db-codegen/ GenerateJpaEntityForSpringBoot.groovy README.md

README 里写明两件事:第一,这个脚本如何安装到 IDEA 的 Groovy Scripts Directory;第二,生成后的代码规范,包括主键策略、字段映射、Lombok 约定。团队新人配置完环境后,少问很多重复问题。

另外,IDEA 每次大版本升级后,Groovy 脚本的 API 可能会有变化,保持脚本在仓库里意味着你可以在升级后快速修改并同步给团队。这个收益在多人协作的场景下特别明显。

最后分享一个我实际操作中的体会:这个脚本前前后后改了三四版,一开始只是加上 Lombok 和注释,后来才逐步加入类型映射、主键策略配置。现在每次新建表,右键一键生成实体类,再花两分钟检查一下关联关系和审计字段,基本就能提交代码。写脚本这件事,前期花几个小时,后面省下的时间远远不止这几个小时。如果你也每天被实体类生成折磨,强烈建议照着文章的思路定制一版自己的脚本,不用追求一步到位,先解决最痛的问题,再慢慢迭代。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 7:40:09

libusb-1.0.26二进制包使用全指南:跨平台配置与底层通信排错

简介&#xff1a;本资源为libusb-1.0.26版本的跨平台二进制文件合集&#xff0c;面向嵌入式开发、USB设备驱动编写及底层通信调试的中高级开发者&#xff0c;解决Windows/Linux/macOS多环境下快速集成USB底层通信能力的问题。压缩包共95个文件&#xff0c;涵盖18个测试工具&…

作者头像 李华
网站建设 2026/10/13 7:40:08

混合计算模式:大数据平台如何实现批流一体与统一资源调度

2. 到底在解决什么问题&#xff1a;先看场景再谈架构这两年做数据平台&#xff0c;只要摊子铺得够大&#xff0c;早晚都会撞上一个尴尬局面&#xff1a;不同的业务组上了不同的计算引擎&#xff0c;有的用批处理框架&#xff0c;有的用流处理引擎&#xff0c;有的干脆直接用一套…

作者头像 李华
网站建设 2026/10/13 7:39:59

鸿蒙开发实战:React Native内置组件从踩坑到落地

项目走到了第三天。前两天把环境从Node到HarmonyOS SDK逐个排掉&#xff0c;又立好了导航骨架&#xff0c;我以为这套React Native代码跑在鸿蒙上应该问题不多了——结果真正开始写康复系统的业务页面&#xff0c;也就是把内置组件一个个摆到界面上时&#xff0c;才发现该踩的坑…

作者头像 李华
网站建设 2026/10/13 7:39:21

XGraph实战:图神经网络从训练到部署的工程化落地指南

简介&#xff1a;面向VC开发者的专业曲线绘制控件XGraph&#xff0c;以完整工程包形式提供。控件基于MFC/ATL框架实现&#xff0c;支持同一坐标系内多条曲线叠加显示&#xff0c;通过颜色、线型与标记区分数据系列&#xff0c;并提供坐标轴刻度自定义、动态缩放滚动及鼠标悬停取…

作者头像 李华
网站建设 2026/10/13 7:38:59

Qt+C++飞机大战实战项目:可编译可调试可扩展的游戏工程

简介&#xff1a;这是一份基于C语言实现的经典2D射击游戏——飞机大战的完整开源项目&#xff0c;面向C初学者与图形编程入门者&#xff0c;适用于课程设计、课设实践及小型游戏开发能力训练。项目采用轻量级架构&#xff0c;包含55个文件&#xff0c;涵盖13个头文件&#xff0…

作者头像 李华
网站建设 2026/10/13 7:38:05

多分类GBDT实战:从损失函数到参数调优与避坑指南

做多分类模型时&#xff0c;不少人在二分类迈过去以后&#xff0c;第一个想法就是用GBDT直接怼上去。梯度提升树在表格数据上的表现确实很稳&#xff0c;但多分类和回归、二分类有本质区别&#xff1a;损失函数不再只有一条残差曲线&#xff0c;而是同时拟合多个类别的“竞争关…

作者头像 李华