Spring Data与Hibernate中getReferenceById替代findById的适用场景
先明确当前版本下两个方法的核心差异:
findById(id).orElseThrow():立即执行SQL查询,返回真实的实体对象;若ID不存在,调用时直接抛出NoSuchElementException(因为orElseThrow)。getReferenceById(id):返回代理对象,不会立即查询数据库;只有当访问代理的非ID属性时,才会触发SQL查询加载真实数据;若ID不存在,异常会在首次访问非ID属性时抛出EntityNotFoundException。
仍需使用getReferenceById的场景
1. 仅需关联实体ID(无需加载实体其他属性)
当你只需要用实体的ID来建立关联关系(比如设置外键),不需要访问该实体的其他字段时,用getReferenceById可以避免不必要的SQL查询,提升性能。
比如创建订单时关联用户,只需要用户ID即可:
Order order = new Order(); order.setUser(userRepository.getReferenceById(userId)); // 不会查询用户的其他数据 orderRepository.save(order);
此时Hibernate仅会将userId作为外键存入订单表,无需执行SELECT * FROM user WHERE id = ?,减少数据库交互开销。
2. 批量关联操作场景
在批量创建/更新关联实体时,使用getReferenceById可以避免N次重复的SELECT查询。比如批量给100个用户分配角色,每个用户都用getReferenceById获取代理,只在必要时加载用户数据,甚至全程不需要加载,大幅降低数据库压力。
3. 延迟验证实体存在性(后续流程会自然触发属性访问)
如果业务流程中后续必然会访问实体的非ID属性,那么可以用getReferenceById延迟查询时机,让异常在实际需要数据时抛出。比如在一个事务中,先获取用户代理,之后要检查用户的状态、权限等属性,此时异常会在检查属性时抛出,逻辑更连贯。
更适合用findById().orElseThrow()的场景
1. 需要立即确认实体存在
在接口入口、业务流程起始点,需要快速验证ID对应的实体是否存在,避免后续执行大量逻辑后才抛出异常。比如修改用户信息的接口,第一步就用findById(id).orElseThrow()确认用户存在,直接返回404错误,无需继续执行后续逻辑。
2. 需要立即访问实体的非ID属性
如果拿到实体后马上要使用其非ID字段(比如用户名、邮箱、状态等),直接用findById加载真实实体更直观,避免代理延迟查询带来的潜在异常(比如在事务外访问属性导致LazyInitializationException)。
注意事项
getReferenceById返回的代理对象必须在事务上下文中访问非ID属性,否则会抛出LazyInitializationException(因为没有会话支持延迟加载)。getReferenceById的异常触发时机是首次访问非ID属性,而非调用方法时,需要注意异常处理的位置,避免在错误的阶段抛出异常导致业务逻辑混乱。
内容的提问来源于stack exchange,提问作者Klaus Native

