oracle的批量插入

1、创建表

复制代码
create table ROCKLPMSREPORT.a1(id number, a varchar2(1), b varchar2(1), c varchar2(1), d varchar2(1));

2、批量插入数据

复制代码
insert all 
      into ROCKLPMSREPORT.a1(id, a, b, c, d) values (1, 'a', 'a', 'a', 'a')   
      into ROCKLPMSREPORT.a1(id, a, b, c, d) values (2, 'b', 'b', 'b', 'b')
    select 1 from dual;

3、查询数据

复制代码
select * from ROCKLPMSREPORT.a1;

4、问题

写法和mysql的写法不一样,如果在oracle里执行

复制代码
INSERT INTO user (name, age) VALUES ('张三', 20), ('李四', 20), ('王五', 20)

这样的mysql语法的SQL语句会报错"nested exception is java.sql.SQLSyntaxErrorException: ORA-00933: SQL 命令未正确结束"。

另外,在实际项目中,表字段会有十几个,要插入的数据会有几千条,在mybatis中这样使用oracle的写法,

会报错:

复制代码
at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:39) ~[mybatis-plus-core-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.parser.JsqlParserSupport.parserMulti(JsqlParserSupport.java:73) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.beforePrepare(TenantLineInnerInterceptor.java:85) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:102) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59) ~[mybatis-3.5.13.jar:3.5.13]
	at com.sun.proxy.$Proxy317.prepare(Unknown Source) ~[na:na]
	at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:90) ~[mybatis-3.5.13.jar:3.5.13]
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49) ~[mybatis-3.5.13.jar:3.5.13]
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) ~[mybatis-3.5.13.jar:3.5.13]
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) ~[mybatis-3.5.13.jar:3.5.13]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_152]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_152]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_152]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_152]
	at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49) ~[mybatis-3.5.13.jar:3.5.13]
	at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:106) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59) ~[mybatis-3.5.13.jar:3.5.13]
	at com.sun.proxy.$Proxy316.update(Unknown Source) ~[na:na]
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) ~[mybatis-3.5.13.jar:3.5.13]
	... 167 common frames omitted
Caused by: java.util.concurrent.ExecutionException: net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: "INTO" "INTO"
    at line 3, column 13.

Was expecting one of:

    "("
    "OUTPUT"
    "SET"
    "VALUE"
    "VALUES"
    "WITH"
    <K_SELECT>

	at java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:1.8.0_152]
	at java.util.concurrent.FutureTask.get(FutureTask.java:206) ~[na:1.8.0_152]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:322) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:299) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:274) ~[jsqlparser-4.6.jar:na]
	at com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal.parseStatements(JsqlParserGlobal.java:51) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.parser.JsqlParserSupport.parserMulti(JsqlParserSupport.java:62) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	... 184 common frames omitted
Caused by: net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: "INTO" "INTO"
    at line 3, column 13.

Was expecting one of:

    "("
    "OUTPUT"
    "SET"
    "VALUE"
    "VALUES"
    "WITH"
    <K_SELECT>

	at net.sf.jsqlparser.parser.CCJSqlParser.generateParseException(CCJSqlParser.java:38866) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParser.jj_consume_token(CCJSqlParser.java:38696) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParser.Insert(CCJSqlParser.java:3332) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParser.SingleStatement(CCJSqlParser.java:381) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParser.Statements(CCJSqlParser.java:954) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil$2.call(CCJSqlParserUtil.java:317) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil$2.call(CCJSqlParserUtil.java:314) ~[jsqlparser-4.6.jar:na]
	at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) ~[na:1.8.0_152]
	at java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:1.8.0_152]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_152]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_152]
	... 1 common frames omitted

该怎么解决呢?豆包给出的建议是

一、采用$符号,绕过 MyBatis 的 SQL 解析(推荐,最小改动)

关键修改说明

  1. 添加statementType="STATEMENT":跳过 MyBatis 的 SQL 解析,直接执行原生 SQL。

  2. 参数占位符从#{}改为${}:因为STATEMENT模式不支持预编译占位符#{},需注意防止 SQL 注入(确保传入的参数是可信的业务数据)。

    INSERT ALL INTO ROCKLPMSREPORT.PROCESS_PARAMETERS ( id, parameters_name, instrument_number, season, operating_parameters_up, operating_parameters_low, low_limit_alarm_value, high_limit_alarm_value, subordinate_code, file_url, change_reason, describe, create_date, update_user, update_date, create_user, mark, state, combination_identification, table_id, unit, region, sort ) VALUES ( ${item.id}, ${item.parametersName}, ${item.instrumentNumber}, ${item.season}, ${item.operatingParametersUp}, ${item.operatingParametersLow}, ${item.lowLimitAlarmValue}, ${item.highLimitAlarmValue}, ${item.subordinateCode}, ${item.fileUrl}, ${item.changeReason}, ${item.describe}, ${item.createDate}, ${item.updateUser}, ${item.updateDate}, ${item.createUser}, ${item.mark}, ${item.state}, ${item.combinationIdentification}, ${item.tableId}, ${item.unit}, ${item.region},${item.sort} ) SELECT 1 FROM dual

关键修改说明

  1. 添加statementType="STATEMENT":跳过 MyBatis 的 SQL 解析,直接执行原生 SQL。
  2. 参数占位符从#{}改为${}:因为STATEMENT模式不支持预编译占位符#{},需注意防止 SQL 注入(确保传入的参数是可信的业务数据)。

我在项目中使用的代码如下:

复制代码
<insert id="insertBatch2" parameterType="java.util.List" statementType="STATEMENT">
        INSERT ALL
        <foreach collection="list" item="item" separator=" ">
            INTO ROCKLPMSREPORT.PROCESS_PARAMETERS (
            id, parameters_name, instrument_number,
            season, operating_parameters_up, operating_parameters_low,
            low_limit_alarm_value, high_limit_alarm_value, subordinate_code,
            file_url, change_reason, describe,
            create_date, update_user, update_date,
            create_user, mark, state,
            combination_identification, table_id, unit,
            region, sort
            )
            VALUES (
            '${item.id}', -- 数值类型无需单引号
            ${item.parametersName != null ? "'" + item.parametersName + "'" : "null"},
            ${item.instrumentNumber != null ? "'" + item.instrumentNumber + "'" : "null"},
            ${item.season != null ? "'" + item.season + "'" : "null"},
            ${item.operatingParametersUp != null ? item.operatingParametersUp : "null"},
            ${item.operatingParametersLow != null ? item.operatingParametersLow : "null"},
            ${item.lowLimitAlarmValue != null ? item.lowLimitAlarmValue : "null"},
            ${item.highLimitAlarmValue != null ? item.highLimitAlarmValue : "null"},
            ${item.subordinateCode != null ? "'" + item.subordinateCode + "'" : "null"},
            ${item.fileUrl != null ? "'" + item.fileUrl + "'" : "null"},
            ${item.changeReason != null ? "'" + item.changeReason + "'" : "null"},
            ${item.describe != null ? "'" + item.describe + "'" : "null"},
            SYSDATE,
            ${item.updateUser != null ? "'" + item.updateUser + "'" : "null"},
            SYSDATE,
            ${item.createUser != null ? "'" + item.createUser + "'" : "null"},
            ${item.mark != null ?  + item.mark : "null"},
            ${item.state != null ? item.state : "null"},
            ${item.combinationIdentification != null ? "'" + item.combinationIdentification + "'" : "null"},
            ${item.tableId != null ? "'" + item.tableId + "'" : "null"},
            ${item.unit != null ? "'" + item.unit + "'" : "null"},
            ${item.region != null ? "'" + item.region + "'" : "null"},
            ${item.sort != null ? item.sort : "null"}
            )
        </foreach>
        SELECT 1 FROM dual
    </insert>

但是在java的mybatis中还是报错:

复制代码
Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Failed to process, Error SQL: INSERT ALL
          
            INTO ROCKLPMSREPORT.PROCESS_PARAMETERS (
            id, parameters_name, instrument_number,
            season, operating_parameters_up, operating_parameters_low,
            low_limit_alarm_value, high_limit_alarm_value, subordinate_code,
            file_url, change_reason, describe,
            create_date, update_user, update_date,
            create_user, mark, state,
            combination_identification, table_id, unit,
            region, sort
            )
            VALUES (
            'de07e839134a40b6bcd5dfabd01ba0a3', -- 数值类型无需单引号
            '污油罐压力',
            'PG01004',
            '夏季',
            0.08,
            0.02,
            null,
            null,
            '65115613',
            '[]',
            null,
            null,
            SYSDATE,
            '何明',
            SYSDATE,
            '何明',
            0,
            1,
            'cfc57d7ad0bb4e91a3772a2a967e6339',
            '80044eb6c7df4fcf909a10b6267dbd97',
            'Mpa',
            '原料气过滤计量系统',
            1
            )
           
            INTO ROCKLPMSREPORT.PROCESS_PARAMETERS (
            id, parameters_name, instrument_number,
            season, operating_parameters_up, operating_parameters_low,
            low_limit_alarm_value, high_limit_alarm_value, subordinate_code,
            file_url, change_reason, describe,
            create_date, update_user, update_date,
            create_user, mark, state,
            combination_identification, table_id, unit,
            region, sort
            )
            VALUES (
            '5886148f46e74bedbd7c6781600393e2', -- 数值类型无需单引号
            '污油罐压力',
            'PG01004',
            '冬季',
            0.08,
            0.02,
            null,
            null,
            '65115613',
            '[]',
            null,
            null,
            SYSDATE,
            '何明',
            SYSDATE,
            '何明',
            0,
            1,
            'cfc57d7ad0bb4e91a3772a2a967e6339',
            '80044eb6c7df4fcf909a10b6267dbd97',
            'Mpa',
            '原料气过滤计量系统',
            2
            )
         
        SELECT 1 FROM dual
	at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:39) ~[mybatis-plus-core-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.parser.JsqlParserSupport.parserMulti(JsqlParserSupport.java:73) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor.beforePrepare(TenantLineInnerInterceptor.java:85) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:102) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59) ~[mybatis-3.5.13.jar:3.5.13]
	at com.sun.proxy.$Proxy317.prepare(Unknown Source) ~[na:na]
	at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:90) ~[mybatis-3.5.13.jar:3.5.13]
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49) ~[mybatis-3.5.13.jar:3.5.13]
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117) ~[mybatis-3.5.13.jar:3.5.13]
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76) ~[mybatis-3.5.13.jar:3.5.13]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_152]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_152]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_152]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_152]
	at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49) ~[mybatis-3.5.13.jar:3.5.13]
	at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:106) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:59) ~[mybatis-3.5.13.jar:3.5.13]
	at com.sun.proxy.$Proxy316.update(Unknown Source) ~[na:na]
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197) ~[mybatis-3.5.13.jar:3.5.13]
	... 167 common frames omitted
Caused by: java.util.concurrent.ExecutionException: net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: "INTO" "INTO"
    at line 3, column 13.

Was expecting one of:

    "("
    "OUTPUT"
    "SET"
    "VALUE"
    "VALUES"
    "WITH"
    <K_SELECT>

	at java.util.concurrent.FutureTask.report(FutureTask.java:122) ~[na:1.8.0_152]
	at java.util.concurrent.FutureTask.get(FutureTask.java:206) ~[na:1.8.0_152]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:322) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:299) ~[jsqlparser-4.6.jar:na]
	at net.sf.jsqlparser.parser.CCJSqlParserUtil.parseStatements(CCJSqlParserUtil.java:274) ~[jsqlparser-4.6.jar:na]
	at com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal.parseStatements(JsqlParserGlobal.java:51) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	at com.baomidou.mybatisplus.extension.parser.JsqlParserSupport.parserMulti(JsqlParserSupport.java:62) ~[mybatis-plus-extension-3.5.4.jar:3.5.4]
	... 184 common frames omitted
Caused by: net.sf.jsqlparser.parser.ParseException: Encountered unexpected token: "INTO" "INTO"
    at line 3, column 13.

但是这段SQL语句放在dbeaver里面执行是正常的:

二、改用 Oracle 批量插入的兼容写法(无 SQL 注入风险)

放弃INSERT ALL,改用UNION ALL拼接查询的方式实现批量插入,这种写法兼容所有 MyBatis 版本,且支持预编译参数#{},更安全。

复制代码
<insert id="insertBatch" parameterType="java.util.List">
    INSERT INTO ROCKLPMSREPORT.PROCESS_PARAMETERS (
        id, parameters_name, instrument_number,
        season, operating_parameters_up, operating_parameters_low,
        low_limit_alarm_value, high_limit_alarm_value, subordinate_code,
        file_url, change_reason, describe,
        create_date, update_user, update_date,
        create_user, mark, state,
        combination_identification, table_id, unit,
        region, sort
    )
    SELECT * FROM (
        <foreach collection="list" item="item" separator="UNION ALL">
            SELECT
                #{item.id}, #{item.parametersName}, #{item.instrumentNumber},
                #{item.season}, #{item.operatingParametersUp}, #{item.operatingParametersLow},
                #{item.lowLimitAlarmValue}, #{item.highLimitAlarmValue}, #{item.subordinateCode},
                #{item.fileUrl}, #{item.changeReason}, #{item.describe},
                #{item.createDate}, #{item.updateUser}, #{item.updateDate},
                #{item.createUser}, #{item.mark}, #{item.state},
                #{item.combinationIdentification}, #{item.tableId}, #{item.unit},
                #{item.region},#{item.sort}
            FROM dual
        </foreach>
    )
</insert>

方案优势

  • 支持#{}预编译参数,避免 SQL 注入风险;
  • 兼容所有 MyBatis 版本和 Oracle 版本;
  • 执行效率与INSERT ALL基本一致。

额外注意事项

  1. 确保传入的list不为空:建议在调用该方法前增加非空校验,否则会生成无效 SQL;
  2. 批量插入数量限制:Oracle 单次 SQL 语句长度有限,若批量插入数据量过大(如超过 1000 条),建议分批次插入;
  3. 字段类型匹配:确保#{}中的参数类型与数据库字段类型一致(如日期类型需传入Date对象,而非字符串)。

然后还是遇到问题了:

复制代码
SQL 错误 [1790] [42000]: ORA-01790: 表达式必须具有与对应表达式相同的数据类型



Error position: line: 71 pos: 1381

查了资料,在mapper.xml里面写代码,可以设置每个字段的类型。最终成功搞好了oracle批量插入的问题。mapper.xml里的代码如下:

复制代码
<insert id="insertBatch" parameterType="java.util.List">
        INSERT INTO ROCKLPMSREPORT.PROCESS_PARAMETERS (
        id, parameters_name, instrument_number,
        season, operating_parameters_up, operating_parameters_low,
        low_limit_alarm_value, high_limit_alarm_value, subordinate_code,
         change_reason, describe,
         update_user,
        create_user, mark, state,
        combination_identification, table_id, unit,
        region, sort
        )
        SELECT
        id,
        parameters_name,
        instrument_number,
        season,
        operating_parameters_up,
        operating_parameters_low,
        low_limit_alarm_value,
        high_limit_alarm_value,
        subordinate_code,
        change_reason,
        DESCRIBE,
        update_user,
        create_user,
        mark,
        state,
        combination_identification,
        table_id,
        unit,
        region,
        sort
            FROM (
        <foreach collection="list" item="item" separator="UNION ">
            SELECT
            #{item.id, jdbcType=VARCHAR} AS id, #{item.parametersName, jdbcType=VARCHAR} AS parameters_name, #{item.instrumentNumber, jdbcType=VARCHAR} AS instrument_number,
            #{item.season, jdbcType=VARCHAR} AS season, #{item.operatingParametersUp, jdbcType=DECIMAL} AS operating_parameters_up, #{item.operatingParametersLow, jdbcType=DECIMAL} AS operating_parameters_low,
            #{item.lowLimitAlarmValue, jdbcType=DECIMAL} AS low_limit_alarm_value, #{item.highLimitAlarmValue, jdbcType=DECIMAL} AS high_limit_alarm_value, #{item.subordinateCode, jdbcType=VARCHAR} AS subordinate_code,
             #{item.changeReason, jdbcType=VARCHAR} AS change_reason, #{item.describe, jdbcType=VARCHAR} AS DESCRIBE,
             #{item.updateUser, jdbcType=VARCHAR} AS update_user,
            #{item.createUser, jdbcType=VARCHAR} AS create_user, #{item.mark, jdbcType=BIGINT} AS mark, #{item.state, jdbcType=BIGINT} AS state,
            #{item.combinationIdentification, jdbcType=VARCHAR} AS combination_identification, #{item.tableId, jdbcType=VARCHAR} AS table_id, #{item.unit, jdbcType=VARCHAR} AS unit,
            #{item.region, jdbcType=VARCHAR} AS region,#{item.sort, jdbcType=INTEGER} AS sort
            FROM dual
        </foreach>
        )
    </insert>

最后虽然执行成功了,但是1千1百多条数据,插入到表里,花了9秒多,每500条插入一次,第一次花了5秒多,第二次花了3秒多,第三次花了0.5秒,还是不够快。

一次插入1条的情况,1千1百多条数据,也只用了10秒左右。

相关推荐
ClouGence2 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle
曹牧2 天前
Oracle EXPLAIN PLAN
数据库·oracle
贤时间2 天前
codex 助力oracle ebs 开发
数据库·oracle
秉承初心2 天前
PostgreSQL 数据性能瓶颈突破实战
数据库·postgresql·oracle
Curvatureflight3 天前
MySQL 深分页越来越慢?从 LIMIT OFFSET 改成游标分页
数据库·oracle
XZ-0700013 天前
MySQL事务
数据库·mysql·oracle
tiancaijiben3 天前
阿里云函数计算FC如何实现网站的定时任务与自动化
数据库·oracle·dba
xfhuangfu3 天前
Oracle 19c 多租户体系架构介绍
数据库·oracle·架构
杨云龙UP3 天前
Spotlight 接入 Oracle 数据库监控操作指南 2026-06-16
数据库·oracle·性能监控·预警·阈值·spotlight·瓶颈分析
unique3 天前
AI Coding 采集方案探索
jvm·人工智能·oracle