若依实现SQL自动生成ER图
在我们日常开发中或者写文档或者毕设的时候,文档中需要有数据库表的ER图来体现表结构实体和属性的关系,那么就需要我们手动的用一些画图工具来画ER图,如果是一张两张表手工画一下是可以的,如果有几十张表,甚至更多,画起来是非常麻烦消耗时间的。
今天我用若依开发了SQL自动生成ER图的工具。
地址 Gitee地址
功能
我们做了SQL导入按钮,可以把我们若依系统中的表直接导入就可以直接生成ER图,因为我们若依本身就是一个脚手架,可以帮我们在此基础上开发功能,那么基础脚手架有了,现在再加上我们可以直接生成ER图,这样是非常方便的,写文档,或者毕设论文会有很大的帮助。
1.输入你需要生成ER图的建表语句,可以输入多条数据库建表语句。
2.你可以选择系统自带的数据库表生成ER图,可以选择多个表。
3.支持多表同时生成ER图
4.支持导出ER图片
示列代码
package com.manage.function.sqltoer.sqlback.service;
import com.manage.function.sqltoer.sqlback.model.ColumnInfo;
import com.manage.function.sqltoer.sqlback.model.ForeignKeyInfo;
import com.manage.function.sqltoer.sqlback.model.TableInfo;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.ForeignKeyIndex;
import net.sf.jsqlparser.statement.create.table.Index;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author jinghj
* @date 2025-03-04
*/
@Slf4j
@Service
public class SqlParserService {
public List<TableInfo> parseSql(String sql) {
List<TableInfo> tables = new ArrayList<>();
try {
log.info("开始解析SQL: {}", sql);
// 预处理 SQL,移除 UNIQUE INDEX 语句
String processedSql = preprocessSql(sql);
// 解析多个SQL语句
Statements statements = CCJSqlParserUtil.parseStatements(processedSql);
log.info("成功解析SQL语句,共有{}条语句", statements.getStatements().size());
for (Statement statement : statements.getStatements()) {
log.debug("处理语句: {}", statement);
if (statement instanceof CreateTable) {
CreateTable createTable = (CreateTable) statement;
log.info("解析CREATE TABLE语句: {}", createTable.getTable().getName());
TableInfo tableInfo = parseCreateTable(createTable);
tables.add(tableInfo);
}
}
log.info("SQL解析完成,共解析出{}个表", tables.size());
return tables;
} catch (JSQLParserException e) {
log.error("SQL解析错误: {}", e.getMessage());
throw new RuntimeException("SQL语法错误: " + e.getMessage());
} catch (Exception e) {
log.error("处理SQL时发生意外错误", e);
throw new RuntimeException("处理SQL时发生错误: " + e.getMessage());
}
}
private String preprocessSql(String sql) {
// 使用正则表达式移除 UNIQUE INDEX 语句
String[] lines = sql.split("\n");
StringBuilder processedSql = new StringBuilder();
for (String line : lines) {
// 跳过包含 UNIQUE INDEX 的行
if (!line.trim().toUpperCase().contains("UNIQUE INDEX")) {
processedSql.append(line).append("\n");
}
}
return processedSql.toString();
}
private TableInfo parseCreateTable(CreateTable createTable) {
TableInfo tableInfo = new TableInfo();
String tableName = createTable.getTable().getName();
tableInfo.setTableName(tableName);
List<ColumnInfo> columns = new ArrayList<>();
List<String> primaryKeys = new ArrayList<>();
List<ForeignKeyInfo> foreignKeys = new ArrayList<>();
// 解析列定义
log.debug("开始解析表{}的列定义", tableName);
for (ColumnDefinition col : createTable.getColumnDefinitions()) {
ColumnInfo columnInfo = new ColumnInfo();
columnInfo.setName(col.getColumnName());
columnInfo.setType(col.getColDataType().getDataType());
columnInfo.setNullable(true); // 默认可为空
// 提取列注释
String columnComment = "";
List<String> columnSpecs = col.getColumnSpecs();
if (columnSpecs != null) { // 检查 columnSpecs 是否为 null
columnComment = columnSpecs.stream()
.filter(spec -> spec.toUpperCase().contains("COMMENT"))
.map(spec -> {
// 添加调试日志
log.debug("列规格列表: {}", columnSpecs);
int commentIndex = columnSpecs.indexOf(spec);
if (commentIndex >= 0 && commentIndex + 1 < columnSpecs.size()) {
String comment = columnSpecs.get(commentIndex + 1);
log.debug("找到注释内容: [{}]", comment);
// 去除单引号
return comment.replaceAll("^'|'$", "");
}
return col.getColumnName();
})
.findFirst().orElse(col.getColumnName());
}
// 如果columnComment为空,设置为col.getColumnName()
if (columnComment == null || columnComment.isEmpty()) {
columnComment = col.getColumnName();
}
columnInfo.setComment(columnComment);
log.info("列{}的注释: {}", col.getColumnName(), columnInfo.getComment());
// 检查列约束
if (columnSpecs != null) { // 检查 columnSpecs 是否为 null
String columnSpecsStr = String.join(" ", columnSpecs).toUpperCase();
if (columnSpecsStr.contains("PRIMARY KEY")) {
primaryKeys.add(col.getColumnName());
log.info("找到主键: {}.{}", tableName, col.getColumnName());
}
if (columnSpecsStr.contains("NOT NULL")) {
columnInfo.setNullable(false);
}
}
columns.add(columnInfo);
}
// 提取表注释
String tableComment = "";
List<String> tableOptionsStrings = createTable.getTableOptionsStrings();
if (tableOptionsStrings != null) {
tableComment = tableOptionsStrings.stream()
.filter(option -> option.equalsIgnoreCase("COMMENT"))
.findFirst()
.map(commentKey -> {
int commentIndex = tableOptionsStrings.indexOf(commentKey);
List<String> options = tableOptionsStrings;
// 处理两种格式:
// 1. COMMENT '内容'
// 2. COMMENT = '内容'
if (commentIndex + 1 < options.size()) {
String nextToken = options.get(commentIndex + 1);
// 格式1: 直接跟随注释内容
if (nextToken.startsWith("'")) {
String fullComment = String.join(" ", options.subList(commentIndex + 1, options.size()));
return parseQuotedString(fullComment);
}
// 格式2: 包含等号
if (nextToken.equals("=") && commentIndex + 2 < options.size()) {
String commentValue = options.get(commentIndex + 2);
return parseQuotedString(commentValue);
}
}
log.warn("无法解析表注释格式: {}", options);
return tableName;
})
.orElse(tableName);
} else {
tableComment = tableName; // 如果 tableOptionsStrings 为 null,设置表注释为表名
}
tableInfo.setComment(tableComment);
log.info("表{}的注释: {}", tableName, tableInfo.getComment());
// 解析外键约束
if (createTable.getIndexes() != null) {
log.debug("开始解析表{}的外键约束", tableName);
for (Index index : createTable.getIndexes()) {
if (index instanceof ForeignKeyIndex) {
ForeignKeyIndex fk = (ForeignKeyIndex) index;
ForeignKeyInfo fkInfo = new ForeignKeyInfo();
fkInfo.setColumnName(fk.getColumns().get(0).getColumnName());
fkInfo.setReferenceTable(fk.getTable().getName());
fkInfo.setReferenceColumn(fk.getReferencedColumnNames().get(0));
foreignKeys.add(fkInfo);
log.info("找到外键: {}.{} -> {}.{}",
tableName, fkInfo.getColumnName(),
fkInfo.getReferenceTable(), fkInfo.getReferenceColumn());
}
}
}
tableInfo.setColumns(columns);
tableInfo.setPrimaryKeys(primaryKeys);
tableInfo.setForeignKeys(foreignKeys);
log.info("表{}解析完成: {}列, {}个主键, {}个外键",
tableName, columns.size(), primaryKeys.size(), foreignKeys.size());
return tableInfo;
}
// 辅助方法:解析带引号的字符串
private String parseQuotedString(String input) {
// 匹配单引号或双引号包裹的内容
Pattern pattern = Pattern.compile("^(['\"])(.*)\\1$");
Matcher matcher = pattern.matcher(input);
if (matcher.find()) {
return matcher.group(2);
}
// 如果未匹配到完整引号,尝试直接处理
return input.replaceAll("^['\"]|['\"]$", "");
}
}
交流
🐧:1601078502
截图