<think>我们正在解决MyBatis插入数据时出现的IndexOutOfBoundsException异常。
根据错误日志 `Cause: java.lang.IndexOutOfBoundsException: Index 4 out of bounds for length 0`,表明在访问索引4时,底层数组长度为0。
这种异常通常发生在MyBatis解析SQL参数时,试图访问不存在的参数位置。
可能的原因:
1. Mapper XML文件中使用了索引参数(如#{0}, #{1}等),而实际传入的参数数量不足。
2. 动态SQL(如<foreach>)在处理空集合时出现异常(特别是在旧版本MyBatis中)。
3. 在SQL中引用了不存在的参数位置(例如,参数列表只有3个,却引用了第4个参数)。
解决方案:
1. 检查Mapper XML中的SQL语句,确保没有使用索引参数(如#{0}, #{1})而应该使用命名参数(如#{param1}, #{param2})或直接使用属性名(如果参数是对象)。
2. 在动态SQL(如<foreach>)中,确保集合不为空(添加非空判断)。
3. 确保传入的参数数量与SQL中引用的参数数量一致。
下面我们针对上述原因逐一给出解决方案:
原因1:使用索引参数
错误示例:
<insert id="insertUser">
INSERT INTO USER (name, age, email) VALUES (#{0}, #{1}, #{2})
</insert>
当传入的参数只有一个(如一个User对象)时,使用索引0就会抛出异常,因为只有一个参数(即索引0对应整个User对象,索引1和2不存在)。
正确做法:使用属性名
<insert id="insertUser">
INSERT INTO USER (name, age, email) VALUES (#{name}, #{age}, #{email})
</insert>
或者使用命名参数(在Mapper接口中使用@Param注解)。
原因2:动态SQL中的空集合
在批量插入时,如果传入的List为空,且没有进行空集合检查,旧版本MyBatis可能会抛出IndexOutOfBoundsException。
解决方案:在动态SQL外添加非空判断
<insert id="batchInsert">
<if test="list != null and list.size() > 0">
INSERT INTO table (col1, col2)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.field1}, #{item.field2})
</foreach>
</if>
</insert>
原因3:参数数量不匹配
确保Mapper接口方法的参数个数与XML中引用的参数个数一致。
例如,Mapper接口方法:
void insertUser(@Param("name") String name, @Param("age") Integer age, @Param("email") String email);
在XML中引用参数时,必须使用对应的命名参数:
#{name}, #{age}, #{email}
如果XML中写了第四个参数(如#{address}),而接口方法没有提供,就会出现索引越界。
针对你的错误信息(Index 4 out of bounds for length 0),很可能是你在SQL中引用了第5个参数(因为索引从0开始,索引4是第5个),而实际传入的参数数量为0。请检查SQL中是否有类似#{4}的引用。
如果经过检查没有发现明显的索引参数(如#{0})的使用,那么可能是MyBatis内部在处理SQL参数时出现了问题,尤其是在旧版本MyBatis中,可以尝试升级到最新版本。
另外,检查是否在SQL中使用了错误的参数占位符写法,例如在有些情况下可能错误地写成了${},但这里主要是索引越界。
完整示例:
假设我们有一个批量插入的Mapper,如下:
Mapper接口:
public interface UserMapper {
void batchInsert(@Param("users") List<User> users);
}
XML配置:
<insert id="batchInsert">
INSERT INTO user (name, age)
VALUES
<foreach collection="users" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert>
如果传入的users为空,那么<foreach>内部会生成一个空的VALUES列表,导致SQL语法错误(但不会抛出IndexOutOfBoundsException)。但是,在旧版本的MyBatis中,可能会先触发空集合处理错误,从而抛出IndexOutOfBoundsException。
因此,我们除了在XML中做空集合判断外,在Java代码中也可以进行判断:
public void batchInsertUsers(List<User> users) {
if (users == null || users.isEmpty()) {
return; // 或者抛出自定义异常
}
userMapper.batchInsert(users);
}
此外,建议升级MyBatis到最新稳定版本,因为该问题在较新的版本中已经修复。
如果问题仍未解决,请检查MyBatis的日志,查看实际生成的SQL和参数绑定情况,这有助于定位问题。
最后,我们给出一个修正后的完整示例:
Mapper接口:
public interface UserMapper {
void batchInsert(@Param("userList") List<User> userList);
}
XML配置:
<insert id="batchInsert">
<if test="userList != null and userList.size() > 0">
INSERT INTO user (name, age)
VALUES
<foreach collection="userList" item="item" separator=",">
(#{item.name}, #{item.age})
</foreach>
</if>
</insert>
调用代码:
List<User> users = ...; // 获取用户列表
// 确保不为空
if (users != null && !users.isEmpty()) {
userMapper.batchInsert(users);
}
通过以上步骤,应该可以解决该问题。
如果仍然遇到问题,请提供具体的Mapper XML配置和调用代码,以便更准确地定位。
注意:在MyBatis中,使用索引参数(如#{0})的方式在3.4.1版本之后已经不再推荐,建议全部使用命名参数。
§§相关问题§§:
1. MyBatis中如何正确使用命名参数?
2. MyBatis动态SQL中<foreach>标签有哪些常用属性?
3. 如何避免MyBatis在批量插入时传入空集合导致的错误?
4. MyBatis中#{}和${}有什么区别?
</think>### 问题分析:IndexOutOfBoundsException in MyBatis (Index 4 for length 0)
#### 根本原因:
当MyBatis报告`IndexOutOfBoundsException: Index 4 out of bounds for length 0`时,表明**SQL参数绑定过程**发生了问题:
1. **参数索引越界**:
- MyBatis试图访问第5个参数(索引从0开始,索引4=第5个参数)
- 但实际传入的参数集合为空(length=0)
2. 常见触发场景:
```xml
<!-- 错误示例:索引参数引用 -->
INSERT INTO users
VALUES (#{0}, #{1}, #{2}, #{3}, #{4})
```
其中`#{4}`试图访问第5个参数,但实际传入参数不足
3. 其他可能原因:
- 动态SQL中`<foreach>`操作空集合
- 参数传递与SQL占位符数量不匹配
- MyBatis版本缺陷(3.4.x之前对空集合处理有问题)
---
### 完整解决方案:
#### 1. 停止使用索引参数(最根本方案)
```xml
<!-- 错误方式:索引参数 -->
<insert id="insertUser">
INSERT INTO users VALUES
(#{0}, #{1}, #{2}, #{3}, #{4})
</insert>
<!-- 正确方式:命名参数 -->
<insert id="insertUser">
INSERT INTO users(name, email, age, created_at, updated_at)
VALUES (
#{user.name},
#{user.email},
#{user.age},
#{createTime},
#{updateTime}
)
</insert>
```
#### 2. 修复Mapper接口参数匹配
```java
// Mapper接口正确写法
public interface UserMapper {
// ✅ 命名参数与XML匹配
void insertUser(
@Param("user") User user,
@Param("createTime") LocalDateTime createTime,
@Param("updateTime") LocalDateTime updateTime
);
}
// 调用示例
User user = new User("John", "john@test.com", 30);
userMapper.insertUser(
user,
LocalDateTime.now(),
LocalDateTime.now()
);
```
#### 3. 空集合安全处理
```xml
<!-- 批量插入时添加空集合检查 -->
<insert id="batchInsert">
<if test="!userList.isEmpty()">
INSERT INTO users(name, email)
VALUES
<foreach collection="userList" item="u" separator=",">
(#{u.name}, #{u.email})
</foreach>
</if>
</insert>
```
#### 4. 参数数量一致性检查
```java
// Java端参数验证
public void safeInsert(User user) {
if (user == null) {
throw new IllegalArgumentException("用户对象不能为空");
}
if (user.getEmail() == null || user.getName() == null) {
throw new IllegalArgumentException("必填字段缺失");
}
userMapper.insertUser(user);
}
```
#### 5. MyBatis配置优化(可选)
```xml
<!-- mybatis-config.xml -->
<settings>
<!-- 启用详细日志 -->
<setting name="logImpl" value="SLF4J"/>
<!-- 3.5.6+版本新增空集合处理 -->
<setting name="safeForeach" value="true"/>
</settings>
```
---
### 紧急调试技巧:
1. **启用MyBatis完整日志**:
```java
// 临时日志设置(无需重启)
Logger logger = Logger.getLogger("org.mybatis");
logger.setLevel(Level.FINEST);
```
2. **验证参数绑定**:
```sql
/* 实际执行的SQL日志示例 */
-- 参数: [null, null, null, null, null] (size=5)
-- SQL: INSERT INTO users VALUES (?,?,?,?,?)
```
3. **使用MyBatis参数检查工具**:
```java
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
MapperMethod.ParamMap<Object> paramMap = new MapperMethod.ParamMap<>();
paramMap.put("user", user);
paramMap.put("createTime", LocalDateTime.now());
// 验证参数数量
int paramCount = sqlSession.getConfiguration()
.getMappedStatement("insertUser")
.getBoundSql(paramMap)
.getParameterMappings()
.size();
System.out.println("Required params: " + paramCount);
} finally {
sqlSession.close();
}
```
---
### 根本预防措施:
1. **严格禁止索引参数**:永远不使用`#{0}`, `#{1}`等索引形式
2. **DTO参数验证**:
```java
@Data
public class UserInsertDTO {
@NotBlank
private String name;
@Email
private String email;
@NotNull
private LocalDateTime createTime;
}
```
3. **单元测试覆盖边界情况**:
```java
@Test
public void testInsertWithNullParams() {
assertThrows(DataIntegrityViolationException.class, () -> {
userMapper.insertUser(null, null, null);
});
}
```
---
###