mybatis使用foreach批量insert异常的解决办法

本文解决了一个在使用MyBatis框架进行批量插入操作时遇到的SQL语法错误问题,通过调整数据库连接配置,成功解决了因多条SQL语句拼接引起的异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

异常

org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO t_user_role(userid,roleid)VALUES(1,3)
		 ; 
			INSERT INTO t_user_ro' at line 3
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: INSERT INTO t_user_role(userid,roleid)VALUES(?,?)    ;     INSERT INTO t_user_role(userid,roleid)VALUES(?,?)    ;     INSERT INTO t_user_role(userid,roleid)VALUES(?,?)
### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO t_user_role(userid,roleid)VALUES(1,3)
		 ; 
			INSERT INTO t_user_ro' at line 3
; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO t_user_role(userid,roleid)VALUES(1,3)
		 ; 
			INSERT INTO t_user_ro' at line 3
	at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:231)
	at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:73)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:371)
	at com.sun.proxy.$Proxy13.insert(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.insert(SqlSessionTemplate.java:240)
	at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:51)
	at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52)
	at com.sun.proxy.$Proxy14.addRoles(Unknown Source)
	at com.atguigu.atcrowdfunding.manager.service.impl.UserServiceImpl.addRoles(UserServiceImpl.java:139)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
	at com.sun.proxy.$Proxy17.addRoles(Unknown Source)
	at com.atguigu.atcrowdfunding.manager.controller.UserController.doAddRoles(UserController.java:271)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:175)
	at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:446)
	at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:434)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:945)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:876)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:931)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:833)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:807)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:673)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1526)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1482)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Unknown Source)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO t_user_role(userid,roleid)VALUES(1,3)
		 ; 
			INSERT INTO t_user_ro' at line 3
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
	at java.lang.reflect.Constructor.newInstance(Unknown Source)
	at com.mysql.jdbc.Util.handleNewInstance(Util.java:404)
	at com.mysql.jdbc.Util.getInstance(Util.java:387)
	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:941)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3870)
	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3806)
	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2470)
	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2617)
	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2550)
	at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861)
	at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:1192)
	at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.execute(NewProxyPreparedStatement.java:823)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.apache.ibatis.logging.jdbc.PreparedStatementLogger.invoke(PreparedStatementLogger.java:62)
	at com.sun.proxy.$Proxy24.execute(Unknown Source)
	at org.apache.ibatis.executor.statement.PreparedStatementHandler.update(PreparedStatementHandler.java:44)
	at org.apache.ibatis.executor.statement.RoutingStatementHandler.update(RoutingStatementHandler.java:69)
	at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:48)
	at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:105)
	at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:71)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:152)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:141)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:358)
	... 65 more

异常分析

mapper.xml导致的错误地方
  <insert id="addRoles">
		<foreach collection="data.ids" item="id" separator=";">
			INSERT INTO t_user_role(userid,roleid)VALUES(#{userId},#{id})
		</foreach>
  </insert>

异常中说

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'INSERT INTO t_user_role(userid,roleid)VALUES(1,3)

首先觉得是 sql语句的问题,但是用sqlyog测试发现没有问题
后来发现是数据库对多个语句拼在一起的操作不支持问题

解决办法

在jdbcUrl中加入allowMultiQueries=true即可解决

jdbc.url=jdbc:mysql://localhost:3306/atcrowdfunding?allowMultiQueries=true&rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8
<think>我们正在讨论的是数据库操作中的两种插入方式:普通的单行插入(INSERT INTO)和批量插入(BATCH INSERT)。用户询问两者的区别,我将从多个维度进行对比。 首先需要明确:在标准SQL中,并没有`BATCH INSERT`这个关键字。批量插入通常是通过以下方式实现的: 1. 使用多行VALUES子句(如`VALUES (...), (...), ...`) 2. 通过程序实现批处理(如JDBC的addBatch/executeBatch) 3. 数据库特有的批量加载工具(如MySQL的LOAD DATA INFILE) 下面详细对比两者的区别: ### 1. 语法差异 - **普通INSERT**:每次插入一行 ```sql INSERT INTO table (col1, col2) VALUES (val1, val2); ``` - **批量INSERT**:单条语句插入多行(以MySQL为例) ```sql INSERT INTO table (col1, col2) VALUES (val1, val2), (val3, val4), ...; -- 这是最常用的批量插入形式[^1] ``` ### 2. 性能差异(核心区别) | 指标 | 普通INSERT | 批量INSERT | |--------------------|-------------------------------|--------------------------------| | 网络开销 | N次请求带来N倍网络延迟 | 1次请求完成N行插入 | | 事务开销 | 自动提交时产生N次事务日志写入 | 单事务日志记录多行操作 | | 解析优化开销 | 重复解析N次相似语句 | 仅解析1次 | | 典型适用场景 | 单条数据插入 | 数据迁移、日志批量入库等 | > **关键数据**:MySQL官方测试表明,批量插入万行数据时,逐行插入耗时约为批量插入的**50倍**以上[^3]。 ### 3. 编程实现差异(以Java+MyBatis为例) - **普通INSERT**循环执行: ```java for(Data d : dataList){ mapper.insert(d); // 每次执行一个INSERT } ``` - **批量INSERT**的三种实现: - **方案1**:MyBatisforeach动态SQL(适用于小批量) ```xml <insert id="batchInsert"> INSERT INTO table (col1, col2) VALUES <foreach item="item" collection="list" separator=","> (#{item.val1}, #{item.val2}) </foreach> </insert> ``` - **方案2**:JDBC批处理模式(推荐中等批量) ```java SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); try { DataMapper mapper = session.getMapper(DataMapper.class); for(Data d : dataList){ mapper.insert(d); // 加入批处理队列 } session.commit(); // 统一提交 } finally { session.close(); } ``` - **方案3**:数据库特有工具(如Oracle的SQL*Loader) ### 4. 事务一致性处理 - **普通INSERT**:默认自动提交时,每条插入独立事务 - **批量INSERT**: - 显式事务包裹可保证原子性: ```sql START TRANSACTION; INSERT INTO ... VALUES (...), (...); -- 批量插入 COMMIT; ``` - 部分失败时需注意: - MySQL默认会中断并回滚整个批量操作 - 添加`IGNORE`关键字可跳过错误行继续执行: ```sql INSERT IGNORE INTO table ... -- 忽略重复键等错误[^1] ``` ### 5. 数据库支持差异 | 数据库 | 批量INSERT支持方式 | |--------------|---------------------------------------------| | MySQL | 多VALUES语法(上限受max_allowed_packet限制)| | Oracle | 推荐使用`INSERT ALL`或FORALL(PL/SQL) | | PostgreSQL | 与MySQL语法兼容 | | SQL Server | 支持多VALUES语法,另有BULK INSERT命令 | > **Oracle特例**:MyBatis中需用`UNION ALL`实现批量插入[^2]: > ```xml > <insert id="batchInsert"> > INSERT INTO table (col1, col2) > <foreach item="item" collection="list" separator="union all"> > SELECT #{item.val1}, #{item.val2} FROM dual > </foreach> > </insert> > ``` ### 使用建议 1. **小数据量**(<100行):推荐多VALUES语法 2. **中等数据量**(100-5000行):使用JDBC批处理 3. **大数据量**(>5000行): - MySQL:`LOAD DATA INFILE` - Oracle:SQL*Loader - PostgreSQL:`COPY`命令 ### 风险提示 - **SQL注入**:动态拼接SQL时需严格参数化 - **内存溢出**:超大批量操作需分批次提交 - **锁竞争**:长时间批量插入可能阻塞查询 §§相关问题§§ 1. 如何确定不同数据库批量插入的最佳批处理大小? 2. MyBatis批量插入时如何获取自动生成的主键? 3. 使用JDBC批处理时如何优化内存占用? 4. 数据库事务隔离级别对批量插入有何影响?</think>### Batch InsertInsert Into 的区别详解 在数据库操作中,`INSERT INTO` 是标准的单行插入语法,而 **Batch Insert批量插入)是一种高效插入多行数据的技术**,两者核心区别如下: --- #### 1. **语法结构不同** - **普通 `INSERT INTO`** 每次仅插入单行数据: ```sql INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'); ``` - **Batch Insert** 通过扩展 `VALUES` 子句插入多行(标准 SQL 语法): ```sql INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com'), ('Bob', 'bob@example.com'), -- 多行数据用逗号分隔 ('Charlie', 'charlie@example.com'); ``` > 📌 **关键区别**:Batch Insert 的 `VALUES` 后支持多组数据,单条语句完成多行插入[^1]。 --- #### 2. **性能差异显著** | 指标 | 普通 INSERT INTO | Batch Insert | |--------------------|-----------------------|-----------------------| | **网络开销** | N 次请求(N=行数) | 1 次请求 | | **事务日志写入** | N 次日志记录 | 1 次日志记录 | | **10,000行耗时** | ~12秒 | ~0.3秒(快40倍以上) | | **适用场景** | 单条数据插入 | 数据迁移/日志批量入库 | > 💡 **原理**:Batch Insert 减少网络往返和事务日志写入次数,大幅提升效率[^3]。 --- #### 3. **编程实现方式不同** - **MyBatis 中的 Batch Insert 实现** 需使用 `SqlSession` 的批处理模式: ```java // 开启批处理模式 SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); try { UserMapper mapper = session.getMapper(UserMapper.class); for (User user : userList) { mapper.insert(user); // 加入批处理队列 } session.commit(); // 统一提交 } finally { session.close(); } ``` 普通 `INSERT INTO` 会立即提交每条语句[^3][^4]。 - **动态 SQL 实现(Oracle 示例)** ```xml <insert id="batchInsert"> INSERT INTO users (name, email) <foreach item="item" collection="list" separator="union all"> SELECT #{item.name}, #{item.email} FROM dual </foreach> </insert> ``` 普通插入无需复杂循环[^2]。 --- #### 4. **事务与错误处理** - **事务控制** - Batch Insert:通常包裹在单事务中(`START TRANSACTION; ... COMMIT;`),全成功或全回滚。 - 普通 INSERT:默认自动提交(每条语句独立事务)。 - **错误处理** - Batch Insert:单行错误可能导致整个批次失败(MySQL 可通过 `INSERT IGNORE` 跳过错误行)。 - 普通 INSERT:单行失败不影响其他行。 --- #### 5. **数据库兼容性差异** | 数据库 | Batch Insert 支持方式 | |--------------|------------------------------------------| | **MySQL** | 多 VALUES 语法(`VALUES (...), (...)`) | | **Oracle** | `INSERT ALL` 或 `UNION ALL` 子查询[^2] | | **PostgreSQL**| 同 MySQL 多 VALUES 语法 | | **SQL Server**| 多 VALUES 语法或 `BULK INSERT` 命令 | > ⚠️ 注意:Oracle 需用特殊语法(如 `FROM dual`)模拟批量插入[^2]。 --- ### 总结:核心区别对比 | 特性 | `INSERT INTO` | Batch Insert | |---------------------|-----------------------------|----------------------------| | **插入行数** | 单行 | 多行(单语句) | | **性能** | 低(适合零星插入) | 高(适合大数据量) | | **事务开销** | 多次事务日志 | 单次事务日志 | | **编程复杂度** | 简单 | 需批处理模式或动态 SQL | | **错误影响范围** | 单行 | 可能影响整个批次 | **优先选择 Batch Insert 的场景**: ✅ 数据导入/迁移 ✅ 日志批量存储 ✅ 任何需要插入超过 10 行的操作 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值