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 id="insertBatch" 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}, ${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} ) </foreach> SELECT 1 FROM dual </insert>

关键修改说明

  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秒左右。

相关推荐
卤炖阑尾炎4 小时前
MySQL 数据库初体验:从基础概念到服务部署全攻略
数据库·mysql·oracle
pangares5 小时前
MySQL四种备份表的方式
mysql·adb·oracle
@insist1236 小时前
软件设计师-数据库核心:事务 ACID 特性、并发控制与备份恢复技术全解
数据库·oracle·软考·软件设计师·软件水平考试
正在走向自律6 小时前
Oracle替换工程实践深度解析——从技术落地到成本优化的全维度攻坚
数据库·oracle·kingbasees·数据库替换
杨云龙UP6 小时前
Oracle DG / ADG日常巡检操作指南
linux·运维·服务器·数据库·ubuntu·oracle
执笔画流年呀6 小时前
简单使用MySQL
数据库·mysql·oracle
云贝教育-郑老师6 小时前
【5分钟学会部署OpenTenBase V5.0 for CentOS 7.8】
数据库·oracle
kiku18188 小时前
Mysql数据库
数据库·mysql·oracle
oioihoii9 小时前
防患未然,金仓数据库SQL防火墙筑牢数据安全“第一道门”
数据库·sql·oracle