普通视图

Received before yesterday

SQL解析工具JSQLParser

2025年2月1日 00:00

本文未完待续…

一、引言

JSQLParser(GitHub:https://github.com/JSQLParser/JSqlParser)是一个Java语言的SQL语句解析工具,功能十分强大,它可以将SQL语句解析成为Java类的层次结构,还支持改写SQL,常见的持久层框架MyBatis-Plus就采用它作为SQL解析工具来实现某些功能。

二、JSQLParser常见类

2.1 Class Diagram

2.2 Statement

可以理解为能够表示任意一种SQL语句的对象,Select、Update、Delete、Insert都是它的子类,例如以下用法:

Statement statement = JsqlParserGlobal.parse(sql);if (statement instanceof Insert) {    this.processInsert((Insert) statement, index, sql, obj);} else if (statement instanceof Select) {    this.processSelect((Select) statement, index, sql, obj);} else if (statement instanceof Update) {    this.processUpdate((Update) statement, index, sql, obj);} else if (statement instanceof Delete) {    this.processDelete((Delete) statement, index, sql, obj);}

2.3 Expression

是JSqlParser库中的一个核心接口,是用于表示SQL语句中的各种表达式的基类接口,通过调用对象的.toString()方法,就能看到具体的语句结构。

例如:

  1. 基本值
    • LongValue(整数值)、StringValue(字符串值)、DoubleValue(浮点数值)等。
  2. 列引用
    • Column(表示列名,如 column_nametable.column)。
  3. 运算符
    • Addition+)、Subtraction-)、Multiplication*)、Division/)等。
  4. 函数调用
    • Function(如 COUNT(*)SUBSTRING(str, 1, 2))。
  5. 条件表达式
    • EqualsTo=)、NotEqualsTo<>!=)、GreaterThan>)、LikeExpressionLIKE)等。
  6. 逻辑表达式(BinaryExpression)
    • AndExpressionAND)、OrExpressionOR)、NotExpressionNOT)。
  7. 子查询
    • SubSelect(如 (SELECT ...))。
  8. Case 表达式
    • CaseExpressionCASE WHEN ... THEN ... END)。
  9. 其他复杂表达式
    • CastExpressionCAST(... AS ...))、IntervalExpression(时间间隔)等。

2.4 Select

用于表示查询SQL语句,有三个常见子类:PlainSelect,ParenthesedSelect,SetOperationList

2.5 Update

用于表示更新的SQL语句

获得对应表

Table table = update.getTable();

获得要更新的值

List<UpdateSet> sets = update.getUpdateSets();

获取where条件

Expression expression = update.getWhere()

2.6 Delete

用于表示删除的SQL语句

获得对应表

Table table = delete.getTable();

获取where条件

Expression expression = delete.getWhere()

2.7 Insert

用于表示添加SQL语句,有以下几种常见方法

获取添加的列

List<Column> columns = insert.getColumns();

获取添加的值

Values values = insert.getValues();

获取添加时冲突进行更新的结构

INSERT INTO ... VALUES ...ON DUPLICATE KEY UPDATE ...
List<UpdateSet> duplicateUpdateColumns = insert.getDuplicateUpdateSets();

insert select的结构,获取select

INSERT ... SELECT ...
Select select = insert.getSelect();

2.8 PlainSelect

用于表示最常规的那种查询结构,例如:

select...from...join...where...

获取select后面的结构

List<SelectItem<?>> selectItems = plainSelect.getSelectItems();

获取select语句的where结构

Expression where = plainSelect.getWhere();

获取查询的from后的结构(表,子查询等)

FromItem fromItem = plainSelect.getFromItem();

存在连接查询时,获取连接查询(left/right/inner)join后的结构

List<Join> joins = plainSelect.getJoins();

2.9 SetOperationList

用于表示多个select语句通过unionunion all连接在一起的联合查询SQL对象

select...from...union allselect...from...union allselect...from...

将语句拆分,获取构成它的若干select

SetOperationList operationList = (SetOperationList) selectBody;List<Select> selectBodyList = operationList.getSelects();

2.10 ParenthesedSelect

用于表示子查询,被小括号包裹的一个查询结构,例如:

(select....from...) as t

“去括号”,得到一个PlainSelect

ParenthesedSelect parenthesedSelect = (ParenthesedSelect) selectBody;Select select = parenthesedSelect.getSelect();

2.11 FromItem

接口,from后面的SQL结构,ParenthesedSelect,ParenthesedFromItem,Table都是它的实现

FromItem fromItem = plainSelect.getFromItem();if (fromItem instanceof Table) {    }else if (fromItem instanceof ParenthesedSelect) {    }else if (fromItem instanceof ParenthesedFromItem) {    }

2.12 Table

用于表示SQL中的表

2.13 ParenthesedFromItem

小括号包裹的可被查询的结构,但不是子查询,不常用,例如小括号包裹的join:

(tab1 join tab2)

2.14 SelectItem

用于表示select语句中,select和from之间的部分,例如:

select    fun(1, 2) as a,    (select x from ...) as b,    name as c,    exists (...) AS dfrom t
List<SelectItem<?>> selectItems = plainSelect.getSelectItems();selectItems.forEach(selectItem -> {    Expression expression = selectItem.getExpression();    if (expression instanceof Select) {            }    else if (expression instanceof Function) {    }    else if (expression instanceof ExistsExpression) {    }});

2.15 BinaryExpression

泛指比较符号:and or = >= =<,这种结构左右连接着其他结构。EqualsTo,OrExpression,AndExpression都是它的子类。

获取左右两侧的结构:

BinaryExpression expression = (BinaryExpression) obj;Expression left = expression.getLeftExpression();Expression right = expression.getRightExpression();

2.16 InExpression

x in (...)

获取右侧的结构,可能是子查询或(*,*,*...)

InExpression expression = (InExpression) obk;Expression inExpression = expression.getRightExpression();

2.17 ExistsExpression

exists (...)

获取右侧结构

ExistsExpression expression = (ExistsExpression) obj;Expression e = expression.getRightExpression() ;

2.18 NotExpression

not,与其他的配合使用,例如:

not in (...)not exists (...)

获取not后面的结构,会提取出in exists等结构

NotExpression expression = (NotExpression) obj;Expression e = expression.getExpression();

2.19 Parenthesis

代表小括号()括起来的结构

(...)

去括号,拿到括号中的结构:

Parenthesis expression = (Parenthesis) obj;Expression e = expression.getExpression();

2.20 Function

函数结构,通常会获取参数,对参数进行操作

fun()
ExpressionList<?> parameters = function.getParameters();if (parameters != null) {    parameters.forEach(expression -> {        if (expression instanceof Select) {                    }         else if (expression instanceof Function) {                    }     });}

2.21 EqualsTo

=

2.22 OrExpression

or

2.23 AndExpression

and

2.24 Join

SQL中连接查询的join结构,从Select中获得。

获取join后的结构,一般可能是表也可能是子查询

FromItem joinItem = join.getRightItem();

判断是否为隐式内连接

join.isSimple();

判断是内/左/右连接

join.isRight();join.isInner();join.isLeft();

获取join的on条件

Collection<Expression> originOnExpressions = join.getOnExpressions();

改写join的on条件

join.setOnExpressions(onExpressions);

2.25 Column

用于表示SQL中的字段对象,例如从一个Insert对象获取SQL要添加的全部字段:name,age,tenant_id

INSERT INTO t_user (name, age, tenant_id) VALUES ('liming', 15), ('zhaoying', 16)
List<Column> columns = insert.getColumns();

2.26 UpdateSet

UpdateSet是一种类似xx = xx, ...的结构,出现在update的set后面

update user set username = 5 where id = 1 
List<UpdateSet> sets = update.getUpdateSets();

也能在insert语句处理添加的数据冲突的情况时,出现在ON DUPLICATE KEY UPDATE后面

INSERT INTO table_name (col1, col2) VALUES (val1, val2)ON DUPLICATE KEY UPDATE col1 = val3, col2 = col4 + 1;
List<UpdateSet> duplicateUpdateColumns = insert.getDuplicateUpdateSets();

2.27 ExpressionList

Expression列表,本质上是List<Expression>,当insert语句values后面批量跟了多组值,就能得到这种结构。

('liming', 15), ('zhaoying', 16)
Values values = insert.getValues();ExpressionList<Expression> expressions = (ExpressionList<Expression>) values.getExpressions();

2.28 ParenthesedExpressionList

继承自ExpressionList,本质上也是List<Expression>,一种带着括号的Expression结构,例如获取insert语句values后面的值就能得到这种结构

('liming', 15)
Values values = insert.getValues();ExpressionList<Expression> expressions = (ExpressionList<Expression>) values.getExpressions();if (expressions instanceof ParenthesedExpressionList) {    // ParenthesedExpressionList} else {    // ExpressionList}

附:类路径

net.sf.jsqlparser.statement.Statement
net.sf.jsqlparser.statement.select.Select
net.sf.jsqlparser.statement.update.Update
net.sf.jsqlparser.statement.delete.Delete
net.sf.jsqlparser.statement.insert.Insert
net.sf.jsqlparser.schema.Table
net.sf.jsqlparser.expression.Expression
net.sf.jsqlparser.statement.select.ParenthesedSelect
net.sf.jsqlparser.statement.select.SetOperationList
net.sf.jsqlparser.statement.select.SelectItem
net.sf.jsqlparser.expression.BinaryExpression
net.sf.jsqlparser.expression.operators.relational.InExpression
net.sf.jsqlparser.expression.operators.relational.ExistsExpression
net.sf.jsqlparser.expression.NotExpression
net.sf.jsqlparser.expression.Parenthesis
net.sf.jsqlparser.statement.select.ParenthesedFromItem
net.sf.jsqlparser.statement.select.FromItem
net.sf.jsqlparser.expression.Function
net.sf.jsqlparser.expression.operators.relational.EqualsTo
net.sf.jsqlparser.expression.operators.conditional.OrExpression
net.sf.jsqlparser.expression.operators.conditional.AndExpression
net.sf.jsqlparser.statement.select.Join
net.sf.jsqlparser.schema.Column
net.sf.jsqlparser.expression.operators.relational.ExpressionList
net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList

Java实现将数据导出为Word文档

2025年1月10日 00:00

我们在开发一些系统的时候,例如OA系统,经常能遇到将审批单数据导出为word和excel文档的需求,导出为excel是比较简单的,因为excel有单元格来供我们定位数据位置,但是word文档的格式不像表格那样可以轻松的定位,要想将数据导出为一些带有图片和表格的这种结构复杂的word文档该怎样实现呢。

poi-tl [1]是一款可以帮助我们实现这种功能的Java开源项目,它把POI和Freemarker相结合,可以基于我们绘制好的word文档模板来填充数据进去,然后生成新的word文档。poi-tl托管在GitHub:https://github.com/Sayi/poi-tl

例如,我们要生成一个差旅行程单,首先要绘制这样的一个word文档模板,用{{name}}代表姓名进行占位,姓名就是普通文字类型,以此类推。tripList作为渲染行程表格的数据源的名字,是ArrayList集合类型,放在表格的表头,用[from]表示tripList集合中每个元素的from属性的值,渲染到当前行的某一列上,以此类推。最后的三个签署对应的是领导的签名笔迹图片,图片类型要用变量名前多一个@的形式{{@****Pin}}来表示

如果表格中某一列是图片,则表示为[@变量]

模板绘制好以后,开始使用poi-tl工具生成word文档,首先新建maven项目,引入poi-tl的依赖和需要的其他依赖,然后将这个绘制好的word模板文件放在工程的根目录

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <groupId>org.example</groupId>    <artifactId>poi-tl</artifactId>    <version>1.0-SNAPSHOT</version>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>com.deepoove</groupId>            <artifactId>poi-tl</artifactId>            <version>1.12.2</version>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.24</version>            <scope>provided</scope>        </dependency>    </dependencies></project>

然后,新建一个Entity类: org.example.TravelApplyExportVO

package org.example;import com.deepoove.poi.data.PictureRenderData;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.util.ArrayList;import java.util.List;@Data@AllArgsConstructor@NoArgsConstructorpublic class TravelApplyExportVO {    private String no;    private String name;    private String dept;    private String employeeNo;    private String start;    private String end;    private String days;    private String address;    private String reason;    /**     * com.deepoove.poi.data.PictureRenderData 代表图片     */    private PictureRenderData applyPin;    private PictureRenderData bossPin;    private PictureRenderData leaderPin;    private String date;    /**     * 用于渲染表格的集合     */    private List<Route> tripList = new ArrayList<>();    @Data    @AllArgsConstructor    @NoArgsConstructor    public static class Route {        private String from;        private String to;        private String flight;        private String depTime;        private String arrTime;        private String cabin;    }}

新建测试类: org.example.Main,用poi-tl组件基于刚刚绘制的word模板生成一个差旅行程单

package org.example;import com.deepoove.poi.XWPFTemplate;import com.deepoove.poi.config.Configure;import com.deepoove.poi.data.PictureRenderData;import com.deepoove.poi.data.Pictures;import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;import lombok.SneakyThrows;import java.nio.file.Files;import java.nio.file.Paths;import java.util.ArrayList;public class Main {    @SneakyThrows    public static void main(String[] args) {        TravelApplyExportVO vo = new TravelApplyExportVO();        vo.setNo("202500001");        vo.setName("lzj");        vo.setDept("技术部");        vo.setEmployeeNo("00000001");        vo.setStart("2025-01-01");        vo.setEnd("2025-02-01");        vo.setDays("30");        vo.setAddress("中国香港");        vo.setReason("系统维护");        // 在项目根路径读取笔迹图片,并设置大小        PictureRenderData data1 = Pictures.ofBytes(Files.readAllBytes(Paths.get("img2.png")))                .size(120, 60)                .create();        PictureRenderData data2 = Pictures.ofBytes(Files.readAllBytes(Paths.get("img.png")))                .size(120, 60)                .create();        PictureRenderData data3 = Pictures.ofBytes(Files.readAllBytes(Paths.get("img.png")))                .size(120, 60)                .create();        vo.setApplyPin(data1 );        vo.setBossPin( data2);        vo.setLeaderPin( data3);        vo.setDate("2025-01-10");        // 行程List,最终渲染到文档的表格中        vo.setTripList(new ArrayList<TravelApplyExportVO.Route>() {            {                add(new TravelApplyExportVO.Route("BJX","ZQZ","ZH5643","2025-01-01 15:00","2025-01-01 16:00","E"));                add(new TravelApplyExportVO.Route("ZQZ","CDE","JUH6532","2026-01-01 15:00","2025-12-01 16:00","A"));                add(new TravelApplyExportVO.Route("BJX","ZQZ","KJU0954","2027-01-01 15:00","2025-05-01 16:00","Q"));            }        });        LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();        // !!将tripList通过表格来渲染        Configure config = Configure.builder()                .bind("tripList", policy)                .build();        XWPFTemplate template = XWPFTemplate                .compile("模板.docx", config)                .render(vo);        template.writeAndClose(Files.newOutputStream(Paths.get("output.docx")));    }}

然后领导签名笔记图片素材img.png,img2.png也需要放进工程根目录下

都完成后,执行main()方法测试,程序运行结束后,将在根路径生成文件output.docx,打开就是我们想要的效果了。

参考

❌