若依实现SQL自动生成ER图

若依实现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

截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值