为何JPA Buddy不建议在JPA实体上使用@Data注解?
JPA Buddy的提示并非无的放矢,@Data在JPA实体上的风险核心在于:它默认基于所有字段生成equals()、hashCode()和toString()方法,而JPA实体的生命周期(新建、持久化、懒加载代理)和关联关系很容易触发这些方法的潜在问题。以下是几个真实生产场景中踩过的坑:
场景1:懒加载关联引发栈溢出或性能雪崩
假设有一对关联实体:
@Entity @Data public class Order { @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private User user; } @Entity @Data public class User { @Id private Long id; @OneToMany(mappedBy = "user") private List<Order> orders; }
当你打印Order对象(比如日志输出),@Data生成的toString()会遍历所有字段,包括懒加载的user;而User的toString()又会遍历orders集合,反过来触发每个Order的toString(),最终导致栈溢出。
就算没有循环引用,触发懒加载也会带来性能问题:比如你只是想打印订单ID,结果意外触发数据库查询加载关联的用户数据,高并发场景下会造成不必要的DB压力,甚至引发性能雪崩。
场景2:未持久化实体放入集合后“失踪”
新建一个还未保存到数据库的Order实体(此时id为null),放入HashSet:
Set<Order> orderSet = new HashSet<>(); Order order = new Order(); order.setAmount(100); orderSet.add(order); // 修改订单金额 order.setAmount(200); // 此时orderSet.contains(order)返回false!
因为@Data的hashCode()基于所有字段生成,修改amount后哈希值改变,集合无法定位到原来的元素,导致后续查询、删除操作全部失效,出现数据不一致问题。
场景3:代理对象的equals判断错误
Hibernate对懒加载实体会生成代理类(比如Order$HibernateProxy$XXX),当你拿代理对象和真实对象对比时:
// 从数据库懒加载获取的代理对象 Order proxyOrder = orderRepository.findById(1L).get(); // 手动创建的真实对象 Order realOrder = new Order(); realOrder.setId(1L); // @Data生成的equals会返回false! System.out.println(proxyOrder.equals(realOrder)); // false
@Data的equals()用this.getClass() == o.getClass()判断类型,而代理类是原实体类的子类,类型不匹配,导致明明是同一个实体却被判定为不相等。这在批量操作、缓存对比时会引发逻辑错误。
JPA Buddy推荐方案的优势
它生成的代码针对性解决了这些问题:
equals()仅对比id(当id不为null时),避开关联字段和代理类的干扰hashCode()固定返回类的哈希值,不会随字段变化而改变,适合集合存储- 拆分@Data为
@Getter+@Setter+@ToString+@RequiredArgsConstructor,可以手动控制toString()是否包含懒加载字段,避免意外触发查询
你没遇到问题,大概率是项目场景简单:实体关联少、很少用集合存储未持久化实体、或日志输出不会触发全字段toString。但随着项目规模扩大,这些坑很可能突然出现,排查起来非常麻烦。
内容的提问来源于stack exchange,提问作者Georgii Vlasov

