用PDManer实现数据库表建模、建表语句生成与代码生成
上个月接手一个新模块产品给的需求文档里附了三张 Excel 表格每一行是一个字段。我当时的第一反应是又要开始手工整理建表语句了。后来我在团队里重新把 PDManer 捡起来用数据库表建模把三十多张表的字段、关系、索引全部画清楚再生成建表语句并且用它的代码生成功能直接产出 Spring Boot 的实体和 Mapper 骨架。整个过程比预想中顺利也踩了一些文档里不会写的坑。这篇就记录一下我怎么用 PDManer 完成数据库表建模、建表语句生成和代码生成给正在选型或者刚开始用的同学一点参考。1. 为什么我在项目里选 PDManer 而不是其他建模工具1.1 没有建模工具的日子Excel、口头约定和失控的 DDL之前很长一段时间我们团队维护表结构主要靠 Excel。每个模块维护一份 sheet谁改了字段就发一版新的命名经常不统一注释偶尔还会写错。最难受的是“字段改了但建表脚本没同步”Excel 里已经加了status字段测试库却还是老结构排查问题的时候根本说不清到底哪份是准的。后来试过直接用 Navicat 建表快是快但建完就散了没有设计过程后面想整理字段关系非常痛苦。等到要写数据字典或者评审的时候又要对着数据库把每个表手工抄一遍效率极低。再后来我意识到缺的不是“建表工具”而是“建模工具”。数据库表建模应该先把结构、关系、索引这些设计沉淀成一个统一模型然后再从这个模型生成不同数据库的建表语句。这样才能保证设计文档、DDL、代码三者的口径一致。1.2 PDManer 真正打动我的三个功能PDManer 是开源免费的中文建模工具最初叫 PDMan后来改名“元数建模”。我一共对比过好几个工具最终确定用它主要看中三点建表语句生成足够方便模型画完以后可以直接导出 MySQL、PostgreSQL、Oracle、SQL Server 等常见数据库的 DDL不用每个库写一套方言。代码生成能力开箱即用它内置了 Java、MyBatis 相关的代码生成模板能根据表结构生成实体类、Mapper 等骨架代码省掉一大半重复工作。模型文件是 JSON 结构存的是文本文件天然适合放进 Git 管理评审变更的时候能看到模型层面的改动而不是只给一句“我加了张表”。我用下来的真实感受是它没有 PowerDesigner 那么重也没有纯手写 SQL 那么“裸”。对于一个中小团队来说这个平衡点比较舒服。1.3 和 PowerDesigner、Navicat Data Modeler 放在一起比如果你也在选型我列一张对比表是我实际用过的印象工具成本学习曲线代码生成模型文件可否进 GitPDManer免费开源低中文界面友好内置 MyBatis/Java 模板可自定义可以JSON 文件PowerDesigner商业授权高功能多但操作繁琐需要额外配置二进制文件合并不方便Navicat Data Modeler商业中基本没有模型文件相对弱最终选择 PDManer还有一个原因是团队里有几个同学没接触过建模工具PDManer 是其中上手成本最低的。它既能让我这种偏后端的人快速建模也能让新人打开后自己看懂表关系。2. 环境准备与基础建模流程2.1 下载安装别小看版本选择这一步PDManer 在官网和 GitHub Releases 都有安装包Windows 版本解压就能用macOS 和 Linux 也有对应版本。我第一次用是直接下载最新版后来发现一个问题旧版本创建的模型文件新版本打开一般没毛病但新版本保存之后再拿旧版本打开大概率会不兼容。所以我的建议是团队内部统一用同一个大版本不要一个人升级另一个人还停在旧版。下载之后最好把安装目录放到一个固定位置比如D:\Tools\PDManer并且路径里不要带中文和空格避免某些模板生成或者数据库连接环节出现奇怪问题。启动之后界面是中文的左侧是模型导航中间是画布下方是字段编辑区域。不会像 PowerDesigner 那样扑面而来一堆概念这点对新手很友好。2.2 建库建表从一张用户表开始新建模型的时候选择数据库类型我这次以 MySQL 为例。PDManer 会按这个类型来做字段类型的默认映射所以一开始选对很重要。建表的操作很直观在左侧分组上右键“新建表”填表名和表注释。我习惯把表名写成小写下划线风格比如user_account表注释写清楚业务含义例如“用户账户表”。字段编辑区里维护每一列字段名用下划线风格比如user_name类型选择varchar(64)长度、精度直接填主键字段勾选“主键”和“自增”允许为空、默认值、字段注释都要写全举个例子建一张最简单的用户表字段名类型主键自增允许空默认值注释idbigint是是否无主键IDuser_namevarchar(64)否否否无用户名emailvarchar(128)否否是无邮箱statustinyint否否否1状态1启用0禁用create_timedatetime否否否CURRENT_TIMESTAMP创建时间update_timedatetime否否否CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP更新时间填完之后保存模型这时候其实已经完成了最核心的数据库表建模工作。后面的关系、索引、代码生成都是在这个基础之上做的。2.3 关系、索引和模型检查光有表还不够。多张表之间通常有外键关系比如订单表里有user_id引用用户表的id。PDManer 支持在画布上通过连线来设置这种关系。我通常会在订单表的user_id字段上直接设置“外键”目标表选择user_account目标字段选择id。软件会自动在表之间画一条关系线。需要注意的是外键关系反映的是业务逻辑不一定要在数据库物理层面真的加外键约束。很多互联网项目反而会刻意不用物理外键只保留逻辑关系。这个偏好可以在 PDManer 里通过是否勾选“生成外键约束”来控制。索引方面我在用户名上加了唯一索引因为登录场景需要根据用户名查询在订单表的user_id create_time上建了普通联合索引用来支撑“查某个用户最近订单”的查询。模型画完之后我会用“模型检查”功能扫一遍重点看有没有表漏了主键、字段类型填得是否完整、注释是否为空。这个检查在评审前很有用能避免设计文档里出现明显低级错误。3. 建表语句生成从模型到可执行 DDL3.1 一分钟导出一份能跑的 MySQL DDL模型建立完最直观的产出就是建表语句生成。PDManer 里生成 DDL 的入口非常浅选择要导出的表点击“生成建表语句”或者用工具栏里的 SQL 预览会弹出一个 SQL 预览窗口。我实际操作时会做几个配置目标数据库选 “MySQL”这样类型映射才是 MySQL 那套。勾选“包含表注释、字段注释”不然生成的脚本里全是光秃秃的列名。勾选“包含索引”唯一索引、普通索引会一并生成。外键约束我通常会根据项目规范决定是否生成如果团队约定不用物理外键这里就不勾。生成结果类似下面这样DROP TABLE IF EXISTS user_account; CREATE TABLE user_account ( id bigint NOT NULL AUTO_INCREMENT, user_name varchar(64) NOT NULL COMMENT 用户名, email varchar(128) DEFAULT NULL COMMENT 邮箱, status tinyint NOT NULL DEFAULT 1 COMMENT 状态1启用0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_user_name (user_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户账户表;看到这个输出基本就可以直接拿去执行了。和我以前手写 DDL 相比最大的好处是模型和脚本不会各说各话。3.2 生成之后为什么我还得手动改这几处虽然 PDManer 生成的 DDL 已经很完整但我在实际项目中还是总结出几个需要人工盯一眼的地方。第一是字符集。不同版本对不同数据库类型的默认值可能不一样如果是 MySQL我建议统一显式声明ENGINEInnoDB DEFAULT CHARSETutf8mb4不要依赖数据库全局配置否则测试和生产环境很容易出现索引超长或者中文乱码问题。第二是默认值的行为。比如update_time这个字段我习惯用DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP但有些版本生成时只会带DEFAULT CURRENT_TIMESTAMP需要手工补上ON UPDATE CURRENT_TIMESTAMP。第三是外键命名。自动生成的外键名有时是一长串随机字符串在团队要求规范命名时会不过审。我会在模型里提前把外键名改成fk_表名_字段名这种风格导出后就干净很多。所以我的工作流是先把 PDManer 导出的 SQL 在本地库跑一遍然后用数据库客户端连上去看表结构重点确认注释、默认值、索引。确认没问题再提交到 Git而不是直接把生成窗口里的内容复制到生产环境执行。3.3 增量变更模型改了脚本怎么跟上项目迭代中一定会改表结构比如新增一个字段、修改字段长度、加索引。PDManer 本身不是专门的“数据库迁移”工具我不建议把它当成增量脚本生成器用。我的做法是分成两层模型文件的变更交给 Git 来记录每次改完模型提交信息里写清楚这次改了哪些表、哪些字段。实际的数据库结构变更交给 Flyway 或 Liquibase 这类迁移工具去落地。我从 PDManer 导出最新的全量 DDL或者手动写出这轮的ALTER TABLE语句作为新的 migration 脚本提交。这样做的好处是模型表达的是“目标状态”迁移脚本表达的是“变更路径”两者不冲突。如果你只是想快速拿增量 SQL也可以把新模型的 DDL 全量导出再利用文本对比工具和旧版 DDL 做 diff但效率不高只适合临时用一下。4. 代码生成模板机制与 Spring Boot 实战4.1 代码生成到底能生成哪些东西PDManer 的代码生成是我最终愿意花时间研究它的重要原因。选中一张表或者多张表进入代码生成界面配置好包名、输出目录它会根据表结构生成对应的代码骨架。默认模板覆盖了常见的 Java 后端场景主要包括实体类POJO / Entity每个表一个类属性和字段一一对应Mapper 接口MyBatis 的 Mapper XML 文件包含基础的结果映射和 CRUDService 接口和实现类Controller 类比如user_account表生成出来的UserAccount.java大致是这个结构public class UserAccount { private Long id; private String userName; private String email; private Integer status; private LocalDateTime createTime; private LocalDateTime updateTime; // getter/setter 省略 }相比手动建实体类这一步能省下不少时间。尤其是字段特别多的表手写 getter/setter 很容易漏字段生成的基本不会漏。4.2 模板配置先复制内置模板再改自己的PDManer 的代码生成核心是模板机制。内置模板放在安装目录的templates目录下你可以打开看也可以复制一份改成自己团队的风格。我建议不要直接用内置模板生成完就交差而是先花点时间做一个团队统一模板。因为内置模板的包名结构、命名风格不一定符合你的项目规范而且生成结果大概率需要微调。如果你直接改了生成代码那每次生成都还要再改一遍反而更累。模板本身是类似 Java 服务端常见的模板引擎语法核心是变量填充。模型里的表名、字段名、字段类型、注释都会作为变量注入模板你可以在模板文件中用${...}的方式引用。比如字段定义的部分大概是这种思路#list columns as column private ${column.javaType} ${column.javaField}; /#list我第一次调整模板时没有先动内置模板而是新建了一个文件夹把系统模板复制进去然后再一点点改。这样万一改坏了还能回到内置模板继续参考不用担心把自己堵死。4.3 生成之后的微调命名、类型和容易越改越乱的目录代码生成不是终点生成之后我一般会做三轮检查。第一轮检查命名。PDManer 会把下划线字段名自动转成驼峰风格但如果你的表名带了模块前缀生成出来的类名可能会保留前缀需要改成更符合业务的命名或者直接在模板里做规则处理。第二轮检查类型映射。数据库字段类型和 Java 类型不一定完全对应比如tinyint可能会生成Integerdatetime可能生成LocalDateTime也可能是Date这取决于模板和版本。如果项目里规范统一用LocalDateTime就要在模板里把类型的映射关系固定下来。生成后也要逐个表扫一遍防止某个字段类型不对。第三轮是目录规划。我最开始把生成代码直接输出到项目的src/main/java里结果每次重新生成就把之前的修改覆盖掉了。后面我把生成目录单独配置成一个generator-output文件夹生成后用 diff 工具挑出需要的部分再复制到正式代码目录。虽然多了一步但不会心慌。5. 踩坑实录PDManer 使用中容易忽略的细节5.1 数据库方言切换时的字段类型映射坑PDManer 支持多数据库方言但“支持”不等于“完美对应”。我踩过最典型的一个坑是模型建在 MySQL 上后来为了某个客户要支持 PostgreSQL直接把模型切到 PostgreSQL 再生成 DDL结果发现json类型、datetime类型都出现了不同程度的偏差。后来我查了一下PDManer 对不同数据库确实有一套默认的字段类型映射但业务表里经常有自定义类型比如 MySQL 的tinyint(1)、bigint unsigned默认映射不一定符合预期。这时候需要手动到“字段类型映射”里去调整甚至针对不同数据库维护不同的映射规则。我的经验是如果你需要同时维护多套数据库方言一开始建模就要明确主目标库别指望一套模型在所有数据库下都完美。生成 DDL 之后一定要在目标数据库真实执行一次不要只看预览窗口。5.2 外键导致建表失败顺序与依赖关系这个问题主要出现在物理外键也会生成的情况。比如订单表引用了用户表生成 DDL 的时候如果用户表脚本在后面那先在库里执行订单表的建表语句就会报“表不存在”或者外键关联失败。PDManer 在生成脚本时一般会尽量按依赖排序但表多、关系复杂之后仍然会翻车。我的标准操作是生成完 DDL 后先检查一遍里面建表的顺序把被引用的表放到前面或者在自己执行时临时开一下外键检查的开关SET FOREIGN_KEY_CHECKS 0; -- 执行建表语句 SET FOREIGN_KEY_CHECKS 1;这个方法适合开发环境和测试环境生产环境我不建议这样做还是老老实实把变更拆成可重复执行的迁移脚本保证顺序可控、可回滚。5.3 多人协作改同一个模型文件Git 冲突处理PDManer 的模型文件是 JSON 格式能进 Git 是优点但多人同时改同一个模型文件时冲突会非常难看。JSON 合并的时候很难自动处理经常是一个人改了表结构另一个人改了代码生成配置最后落到同一个文件上手工解决冲突要花不少时间。我的团队后来定了几条规矩模型文件的修改尽量由同一个人负责其他人如果有改动需求先提出来再统一改。如果确实需要多人同时开发就按模块拆分成多个模型文件不要所有人都往一个文件里堆。拉代码之前先刷新模型避免在旧版本上做修改不然合并时更痛苦。修改完模型文件同时把导出的 DDL 也提交到 Git这样即使模型文件合并失败DDL 还能作为依据。虽然这些规矩听着很简单但坚持下来后模型文件的冲突基本绝迹了。6. 提高建模效率的小技巧6.1 从现有数据库逆向生成模型老项目救星如果你要接手一个老系统数据库里已经有一堆表但没有设计文档用 PDManer 的“从数据库导入”功能可以快速把现有表结构变成模型。操作不复杂新建模型之后选择“数据库导入”填好连接信息勾选要导入的库PDManer 会读取表、字段、注释、主键、索引、外键等信息生成一套可编辑的模型。导入之后我通常会做两件事检查注释是否完整很多老表的注释是空的可以借用字段名推断业务含义补上。检查字段类型和命名把明显不合理的地方梳理出来作为后续重构的输入。这个功能帮我整理过好几个老项目比对着数据库一个个建表再连线快得多。6.2 批量修改字段属性和命名规范自检建模过程中最烦的其实是重复劳动比如几十张表都要加create_time、update_time这两个字段。PDManer 支持多选字段批量设置属性我一般会先建好一张模板表把公共字段都维护好然后再复制表、批量改字段名和注释。命名规范自检也很有用。我习惯在导出 DDL 前把所有表名和字段名拉出来看一眼统一检查是不是都用小写字母和下划线有没有混用驼峰的有没有保留字比如 MySQL 的order、group。发现保留字要么改名要么在模板里统一加反引号避免执行报错。6.3 数据字典导出文档评审时比画图更管用我发现一个现象设计评审时贴一堆 ER 关系图大家反而容易走神。真正高效的方式是先发一份数据字典文档把每张表的字段含义、类型、是否为空、默认值都列出来评审时就聊职责和边界。PDManer 支持把模型导出成文档通常有 HTML、Markdown 等格式。我每次评审前会导出一份 Markdown 格式的数据字典放到项目的docs目录下同时在评审群里发一版。这样产品、后端、测试都有同一个输入不会出现“我说的表和你看的表不是一张表”的情况。另外导出的文档也可以作为最终交付物的一部分省掉为项目单独整理数据库设计文档的时间。最后分享一个我自己的习惯每次建模完成后我不会直接在数据库客户端里手工执行建表语句而是把 DDL 提交到一个迁移脚本目录里和模型文件一起进 Git。PDManer 负责“设计”迁移工具负责“变更落地”这样设计和实现始终能对上。模型文件本身我也会在评审前导出一份最新数据字典发给同事比口头解释高效得多。建模工具不在多把 PDManer 用熟从数据库表建模到建表语句生成再到代码生成这套流程能帮你省下不少时间。