You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从缓存获取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模块处理懒加载代理的序列化:

  1. 添加依赖:
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>
  1. 配置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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 18:40:36