尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

IDEA插件:Java实体类一键生成MySQL/Oracle建表SQL与JSON

IDEA插件:Java实体类一键生成MySQL/Oracle建表SQL与JSON 简介面向 Java 后端开发者的一款 IntelliJ IDEA 插件用于将实体类一键转换为 MySQL、Oracle 建表语句以及 JSON 请求体格式减少手写 SQL 和接口参数样板代码适合需要频繁建表、撰写接口文档或进行前后端联调的场景。压缩包共 18 个文件以 4 个 Java 源码、7 个 XML 配置、Markdown 说明与 License 文件为主包体仅 63KB轻量小巧可直接通过 IDE 的本地安装方式加载也可查看源码学习插件动作注册、菜单扩展等实现细节。插件安装后在实体类上右键选择 ToMysql、ToOracle 或 ToJson 命令生成内容即写入系统剪贴板无需离开编辑器即可完成转换可显著提升日常开发效率。目前已有 773 人学习下载项目还附带 README 说明与授权文件方便二次开发和自定义扩展对 IntelliJ IDEA 插件开发感兴趣的读者同样值得参考。 手写建表 SQL 这种重复劳动哪个后端没经历过字段多了以后一个字段一个字段对着实体敲 DDL写完了还要再手工整理一份 JSON 示例给前端联调碰到 Oracle 和 MySQL 两套库都要支持的项目工作量直接翻倍。我为什么花时间写这个 IDEA 插件就是想把“实体类 - 建表语句 - 请求体示例”这条链路上的机械化操作全部自动化掉。这个工具的核心能力很简单选中一个 Java 实体类右键一按MySQL 建表语句、Oracle 建表语句、JSON 请求体模板三样东西直接生成好不用离开 IDE不用复制类名去网页工具里折腾更不用手动对齐字段类型和注释。这个插件适合谁如果你平时维护的业务系统里有大量的 PO/DO/VO 类每个类都要对应一张表接口文档还需要附带 JSON 示例那你大概率会被这种重复劳动恶心到。这个插件直接面向 Java 后端开发者尤其是实体类数量多、数据库字段规范严格、接口文档要求结构完整的项目场景。下面我从设计思路、核心实现、实操过程、常见问题几个角度完整拆一遍代码结构、关键配置和处理逻辑都会放出来感兴趣的可以直接照着思路自己搭一个细节足够你落地。1. 整体设计思路为什么做成 IDEA 插件而不是独立工具先聊设计取舍。当时摆在面前的有两个方向一个是做成 Maven 插件在编译期扫描实体类生成 SQL另一个是做成 IDEA 插件在开发期通过图形界面直接操作。我最后选了 IDEA 插件最核心的原因是我希望生成动作和开发动作发生在同一个上下文里。开发者就在写实体类的那个窗口里手指一按就能看到生成结果而不是切到命令行跑一个mvn compile再打开 target 目录翻文件。而且 IDEA 插件可以直接利用 IDE 的语法解析能力拿到类的完整结构不需要像 Maven 插件那样通过反射去读取 class 文件这样即使代码还没编译过只要是编辑器里能识别的状态就能处理。插件在技术实现上走了标准的 IntelliJ Platform Plugin 路线。动作入口继承AnAction注册到编辑器右键菜单和Generate菜单组里。模型解析基于 IDEA 的 PSIProgram Structure Interface体系读取类名、字段名、字段类型、注释不需要依赖反射。类型映射自己维护一套 Java 类型到 MySQL/Oracle 数据类型的映射表支持注解覆盖默认映射。文本生成用模板字符串拼接 SQL 和 JSON不引入额外的模板引擎减少依赖体积。UI 反馈生成结果统一输出到 IDEA 的 Tool Window带语法高亮支持一键复制。1.1 核心需求解析把这个插件要解决的三个核心问题拆开看每个都对应一条独立的生成逻辑。第一实体转 MySQL 建表语句。实体类上有类注释、字段注释字段有类型、长度、是否为空、默认值等信息这些信息通过 PSI 都能拿到转成 DDL 的关键在于类型映射的准确性。比如LocalDateTime要映射到datetimeBigDecimal要映射到decimal还要带上精度参数String默认给varchar(255)但最好允许注解指定长度。还有一个细节是 MySQL 的DROP TABLE IF EXISTS和CREATE TABLE的拼装顺序以及表名如果和关键字冲突需要自动加反引号。第二实体转 Oracle 建表语句。Oracle 和 MySQL 的 DDL 差异不小字符串类型要用VARCHAR2自增主键要用序列加触发器注释语法完全不同MySQL 是COMMENT xxx写在列定义的后面Oracle 是独立的COMMENT ON COLUMN 表名.列名 IS xxx。这些差异必须要在生成器里分别处理不能只改个数据库类型名就完事。还有分页、驱动这些跟建表无关的东西插件完全不碰避免引入无关复杂度。第三实体转 JSON 请求体。这个功能的本质是把 Java 对象的结构转成 JSON但有个场景上的差别接口文档里的 JSON 示例通常不需要真实值只需要结构化的示例数据。所以生成策略采用空值 JSON 模板加注释说明String字段给一个空字符串示例Integer字段给0对象嵌套按类结构递归展开列表字段给一个空数组。这样前端看到的结构和后端实体完全对齐又不用费劲编造假数据。1.2 技术选型与依赖范围IDEA 插件开发的依赖范围是个容易被忽略的坑。开发插件时默认会引入intellij.idea这个 dependency里面带着完整 IDE 的 jar 包但如果你只是生成文本、解析 PSI完全用不到那么重的依赖。用sinceBuild和untilBuild声明好插件兼容的 IDEA 版本。对 Java 版本的要求是 11 以上因为 IDEA 2020.2 之后的插件运行环境就是 JBR 11 起步。插件开发本身的工程结构遵循 Gradle 标准build.gradle 里应用org.jetbrains.intellij插件设置intellij.version和intellij.typetype 对免费社区版用IC对旗舰版用IU。本地调试直接runIde任务就能拉起一个沙箱 IDEA 实例断点调试和普通 Java 开发没有任何区别。我实际开发时踩过一个坑直接用plugin.xml里声明dependscom.intellij.modules.java/depends的话社区版跑不起来因为 Java 模块只在旗舰版和社区版里以不同方式存在正确做法是dependscom.intellij.modules.platform/depends加上对 Java PSI 的显式依赖。2. 核心功能实现三个生成器的逻辑拆解插件的核心代码整体上分三大块实体解析器、SQL 生成器、JSON 生成器。实体解析器负责从 PSI 对象中提取结构化信息SQL 生成器和 JSON 生成器消费这些结构化信息分别产出对应文本。实体解析器是最底层的组件它的输入是PsiClass输出是一个中间结构public class EntityModel { private String tableName; private String entityComment; private ListFieldModel fields; } public class FieldModel { private String columnName; private String fieldName; private String fieldType; // 反射类型全限定名比如 java.lang.String private String columnComment; private Integer length; // 显式长度没有则为 null private Integer precision; // 精度用于 decimal private Integer scale; // 小数点位数 private boolean primaryKey; private boolean notNull; private String defaultValue; private boolean autoIncrement; }这个中间结构的好处是数据库方言的差异在生成器这一层隔离开解析器只用关心 Java 侧的东西。比如主键判断解析器会检查字段名是否为id或者是否存在TableId注解这些逻辑和具体生成 MySQL 还是 Oracle 的 SQL 无关。后续如果我还想支持 PostgreSQL只需要新写一个生成器复用同一套EntityModel就行了。2.1 实体转 MySQL类型映射与注释处理MySQL 生成器拿到EntityModel之后先拼表名默认取类名转下划线格式比如UserOrder会转成user_order。如果类上有TableName注解直接用注解值覆盖。这个转换函数是我手写的核心逻辑就是大小写字母交替处插入下划线再统一转小写实测覆盖绝大多数命名场景。字段类型映射是整个生成过程的核心我维护了一张映射表Java 类型MySQL 类型说明Stringvarchar(255)有 Column(length) 时用注解值Long / longbigint主键且有自增注解时加 AUTO_INCREMENTInteger / intint常规整数BigDecimaldecimal(10,2)精度和标度可从注解读取LocalDateTimedatetime日期时间也可根据注解转成 date/timestampLocalDatedate仅日期LocalTimetime仅时间Boolean / booleantinyint(1)布尔值用 0/1 存储byte[]blob二进制大对象这里要单独讲讲Column注解的解析。MyBatis-Plus 的TableField和 JPA 的Column都是插件需要支持的注解解析方式就是读取 PSI 注解属性。注解属性在 PSI 里是PsiNameValuePair通过PsiAnnotation.findAttributeValue()拿到之后再去取对应的字符串或数字值。以长度为例伪代码如下PsiAnnotation columnAnno field.getAnnotation(javax.persistence.Column); if (columnAnno ! null) { PsiNameValuePair lengthAttr columnAnno.findAttribute(length); if (lengthAttr ! null) { // 解析字符串值得到 Integer return Integer.parseInt(lengthAttr.getValue().getText().replaceAll(\, )); } }还有索引和唯一约束这些是建表语句里很重要但很容易漏掉的部分。因为 MySQL 的 DDL 里索引通常写在字段定义之后所以生成器要先收集所有需要建索引的列最后统一拼到CREATE TABLE的末尾。Index这种注解在 MyBatis-Plus 里不是标准字段注解所以我自定义了一个GenIndex注解专门给插件消费。2.2 实体转 Oracle方言差异处理Oracle 生成器在整体流程上和 MySQL 生成器是一致的但细节差异很多这里展开讲一下最关键的几个点。第一个是类型映射差异。Oracle 没有varchar只有varchar2字符串类型默认最长 4000 字节和 MySQL 的varchar(255)有本质区别。BigDecimal在 Oracle 里对应number(p,s)如果没给精度就直接number允许任意精度。LocalDateTime对应timestamp比 MySQL 的datetime精度更高默认 6 位小数秒。布尔值在 Oracle 里没有原生的 boolean 列类型实际项目一般用number(1)存 0/1或者char(1)存 Y/N默认生成number(1)。第二个是主键策略。Oracle 的官方推荐主键生成方式是序列加触发器BIGINT自增列不能直接写在 DDL 里要先创建序列CREATE SEQUENCE SEQ_USER_ORDER_ID START WITH 1 INCREMENT BY 1 NOCACHE;然后创建触发器CREATE OR REPLACE TRIGGER TRG_USER_ORDER_ID BEFORE INSERT ON user_order FOR EACH ROW WHEN (NEW.id IS NULL) BEGIN SELECT SEQ_USER_ORDER_ID.NEXTVAL INTO :NEW.id FROM DUAL; END;这两个对象必须在建表语句之后生成所以输出结果我设计成三段式表结构、序列、触发器。拼字符串的时候用三个 StringBuilder 分别维护最后按顺序输出。用WHEN (NEW.id IS NULL)这个条件是为了兼容某些场景下手动指定 ID 的情况不然每次插入都会强制走序列跟业务上的特殊逻辑冲突。第三个是注释语法。Oracle 不支持列内联注释COMMENT ON COLUMN只能独立一行一条。生成的时候我直接在CREATE TABLE结束后统一追加用--说明这是字段注释语句。这里有个我一直觉得 Oracle 设计得很反人类的地方注释语句用的列名必须是大写因为 Oracle 默认存储就是大写列名如果写小写注释会挂到一个不存在的列上。所以生成器里要对列名做toUpperCase()。2.3 实体转 JSON结构递归与示例值填充JSON 请求体的生成逻辑相对 SQL 简单一点但递归结构处理上有个设计决策值得说一下是直接对PsiClass做结构遍历还是用反射拿 Java 对象做序列化。我选了前者因为很多时候实体类还没有编译甚至在写实体和生成器的当下代码是编译不过的状态反射根本走不通。PSI 遍历没有这个限制只要编辑器的语法分析能通过它就能拿到类型信息。代价是自己要处理继承和泛型这些反射里现成的能力但实际实体类转 JSON 这个场景里继承用得并不多泛型也就是ListProduct这类最基础的用法手动处理完全可控。递归遍历的时候用一个JsonBuilder类它维护一个带有当前缩进级别的可扩展输出结构。每次处理方法调用前先判断字段类型基本类型或包装类型直接输出示例值。String输出string或者根据类名给出更语义化的示例值。自定义对象递归调用增加缩进。ListT输出[]并在注释里标注元素类型。嵌套层级过深容易导致 JSON 过长我设置了一个最大递归层级默认 5 层超过就直接输出空对象{}并在注释里提示。实际用下来这个阈值还能接受因为业务请求体的嵌套深度一般不超过 4 层5 层已经留了余量。生成结果支持两种模式带注释版和纯 JSON 版。带注释版每种字段输出一个注释行说明“字段名、类型、含义、是否必填”适合直接贴到接口文档里。纯 JSON 版则去掉所有注释适合快速放到 Postman 里做联调。这两个模式通过插件的 Settings 配置项切换不单独做 UI。3. 实操过程从安装到生成一份完整建表脚本下面走一遍实际使用流程。插件开发完打包好的 jar 文件在 IDEA 里按File - Settings - Plugins - Install Plugin from Disk选择 jar 包安装重启 IDE 就生效了。需要支持 IntelliJ IDEA 2021.3 及以上版本实测过 2021.3 到 2024.1 均可正常使用。安装完成后打开任意 Java 项目找到目标实体类。以这个UserInfo为例/** * 用户信息表 */ TableName(t_user_info) public class UserInfo { TableId(type IdType.AUTO) private Long id; /** 用户名 */ Column(length 64) private String username; /** 密码 */ Column(length 128) private String password; /** 邮箱 */ private String email; /** 年龄 */ private Integer age; /** 创建时间 */ Column(length 20) private LocalDateTime createTime; }在编辑器里右键找到Gen SQL JSON菜单项展开后有三个子项MySQL DDL、Oracle DDL、JSON Request。任意点击一个IDEA 底部会弹出插件的输出面板里面就是生成好的文本。这是 MySQL DDL 的输出结果-- 表: t_user_info / 注释: 用户信息表 DROP TABLE IF EXISTS t_user_info; CREATE TABLE t_user_info ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(64) DEFAULT COMMENT 用户名, password varchar(128) DEFAULT COMMENT 密码, email varchar(255) DEFAULT COMMENT 邮箱, age int DEFAULT NULL COMMENT 年龄, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户信息表;注意几个细节id因为带了TableId(type IdType.AUTO)注解插件自动识别为自增主键username因为带了Column(length 64)生成的长度是 64不是默认的 255create_time因为是LocalDateTime类型自动映射成了datetime。这些细节都是类型映射配置生效的直接体现。Oracle DDL 的输出则完全不同-- 表: t_user_info / 注释: 用户信息表 DROP TABLE t_user_info; CREATE TABLE t_user_info ( id NUMBER(19) NOT NULL, username VARCHAR2(64) DEFAULT , password VARCHAR2(128) DEFAULT , email VARCHAR2(255) DEFAULT , age NUMBER(10), create_time TIMESTAMP DEFAULT SYSTIMESTAMP, CONSTRAINT PK_T_USER_INFO PRIMARY KEY (id) ); -- 序列 CREATE SEQUENCE SEQ_T_USER_INFO_ID START WITH 1 INCREMENT BY 1 NOCACHE; -- 触发器 CREATE OR REPLACE TRIGGER TRG_T_USER_INFO_ID BEFORE INSERT ON t_user_info FOR EACH ROW WHEN (NEW.id IS NULL) BEGIN SELECT SEQ_T_USER_INFO_ID.NEXTVAL INTO :NEW.id FROM DUAL; END; -- 字段注释 COMMENT ON COLUMN t_user_info.id IS 主键ID; COMMENT ON COLUMN t_user_info.username IS 用户名;表名没有加引号是因为在 Oracle 里不加引号自然就是大写存储符合规范。NUMBER(19)是分配Long类型自然映射到的精度不会像 MySQL 的bigint那样看一眼就知道有 8 字节但语义上是一致的。注释全部放在建表语句之后这是为了保持脚本在 SQL*Plus 里能顺序执行不会因为注释打断语句块。3.1 输出面板与一键复制输出面板的设计参考了 IDEA 自带的数据库工具顶部是一个JTextArea做只读展示底部放两个按钮“复制全部”和“关闭”。复制按钮注册了CopyPasteManager点击后直接写入系统剪贴板方便转到 Navicat 或 PL/SQL Developer 里执行。面板内容按生成类型自动做语法高亮SQL 用浅蓝色区分关键字JSON 用不同颜色区分键名和字符串值这个效果是通过 JEditorPane 的HTMLDocument做的没有引入完整语法高亮框架。颜色高亮这个功能原本没打算做但实际用了几天后发现没有高亮找字段名很费劲尤其是字段多的时候。我花了大约半天时间写了一个简单的文本高亮器维护一个 SQL 关键字集合遍历输出文本对关键字和注释分别上色。在数据量上来之后还是比看纯文本舒服很多。提示如果 SQL 里有中文注释需要确认 IDEA 沙箱环境的默认字符集是 UTF-8否则贴到 Windows 的 CMD 窗口执行时可能乱码。这个问题不影响生成但会影响显示注意运行-Dfile.encodingUTF-8启动参数即可。3.2 配置项与自定义扩展插件在 IDEA 的Settings - Tools - Gen SQL JSON下面提供了几个配置项默认值就能满足大部分场景但做特殊定制时很有用默认字符串长度默认 255可改成 128 或 64。表名前缀比如统一加t_默认关闭按类名原样转换。默认 MySQL 引擎默认InnoDB可选MyISAM。默认字符集默认utf8mb4可选utf8。JSON 生成模式默认带注释可选纯 JSON。Oracle 主键方式默认序列加触发器可选无主键策略只建表。这些配置会存到PersistentStateComponent里IDEA 自动管理序列化和持久化不用自己写配置文件读写。没有提供过多开关是刻意控制复杂度因为配置项的边际收益递减加太多反而让设置界面难用。4. 常见问题与排查技巧开发和使用这个插件过程中我先后遇到过不少问题这里挑几个典型的整理成速查表后续维护或者二次开发时可以少踩点坑。问题表现排查与解决插件安装后菜单不显示右键菜单里找不到 Gen SQL JSON检查 plugin.xml 里 action 注册的parentGroup可能需要改成EditorPopupMenu而不是GenerateGroup字段注释总是为空生成 SQL 里 COMMENT 是空字符串PSI 取注释时要用PsiDocComment直接从field.getFirstChild()找不一定能拿到推荐用field.getDocComment()类型映射不生效LocalDateTime被映射成varchar检查类型匹配用的是全限定名还是简单类名LocalDateTime的 PSI 类型拿到的是java.time.LocalDateTime匹配时要全名比较多模块项目扫不到实体右键时提示无法解析类ActionEvent的PsiFile可能为空需要通过CommonDataKeys.PSI_FILE再取一次同时判断event.getProject()重复生成带括号的默认值默认值CURRENT_TIMESTAMP被加了两层括号生成 SQL 时要注意MySQL 里DEFAULT CURRENT_TIMESTAMP不需要括号但DEFAULT (abc)需要建议默认值统一走一个 escape 函数判断内部是否已带括号IDEA 升级后插件失效报PluginIncompatibleException修改 build.gradle 里的untilBuild或者用DynamicPluginV2声明兼容新版本4.1 踩过的坑PSI 注释获取的正确姿势PSI 注释获取是插件开发中最基础也最容易出错的一块。JDK 文档注释在 PSI 里的呈现是PsiDocComment它挂在PsiField上但获取方式不能想当然地直接遍历子节点。我在最初版本里遍历field.getChildren()找PsiDocComment结果经常找到的是空节点。后来改用官方推荐的PsiDocCommentUtil.getDocComment(field)稳定可靠。不同注释格式的解析在生成时也要区分。/** 用户名 */取文本内容时会带上*和空格需要做一个清洗把每行首尾的空白字符去掉再把行首的*去掉最后拼接成一个单行字符串。这是个小而常见的坑很多文档注释是多行的/** * 用户名 * 同时用于登录和展示 */如果直接取原始文本拼接生成出来的 SQL 注释会变成用户名\n * 同时用于登录和展示在 SQL 里会断行出错。清洗函数必须把这些换行和星号处理干净。4.2 踩过的坑IDEA Action 注册时的isEnabled条件Action 注册到右键菜单后不在 Java 文件上点击时菜单项应该置灰不然用户在任何文件上比如 XML、properties都会看到 Gen SQL JSON 菜单点了之后弹一个莫名其妙的错误。这个需求用AnAction的update()方法实现在里面判断当前文件类型Override public void update(AnActionEvent e) { PsiFile psiFile e.getData(CommonDataKeys.PSI_FILE); boolean enabled psiFile instanceof PsiJavaFile; e.getPresentation().setEnabledAndVisible(enabled); }setEnabledAndVisible这个方法同时控制了可用状态和可见状态比较省心。实测还有一个小小的联动问题如果当前选中的是类名而不是整个类文件PsiFile还是能正确返回PsiJavaFile但PsiElement可能指向类名这个节点需要往上找到PsiClass再操作。正确做法是先用CommonDataKeys.PSI_ELEMENT拿当前元素然后递归往上找PsiClass父节点。4.3 踩过的坑递归解析时碰到循环引用实体类 A 里有一个字段是 B 类型B 里又有一个字段是 A 类型这种双向关联在业务里很常见。做 JSON 生成时如果不加处理递归会无限嵌套下去直到栈溢出。我的方案是为每个已访问的类维护一个“解析中”集合进入解析前先检查该类是否已在集合中在就返回{}并附加注释“循环引用已截断”。这样既防止了栈溢出又保留了一定可读性。处理 SQL 生成时循环引用不存在问题因为建表只分析当前一个表的字段不会去生成关联表的 DDL。但如果我后续想扩展“生成所有关联表”的功能就必须先做拓扑排序确定建表顺序否则外键引用会失败。这是一个明确标记的待扩展点。5. 后续扩展思路与建议插件目前的版本已经能覆盖日常开发中的常见场景但还有一些方向可以继续完善按优先级从高到低排列支持 PostgreSQL 方言。PostgreSQL 的 DDL 和 MySQL 又有很大差别比如SERIAL自增、TEXT类型、COMMENT ON语法和 Oracle 很像类型映射表增加一个方言枚举就能接入。支持导出增量 SQL只输出变更字段的 ALTER TABLE 语句。这个功能的难点在于需要先解析现网表的实际结构不能只靠实体类实体类和现网表的字段差异比对可以采用字符串拉链算法实现。支持从 SQL 反向生成实体类。不只是从 Java 到 SQL还要能从 SQL 到 Java这在接手老项目、根据数据库表反推实体时非常有用。增加模板引擎如 Freemarker 或 Velocity让用户自定义生成模板。这个对个性化需求多的团队帮助很大但对插件本身的结构要求会更高文本生成从硬编码字符串切换到模板渲染意味着单元测试的基准也要改。选择优先做 PostgreSQL 的原因很现实身边的项目用 MySQL 和 Oracle 偏多但 PostgreSQL 的用户基数也在快速增长一旦有人提出“能不能也支持 PG”这时候再回来改类型映射、改自增策略、改注释语法改动范围不小。提前把方言抽象做好后续接 PG 的时间可以压缩到一周以内。6. 总结与个人体会我在实际开发这个插件的过程中最大的体会是写 IDEA 插件跟写普通 Web 后端的思路差别很大核心不在于你能调多少 API而在于你对“IDE 内部的数据结构”理解得清不清楚。PsiClass不是反射里的Class它存在的前提是语法分析成功默认严格模式宁可拿不到信息也不降级。类型映射表的设计决定了这个插件 80% 的价值务必一开始就设计得可配置不要写死在 switch-case 里。最后再分享一个非常实用的调试技巧IDEA 插件开发时直接在build.gradle里配置runIde的 JVM 参数比如jvmArgs -Xmx2G能避免沙箱 IDEA 因内存不足崩溃。调试快捷键和日常开发保持一致断点一旦命中就能看到 PSI 树的全貌这一步对理解 PSI 结构帮助极大也让我从“写面向对象的 Java”切换到了“写面向 PSI 结构的 Java”这个坎一迈过去插件开发的大部分难点就已经解决了。本文还有配套的精品资源点击获取
返回列表