如何修复Spring Boot中因实体toString/equals方法引发的LazyInitializationException?
你的问题核心很明确:toString()和equals()方法中直接引用了懒加载的transfers集合,当你从仓库获取User对象后,Hibernate Session已经关闭,此时调用这两个方法访问未初始化的懒加载集合,就触发了LazyInitializationException。而且这两个方法是单元测试必需的,下面给你几个针对性的修复方案:
方案1:给单元测试添加事务注解(最省心的测试场景解决方案)
如果只是单元测试中出现这个问题,最简单的办法是给测试类或测试方法加上@Transactional注解,让Hibernate Session在整个测试方法执行期间保持打开状态:
@SpringBootTest @Transactional public class UserServiceTest { @Test public void testTransferToFriend() { // 你的测试逻辑 // 此时调用User的toString/equals,懒加载的transfers会被正常初始化 } }
这个方案不需要修改业务代码,完全适配测试场景的需求。
方案2:修改toString/equals,排除懒加载集合
如果测试中不需要对比transfers集合的内容,可以直接把它从toString()和equals()中移除,保留核心字段的输出和对比:
@Override public String toString() { return "User{" + "id=" + id + ", email='" + email + '\'' + ", password='" + password + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", ibanCode=" + ibanCode + ", bicCode=" + bicCode + ", balance=" + balance + ", friendsList='" + friendsList + '\'' + // 移除transfers的引用 '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return ibanCode == user.ibanCode && bicCode == user.bicCode && Double.compare(user.balance, balance) == 0 && Objects.equals(id, user.id) && Objects.equals(email, user.email) && Objects.equals(password, user.password) && Objects.equals(firstName, user.firstName) && Objects.equals(lastName, user.lastName) && Objects.equals(friendsList, user.friendsList); // 移除transfers的对比 }
如果测试中确实需要验证transfers集合,可以单独编写断言逻辑,不用依赖equals()方法。
方案3:查询时主动加载transfers集合
如果你不想修改toString()和equals(),可以在查询User的时候主动初始化transfers集合,确保Session关闭前就完成加载:
方式A:自定义Repository查询方法(推荐)
在UserRepository中添加带JOIN FETCH的查询方法,主动关联加载transfers:
public interface UserRepository extends JpaRepository<User, Long> { @Query("SELECT u FROM User u JOIN FETCH u.transfers WHERE u.id = :userId") Optional<User> findByIdWithTransfers(@Param("userId") Long userId); }
然后在业务方法中调用这个方法代替findById(),这样获取到的User对象的transfers已经被初始化,后续调用toString()/equals()就不会触发异常。
方式B:设置EAGER加载(谨慎使用)
你之前尝试过的fetch="EAGER"其实也能解决问题,但要注意:EAGER加载会让每次查询User都自动加载transfers集合,可能会带来不必要的性能开销,只在确实需要全局加载的场景使用。
为什么你之前的尝试没完全生效?
你设置spring.jpa.properties.hibernate.enable_lazy_load_no_trans=false后仍然报错,大概率是因为toString()/equals()调用时Session已经彻底关闭,这个配置只适用于Session还处于"软关闭"状态的场景;而fetch="EAGER"可能是配置没有正确生效(比如注解写法错误,应该是fetch = FetchType.EAGER),或者你在测试中还是用了旧的查询方法。
内容的提问来源于stack exchange,提问作者David

