简介:面向Java开发者与数据库运维人员的轻量级文档工具,可替代PowerDesigner等重型建模软件,按数据库表DDL自动生成Markdown与Word格式的库表说明文档,适合需要低成本维护数据字典的团队使用。压缩包共21个文件,约188KB,其中10个Java源文件承载数据库连接配置、元数据读取与文档渲染等核心逻辑,2个XML文件负责Maven构建与打包,start.bat与start.sh分别对应Windows、Linux启动脚本,另附HTML说明页、README.md及若干界面示意图,便于快速上手。已有328人学习下载。读者可从中了解MySQL、Oracle、PostgreSQL三类数据库的参数化连接入口,借助DDL解析与模板输出思路直接运行mvn package打包,并参考发布包目录结构,适合希望用轻量方案批量导出库表文档的开发者。
1. database-doc-generator 是什么:我为什么把它当 PowerDesigner 的轻量替代
接手一个跑了七八年的业务库,三百多张表,字段注释东一块西一块,验收时要交一份表结构说明书。PowerDesigner 不是不能用,是太重:打开模型慢,导出 Word 还要先调模板,改一轮字段就得重新导。database-doc-generator 这个 Java 小工具的定位正好相反,它把数据库表的 DDL 当输入,抽出表名、字段、类型、可空、默认值、索引和注释,直接渲染成 Markdown 和 Word 两份文档。Markdown 进 Git 能 diff,Word 拿去交付。适合后端、数据治理、运维交接和项目验收前快速补文档;不适合拿它做 ER 图设计或反向工程建模。你要是只想让表结构有份能看、能查、能进版本库的说明,它比重型建模工具顺手。
2. 从 DDL 到文档:解析链路与 Java 技术选型
先把链路拆开看:DDL 文本进来,解析器把CREATE TABLE拆成表、字段、索引、注释,装进一个与渲染无关的中间模型,再由 Markdown 渲染器和 Word 渲染器各自取数。这条链路决定了后面所有参数怎么设——解析层丢注释,模板层再怎么调都补不回来;中间模型字段顺序乱了,Word 里字段顺序也跟着乱。所以我不急着改模板,先把解析和模型这两段看清楚。
2.1 DDL 解析还是 JDBC 元数据:两条路线的取舍
DDL 解析和 JDBC 元数据不是二选一,而是主辅关系。DDL 是数据库结构的源文件,能进 Git,能离线跑,不依赖目标库的连接权限;JDBC 元数据胜在实时,但注释、默认值、索引这些细节在不同驱动和不同数据库版本下表现不一致。这个工具把 DDL 当主输入,我一般也会保留一条 JDBC 对账通道:生成前用元数据查一遍表数量、字段数量,防止 DDL 文件是旧版本。PowerDesigner 重在模型库和图形化,这个工具轻在只做“结构说明文档”,不维护设计模型。
中间模型是整条链路的核心。解析器输出什么、模板能取到什么,全看这个对象怎么设计。我的习惯是只保留文档真正要用的字段,不要把 JDBC 的ResultSet或解析器的语法树直接塞给模板。模板一旦依赖具体实现,后面换解析器就要重写模板。
// 中间模型只保留文档要用的字段,解析器和渲染器都依赖它 public class TableDoc { private String schema; // 库名或 schema 名,用于分目录 private String name; // 表名 private String comment; // 表注释,可能为空 private final List<ColumnDoc> columns = new ArrayList<>(); // 必须保序 private final List<IndexDoc> indexes = new ArrayList<>(); // 主键、唯一键、普通索引 // getter/setter 省略 }这段代码的逻辑是先把“表”抽象出来,字段和索引用List而不是Map,因为文档里的字段顺序要和 DDL 一致,用HashMap会乱序,这是后面排查章节里最常见的一类翻车。参数说明:schema用于输出目录分层,name是表名,comment允许为空,渲染层要做回退;columns和indexes保持插入顺序。解析器我一般先看它怎么处理反引号、方括号、双引号三种标识符包裹;如果只支持一种,跨库 DDL 就会出问题。另一个检查点是CREATE TABLE后面的表选项,解析器可以忽略,但不能因为ENGINE=...这类尾巴把后面的字段截断。
2.2 文档渲染层:Markdown 模板与 Word 模板怎么分工
渲染层最容易被写成字符串拼接:sb.append("## " + table.getName())。小工具这样写能跑,表一多、模板一改就难受。Markdown 和 Word 的差异决定了要分开:Markdown 是纯文本,适合模板引擎渲染,产物能 diff;Word 是 zip 包里的 XML,样式、页眉页脚、表格边框都在模板里,适合用 docx 模板加占位符替换。常见做法是 Markdown 用 Freemarker 或 Velocity,Word 用 POI 读模板再替换。选型理由不是哪个高级,而是谁能把“内容”和“样式”分开。
Markdown 模板要尽量薄。表名、字段、类型、可空、默认值、注释这六列够用,不要一开始就加“字符集”“排序规则”“存储引擎”这些列,不同数据库不一定有,模板里全是条件判断,后面很难维护。模板变量名要和中间模型对齐,不要用数据库列名当变量名,否则换一套命名就要改模板。
<#-- md.ftl:一张表一个二级标题,字段表按顺序输出 --> ## ${table.name}<#if table.comment??>(${table.comment})</#if> | 字段 | 类型 | 可空 | 默认值 | 注释 | | --- | --- | --- | --- | --- | <#list table.columns as c> | ${c.name} | ${c.dataType} | ${c.nullable?string('是','否')} | ${c.defaultValue!'-'} | ${c.comment!'-'} | </#list>这段模板的逻辑是:表注释存在就拼在标题后面,字段列表按columns的顺序循环输出;!'-'是空值回退,避免模板里出现null。参数说明:table.comment??判断变量是否存在,c.nullable?string('是','否')把布尔值转成中文,c.defaultValue!'-'在默认值为空时显示短横线。模板里不要写查询逻辑,也不要在渲染阶段再连数据库,渲染器只做展示。Word 模板的占位符要单独放一个文本 run,具体在 4.1 里说。
2.3 把工具跑起来:JDK、构建与第一条生成命令
跑起来之前先确认 JDK 和 Maven。JDK 8 及以上都能编译,但项目如果用了 record 或新语法,就按pom.xml里的source/target对齐。构建命令不要加太多花活,先看单元测试里有没有 DDL 样例,那是最好的输入。我一般会先拿一张三字段的表跑通,再上全库,不然解析器报错时你分不清是语法问题还是数据量问题。
# 1. 构建,产物在 target 下;jar 名以实际构建结果为准 mvn -q clean package # 2. 准备一份最小 DDL,先别拿全库试 mkdir -p sql docs cat > sql/demo.sql <<'SQL' CREATE TABLE demo_user ( id BIGINT NOT NULL COMMENT '主键', user_name VARCHAR(64) NOT NULL COMMENT '用户名', created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (id) ); SQL # 3. 跑生成,参数按 --help 对齐;这里用最常见的 ddl/out/format 三个 java -jar target/*.jar --ddl sql/demo.sql --out docs --format md,docx这段命令的逻辑是:先构建,再准备最小 DDL,最后跑生成。--ddl是输入文件,--out是输出目录,--format控制产物类型。参数说明:如果项目用配置文件而不是命令行,等价键名通常是input.ddl、output.dir、output.formats,以 README 为准;target/*.jar用通配是为了避开具体版本号。失败时先看异常第一行是不是解析器报的行号,再看输出目录有没有文件,最后打开 Word 确认能不能正常显示。注意不要拿生产库全量 DDL 直接跑,先截三张表,确认字段、注释、索引都对得上,再放大输入。
3. 接入自己的数据库:连接配置、DDL 采集与字段映射
拿自己的库跑,问题通常不在生成器,而在输入。DDL 里混了临时表、备份表、历史遗留的重复索引,生成出来的文档就会又长又乱。这一章按“先拿到干净 DDL,再对齐字段映射,最后出 Markdown”的顺序走,每一步都留一个可核对的点。
3.1 连接参数与 DDL 导出:先拿到干净的 CREATE TABLE
DDL 的来源一般有三种:数据库客户端自带的 schema 导出、SHOW CREATE TABLE逐表取、以及从版本库里的建表脚本拿。前两种适合存量库,第三种适合有迁移脚本的项目。不管哪种,导出时只保留结构,不要带数据;表选项里的自增当前值、字符集、引擎这些可以留着,但解析器要能跳过。导出后先搜一遍_tmp、_bak、_old这类表名,能过滤就过滤,不然文档页数会翻倍。
连接配置这块,工具如果直连数据库,参数一般就是地址、端口、库名、用户名、密码、驱动类。我的习惯是把密码放环境变量,不写进配置文件;DDL 模式优先于直连模式,因为文档生成不需要写权限,也不该拿生产库的连接串到处跑。下面这条 SQL 用来对账,不依赖具体数据库厂商,标准information_schema大多能用:
-- 用 information_schema 对账:表数量、字段数量、注释是否为空 SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default, ordinal_position FROM information_schema.columns WHERE table_schema = 'your_schema' ORDER BY table_name, ordinal_position;这条 SQL 的逻辑是拿字段级元数据和 DDL 解析结果做对照:表数量对不上,说明 DDL 文件旧了;字段数量对不上,说明解析器漏了字段;column_default为空但 DDL 里有默认值,说明默认值解析有问题。参数说明:table_schema换成自己的库名或 schema 名,ordinal_position用来检查字段顺序。导出 DDL 时尽量去掉AUTO_INCREMENT=...这类表选项,解析器不关心,但正则容易被干扰。对账这一步不写进生成流程,手工跑一次就够,目的是确认输入可信。
3.2 表、字段、索引、注释的映射规则
映射规则要在解析前定好,不要等模板报错再补。表这一层需要 schema、表名、表注释;字段这一层需要字段名、类型、可空、默认值、注释、顺序;索引这一层需要索引名、索引类型、索引列、是否唯一。下面这张表是我一般会对照的映射关系,左边是 DDL 片段,中间是中间模型,右边是 Markdown 列和 Word 占位符:
| DDL 片段 | 中间模型字段 | Markdown 列 | Word 占位符 |
|---|---|---|---|
id BIGINT NOT NULL | name/dataType/nullable | 字段/类型/可空 | ${col.name} |
COMMENT '主键' | comment | 注释 | ${col.comment} |
DEFAULT CURRENT_TIMESTAMP | defaultValue | 默认值 | ${col.defaultValue} |
PRIMARY KEY (id) | indexes | 索引说明 | ${table.indexes} |
注释和默认值是最容易出错的两块。字段内联注释一般是COMMENT '...',但有些 DDL 方言用独立的COMMENT ON COLUMN语句,解析器如果只认内联写法,注释列就会全空。默认值里可能带函数、带引号、带NULL,正则不能贪心,否则会把后面的COMMENT一起吃进去。
// 从字段定义尾部抓 COMMENT,处理单引号转义;贪婪匹配会跨字段,必须用保守写法 private static final Pattern COMMENT_PATTERN = Pattern.compile("COMMENT\\s+'((?:[^'\\\\]|\\\\.)*)'", Pattern.CASE_INSENSITIVE); // 默认值单独抓,避免把 COMMENT 里的内容当默认值 private static final Pattern DEFAULT_PATTERN = Pattern.compile("DEFAULT\\s+(CURRENT_TIMESTAMP|NULL|'((?:[^'\\\\]|\\\\.)*)'|[^\\s,]+)", Pattern.CASE_INSENSITIVE);这段正则的逻辑是:COMMENT_PATTERN用(?:[^'\\]|\\.)*允许转义单引号,避免注释里出现\'时截断;DEFAULT_PATTERN把CURRENT_TIMESTAMP、NULL、字符串和普通值分开匹配。参数说明:两个Pattern都编译一次复用,不要每行字段重新编译;CASE_INSENSITIVE兼容大小写写法。索引解析要按 DDL 原样保留列顺序,主键排在最前,唯一键次之,普通索引最后;索引名重复时按名字去重,不要按列去重,否则联合索引会被拆散。表名和字段名的标识符包裹符要单独处理,反引号、双引号、方括号各一套,不要和注释正则混用。
3.3 生成 Markdown 文档:模板变量与目录结构
Markdown 适合进版本库,所以输出目录要稳定,文件名要可预测。我一般按docs/db-doc/{schema}/{table}.md放单表文档,再加一个docs/db-doc/{schema}/index.md做表清单。表名做文件名前要净化,去掉路径分隔符和..,防止表名里带特殊字符导致写到目录外面。模板用外部文件,不要每次改样式都重新打包。
# 指定模板和输出目录,按 schema 分目录;表名做文件名前先净化 java -jar target/*.jar \ --ddl sql/schema.sql \ --template templates/md.ftl \ --out docs/db-doc \ --format md \ --title "业务库表结构说明"这段命令的逻辑是:--template覆盖内置模板,--out指定输出根目录,--format md只出 Markdown,--title写文档标题。参数说明:如果工具没有--template参数,把模板放到src/main/resources/templates再重新构建,不要直接改 jar 里的文件;--out下面会按 schema 建子目录。失败时先看模板报错的行号,常见是变量名写错或者字段为空导致InvalidReferenceException。和常见误用的差别在于:不要在模板里查数据库,也不要在模板里做排序,排序属于中间模型的事;模板只负责把已经排好序的数据摆到表格里。
4. 生成 Word 文档:模板占位符、样式与批量输出
Word 是交付物,样式比内容更容易被挑毛病。我的做法是先在 Word 里把模板调好:标题样式、表头底纹、字段表格边框、页眉页脚、页码,全部固定;生成器只负责替换占位符和按表循环。这样改样式不用动 Java 代码,改内容不用动 Word 模板。
4.1 Word 模板的占位符设计:一张表一个区块
docx 模板里的占位符不要写太复杂,${table.name}、${table.comment}、${col.name}这种单值替换最稳。Word 会把一段文字拆成多个 run,你在编辑器里看到的是${table.name},底层可能是$、{table.name、}三段,直接按 run 替换会漏。常见做法是先取整段文本,替换完再清空 run 重写;代价是段落内局部加粗会丢,所以只对占位符段落用这个方法。
// 只做单值替换,表循环在模板外由生成器按表逐个写;关键是跨 run 处理 static void replaceInParagraph(XWPFParagraph p, Map<String, String> vars) { String text = p.getText(); for (Map.Entry<String, String> e : vars.entrySet()) { text = text.replace("${" + e.getKey() + "}", e.getValue()); } // 清空原有 run,再写一个新 run,避免占位符被 Word 拆成多段 for (int i = p.getRuns().size() - 1; i >= 0; i--) { p.removeRun(i); } XWPFRun run = p.createRun(); run.setText(text); }这段代码的逻辑是先把整段文字拼起来做替换,再清空原有 run,最后写一个新 run。参数说明:vars的 key 不带${},调用时传table.name这种形式;p.getText()会带上所有 run 的文本,适合占位符段落,不适合有复杂样式的正文。注意:如果一段里既有加粗标签又有占位符,不要用这个方法,改为按 run 边界查找占位符;否则加粗会丢。模板里的表格行循环,我一般不在 Word 里做,而是生成器按表逐个写区块,一张表一个二级标题加一张字段表,逻辑更直。
4.2 批量生成与文件命名:按 schema 分目录
批量生成最容易出问题的是文件名。表名里可能有 schema 前缀、空格、斜杠、中文、点号,直接拼路径会写出奇怪的文件,甚至写到目标目录外面。落盘前统一净化,只保留字母、数字、点、下划线、短横线,..直接替换掉。文件名稳定,版本库 diff 才有意义;不要加时间戳,否则每次生成都是新文件。
// 表名可能带 schema 前缀或特殊字符,落盘前统一净化,防止路径穿越 static String safeFileName(String schema, String table) { String raw = (schema == null ? "" : schema + "__") + table; String name = raw.replaceAll("[^a-zA-Z0-9._-]", "_"); if (name.contains("..")) { name = name.replace("..", "_"); } return name + ".docx"; }这段代码的逻辑是先把 schema 和表名拼起来,再用白名单替换非法字符,最后处理..。参数说明:schema可以为空,为空时只拼表名;文件名用双下划线分隔 schema 和表名,避免和表名里的下划线混淆。批量输出时我一般分两种:单表 docx 放docs/db-doc/{schema}/tables/,汇总 docx 放docs/db-doc/{schema}/index.docx。汇总文档不要把所有表塞进一个巨大文件,超过两百张表打开会卡,评审时也不好翻。生成顺序按表名排序,保证每次输出一致。
4.3 输出校验:用解压和文本抽取快速查错
Word 生成完不要只看文件大小,打开第一页没报错不代表内容对。docx 本质是 zip,正文在word/document.xml,用unzip -p抽出来去标签,就能快速查未替换的占位符和空表。Markdown 更简单,数标题行和表格行。校验脚本不用写复杂,三条命令够用。
# 1. 检查 docx 是不是合法 zip,以及正文里有没有未替换的占位符 unzip -p docs/db-doc/your_schema__demo_user.docx word/document.xml \ | sed 's/<[^>]*>/ /g' \ | grep -E '\$\{|表名|字段名' || echo "no placeholder left" # 2. 检查 Markdown 的表数量与字段行数 grep -c '^## ' docs/db-doc/your_schema/demo_user.md grep -c '^| ' docs/db-doc/your_schema/demo_user.md这段脚本的逻辑是:unzip -p直接读 XML,sed去掉标签,grep查未替换的${或占位符文字;Markdown 用grep -c数二级标题和表格行。参数说明:路径按实际输出改,your_schema__demo_user.docx是净化后的文件名;grep -c '^| '包含表头分隔行,字段数要减 2。校验点包括表数量等于 DDL 里CREATE TABLE数量、字段行数等于列数加表头两行、空注释占比不要超过预期。Word 如果提示文件损坏,优先查 zip 条目是否完整,再看替换后的 XML 有没有非法字符。
5. 避坑与排查:DDL 方言、注释丢失和模板渲染翻车
这个工具链路短,问题也集中:输入 DDL 不干净、解析器漏字段、模板变量对不上、输出文件打不开。下面这几条是我实际踩过的,按“现象 → 原因 → 解决”写,遇到同类问题可以先对号入座。
5.1 解析与元数据类问题
现象:生成的 Markdown 里注释列全是-,Word 里表注释也是空。原因通常是 DDL 方言不匹配:一种写法是字段内联COMMENT '...',另一种是独立的COMMENT ON COLUMN ... IS '...'语句,解析器只认前一种,后一种就被跳过。还有一种情况是走 JDBC 元数据拿注释,但驱动或连接参数没开注释返回,getRemarks一直是空。解决分两步:先确认输入 DDL 用的是哪种注释语法,独立注释语句要在解析后合并
本文还有配套的精品资源,点击获取