Lombok的@Builder与Mybatis-Plus配合使用踩坑

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时自己也会使用构建者模式,结果这里直接把无参构造是私有的忽略了,还一顿乱改,属实是浪费时间了,方向走错了,再怎么努力也白搭,还加班!

相关推荐
舒一笑10 分钟前
如何优雅统计知识库文件个数与子集下不同文件夹文件个数
后端·mysql·程序员
IT果果日记12 分钟前
flink+dolphinscheduler+dinky打造自动化数仓平台
大数据·后端·flink
Java技术小馆23 分钟前
InheritableThreadLoca90%开发者踩过的坑
后端·面试·github
寒士obj32 分钟前
Spring容器Bean的创建流程
java·后端·spring
掉鱼的猫44 分钟前
Spring AOP 与 Solon AOP 有什么区别?
java·spring
不是光头 强1 小时前
axure chrome 浏览器插件的使用
java·chrome
笨蛋不要掉眼泪1 小时前
Spring Boot集成腾讯云人脸识别实现智能小区门禁系统
java·数据库·spring boot
桃源学社(接毕设)1 小时前
云计算下数据隐私保护系统的设计与实现(LW+源码+讲解+部署)
java·云计算·毕业设计·swing·隐私保护
数字人直播2 小时前
视频号数字人直播带货,青否数字人提供全套解决方案!
前端·javascript·后端
shark_chili2 小时前
提升Java开发效率的秘密武器:Jadx反编译工具详解
后端