seata要支持Oracle批量插入的语法了吗?

引言

大家好!大家都知道seata是分布式事务的解决方案之一,它不仅使您轻松快捷地通过注解就可以实现分布式事务,而且还有AT,XA,TCC,Saga四种模式可以选择,可以为您的项目提供最适合的方案。随着seata不断发展,它也在不断地覆盖更多的业务场景。在未发布的2.6.0版本中,seata就可以支持Oracle的批量插入语法了!接下来让我们解析一下它到底是怎么做到的吧❤️

问题起源

现在有一个SQL语句:insert all into a (id) values(1) into a (id) values(2) SELECT 1 FROM DUAL 进入seata就会被解析成OracleMultiInsertStatement这个对象,但是在原来seata的代码中,DruidSQLRecognizerFactoryImpl这个工厂实现类,并不支持解析获取到OracleMultiInsertStatement这个对象(只支持最通用的增删改查)

解决步骤

问题分析

要支持这个语法,我们就必须先明白,Oracle Insert Batch语法到底是什么样的?以及我们seata到底要支持这个语法到哪一步?没有全支持的原因到底是什么?这三个问题我们会在下面一一解答。

什么是 Oracle Multi-Insert?

Oracle 支持一种特殊的 INSERT 语法,可以在一条语句中向多个表插入数据,根据条件决定插入到哪个(或哪些)表。例如下面这个SQL语句:

什么是INSERT ALL / FIRST?

1.INSERT FIRST ,它根据条件,将一条源数据插入到多个目标表中的"第一个满足条件"的表中(仅插入一次),从上到下依次判断 WHEN 条件一旦某个条件为真,就执行对应的 INTO,然后跳过后续所有分支每行源数据最多只被插入一次 (即使多个条件都满足),ELSE 是可选的;如果没有 ELSE 且所有条件都不满足,则该行被丢弃。

2.INSERT ALLINSERT FIRST相反,满足多个条件就插入多次

ps:这种语法在标准 SQL 中不存在,是 Oracle 的扩展。

java 复制代码
public class OracleMultiInsertStatement extends OracleStatementImpl

这是一个 AST(抽象语法树)节点类 ,用于表示上述 INSERT ALL / FIRST 语句

核心字段解释

字段 类型 说明
option Option enum (ALL, FIRST) 表示是 INSERT ALL 还是 INSERT FIRST
subQuery SQLSelect 最后的 SELECT ... 子查询,提供数据源
entries List<Entry> 插入规则列表,每个 Entry 是一个插入分支(可能是条件分支或无条件插入)
hints List<SQLHint> Oracle 的优化器提示(如 /*+ APPEND */

内部类详解

1.Entry接口

java 复制代码
public static interface Entry extends OracleSQLObject

这是 entries 列表中元素的通用接口。目前有两个实现:

  • ConditionalInsertClause(带 WHEN/ELSE 的条件插入)
  • InsertIntoClause(无条件直接插入)

这样写是为了可以支持混合写法

  1. ConditionalInsertClauseItem

代表一个 WHEN ... THEN ... 分支。

  • when: SQLExpr → 条件表达式(如 dept = 'IT'
  • then: InsertIntoClause → 满足条件时要执行的 INSERT INTO ...

要记住的是,ConditionalInsertClause包含了ConditionalInsertClauseItem全部。

seata支持该语法到哪一步呢?

目前,seata只支持同一个表的多插入(列一致),而且也不支持条件插入与Insert First语法。 那这样读者就会有疑问了,那这样岂不是和SQL多插入语法差不多吗?是的,你的猜测不错。

对于上述不支持的情况,seata会抛出NotSupportYetException这个自定义异常给上游应用层捕获,代表seata不支持这种情况。

没有全支持Oracle批量插入的语法原因是什么?

首先我们不是为了支持而支持,seata是一个分布式事务中间件,如果是涉及到不同表的更改,我们更加推荐在分支事务来进行更改,而不是在同一个SQL语句里面进行更改。拆成多个单表 SQL,在多个分支事务中执行,是更安全、可回滚的做法。

关于条件插入和Insert First语法,我们更推荐是在服务层处理条件逻辑,而不是留到数据层再进行处理。数据库应聚焦于高效存储与查询,而非复杂业务逻辑

这样做的核心考量包括:保障分布式事务的可靠性、提升代码可维护性、避免数据库方言绑定,以及确保业务逻辑的统一管控,而不仅仅是执行效率。

支持步骤一: OracleMultiInsertRecognizer

ok,讲完了定义与原因,接下来就应该来讲如何实现了这种简化的Oracle批量插入语法了。

我们首先应该实现OracleMultiInsertRecognizer这个识别器类,这里运用了策略模式,模板方法模式,单一职责等多种设计模式与原则。

java 复制代码
public class OracleMultiInsertRecognizer extends BaseOracleRecognizer implements SQLInsertRecognizer

设计模式

策略模式的体现

  • OracleMultiInsertRecognizer 实现了 SQLInsertRecognizer 接口。
  • Seata 在运行时会根据 SQL 的 AST 类型(如 SQLInsertStatementOracleMultiInsertStatement 等)动态选择对应的 Recognizer 实现类

模板方法模式的体现

  • 继承自 BaseOracleRecognizer(基类),该基类定义了通用解析流程,子类可以直接继承复用抽象父类的方法。

单一职责原则 + 接口隔离

  • SQLInsertRecognizer 接口只定义 INSERT 相关的方法(getTableName, getInsertColumns, getInsertRows 等);
  • OracleMultiInsertRecognizer 只负责识别一种特定语法,不处理 UPDATE/DELETE;
  • 校验逻辑(validateSingleTableConsistency)与数据提取逻辑(getInsertRows)分离。

具体逻辑

这里挑几个重要的讲即可,剩下大家去看源码即可。

  1. validateSingleTableConsistency()

这是最核心的校验逻辑

  • 遍历所有 Entry(每个 INTO ... 子句);

  • 如果遇到 ConditionalInsertClause(即 WHEN...THEN)→ 抛出异常,说明并不支持这种语法;

  • 对每个 InsertIntoClause

    • 提取表名(通过 OracleOutputVisitor 渲染 AST 得到字符串);
    • 提取列名(只支持 SQLIdentifierExpr,即普通列名);
    • 与第一个子句对比:表名必须相同(忽略大小写),列列表必须完全一致
  • 成功后缓存 validatedTableNamevalidatedColumns

2.getInsertRows(...)

用于提取所有要插入的具体值(用于生成 undo log)

  • 遍历每个 InsertIntoClauseVALUES 列表;

  • 对每个表达式做类型判断:

    • SQLNullExpr → 转为 Null.get()(Seata 内部空值表示)
    • SQLValuableExpr(如字符串、数字)→ 取 .getValue()
    • SQLVariantRefExpr(如 :var 绑定变量)→ 保留变量名
    • SQLMethodInvokeExpr(如 SYSDATE)→ 标记为 SqlMethodExpr.get()
    • SQLSequenceExpr(如 seq.nextval)→ 封装为 SqlSequenceExpr
    • 其他复杂表达式 → 若该位置是主键,则报错;否则标记为 NotPlaceholderExpr.get()

支持步骤二:OracleOperateRecognizerHolder

这个容器可以根据ast语法的不同,从而返回不同的识别器,有策略模式的思想在里面。 我们在这新添一个可以返回我们第一步做的批量插入的识别器。

java 复制代码
public SQLRecognizer getMultiInsertRecognizer(String sql, SQLStatement ast) {
    return new OracleMultiInsertRecognizer(sql, ast);
}

支持步骤三:DruidSQLRecognizerFactoryImpl

当在druid识别器工厂类识别到相关的sqlStatement,我们就可以对应入座,不同的sqlStatement分配不同的识别器

java 复制代码
else if (sqlStatement instanceof OracleMultiInsertStatement) {
    OracleMultiInsertStatement stmt = (OracleMultiInsertStatement) sqlStatement;
    if (stmt.getOption() == OracleMultiInsertStatement.Option.FIRST) {
        throw new NotSupportYetException("INSERT FIRST not supported yet");
    }
    // Use specialized methods to handle Oracle bulk inserts
    recognizer =
            ((OracleOperateRecognizerHolder) recognizerHolder).getMultiInsertRecognizer(sql, sqlStatement);
}

总结

那么以上就是seata将要支持的oracle批量插入的解析了,后面我将更新更多有关于seata的解析,喜欢的可以点个关注,谢谢你们

相关推荐
Lisonseekpan2 小时前
IntelliJ IDEA 快捷键全解析与高效使用指南
java·ide·后端·intellij-idea
SeaTunnel2 小时前
结项报告完整版:Apache SeaTunnel 支持 Flink 引擎 Schema Evolution 功能
java·大数据·flink·开源·seatunnel
q***71853 小时前
常见的 Spring 项目目录结构
java·后端·spring
神的孩子都在歌唱3 小时前
es开源小工具 -- 分析器功能
大数据·elasticsearch·开源
cxr8283 小时前
深度解析顶级 Doc Agent System Prompt 的架构与实践
网络·人工智能·架构·prompt·ai智能体·ai赋能·上下文工程
CoderJia程序员甲3 小时前
GitHub 热榜项目 - 日榜(2025-11-10)
ai·开源·llm·github
IT_陈寒3 小时前
Java 17实战:我从老旧Spring项目迁移中总结的7个关键避坑点
前端·人工智能·后端
q***06294 小时前
环境安装与配置:全面了解 Go 语言的安装与设置
开发语言·后端·golang