
说实话我入行头两年一直是手写数据库文档的。每次项目一迭代表结构一变就得对着 Navicat 一张一张表截图、复制字段注释再粘贴到 Word 里排版。一个几十张表的项目光文档就得耗掉大半天。后来我摸到了 Screw 这个工具配合 IDEA 一键就能把整个数据库的文档导出来格式还特别规整。这篇就把我从配置到踩坑的全过程掰开揉碎讲清楚照着做就能省下半天时间。1. 为什么我放弃了手写数据库文档改用Screw先说清楚 Screw 是干什么的。它是一个开源的小工具全名是 screw-core核心功能就是读取数据库表结构自动生成完整的数据字典文档。输出的格式支持 HTML、Word、Markdown甚至还可以直接打成离线包。它解决的痛点非常明确数据库文档的维护成本。只要数据库连接配置没问题点一下执行几分钟内就能拿到一份覆盖所有表、所有字段、所有索引的文档不用再一张表一张表去复制粘贴。我之前也试过别的方案。比如用 Navicat 的导出功能但它的导出结果偏“数据备份”风格不是文档形态字段注释、默认值、主外键这些关键信息往往对不上号可读性很差。也用过网上一些在线生成工具但那些工具得把数据库结构上传到第三方服务器数据库地址、用户名密码直接暴露风险太大公司安全评审这关就过不去。Screw 是纯本地执行不联网、不上传数据所有生成动作都在本地 JVM 里完成这点是它在安全性上最大的优势。选 Screw 还有一个更实际的理由它跟 Java 项目的生态融合得极其自然。我大部分项目都是 Spring BootMaven 依赖一加配个连接信息就能跑。不需要额外安装任何客户端也不需要启动独立服务IDEA 里写好一个测试类或者工具类直接 run 就完事。这一点特别适合已经用 IDEA 做日常开发的团队学习成本几乎为零。另外它的文档可定制程度也不低。模板用的 Freemarker生成出来的 HTML 页面有目录、有层级、有表名加粗、有字段类型高亮交付给前端或者测试看着也专业。Word 格式还可以让非技术同事直接改不用安装什么渲染插件。这套能力作为“文档基础设施”已经够用了。2. Maven依赖怎么加版本千万别乱选Screw 的使用入口是 Java API所以第一步是在项目里引入 Maven 依赖。先说明一下我用的环境是 IntelliJ IDEA 2022.3 JDK 8虽然高版本的 IDEA 也没问题但加依赖之前最好确认一下项目编译版本跟 Screw 的兼容性是稳定的。依赖有三大块Screw 核心包、模板引擎 Freemarker、数据库驱动。dependency groupIdcn.smallbun.screw/groupId artifactIdscrew-core/artifactId version1.0.5/version /dependency这是核心包一定要用正式发布的版本别用 Snapshot 快照版。我曾经图新鲜试过快照版结果 API 变动导致方法找不到排查起来非常头疼。然后加上模板引擎dependency groupIdorg.freemarker/groupId artifactIdfreemarker/artifactId version2.3.31/version /dependencyScrew 内部生成文档的过程本质上是把数据源信息和表结构信息传给 Freemarker 模板渲染所以这个依赖必须手动补齐。接着是数据库驱动这里特别说一下。Screw 本身不强制你用什么连接池但实际跑起来需要能建立 JDBC 连接。我用的是 HikariCP这也是 Screw 官方示例里推荐的组合dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version4.0.3/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency版本这个东西我多啰嗦一句。mysql-connector-java 的 8.x 和 5.x 差异很大驱动类名都变了如果数据库是 MySQL 5.7可以用 5.1.49 那个版本然后把 driverName 改成com.mysql.jdbc.Driver。你要是上来就复制我的 8.x 配置去连老库大概率会报 SSL 错误或者 ClassNotFound。这一点很多人忽略实际踩坑率很高。如果你用的是 PostgreSQL 或者 Oracle把 mysql 那个驱动换成对应的即可。Screw 对常见的数据库基本都支持SQLServer 也认但连接参数要额外设置一下。文本里我先按 MySQL 的主流程讲其他库的差异后面统一补。3. 核心生成代码直接抄作业依赖加好之后最核心的就是写一段生成文档的 Java 代码。我比较推荐放在普通 main 方法里或者独立写一个测试类这样生成文档就是一个显式动作不会污染业务代码。如果你想把生成文档做成项目启动时自动执行也可以放到 ApplicationRunner 或者 CommandLineRunner 里但我不建议默认开启除非团队有强烈的文档实时同步需求。下面这段是我实测可跑的完整代码注意信息只改你自己的数据库配置就行import cn.smallbun.screw.core.Configuration; import cn.smallbun.screw.core.engine.EngineConfig; import cn.smallbun.screw.core.engine.EngineFileType; import cn.smallbun.screw.core.engine.EngineTemplateType; import cn.smallbun.screw.core.execute.DocumentationExecute; import cn.smallbun.screw.core.process.ProcessConfig; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; public class GenerateDatabaseDoc { public static void main(String[] args) { // 1. 配置数据源 HikariConfig hikariConfig new HikariConfig(); hikariConfig.setDriverClassName(com.mysql.cj.jdbc.Driver); hikariConfig.setJdbcUrl(jdbc:mysql://127.0.0.1:3306/your_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai); hikariConfig.setUsername(root); hikariConfig.setPassword(your_password); hikariConfig.setMaximumPoolSize(5); DataSource dataSource new HikariDataSource(hikariConfig); // 2. 生成配置 EngineConfig engineConfig EngineConfig.builder() .fileOutputDir(/Users/yourname/Desktop/db-docs) .openOutputDir(true) .fileType(EngineFileType.HTML) .produceType(EngineTemplateType.freemarker) .build(); // 3. 表过滤规则可跳过默认全部生成 ProcessConfig processConfig ProcessConfig.builder() // 只看指定表 .designatedTableName(Collections.singletonList(t_user)) // 也可以指定忽略哪些表 // .ignoreTableName(Collections.singletonList(sys_log)) // 忽略表前缀 // .ignoreTablePrefix(Collections.singletonList(t_)) .build(); // 4. 构建配置 Configuration config Configuration.builder() .version(1.0.0) .description(订单系统数据库设计文档) .dataSource(dataSource) .engineConfig(engineConfig) .produceConfig(processConfig) .build(); // 5. 执行生成 new DocumentationExecute(config).execute(); } }上面这段代码有几个关键点值得展开讲。首先是.fileOutputDir()指定的输出目录这个目录必须存在Screw 不会自动帮你创建多级目录如果路径不存在会直接抛异常。我建议先手动建好目录或者你在代码里加几行Files.createDirectories()逻辑省得每次都要去文件系统里折腾。其次是.fileType()这个字段决定了输出格式有三个可选项EngineFileType.HTML、EngineFileType.WORD、EngineFileType.MD。我实际生成下来HTML 的观感最好字段类型有颜色区分还有个简易目录浏览器打开就能直接翻阅Word 适合交付给非技术同事改文案Markdown 适合直接推送到 Git 仓库里维护。想一次出多个格式可以跑三次 main 方法或者把这段封装一下传参数不过我觉得一次一种格式反而好管理不会在目录里生成一堆同名文件搞混。还有个.description()参数这里填的是文档的大标题会显示在生成文档的首页顶部。建议写成项目名加版本号比如“商城用户模块数据库设计 V1.0.0”方便日后追溯。别小看这个描述字段后期文档多了以后靠文件名和描述就能快速定位不然全是 generateDoc_20230231 这种命名翻起来真的要命。ProcessConfig 那段如果你不需要过滤的话甚至可以不 build 直接不传。但如果数据库里有大量不需要关注的历史表、日志表我强烈建议你配置一下 ignore 规则这样生成的文档干净很多读者也不会被无关表分散注意力。过滤规则支持表名精确匹配、表名前缀匹配、表名前缀忽略还有正则匹配。这块后面我会给一个更完整的例子。4. 经常被忽视的字符集和连接参数很多人遇到中文乱码第一反应是“Screw 工具编码问题”但实际上 99% 的情况是 JDBC 连接串没带characterEncodingUTF-8参数。Screw 读取表结构的时候字段注释、表注释都是从数据库的元数据里拿的如果连接层编码不对拿到手就是一堆问号。我最初踩这个坑的时候反复检查模板、改 Freemarker 编码最后才发现是 URL 少了一个参数。另外还有一个隐藏参数serverTimezone。MySQL 8.x 对时区感知非常敏感如果不指定serverTimezoneAsia/ShanghaiSQL 驱动会拿本地 JVM 默认时区去对比可能出现The server time zone value Öйú±ê׼ʱ¼ä is unrecognized的报错。这句话看起来是乱码其实不是文档内容乱码是时区不匹配在报错。加了这个参数以后驱动才会用正确的时区跟数据库通信。这个配置在使用 Navicat 等客户端时通常感觉不到因为客户端帮你做了很多隐式转换但 JDBC 直连就绕不过去了。还有一个容易出事的参数是useSSL。我建议显式加useSSLfalse因为很多本地开发库没配 SSL 证书驱动 8.x 默认会去协商加密导致一条条警告刷屏甚至在某些内网环境下直接连接超时。加上这个参数能免去很多无意义的报错。完整的 URL 长这样jdbc:mysql://127.0.0.1:3306/your_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse这四个参数一个都不能少至少在本地环境里是最优组合。生产环境如果 DBA 有要求禁用 SSL那更好驱动根本不会走 TLS 握手连接建立更快。字符集这块再提一个细节表注释、列注释如果本身在数据库里就是 GBK 存储的而你连接串用了 UTF-8那生成出来的文档还是会有乱码。这种情况基本是老库遗留问题处理方式是先把表结构迁移为 utf8mb4再生成文档。不过更快的临时方案是可以把字符集参数临时改成 GBK生成一次看看效果。这只是救急方案长期建议还是统一数据库字符集。5. 只生成指定表还是全部生成过滤规则怎么用实际项目中数据库里往往一堆表。我上个项目总共 87 张表光日志表、临时表就占了快 20 张。这种情况下如果每次生成全量文档输出文件就有几百 KB打开也慢前端同事翻起来也累。Screw 的 ProcessConfig 里给了很灵活的过滤规则但很多人不会看官方文档只会复制示例里的ignoreTableName。我把常用的几种场景都列一下直接抄场景对应的代码块就行。场景一只生成少量核心表比如只需要导出用户表、订单表、订单明细表ProcessConfig processConfig ProcessConfig.builder() .designatedTableName(Arrays.asList(t_user, t_order, t_order_item)) .build();注意是designatedTableName不是designatedTablePrefix。这个配置一旦传入Screw 就只处理这三张表其他表一律不碰。优点是生成速度快文档体积小缺点是新增核心表忘了加进去文档就不完整。场景二排除掉所有带指定前缀的表比如临时表前缀tmp_历史表前缀his_ProcessConfig processConfig ProcessConfig.builder() .ignoreTablePrefix(Arrays.asList(tmp_, his_)) .build();这个方式适合表前缀规范统一的团队。你只要约定好哪些前缀是不需要对外暴露的以后新表只要按规范命名文档就会自动忽略不用每次去改代码。场景三排除指定名称的表适合少数例外表ProcessConfig processConfig ProcessConfig.builder() .ignoreTableName(Arrays.asList(sys_config, flyway_schema_history)) .build();比如 Flyway 这种工具自动生成的版本控制表、或者是框架内置的配置表用ignoreTableName精确排除是最稳的。最后一个进阶玩法正则匹配。Screw 还支持.ignoreTableRegex()适合表名比较乱的情况。比如所有以下划线结尾的表都可以用正则过滤掉这个功能我用得不多因为有这个时间不如去规范表名命名但如果接手了老系统有一堆像user_bak_2022、order_temp_2021这种表用正则一下就能清干净。代码示例ProcessConfig processConfig ProcessConfig.builder() .ignoreTableRegex(Collections.singletonList(.*_bak_.*)) .build();这里有个经验谈过滤规则尽量用ignoreTablePrefix或者ignoreTableName因为正则写的太宽容易误伤。比如.*_temp这个正则会匹配到project_temp正好是你要留下的表那文档就缺了一部分而且你不会第一时间发现等测试同事说“文档里怎么少了温度结算表”的时候往往已经过了两天。宁可多写几个 ignore 列表也不要图省事写一个通配正则。6. 生成后的文档长什么样怎么检查有没有缺数据生成完毕之后Screw 会弹出一个文件目录窗口前提是.openOutputDir(true)或者你自己去输出目录找。HTML 文件长得很清爽顶部是项目描述和生成时间然后按表分组每张表下面有字段名、数据类型、是否必填、默认值、字段说明。索引信息也会列在表格后面。生成之后我一般会抽查三个点第一是“表数量对不对”。对照数据库里表的总数减去你过滤掉的表看看文档里实际生成的表数量是否一致。怎么快速看HTML 页面通常有目录结构直接数目录节点就行。Word 文档可以看目录页的条目数。第二是“字段说明是否完整”。重点抽查几张业务核心表看每个字段是否都有注释说明。很多老表的字段注释是空的Screw 对没有注释的字段会生成一个空值或者“暂无描述”之类的操作默认占位。这种时候不是工具的问题而是数据库设计层面的历史欠账。要让文档真正有价值光靠工具还不够得回头把注释补齐。我个人的做法是顺手写一个 SQL 批量更新注释虽然一次只能改一张表但每天补几张一周下来核心表的注释就全了。第三是“时间字段的类型有没有显示乱”。MySQL 里 datetime、timestamp 这类类型Screw 读出来的结果在 HTML 上可能显示成datetime在小数位为 0 的时候显示成datetime(0)这主要跟数据库定义有关。如果你数据库里混用了datetime和datetime(0)文档里就会有两种写法不算 bug但会给项目上的同事造成困惑。建议在建表规范里统一定义比如全部datetime。检查完成后我一般还会把生成好的 HTML 压缩成 zip 发给团队或者上传到内网 Wiki。如果你是维护文档的人Markdown 格式更适合放在 Git 里做版本管理每次数据库变更后重新生成提交 diff 的时候直接能看到哪些表、哪些字段变了。这在做数据库版本审计的时候非常好用也比让 DBA 手动整理变更记录靠谱。7. 常见问题排查与避坑指南这部分我直接把这一两年用 Screw 遇到的、以及在社区里见到的典型问题整理成速查表你对照自己的报错去找原因比一个个试快得多。7.1 报错ClassNotFoundException: com.mysql.jdbc.Driver原因基本就一个驱动版本和你配置的 driverClassName 不匹配。如果你把 mysql-connector-java 升到了 8.x驱动类名要写成com.mysql.cj.jdbc.Driver如果还在用 5.x就写com.mysql.jdbc.Driver。我建议直接升级驱动到 8.x毕竟 5.x 的驱动对 MySQL 8 支持不完整连接可以建立但一些元数据查询会异常。7.2 报错The server time zone value ... is unrecognized这个我前面提过就是 URL 少了serverTimezone参数。注意这个参数在 MySQL 8.0 及以上是必须的在 MySQL 5.7 也可能需要。填Asia/Shanghai或者系统对应的时区即可。如果你是在云服务器上部署的 Java 应用千万不能用服务器本地时区去猜直接用Asia/Shanghai最省心。7.3 生成出来的文档是空的没有表信息这种情况一般发生在连接能建立、但 Screw 查不到表信息的场景。常见原因是 JDBC URL 里指定了错误的 schema或者数据库账号权限不足只有连接权限没有查看表定义的权限。你可以先用数据库客户端试试这个账号能不能看到information_schema.TABLES里的数据Screw 就是基于 information_schema 来获取表结构信息的。权限不对的话让 DBA 给账号加一个 SELECT 权限就解决了。7.4 输出目录找不到文件一个很尴尬的情况是代码没报错但生成完不知道该去哪找文件。如果你配了.openOutputDir(true)Screw 在 Windows 上会用桌面路径管理器打开目录但在 macOS 上偶尔会因为系统安全设置没有自动跳转。此时直接看你代码里配置的绝对路径去那里找文件就行。还有一个小坑就是fileOutputDir里如果填的是相对路径它相对的是 Java 进程的工作目录在 IDEA 里直接 run 是模块根目录在jar包运行时就是 jar 所在目录不确定就统一用绝对路径。7.5 Word 文档打开乱码这个稍微少见但一旦遇到就非常头疼。Screw 生成 Word 的原理是用 Freemarker 渲染 XML 模板再压缩成 docx 文件理论上跟字符集关系不大。真遇到乱码先检查是不是用 WPS 打开WPS 对某些 docx 的样式支持不完整建议用 Office 打开验证。如果 Office 也乱码那就换成 HTML 格式生成一次先满足当前人员的阅读需求。说实话在实测里 Word 格式的兼容性没有 HTML 好我后面基本不用 Word 格式了。7.6 频繁连接数据库导致连接池被占满Screw 每次执行只会建立少量连接用完会释放这个并不算大问题。但如果你把它配置成项目启动自动执行而且连接数配置过高例如设置了 20 个连接在并发启动多个微服务时数据库连接数可能会被占满。建议在 HikariCP 配置里把setMaximumPoolSize(5)设为 5 甚至更小反正一次性任务不需要那么大的连接池。7.7 模板报错Unable to load template这是 Freemarker 依赖版本或者模板资源缺失导致的问题。Screw 自带的模板内置在依赖 jar 包里正常不会缺。如果你自己覆盖模板就要确认模板文件放在 classpath 下并且名字跟 Screw 期望的名字完全一致。个人建议刚开始别动模板先用默认的就够用了等摸熟了再加定制化模板。7.8 表注释显示为 null这几乎不是工具问题是数据库设计问题。Screw 只是如实读取元数据表没有注释它读出来就是空。这种问题补注释就行顺手也给同事发个表结构设计规范模板至少法定业务表都写上表用途、维护人、创建时间长期收益很大。8. 结合IDEA运行JavaWeb项目时怎么用最顺手Screw 生成文档的方式虽然简单但如果能跟 IDEA 的日常开发习惯结合起来效率还会再上一个台阶。我现在的做法是这样的给你参考。首先在项目里单独建一个tool包里面只放文档生成相关的类。别把生成代码混在业务启动类里否则同事在阅读代码时会被这段逻辑干扰。类名尽量写清楚比如GenerateDbDocForOrderService一看就知道是给订单服务生成数据库文档的。如果项目模块很多还可以按模块建不同的类每个模块输出不同的文档目录避免全部塞到一个文件里。其次我会在 IDEA 里的 Run Configuration 里给这个 main 方法配置一个常用的运行参数。比如把fileOutputDir抽成 main 方法参数这样切换项目时不用改代码直接改运行配置就行。这个做法非常实用强烈推荐。伪代码如下public class GenerateDbDoc { public static void main(String[] args) { String fileName args.length 0 ? args[0] : default_db; EngineConfig engineConfig EngineConfig.builder() .fileOutputDir(/data/docs/ fileName) // 省略其他配置 .build(); } }然后在 IDEA 的 Program arguments 里填order-service就能把文档生成到/data/docs/order-service目录。如果哪天要生成网关模块的文档把参数一改就好完全不用动代码。还有一个技巧是给这个 main 方法配上快捷键。IDEA 里可以给运行配置绑快捷键不过我觉得没必要直接用右上角的运行按钮就行。如果你公司有自动化参数校验要求也可以写一个简单的脚本用 Maven 的 exec-maven-plugin 来触发这个类生成文档。但实际体验下来我还是觉得 IDEA 里面直接 run 最顺手零配置、零学习成本。如果你们的项目是跑在 Linux 服务器上的想直接在服务器上生成文档也可以把项目打成可执行 jar里面带上启动类然后命令行执行java -jar xxx.jar generateDoc。不过这个场景不多见团队里有个人在 IDEA 里生成完把文件扔到内网盘大家共享就够了。9. 从“生成文档”到“文档自动化”的一次完整实践到这里 Screw 的基本使用已经讲完了。我最后聊一下我把它接入团队工作流的完整实践这个过程其实比工具本身更有参考价值。我当时负责的项目是个典型的单体老系统数据库 80 多张表文档已经腐烂了三个月。新来的同事每次问表结构都要去数据库里现查效率极低而且每个人理解都不一样。我接手后做的第一件事就是把 Screw 集成进去。第一周只是自己手动生成 HTML 版本发给项目群里让大家先看反馈。第二周根据反馈加上了过滤规则把不相关的日志表和框架表剔掉文档瘦身了三分之一。第三周把生成文档的 main 方法接入了 Maven Profile这样任何同事都可以通过一条命令生成最新文档不需要理解 Java 代码细节只需要改数据库地址和账号密码。第四周我做了一个更“自动化”的事情把生成好的文档发布到内网 Wiki并且约定每次上线前重新生成一次保证线上表结构和文档一致。后来发现就算有约定人还是会忘。最后干脆写了一个小脚本每周五下午跑一次生成任务然后用 Git diff 看这周有哪些表结构变了直接往群里丢一个变更摘要。虽然这个脚本当时很简单但确实把文档从“不更新”变成了“每周更新”项目组所有人对数据库结构的认知也统一了。这个过程的收益很直观新人上手查表结构从半个小时缩短到两分钟跨部门沟通时直接甩文档链接不用再截几十张图DBA 做变更评估时也能快速看到当前库的字段全貌。更难得的是因为 Screw 生成的是纯文本信息没有敏感数据文档可以直接存放在代码仓库或内网知识库中不担心数据泄露。如果你也想在自己的项目里复制这套实践我的建议很简单先跑通最简单的 main 方法生成一次 HTML 文档看看效果确认满意后再加过滤规则调整输出位置最后再考虑接入 Maven 或团队自动化流程。工具不难难的是坚持更新文档的习惯而 Screw 恰好把“坚持”的门槛降到了最低。每次数据库改动后顺手跑一次 generator就像代码提交前顺手格式化一样慢慢就成了肌肉记忆。