该工具类是面向 Java 8 运行环境开发的 PostgreSQL 数据库结构文档自动生成组件,核心解决第三方文档生成框架(screw)对 PostgreSQL 的兼容问题,同时提供双生成策略(框架优先 + 自定义兜底),确保数据库结构文档稳定输出为 HTML 格式,完整覆盖表名、字段名、数据类型、长度、主键、可空性、备注等核心结构信息。
核心定位
专为 PostgreSQL 数据库设计,适配 Java 8 语法规范和依赖生态,无需升级 JDK 即可实现数据库文档的自动化生成,兼顾易用性和稳定性,适用于中小型项目的数据库设计文档管理场景。
核心功能与设计思路
1. 双生成策略(容错兜底)
- 优先策略:基于 screw 开源框架生成标准化 HTML 文档,通过配置 PostgreSQL 的 public Schema、连接池参数,最大化复用成熟框架的模板能力(Freemarker);
- 降级策略:若 screw 框架执行失败(如 PostgreSQL 版本兼容、Schema 读取异常),自动切换到纯 JDBC 原生实现的自定义生成逻辑,无第三方框架依赖,保证文档生成不中断。
2. 数据源适配(PostgreSQL 专属)
基于 HikariCP 3.4.5(Java 8 兼容版)构建高性能数据库连接池,针对性配置 PostgreSQL 连接参数:
- 显式指定
currentSchema=public解决表结构读取范围问题; - 配置连接超时、最小 / 最大连接数,适配 Java 8 的 JDBC 资源管理机制;
- 补充 HikariCP 层面的 Schema 参数,双重保障表结构读取准确性。
3. 完整的结构信息采集
通过 JDBC 的DatabaseMetaData原生 API 读取 PostgreSQL 数据库元数据,采集维度包括:
- 表级信息:表名、表备注;
- 列级信息:字段名、数据类型、字段长度、是否可空、是否主键、字段备注;
- 范围控制:仅读取 public Schema 下的用户表(过滤系统表)。
4. Java 8 适配特性(核心改造点)
针对 Java 8 语法和 API 限制做专项适配,避免版本兼容问题:
- 替换 Java 9 + 专属 API:如
List.of()改为new ArrayList<>(),字符集使用StandardCharsets.UTF_8(替代硬编码字符串); - 依赖版本适配:选用 screw 1.0.5、HikariCP 3.4.5、PostgreSQL JDBC 42.2.23 等 Java 8 兼容版本;
- 资源管理:补充
ResultSet手动关闭逻辑,结合try-with-resources确保连接 / 结果集资源闭环,避免内存泄漏; - 中文兼容:写入 HTML 文件时强制指定 UTF-8 编码,新增 HTML 特殊字符转义方法,解决中文乱码和格式错乱问题;
- 目录兼容:自动检查并创建输出目录(
mkdirs()),适配 Java 8 的文件系统操作规范。
5. 人性化的文档输出
- 生成的 HTML 文档包含基础样式美化(表格边框、表头背景、标题居中),便于阅读;
- 输出路径可配置,自动打开输出目录,支持自定义文档名称;
- 异常细化处理:拆分 SQL 异常、IO 异常、通用异常,输出精准的错误提示,便于问题定位。
使用方式
- 配置参数:修改代码中数据库连接信息(URL、用户名、密码)和文档输出路径;
- 直接运行:执行
DocumentUtil类的main方法,工具会自动优先使用 screw 生成文档,失败则触发自定义生成逻辑; - 结果查看:生成完成后自动打开输出目录,HTML 文档包含所有 public Schema 下的表结构信息,可直接在浏览器中打开查看。
核心价值
- 零侵入:无需修改数据库结构,通过 JDBC 元数据读取信息;
- 高兼容:完全适配 Java 8 环境,无需升级 JDK 或重构项目;
- 高可用:双生成策略避免单一框架故障导致文档生成失败;
- 易维护:代码结构清晰,实体类、采集逻辑、生成逻辑分层设计,便于扩展(如新增 Markdown 输出格式)。
项目代码:
package com.njcky.neosourcepm.util;
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;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 数据库文档生成工具(PostgreSQL 适配版 - Java 8 兼容)
*/
public class DocumentUtil {
/**
* 获取 PostgreSQL 数据源(适配 Java 8)
*/
public DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("org.postgresql.Driver");
// PostgreSQL 连接配置(指定 Schema + 兼容参数)
hikariConfig.setJdbcUrl("jdbc:postgresql://localhost:54323/xxx?currentSchema=public&useInformationSchema=true");
hikariConfig.setUsername("xxx");
hikariConfig.setPassword("xxx");
// Hikari 连接池参数(Java 8 兼容)
hikariConfig.setMinimumIdle(5);
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setConnectionTimeout(30000);
hikariConfig.addDataSourceProperty("schema", "public");
return new HikariDataSource(hikariConfig);
}
public static void main(String[] args) {
DocumentUtil documentUtil = new DocumentUtil();
// 1. 配置文档生成引擎(输出 HTML)
EngineConfig engineConfig = EngineConfig.builder()
.fileOutputDir("C:\\Users\\14786\\Desktop\\项目\\数据库设计文档")
.openOutputDir(true)
.fileType(EngineFileType.HTML)
.produceType(EngineTemplateType.freemarker)
.fileName("XY_DB数据库设计文档")
.build();
// 2. 配置处理规则(指定 PostgreSQL Schema)
ProcessConfig processConfig = ProcessConfig.builder()
.designatedTableName(new ArrayList<>()) // Java 8 显式 new ArrayList
.designatedTablePrefix(new ArrayList<>())
.designatedTableSuffix(new ArrayList<>())
.build();
// 3. 构建整体配置
Configuration config = Configuration.builder()
.version("1.0.0")
.description("XY_DB 数据库设计文档(自动生成)")
.dataSource(documentUtil.dataSource())
.engineConfig(engineConfig)
.produceConfig(processConfig)
.build();
// 4. 执行生成(失败则降级到自定义逻辑)
try {
new DocumentationExecute(config).execute();
System.out.println("✅ 数据库文档生成完成!");
} catch (Exception e) {
System.err.println("⚠️ screw 生成失败(PostgreSQL 兼容问题),自动降级到自定义方案:");
e.printStackTrace();
PostgreSqlDocGenerator.main(null);
}
}
/**
* 兜底方案:自定义 PostgreSQL 文档生成(纯 Java 8 实现,不依赖第三方)
*/
static class PostgreSqlDocGenerator {
// 数据库连接配置
private static final String URL = "jdbc:postgresql://localhost:54323/xxx?currentSchema=public";
private static final String USER = "xxx";
private static final String PWD = "xxx";
// 文档输出路径(Java 8 兼容路径格式)
private static final String OUTPUT_PATH = "C:\\Users\\14786\\Desktop\\项目\\数据库设计文档\\XY_DB数据库设计文档_自定义.html";
public static void main(String[] args) {
// 确保输出目录存在(Java 8 兼容)
File outputDir = new File(OUTPUT_PATH).getParentFile();
if (!outputDir.exists()) {
outputDir.mkdirs(); // Java 8 创建多级目录
}
// 尝试生成文档
try (Connection conn = getConnection()) {
List<TableInfo> tableInfos = getTableInfos(conn);
generateHtml(tableInfos);
System.out.println("✅ 自定义文档生成完成:" + OUTPUT_PATH);
} catch (SQLException e) {
System.err.println("❌ 数据库连接/查询失败:" + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("❌ 文件写入失败:" + e.getMessage());
e.printStackTrace();
} catch (Exception e) {
System.err.println("❌ 自定义生成失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 获取数据库连接(Java 8 try-with-resources 兼容)
*/
private static Connection getConnection() throws SQLException {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(URL);
config.setUsername(USER);
config.setPassword(PWD);
config.setDriverClassName("org.postgresql.Driver");
// Java 8 关闭自动提交(可选,增强稳定性)
HikariDataSource dataSource = new HikariDataSource(config);
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
return conn;
}
/**
* 读取数据库表和列信息(纯 Java 8 JDBC 实现)
*/
private static List<TableInfo> getTableInfos(Connection conn) throws SQLException {
List<TableInfo> tableList = new ArrayList<>();
DatabaseMetaData metaData = conn.getMetaData();
// 1. 查询 public Schema 下的所有表
ResultSet tableRs = metaData.getTables(null, "public", "%", new String[]{"TABLE"});
while (tableRs.next()) {
String tableName = tableRs.getString("TABLE_NAME");
String tableRemark = tableRs.getString("REMARKS") == null ? "" : tableRs.getString("REMARKS");
TableInfo tableInfo = new TableInfo(tableName, tableRemark);
// 2. 查询当前表的所有列信息
ResultSet columnRs = metaData.getColumns(null, "public", tableName, "%");
List<ColumnInfo> columnList = new ArrayList<>();
while (columnRs.next()) {
String colName = columnRs.getString("COLUMN_NAME");
String colType = columnRs.getString("TYPE_NAME");
int colSize = columnRs.getInt("COLUMN_SIZE");
String isNullable = columnRs.getString("IS_NULLABLE");
String colRemark = columnRs.getString("REMARKS") == null ? "" : columnRs.getString("REMARKS");
// 判断是否为主键
String isPk = isPrimaryKey(metaData, tableName, colName);
columnList.add(new ColumnInfo(colName, colType, colSize, isNullable, isPk, colRemark));
}
columnRs.close(); // Java 8 手动关闭 ResultSet(避免资源泄漏)
tableInfo.setColumnList(columnList);
tableList.add(tableInfo);
}
tableRs.close();
return tableList;
}
/**
* 判断字段是否为主键(抽离方法,Java 8 代码复用)
*/
private static String isPrimaryKey(DatabaseMetaData metaData, String tableName, String colName) throws SQLException {
ResultSet pkRs = metaData.getPrimaryKeys(null, "public", tableName);
try {
while (pkRs.next()) {
if (colName.equals(pkRs.getString("COLUMN_NAME"))) {
return "YES";
}
}
return "NO";
} finally {
pkRs.close(); // 确保 ResultSet 关闭
}
}
/**
* 生成 HTML 文档(Java 8 字符集兼容)
*/
private static void generateHtml(List<TableInfo> tableList) throws IOException {
StringBuilder html = new StringBuilder();
// HTML 头部(UTF-8 编码,适配 Java 8)
html.append("<!DOCTYPE html>\n")
.append("<html lang=\"zh-CN\">\n")
.append("<head>\n")
.append(" <meta charset=\"UTF-8\">\n")
.append(" <title>XY_DB 数据库设计文档</title>\n")
.append(" <style>\n")
.append(" table { border-collapse: collapse; width: 100%; margin: 20px 0; }\n")
.append(" th, td { border: 1px solid #ccc; padding: 8px; text-align: center; }\n")
.append(" th { background: #f5f5f5; }\n")
.append(" h1 { text-align: center; margin: 20px 0; }\n")
.append(" h2 { color: #333; margin-top: 30px; }\n")
.append(" hr { border: 0; border-top: 1px solid #eee; margin: 20px 0; }\n")
.append(" </style>\n")
.append("</head>\n")
.append("<body>\n")
.append(" <h1>XY_DB 数据库设计文档</h1>\n");
// 遍历表生成内容
for (TableInfo table : tableList) {
html.append(" <h2>表名:").append(escapeHtml(table.getTableName())).append(" (")
.append(escapeHtml(table.getTableRemark())).append(")</h2>\n")
.append(" <table>\n")
.append(" <tr>\n")
.append(" <th>字段名</th>\n")
.append(" <th>类型</th>\n")
.append(" <th>长度</th>\n")
.append(" <th>是否可为空</th>\n")
.append(" <th>是否主键</th>\n")
.append(" <th>备注</th>\n")
.append(" </tr>\n");
// 遍历列
for (ColumnInfo col : table.getColumnList()) {
html.append(" <tr>\n")
.append(" <td>").append(escapeHtml(col.getColumnName())).append("</td>\n")
.append(" <td>").append(escapeHtml(col.getTypeName())).append("</td>\n")
.append(" <td>").append(col.getColumnSize()).append("</td>\n")
.append(" <td>").append(escapeHtml(col.getIsNullable())).append("</td>\n")
.append(" <td>").append(escapeHtml(col.getIsPrimaryKey())).append("</td>\n")
.append(" <td>").append(escapeHtml(col.getColumnRemark())).append("</td>\n")
.append(" </tr>\n");
}
html.append(" </table>\n")
.append(" <hr/>\n");
}
// HTML 尾部
html.append("</body>\n</html>");
// Java 8 写入文件(指定 UTF-8,避免中文乱码)
Files.write(Paths.get(OUTPUT_PATH), html.toString().getBytes(StandardCharsets.UTF_8));
}
/**
* HTML 转义(Java 8 实现,防止 XSS 和格式错乱)
*/
private static String escapeHtml(String str) {
if (str == null || str.isEmpty()) {
return "";
}
return str.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
// ======================== 实体类(Java 8 标准实现) ========================
/**
* 表信息实体
*/
static class TableInfo {
private String tableName;
private String tableRemark;
private List<ColumnInfo> columnList;
public TableInfo(String tableName, String tableRemark) {
this.tableName = tableName;
this.tableRemark = tableRemark;
}
// Getter & Setter(Java 8 标准写法)
public String getTableName() { return tableName; }
public String getTableRemark() { return tableRemark; }
public List<ColumnInfo> getColumnList() { return columnList; }
public void setColumnList(List<ColumnInfo> columnList) { this.columnList = columnList; }
}
/**
* 列信息实体
*/
static class ColumnInfo {
private String columnName;
private String typeName;
private int columnSize;
private String isNullable;
private String isPrimaryKey;
private String columnRemark;
public ColumnInfo(String columnName, String typeName, int columnSize, String isNullable, String isPrimaryKey, String columnRemark) {
this.columnName = columnName;
this.typeName = typeName;
this.columnSize = columnSize;
this.isNullable = isNullable;
this.isPrimaryKey = isPrimaryKey;
this.columnRemark = columnRemark;
}
// Getter(Java 8 标准写法)
public String getColumnName() { return columnName; }
public String getTypeName() { return typeName; }
public int getColumnSize() { return columnSize; }
public String getIsNullable() { return isNullable; }
public String getIsPrimaryKey() { return isPrimaryKey; }
public String getColumnRemark() { return columnRemark; }
}
}
}
项目依赖
<!-- <数据库文档生成> -->
<!-- PostgreSQL JDBC 驱动(Java 8 兼容版) -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.23</version> <!-- 适配 Java 8 的稳定版 -->
</dependency>
<!-- HikariCP 连接池(Java 8 兼容版) -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.4.5</version> <!-- 最后支持 Java 8 的版本 -->
</dependency>
<!-- screw 文档生成(Java 8 兼容版) -->
<dependency>
<groupId>cn.smallbun.screw</groupId>
<artifactId>screw-core</artifactId>
<version>1.0.5</version> <!-- 适配 Java 8 的版本 -->
</dependency>
<!-- Freemarker 模板引擎(Java 8 兼容) -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>