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 解析(推荐,最小改动)
关键修改说明:
-
添加
statementType="STATEMENT":跳过 MyBatis 的 SQL 解析,直接执行原生 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>#{}改为${}:因为STATEMENT模式不支持预编译占位符#{},需注意防止 SQL 注入(确保传入的参数是可信的业务数据)。
关键修改说明:
- 添加
statementType="STATEMENT":跳过 MyBatis 的 SQL 解析,直接执行原生 SQL。 - 参数占位符从
#{}改为${}:因为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基本一致。
额外注意事项
- 确保传入的
list不为空:建议在调用该方法前增加非空校验,否则会生成无效 SQL; - 批量插入数量限制:Oracle 单次 SQL 语句长度有限,若批量插入数据量过大(如超过 1000 条),建议分批次插入;
- 字段类型匹配:确保
#{}中的参数类型与数据库字段类型一致(如日期类型需传入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秒左右。