java-使用druid sqlparser将SQL DDL脚本转化为自定义的java对象

java-使用druid sqlparser将SQL DDL脚本转化为自定义的java对象

    • 一、引言
    • 二、环境
    • 三、待解析的DDL
    • 四、解析后的对象结构
    • 五、完整的UT类
    • 六、控制台输出
    • 总结

一、引言

在日常开发中,有些需要对SQL进行解析的场景,比如读取表结构信息,生成文档、逆向工程或是生成一些业务代码。例如,当我们需要对Oracle的DDL(数据定义语言)进行解析时,一个强大的SQL语法解析器可以帮助我们直接提取出表的结构信息,包括表名、备注、字段信息以及索引信息。这一功能在以下场景中尤为有用:

  • 代码生成:通过解析DDL,我们可以自动生成对应的实体类、数据库访问接口甚至是完整的数据库操作代码,极大地减少了手动编写重复代码的工作量,提高了开发效率。
  • 数据库逆向工程:在接手一个遗留系统或者对现有系统进行重构时,通过解析现有的DDL,可以快速地理解和重构数据库模型,为后续的开发工作打下坚实的基础。
  • 数据库文档化:自动从DDL中提取表结构信息,可以帮助我们生成数据库文档,使得数据库的设计和结构更加清晰,便于团队成员之间的沟通和协作。
  • 数据库迁移:在数据库迁移过程中,解析源数据库的DDL可以帮助我们快速地构建目标数据库的结构,减少迁移过程中的错误和风险。
    本文将记录了基于Druid SQLParser这一工具,对DDL进行解析的详细过程。

二、环境

  • JDK 17
  • Maven依赖
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.16</version>
</dependency>

三、待解析的DDL

以下内容是基于PowerDesigner做的Oracle 10g的DDL脚本,也是我们需要解析的内容,其中包含drop、create、comment等语句,这里我们只对表、字段信息进行解析。

drop index "Index_1";
drop table "sr_user_info" cascade constraints;
/*==============================================================*/
/* Table: "sr_user_info"                                        */
/*==============================================================*/
create table "sr_user_info"  (
   "user_id"            VARCHAR2(64)                    not null,
   "type"               VARCHAR2(64)                    not null,
   "login_name"         VARCHAR2(64),
   "user_name"          VARCHAR2(64),
   "display_name"       VARCHAR2(256),
   "organization"       VARCHAR2(64),
   "region"             VARCHAR2(64),
   "login_count"        INTEGER,
   "request_count"      bigint,
   "status"             INTEGER,
   "last_login_date"    DATE,
   "create_date"        DATE,
   "update_date"        DATE,
   "money"              number(9,2),
   constraint PK_SR_USER_INFO primary key ("user_id")
);
comment on table "sr_user_info" is '记录并统计用户行为';
comment on column "sr_user_info"."user_id" is '用户ID';
comment on column "sr_user_info"."type" is '类型';
comment on column "sr_user_info"."login_name" is '登录名';
comment on column "sr_user_info"."user_name" is '用户名';
comment on column "sr_user_info"."display_name" is '显示名称';
comment on column "sr_user_info"."organization" is '组织结构';
comment on column "sr_user_info"."region" is '区域';
comment on column "sr_user_info"."login_count" is '登录次数';
comment on column "sr_user_info"."request_count" is '请求次数';
comment on column "sr_user_info"."status" is '状态';
comment on column "sr_user_info"."last_login_date" is '最后登录时间';
comment on column "sr_user_info"."create_date" is '创建时间';
comment on column "sr_user_info"."update_date" is '修改时间';
comment on column "sr_user_info"."money" is '金钱';
/*==============================================================*/
/* Index: "Index_1"                                             */
/*==============================================================*/
create unique index "Index_1" on "sr_user_info" (
   "user_id" ASC,
   "region" ASC
);

四、解析后的对象结构

以下是DDL解析后的对象结构。

{
	"TableCode":"sr_user_info",
	"comment":"记录并统计用户行为",
	"columns":[
		{
			"colCode":"user_id",
			"comment":"用户ID",
			"length":64,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"type",
			"comment":"类型",
			"length":64,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"login_name",
			"comment":"登录名",
			"length":64,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"user_name",
			"comment":"用户名",
			"length":64,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"display_name",
			"comment":"显示名称",
			"length":256,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"organization",
			"comment":"组织结构",
			"length":64,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"region",
			"comment":"区域",
			"length":64,
			"pk":false,
			"type":"VARCHAR2"
		},
		{
			"colCode":"login_count",
			"comment":"登录次数",
			"pk":false,
			"type":"INTEGER"
		},
		{
			"colCode":"request_count",
			"comment":"请求次数",
			"pk":false,
			"type":"bigint"
		},
		{
			"colCode":"status",
			"comment":"状态",
			"pk":false,
			"type":"INTEGER"
		},
		{
			"colCode":"last_login_date",
			"comment":"最后登录时间",
			"pk":false,
			"type":"DATE"
		},
		{
			"colCode":"create_date",
			"comment":"创建时间",
			"pk":false,
			"type":"DATE"
		},
		{
			"colCode":"update_date",
			"comment":"修改时间",
			"pk":false,
			"type":"DATE"
		},
		{
			"colCode":"money",
			"length":9,
			"pk":false,
			"precision":2,
			"type":"number"
		}
	]
}

五、完整的UT类

运行下面的单元测试方法ddlParserTest()可以看到完整效果。需要注意的是如果表名、字段名中有些转意字符可以用SQLUtils.normalize(tableName)方法去掉。

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLCommentStatement;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleCreateTableStatement;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OraclePrimaryKey;
import com.alibaba.druid.support.json.JSONUtils;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SqlParserTest {
    private String ddl = "";

    public static class TabInfo{
        public String TableCode;
        public String comment;
        public List<ColumnInfo> columns = new ArrayList<>();
    }
    public static class ColumnInfo{
        public String colCode;
        public String comment;
        public String type;
        public Integer length;
        public Integer precision;
        public boolean pk = false;
    }

    @Test
    public void ddlParserTest(){
        //解析DDL
        var statements = SQLUtils.parseStatements(ddl,DbType.oracle);
        //获取索引值
        java.util.function.BiFunction<List<SQLExpr>,Integer,Integer> getArg=(exprs, index)->{
            if(exprs==null || exprs.size()<index+1)
                return null;
            var expr = exprs.get(index);
            if(!(expr instanceof SQLIntegerExpr))
                return null;
            var intExpr = (SQLIntegerExpr) expr;
            var n = intExpr.getNumber().intValue();
            return n;
        };
        TabInfo tabInfo = new TabInfo();
        List<String> pkCols = new ArrayList<>();
        Map<String,ColumnInfo> colMap = new java.util.HashMap<>();
        //解析表名
        java.util.function.Consumer parserCreateTable = (statement) -> {
            OracleCreateTableStatement table = (OracleCreateTableStatement) statement;
            var tableName = table.getTableName();
            tableName = SQLUtils.normalize(tableName);
            System.out.println("--------------表名------------------");
            System.out.println("表名\t"+tableName);
            tabInfo.TableCode = tableName;
            for (SQLTableElement em : table.getTableElementList()) {
                if(em instanceof SQLColumnDefinition){
                    var col = (SQLColumnDefinition) em;
                    var colName = col.getColumnName();//字段名
                    colName = SQLUtils.normalize(colName);
                    var dataType = col.getDataType();//数据类型对象
                    var type = dataType.getName();//类型
                    var len = getArg.apply(dataType.getArguments(),0);//长度
                    var precision = getArg.apply(dataType.getArguments(),1);//精度
                    System.out.println(String.format("字段:%s\t%s\t%s\t%s"
                            , colName
                            , type
                            , len
                            , precision));
                    //
                    var colInfo = new ColumnInfo();
                    colInfo.colCode = colName;
                    colInfo.type = type;
                    colInfo.length = len;
                    colInfo.precision = precision;
                    colInfo.pk = pkCols.contains(colName);//是否主键字段
                    colMap.put(colName, colInfo);
                    tabInfo.columns.add(colInfo);
                }else if(em instanceof OraclePrimaryKey){
                    var pk = (OraclePrimaryKey) em;
                    var pkName = pk.getName().getSimpleName();//主键名
                    pkName = SQLUtils.normalize(pkName);
                    //主键字段列表
                    pk.getColumns().forEach(x-> {
                        var colName = x.getExpr().toString();
                        colName = SQLUtils.normalize(colName);
                        pkCols.add(colName);
                    });
                    System.out.println(String.format("主键:%s\t%s\t%s\t%s"
                            , pkName
                            , pk.getComment()//注释
                            , pkCols
                            , JSONUtils.toJSONString(pk.getAttributes())));

                }
            }
        };
        //解析注释
        java.util.function.Consumer parserComment = (statement) -> {
            var com = (SQLCommentStatement) statement;
            System.out.println("------------------注释----------------");
            if(com.getType() == SQLCommentStatement.Type.TABLE){
                var tab = com.getOn();
                var tabName = tab.getName().getSimpleName();//表明
                var tabComment = com.getComment().toString();//表注释
                tabComment = SQLUtils.normalize(tabComment);
                System.out.println(String.format("tab注释:%s\t%s"
                        ,tabName
                        ,tabComment
                ));
                //
                tabInfo.comment = tabComment;
            }else if(com.getType() == SQLCommentStatement.Type.COLUMN){
                var tab = com.getOn();
                var tabName = tab.getName().toString();
                var colName = tab.getName().getSimpleName();//字段名
                colName = SQLUtils.normalize(colName);
                var comment = String.valueOf(com.getComment());//字段注释
                comment = SQLUtils.normalize(comment);
                System.out.println(String.format("col注释:%s\t%s"
                        ,colName
                        ,comment));
                //
                var colInfo = colMap.get(colName);
                colInfo.comment = comment;
            }else if(com.getType() == SQLCommentStatement.Type.INDEX){

            }else if(com.getType() == SQLCommentStatement.Type.VIEW){

            }else{
                System.out.println("未知类型"+com.getType());
            }
        };
        //解析语句
        statements.forEach(statement -> {
            if(statement instanceof OracleCreateTableStatement){
                parserCreateTable.accept(statement);
            }else if(statement instanceof SQLCommentStatement){
                parserComment.accept(statement);
            }
        });

        System.out.println("\n------------------JSON----------------");
        String jsonStr = com.alibaba.fastjson.JSON.toJSONString(tabInfo,true);
        System.out.println(jsonStr);
    }
    @BeforeTest
    public void beforeTest() {
        ddl = "drop index \"Index_1\";\n" +
                "drop table \"sr_user_info\" cascade constraints;\n" +
                "/*==============================================================*/\n" +
                "/* Table: \"sr_user_info\"                                        */\n" +
                "/*==============================================================*/\n" +
                "create table \"sr_user_info\"  (\n" +
                "   \"user_id\"            VARCHAR2(64)                    not null,\n" +
                "   \"type\"               VARCHAR2(64)                    not null,\n" +
                "   \"login_name\"         VARCHAR2(64),\n" +
                "   \"user_name\"          VARCHAR2(64),\n" +
                "   \"display_name\"       VARCHAR2(256),\n" +
                "   \"organization\"       VARCHAR2(64),\n" +
                "   \"region\"             VARCHAR2(64),\n" +
                "   \"login_count\"        INTEGER,\n" +
                "   \"request_count\"      bigint,\n" +
                "   \"status\"             INTEGER,\n" +
                "   \"last_login_date\"    DATE,\n" +
                "   \"create_date\"        DATE,\n" +
                "   \"update_date\"        DATE,\n" +
                "   \"money\"              number(9,2),\n" +
                "   constraint PK_SR_USER_INFO primary key (\"user_id\")\n" +
                ");\n" +
                "comment on table \"sr_user_info\" is '记录并统计用户行为';\n" +
                "comment on column \"sr_user_info\".\"user_id\" is '用户ID';\n" +
                "comment on column \"sr_user_info\".\"type\" is '类型';\n" +
                "comment on column \"sr_user_info\".\"login_name\" is '登录名';\n" +
                "comment on column \"sr_user_info\".\"user_name\" is '用户名';\n" +
                "comment on column \"sr_user_info\".\"display_name\" is '显示名称';\n" +
                "comment on column \"sr_user_info\".\"organization\" is '组织结构';\n" +
                "comment on column \"sr_user_info\".\"region\" is '区域';\n" +
                "comment on column \"sr_user_info\".\"login_count\" is '登录次数';\n" +
                "comment on column \"sr_user_info\".\"request_count\" is '请求次数';\n" +
                "comment on column \"sr_user_info\".\"status\" is '状态';\n" +
                "comment on column \"sr_user_info\".\"last_login_date\" is '最后登录时间';\n" +
                "comment on column \"sr_user_info\".\"create_date\" is '创建时间';\n" +
                "comment on column \"sr_user_info\".\"update_date\" is '修改时间';\n" +
                "comment on column \"sr_user_info\".\"money\" is '金钱';\n" +
                "/*==============================================================*/\n" +
                "/* Index: \"Index_1\"                                             */\n" +
                "/*==============================================================*/\n" +
                "create unique index \"Index_1\" on \"sr_user_info\" (\n" +
                "   \"user_id\" ASC,\n" +
                "   \"region\" ASC\n" +
                ");\n";        
    }
}

六、控制台输出


--------------表名------------------
表名	sr_user_info
字段:user_id	VARCHAR2	64	null
字段:type	VARCHAR2	64	null
字段:login_name	VARCHAR2	64	null
字段:user_name	VARCHAR2	64	null
字段:display_name	VARCHAR2	256	null
字段:organization	VARCHAR2	64	null
字段:region	VARCHAR2	64	null
字段:login_count	INTEGER	null	null
字段:request_count	bigint	null	null
字段:status	INTEGER	null	null
字段:last_login_date	DATE	null	null
字段:create_date	DATE	null	null
字段:update_date	DATE	null	null
字段:money	number	9	2
主键:PK_SR_USER_INFO	null	[user_id]	{}
------------------注释----------------
tab注释:"sr_user_info"	记录并统计用户行为
col注释:user_id	用户ID
col注释:type	类型
col注释:login_name	登录名
col注释:user_name	用户名
col注释:display_name	显示名称
col注释:organization	组织结构
col注释:region	区域
col注释:login_count	登录次数
col注释:request_count	请求次数
col注释:status	状态
col注释:last_login_date	最后登录时间
col注释:create_date	创建时间
col注释:update_date	修改时间

总结

不同的DDL定义语法会解析为不同的statment对象,这里只演示了一种DDL格式,比较原生。开源项目中有些很多测试用例其中继承OracleSchemaStatVisitor解析,看着内聚性更强。还需要注意的是,有些DDL将字段注释写在create语句中,上面的代码还要做相应的修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/938672.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【算法】【优选算法】链表

目录 一、链表常用技巧与操作总结二、2.两数相加三、24.两两交换链表中的节点3.1 迭代3.2 递归 四、143.重排链表五、23.合并K个升序链表5.1 堆5.2 分治5.3 暴力枚举 六、25.K个⼀组翻转链表 一、链表常用技巧与操作总结 技巧&#xff1a; 画图解题。使用虚拟头结点。像有插入…

【面试】Redis 常见面试题

一、介绍一下什么是 Redis&#xff0c;有什么特点? Redis 是一个高性能的 key-value 内存数据库。 不同于传统的 MySQL 这样的关系型数据库&#xff0c;Redis 主要使用内存存储数据&#xff08;当然也支持持久化存储到硬盘上&#xff09;&#xff0c;并非是使用 “表” 这样…

【Linux】NET9运行时移植到低版本GLIBC的Linux纯内核板卡上

背景介绍 自制了一块Linux板卡(基于全志T113i) 厂家给的SDK和根文件系统能够提供的GLIBC的版本比较低 V2.25/GCC 7.3.1 这个版本是无法运行dotnet以及dotnet生成的AOT应用的 我用另一块同Cortex-A7的板子运行dotnet的报错 版本不够&#xff0c;运行不了 而我的板子是根本就识…

MySQL Explain 分析SQL语句性能

一、EXPLAIN简介 使用EXPLAIN关键字可以模拟优化器执行SQL查询语句&#xff0c;从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。 &#xff08;1&#xff09; 通过EXPLAIN&#xff0c;我们可以分析出以下结果&#xff1a; 表的读取顺序数据读取…

vue3实现商城系统详情页(前端实现)

目录 写在前面 预览 实现 图片部分 详情部分 代码 源码地址 总结 写在前面 笔者不是上一个月毕业了么&#xff1f;找工作没找到&#xff0c;准备在家躺平两个月。正好整理一下当时的毕业设计&#xff0c;是一个商城系统。还是写篇文章记录下吧 预览 商品图片切换显示…

uniapp 微信小程序 功能入口

单行单独展示 效果图 html <view class"shopchoose flex jsb ac" click"routerTo(要跳转的页面)"><view class"flex ac"><image src"/static/dyd.png" mode"aspectFit" class"shopchooseimg"&g…

6.1 初探MapReduce

MapReduce是一种分布式计算框架&#xff0c;用于处理大规模数据集。其核心思想是“分而治之”&#xff0c;通过Map阶段将任务分解为多个简单任务并行处理&#xff0c;然后在Reduce阶段汇总结果。MapReduce编程模型包括Map和Reduce两个阶段&#xff0c;数据来源和结果存储通常在…

聚观早报 | 百度回应进军短剧;iPad Air将升级OLED

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 12月18日消息 百度回应进军短剧 iPad Air将升级OLED 三星Galax S25 Ultra配色细节 一加Ace 5系列存储规格 小米…

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB 是一款基于南京沁恒&#xff08;WCH&#xff09;推出的高性能、低功耗无线通信芯片CH582F的开发板。以下是该开发板的功能和参数详细介绍&#xff1a; 主要特性 双模蓝牙支持&#xff1a; 支持蓝牙5.0标准&#xff0…

【软件工程复习】

第1章 软件工程概述 1.2软件工程 ​ 1983年IEEE给出的定义&#xff1a;“软件工程是 开发、运行、维护和修复软件的系统方法 ” 1.4软件生存期 软件开发和运行维护由三个时期组成&#xff1a; 软件定义时期软件开发时期运行维护时期 里程碑指可以用来标识项目进程状态的事…

DuckDB: 从MySql导出数据至Parquet文件

在这篇文章中&#xff0c;介绍使用DuckDB将数据从MySQL数据库无缝传输到Parquet文件的过程。该方法比传统的基于pandas方法更高效、方便&#xff0c;我们可以从DuckDB cli实现&#xff0c;也可以结合Python编程方式实现&#xff0c;两者执行核心SQL及过程都一样。 Parquet格式…

safe area helper插件

概述 显示不同机型的必能显示的区域 实现步骤 引入safearea&#xff0c;引入其中的safearea的csharp 为cancas加入gameobject gameobject中加入safearea脚本 将UI作为这个gameobject的子物体&#xff0c;就可以完成显示

数据结构 ——二叉树转广义表

数据结构 ——二叉树转广义表 1、树转广义表 如下一棵树&#xff0c;转换为广义表 root(c(a()(b()()))(e(d()())(f()(j(h()())())))) (根&#xff08;左子树&#xff09;&#xff08;右子树&#xff09;) 代码实现 #include<stdio.h> #include<stdlib.h>//保存…

实现echart大屏动画效果及全屏布局错乱解决方式

如何实现echarts动画效果?如何实现表格或多个垂直布局的柱状图自动滚动效果?如何解决tooltip位置超出屏幕问题,如何解决legend文字过长,布局错乱问题?如何处理饼图的中心图片永远居中? 本文将主要解决以上问题,如有错漏,请指正. 一、大屏动画效果 这里的动画效果主要指&…

【YashanDB知识库】如何处理yasql输入交互模式下单行字符总量超过限制4000字节

现象 在yasql执行sql语句后报错&#xff1a;YASQL-00021 input line overflow (>4000 byte at line 4) 原因 yasql在交互模式模式下单行字符总量限制4000字节&#xff0c;超出该限制即报错。 交互式模式下&#xff0c;yasql会显示一个提示符&#xff0c;通常是 SQL>…

DALL·E 2(内含扩散模型介绍)-生成式模型【学习笔记】

视频链接&#xff1a;DALLE 2&#xff08;内含扩散模型介绍&#xff09;【论文精读】_哔哩哔哩_bilibili&#xff08;up主讲的非常好&#xff0c;通俗易懂&#xff0c;值得推荐&#xff09; 目录 1、GAN模型 2、VAE模型 2.1、AE&#xff08;Auto-Encoder&#xff09; 2.2、…

FPGA 16 ,Verilog中的位宽:深入理解与应用

目录 前言 一. 位宽的基本概念 二. 位宽的定义方法 1. 使用向量变量定义位宽 ① 向量类型及位宽指定 ② 位宽范围及位索引含义 ③ 存储数据与字节数据 2. 使用常量参数定义位宽 3. 使用宏定义位宽 4. 使用[:][-:]操作符定义位宽 1. 详细解释 : 操作符 -: 操作符 …

使用 Vue3 实现摄像头拍照功能

参考资料:MediaDevices.getUserMedia() - Web API | MDN 重要: navigator.mediaDevices.getUserMedia 需要在安全的上下文中运行。现代浏览器要求摄像头和麦克风的访问必须通过 HTTPS 或 localhost&#xff08;被视为安全的本地环境&#xff09;进行,如果上传服务器地址是http…

2024安装hexo和next并部署到github和服务器最新教程

碎碎念 本来打算写点算法题上文所说的题目&#xff0c;结果被其他事情吸引了注意力。其实我之前也有过其他博客网站&#xff0c;但因为长期不维护&#xff0c;导致数据丢失其实是我懒得备份。这个博客现在部署在GitHub Pages上&#xff0c;github不倒&#xff0c;网站不灭&…

RTMP推流平台EasyDSS在无人机推流直播安防监控中的创新应用

无人机与低空经济的关系密切&#xff0c;并且正在快速发展。2024年中国低空经济行业市场规模达到5800亿元&#xff0c;其中低空制造产业占整个低空经济产业的88%。预计未来五年复合增速将达到16.03%。 随着科技的飞速发展&#xff0c;公共安防关乎每一个市民的生命财产安全。在…