从缓存获取A对象后,如何加载其懒加载关联对象B?
问题:缓存懒加载实体引发的LazyInitializationException
实体类定义
class A { Integer id; String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "b_id") private B b; } class B { Integer id; String name; }
问题描述
在Spring Boot中,调用标注@Cacheable的findById方法缓存A对象后,下次从缓存获取A对象时执行a.getB().getId(),会抛出如下异常:
org.hibernate.LazyInitializationException: could not initialize proxy [com.freit.medicalservice.entity.B#1] - no Session
已尝试的方案
- 使用
Hibernate.initialize(obj)(调用前Hibernate.isInitialized(obj)返回true) - 尝试过Stack Overflow相关线程中@beijiaxu提出的方案
- 知晓设置
FetchType.EAGER可解决问题,但不想采用该方式
可行解决方案
1. 缓存前在会话内初始化关联对象
在@Cacheable方法内部,获取A对象后,在当前Session/事务范围内主动初始化B关联,再返回对象用于缓存。这样缓存的A对象关联的B已经被加载,不会出现懒加载异常:
@Cacheable(cacheNames = "aCache") public A findById(Long id) { A a = aRepository.findById(id).orElseThrow(); // 仅当B未初始化时执行初始化,确保在会话内操作 if (a.getB() != null && !Hibernate.isInitialized(a.getB())) { Hibernate.initialize(a.getB()); } return a; }
注:之前调用Hibernate.isInitialized(obj)返回true,可能是因为缓存前B代理已被初始化,或者初始化操作不在会话范围内导致无效,需确保初始化在查询A的同一事务中执行。
2. 转换为DTO缓存,避免直接缓存实体
不要直接缓存JPA实体类,而是转换为数据传输对象(DTO),在转换过程中主动加载关联对象的所需字段:
// 定义DTO类 class ADTO { private Integer id; private String name; private Integer bId; private String bName; // 从实体转换的构造方法 public ADTO(A a) { this.id = a.getId(); this.name = a.getName(); if (a.getB() != null) { this.bId = a.getB().getId(); this.bName = a.getB().getName(); } } }
修改缓存方法返回DTO:
@Cacheable(cacheNames = "aDtoCache") public ADTO findById(Long id) { A a = aRepository.findById(id).orElseThrow(); return new ADTO(a); }
这种方式彻底规避了懒加载问题,因为缓存的是纯数据对象,没有Hibernate代理关联。
3. 配置Redis序列化器支持懒加载代理(针对Redis缓存)
如果使用Redis作为缓存,通过Jackson的Hibernate模块处理懒加载代理的序列化:
- 添加依赖:
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-hibernate5</artifactId> </dependency>
- 配置Redis序列化器:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); // 注册Hibernate模块,自动处理懒加载代理的序列化 objectMapper.registerModule(new Hibernate5Module()); serializer.setObjectMapper(objectMapper); template.setValueSerializer(serializer); template.setKeySerializer(new StringRedisSerializer()); return template; } }
Hibernate5Module会在序列化时自动初始化懒加载对象(或仅序列化已加载的字段),确保反序列化后能正常访问关联对象。
4. Open Session in View(仅调试用,不推荐生产)
在application.properties中添加配置:
spring.jpa.open-in-view=true
该配置会延长Session生命周期到视图渲染阶段,但可能引发性能问题和事务管理风险,仅适合开发调试场景,不建议生产环境使用。
内容的提问来源于stack exchange,提问作者Ali Azlan
相关产品推荐
相关产品推荐

