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;}@Testpublic void ddlParserTest(){//解析DDLvar 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);}@BeforeTestpublic 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语句中,上面的代码还要做相应的修改。
相关文章:
java-使用druid sqlparser将SQL DDL脚本转化为自定义的java对象
java-使用druid sqlparser将SQL DDL脚本转化为自定义的java对象 一、引言二、环境三、待解析的DDL四、解析后的对象结构五、完整的UT类六、控制台输出总结 一、引言 在日常开发中,有些需要对SQL进行解析的场景,比如读取表结构信息,生成文档、…...
React状态管理常见面试题目(一)
1. Redux 如何实现多个组件之间的通信?多个组件使用相同状态时如何进行管理? Redux 实现组件通信 Redux 是一个集中式的状态管理工具,通过共享一个全局 store 来实现多个组件之间的通信。 通信机制: 所有状态保存在 Redux 的全局 store 中。使用 ma…...
jenkins 出现 Jenkins: 403 No valid crumb was included in the request
文章目录 前言解决方式:1.跨站请求为找保护勾选"代理兼容"2.全局变量或者节点上添加环境变量3.(可选)下载插件 the strict Crumb Issuer plugin4.重启 前言 jenkins运行时间长了,经常出现点了好几次才能构建,然后报了Je…...
【前端面试】list转树、拍平, 指标,
这个题目涉及的是将一组具有父子关系的扁平数据转换为树形结构,通常称为“树形结构的构建”问题。类似的题目包括: 1. 组织架构转换 给定一个公司的员工列表,每个员工有 id 和 managerId,其中 managerId 表示该员工的上级。任务…...
游戏引擎学习第43天
仓库 https://gitee.com/mrxiao_com/2d_game 介绍运动方程 今天我们将更进一步,探索运动方程,了解真实世界中的物理,并调整它们,以创建一种让玩家感觉愉悦的控制体验。这并不是在做一个完美的物理模拟,而是找到最有趣…...
NVM:安装配置使用(详细教程)
文章目录 一、简介二、安装 nvm三、配置 nvm 镜像四、配置环境变量五、使用教程5.1 常用命令5.2 具体案例 六、结语 一、简介 在实际的开发和学习中可能会遇到不同项目的 node 版本不同,而出现的兼容性问题。 而 nvm 就可以很好的解决这个问题,它可以在…...
matlab测试ADC动态性能的原理
目录 摘要: 简介: 动态规范和定义 动态规格: 双面到单边的功率谱转换 摘要: 模数转换器(adc)代表了接收器、测试设备和其他电子设备中的模拟世界和数字世界之间的联系。正如本文系列的第1部分中所概述…...
PostgreSQL JSON/JSONB 查询与操作指南
PostgreSQL 提供了强大的 JSON 和 JSONB 数据类型及相关操作,适用于存储和查询半结构化数据。本文将详细介绍其常用操作。 1. 基础操作 1.1 JSON 属性访问 ->: 返回 JSON 对象中的值,结果为 JSON 格式。 SELECT {"a": {"b": 1…...
【Isaac Lab】Ubuntu22.04安装英伟达驱动
目录 1.1 禁用nouveau驱动 1.2 安装必要的依赖项 1.3 下载安装 1.4 查看是否安装成功 1.5 安装CUDA 1.5.1 下载 1.5.2 按照提示进行下载安装 1.5.3 添加环境变量 1.5.4 测试CUDA是否安装成功 1.1 禁用nouveau驱动 输入以下命令打开blacklist.conf文件 sudo vim /etc…...
JS,递归,处理树形数据组件,模糊查询树形结构数据字段
JS递归如何模糊查询树形结构数据,根据数据中的某一个字段值,模糊匹配 直接拿去使用就行 function filterTreeLabel(arr, label) {let result []arr.forEach((item) > {// if (String(item.POBJECT_NAME).toLowerCase().indexOf(label)!-1) {if (String(item.P…...
神州数码DCME-320 online_list.php 任意文件读取漏洞复现
0x01 产品描述: 神州数码DCME-320是一款高性能多业务路由器,专为多用户、多流量和多业务种类需求设计。它采用了...
nginx的内置变量以及nginx的代理
nginx的内置变量 客户端 命令含义$uri可以获取客户端请求的地址,包含主机和查询的参数$request_uri:获取客户端的请求地址,包含主机和查询参数。$host:请求的主机名,客户端—发送请求的url地址$http_user_agent获取客户端请求的浏览器和操作…...
ubuntu监测硬盘状态
安装smartmontools smartctl -l error /dev/sdk smartctl -i /dev/sda lshw -class disk smartctl -H /dev/sd 结果1: 结果2:PASSED,这表示硬盘健康状态良好 smartctl -a /dev/sdb sdk lsblk blkid 测试写入速度 time dd if/dev/zero of…...
3.2.1.2 汇编版 原子操作 CAS
基本原理说明 在 x86 和 ARM 架构上,原子操作通常利用硬件提供的原子指令来实现,比如 LOCK 前缀(x86)或 LDREX/STREX(ARM)。以下是一些关键的原子操作(例如原子递增和比较交换)的汇…...
InnoDB事务系统(二):事务的实现
事务隔离性由锁来实现。原子性、一致性、持久性通过数据库的 redo log 和 undo log 来完成。 redo log 称为重做日志,用来保证事务的原子性和持久性。undo log 用来保证事务的一致性。 有的 DBA 或许会认为 undo 是 redo 的逆过程,其实不然。redo 和 u…...
xdoj :模式匹配
模式匹配 题目描述: 接收信号中包含特定的信号模式,对接收信号进行检测,以统计特定模式出现的次数。 例如接收信号为 9 3 5 7 5 8 6 3 5 7 1 9 3 5 7,如果特定信号为 3 5 7,则接收信号中包含了 3 个特定模式。通过键…...
Redis的基本使用命令(GET,SET,KEYS,EXISTS,DEL,EXPIRE,TTL,TYPE)
目录 SET GET KEYS EXISTS DEL EXPIRE TTL redis中的过期策略是怎么实现的(面试) 上文介绍reids的安装以及基本概念,本章节主要介绍 Redis的基本使用命令的使用 Redis 是一个基于键值对(KEY - VALUE)存储的…...
LruCache(本地cache)生产环境中遇到的问题及改进
问题:单机qps增加时请求摘要后端,耗时也会增加,因为超过了后端处理能力(最大qps,存在任务堆积)。 版本一 引入LruCache。为了避免数据失效,cache数据的时效性要小于摘要后端物料的更新时间&…...
智慧公交指挥中枢,数据可视化 BI 驾驶舱
随着智慧城市的蓬勃发展,公共交通作为城市运营的核心枢纽,正朝着智能化和数据驱动的方向演进。通过整合 CAN 总线技术(Controller Area Network,控制器局域网总线)、车载智能终端、大数据分析及处理等尖端技术,构建的公交“大脑”…...
【计算机网络】期末考试预习复习|上
作业讲解 物理层作业 共有4个用户进行CDMA通信。这4个用户的码片序列为: A: (–1 –1 –1 1 1 –1 1 1);B: (–1 –1 1 –1 1 1 1 –1) C: (–1 1 –1 1 1 1 –1 –1);D: (–1 1 –1 –1 –1 –1 1 –1) 现收到码片序列:(–1 1 –…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...
绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化
iOS 应用的发布流程一直是开发链路中最“苹果味”的环节:强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说,这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发(例如 Flutter、React Na…...
边缘计算网关提升水产养殖尾水处理的远程运维效率
一、项目背景 随着水产养殖行业的快速发展,养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下,而且难以实现精准监控和管理。为了提升尾水处理的效果和效率,同时降低人力成本,某大型水产养殖企业决定…...
【阅读笔记】MemOS: 大语言模型内存增强生成操作系统
核心速览 研究背景 研究问题:这篇文章要解决的问题是当前大型语言模型(LLMs)在处理内存方面的局限性。LLMs虽然在语言感知和生成方面表现出色,但缺乏统一的、结构化的内存架构。现有的方法如检索增强生成(RA…...
uniapp获取当前位置和经纬度信息
1.1. 获取当前位置和经纬度信息(需要配置高的SDK) 调用uni-app官方API中的uni.chooseLocation(),即打开地图选择位置。 <button click"getAddress">获取定位</button> const getAddress () > {uni.chooseLocatio…...
自定义线程池1.2
自定义线程池 1.2 1. 简介 上次我们实现了 1.1 版本,将线程池中的线程数量交给使用者决定,并且将线程的创建延迟到任务提交的时候,在本文中我们将对这个版本进行如下的优化: 在新建线程时交给线程一个任务。让线程在某种情况下…...
