Hibernate中findById与getById的差异及数据查询正确方式
问题1:出现LazyInitializationException而非EntityNotFoundException的原因
getById底层调用JPA标准的EntityManager.getReference()方法,返回的是实体动态代理对象,而非直接查询数据库得到的真实实例。代理对象默认仅初始化ID字段,其余所有属性(即使标注了FetchType.EAGER)都只会在首次访问时触发数据库查询完成初始化。- 你提供的代码中
run方法没有标注@Transactional,userRepo.save(u)执行完成后对应的持久化Session就会被自动关闭。后续你调用newUser.getUsername()访问代理的非ID属性时,Session已经关闭,无法触发代理初始化的查询操作,因此直接抛出懒加载异常,根本没有走到校验实体是否存在的步骤,自然不会触发EntityNotFoundException。
问题2:getById的实用价值与存在意义
getById不仅有实用价值,而且是性能优化的常用手段,核心适用场景如下:
- 关联绑定场景:当你仅需要用实体ID做外键关联时,比如给Order实体绑定所属User,只需要执行
order.setUser(userRepo.getById(userId)),不需要额外发起一次User的查询请求,保存Order时会直接用代理的ID存入外键字段,减少一次数据库IO。 - 部分更新场景:当你只需要更新实体的部分字段,不需要加载全量实体数据时,在事务内通过
getById拿到代理后直接更新对应字段即可,无需查询全量数据,减少内存开销和查询耗时。
问题3:Hibernate查询获取数据的正确选择
根据使用场景选择对应方法即可:
- 如果你需要访问实体的非ID属性、或者需要校验实体是否存在:优先使用
findById,该方法会直接发起数据库查询,返回已经完全初始化的实体实例,不存在代理懒加载问题,找不到对应数据时会返回空,不会抛出未查询到的异常。 - 如果你仅需要用到实体ID做关联或部分更新:在标注了
@Transactional的事务方法内使用getById,可避免不必要的查询,提升性能。 - 复杂多关联查询场景:自定义JPQL/SQL查询时,使用
join fetch手动指定需要加载的关联属性,避免N+1查询问题。 - 额外注意:你现有代码中的双向
@OneToOne未配置mappedBy指定关系维护端,会导致两边都维护外键,建议修改Address类的关联注解为@OneToOne(mappedBy = "address"),将User作为关系维护端,避免多余的数据库更新操作。
内容的提问来源于stack exchange,提问作者milanHrabos
相关产品推荐
相关产品推荐

