Spring/JPA/Hibernate结合Lombok时,获取含多层关联子实体的父实体出现LazyInitializationException的解决方法
这个问题很典型——你已经通过@EntityGraph加载了Shop的bookDaoList,但没有进一步加载每个Book关联的otherDao,导致在持久化上下文关闭后访问otherDao时触发懒加载异常。下面给你几个可行的解决方案:
方案1:扩展EntityGraph实现深度关联加载
最直接高效的方式是修改ShopRepo中的@EntityGraph,把bookDaoList.otherDao也加入到属性路径中,这样JPA会在查询Shop的同时,一次性加载所有关联的Book和对应的Other实体,避免后续懒加载的问题:
@Repository public interface ShopRepo extends JpaRepository<ShopDao, Long> { @EntityGraph(attributePaths = {"bookDaoList", "bookDaoList.otherDao"}) List<ShopDao> findAllByProcessedStatus(String processedStatus); }
这种方式的优势是一次SQL查询完成所有数据加载,不会产生N+1查询的性能问题,也不需要依赖事务上下文。
方案2:确保在事务范围内访问关联属性
如果不想修改Repository的查询逻辑,可以在Service层添加@Transactional注解,让持久化上下文在整个业务方法执行期间保持打开状态,这样当你访问Book的otherDao时,Hibernate会自动触发懒加载:
@Service @Transactional public class ShopService { private final ShopRepo shopRepo; public ShopService(ShopRepo shopRepo) { this.shopRepo = shopRepo; } public List<ShopDao> getShopsByProcessedStatus(String status) { List<ShopDao> shops = shopRepo.findAllByProcessedStatus(status); // 在事务内访问otherDao,会自动加载 shops.forEach(shop -> shop.getBookDaoList().forEach(book -> book.getOtherDao()) ); return shops; } }
注意:这种方式会产生额外的SQL查询(每个Book对应一次查询Other),如果数据量较大,可能会有性能问题。
方案3:显式设置Book与Other的关联为EAGER加载
虽然JPA规范中@OneToOne(主方,即带有@JoinColumn的一方)默认是EAGER加载,但有时候EntityGraph的策略会覆盖这个默认行为。你可以显式在BookDao的otherDao上指定FetchType.EAGER:
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "other_id", referencedColumnName = "id") private OtherDao otherDao;
不过这种方式不推荐,因为EAGER加载会导致所有查询Book的操作都自动加载Other,可能造成不必要的性能开销,灵活性较差。
总结
优先选择方案1,通过EntityGraph精准控制需要加载的关联实体,既保证性能又避免懒加载异常。
内容的提问来源于stack exchange,提问作者Lyst

