1、背景
直接看报错:
java
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
### The error may exist in com/ly/cloud/console/coupon/mapper/ActivityReadRecordMapper.java (best guess)
### The error may involve com.ly.cloud.console.coupon.mapper.ActivityReadRecordMapper.selectList
### The error occurred while handling results
### SQL: SELECT activity_info_id FROM t_activity_read_record WHERE is_valid=1 AND (user_id = ? AND activity_info_id IN (?))
### Cause: java.lang.IndexOutOfBoundsException: Index: 1, Size: 1
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:92)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
at com.sun.proxy.$Proxy169.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:223)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:173)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:78)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:148)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy343.selectList(Unknown Source)
at com.baomidou.mybatisplus.extension.conditions.query.ChainQuery.list(ChainQuery.java:39)
今天进行开发时,发现以往的写法竟然在测试时报错,导致我心里咯噔一下,还以为历史的代码提交都有问题,只是没暴露出来而已。大概就是类似这样的写法:
经过一番排查,说是可能Mybatis-Plus版本太低,在3.5.X版本之前会有绑定参数错误的问题,于是我升级版本又按要求排除一些依赖后发现问题依旧在,至此,我在想今天做了什么多余的操作,突然想到在实体类上加了一个
@Builder
注解,去掉后恢复正常!问题版本如下:
java
@Accessors(chain = true)
@Builder
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_activity_read_record")
public class ActivityReadRecord implements Serializable {
}
2、错误根源
@Builder
注解会使用到无参构造器和全参构造器,单独使用该注解会将无参构造器和全参构造器自动创建并进行私有化,而MyBatis-Plus底层大量操作都是基于反射的,那么就需要一个public
的无参构造器,所以会有问题。 所以需要添加一个@NoArgsConstructor
注解添加无参构造器。又因为Lombok的设计哲学,当外部操控了构造器的创建,那么@Builder
的私有构造器就会失效,而且它还依赖一个全参构造器所以还需要添加一个@AllArgsConstructor
注解创建全参构造器供@Builder
使用。
2.1、 @Builder
修饰的类长什么样子?
原始 Java 源码(Person.java
)
java
import lombok.Data;
import lombok.Builder;
@Data
@Builder
public class Person {
private String name;
private Integer age;
}
编译后(Person.class
)反编译结果
java
public class Person {
private String name;
private Integer age;
// 1. 私有的无参构造函数(由 @Builder 生成,但默认是 private!)
private Person() {
}
// 2. 全参构造函数(由 @Builder 使用)
private Person(String name, Integer age) {
this.name = name;
this.age = age;
}
// 3. Getter
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
// 4. Setter
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
// 5. toString, equals, hashCode (略)
// 6. Builder 模式相关代码(内部静态类)
public static Person.PersonBuilder builder() {
return new Person.PersonBuilder();
}
public static class PersonBuilder {
private String name;
private Integer age;
PersonBuilder() {
}
public Person.PersonBuilder name(String name) {
this.name = name;
return this;
}
public Person.PersonBuilder age(Integer age) {
this.age = age;
return this;
}
public Person build() {
return new Person(name, age); // 调用私有全参构造
}
public String toString() {
return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ")";
}
}
}
3、官方建议数据实体类最佳实践
java
@Accessors(chain = true)
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_activity_read_record")
public class ActivityReadRecord implements Serializable {
}
4、一些关于Lombok注解使用技巧
4.1、单个注解说明
注解 | 生成构造函数 | 访问级别 | 说明 | MyBatis 安全 |
---|---|---|---|---|
@Data |
仅 无参构造 | public |
为所有字段生成 getter/setter,无参构造用于反射 | ✅ 是 |
@Getter / @Setter |
❌ 不生成 | --- | 仅生成访问器 | ✅ |
@ToString |
❌ 不生成 | --- | 仅生成 toString() |
✅ |
@EqualsAndHashCode |
❌ 不生成 | --- | 仅生成 equals/hashCode |
✅ |
@NoArgsConstructor |
无参构造 | public (默认) |
可用 access 指定级别 |
✅ |
@AllArgsConstructor |
全参构造 | public (默认) |
按字段顺序传参 | ✅ |
@RequiredArgsConstructor |
必需参数构造 | public (默认) |
仅为 final 或 @NonNull 字段生成 |
✅ |
@Builder |
private 无参 + private 全参 |
private |
仅当没有其他构造函数注解时自动生成 | ⚠️ 否(若不配对) |
4.2、避坑指南
陷阱 | 正确做法 |
---|---|
@Builder 导致 MyBatis 查询失败 |
实体类不用 @Builder ,或必须配 @NoArgsConstructor + @AllArgsConstructor |
@Builder 编译报错 |
检查是否加了 @NoArgsConstructor 却没加 @AllArgsConstructor |
final 字段无法 Builder |
用 @RequiredArgsConstructor 或手动加构造函数 |
链式调用失效 | 加 @Accessors(chain = true) |
JSON 反序列化失败 | 确保有 public 无参构造或 @JsonCreator |
5、反思
写代码的时候还是要动点脑子,不能太轻松,容易栽跟头,注解虽然好用,但是具体场景使用的时候把握的不够敏锐,之前不用Lombok时自己也会使用构建者模式,结果这里直接把无参构造是私有的忽略了,还一顿乱改,属实是浪费时间了,方向走错了,再怎么努力也白搭,还加班!