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

Spring Boot查询Person集合时JSON序列化懒加载异常问题

解决Hibernate懒加载集合的JSON序列化异常

这个问题的核心原因很明确:你在searchByExpression方法里手动关闭了Hibernate Session,而Person实体的otherNames等@OneToMany关联默认是**懒加载(FetchType.LAZY)**的。当Spring MVC试图把查询到的Person列表序列化成JSON时,需要访问这些懒加载集合,但此时Session已经关闭,无法初始化代理对象,于是抛出了failed to lazily initialize a collection of role... no Session的异常。而通过ID查询正常,是因为Spring Data JPA的findOne方法默认在事务上下文里执行,Session会保持到方法结束,序列化时可以正常初始化关联。

下面给你几种可行的解决方案,按推荐程度排序:

1. 移除手动关闭Session的代码,添加事务注解

你手动调用session.close()是错误的——EntityManager是由Spring容器管理的,Session的生命周期应该交给Spring来控制。给你的服务层方法添加@Transactional注解,让Spring在方法执行完毕后自动关闭Session,同时确保序列化时Session仍然可用:

修改你的服务层代码:

import org.springframework.transaction.annotation.Transactional;

@SuppressWarnings("unchecked")
@Override
@Transactional // 添加事务注解
public List<Person> searchByExpression(List<Criterion> expressions) {
    Session session = entityManager.unwrap(Session.class);
    List<Person> persons = null;
    try {
        Criteria criteria = session.createCriteria(Person.class);
        for (Criterion simpleExpression : expressions) {
            criteria.add(simpleExpression);
        }
        persons = criteria.list();
    }catch (Exception e) {
        e.printStackTrace();
    }
    // 移除这行手动关闭Session的代码:session.close(); 
    return persons;
}

2. 在Criteria查询中主动抓取关联集合

如果不想依赖事务的Session生命周期,可以在查询时就主动加载需要的关联集合,这样即使Session关闭,集合已经被初始化了。用Criteria的setFetchMode或者createAlias结合fetch来实现:

import org.hibernate.FetchMode;

@SuppressWarnings("unchecked")
@Override
public List<Person> searchByExpression(List<Criterion> expressions) {
    Session session = entityManager.unwrap(Session.class);
    List<Person> persons = null;
    try {
        Criteria criteria = session.createCriteria(Person.class);
        // 主动抓取otherNames集合,使用JOIN方式
        criteria.setFetchMode("otherNames", FetchMode.JOIN);
        // 如果还有photos、travelDocuments需要序列化,也可以加上
        // criteria.setFetchMode("photos", FetchMode.JOIN);
        // criteria.setFetchMode("travelDocuments", FetchMode.JOIN);
        
        // 加入去重,避免JOIN导致的重复结果
        criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
        
        for (Criterion simpleExpression : expressions) {
            criteria.add(simpleExpression);
        }
        persons = criteria.list();
    }catch (Exception e) {
        e.printStackTrace();
    }
    session.close();
    return persons;
}

3. 修改关联的FetchType为EAGER

直接把@OneToMany注解的fetch属性改成EAGER,这样每次查询Person时都会自动加载关联集合。这种方式最简单,但要注意性能问题——如果Person的关联集合数据量很大,会导致查询变慢:

修改Person实体的关联字段:

@OneToMany(mappedBy="person", fetch = FetchType.EAGER) // 添加fetch属性
private List<PersonOtherName> otherNames;

@OneToMany(mappedBy="person", fetch = FetchType.EAGER)
private List<Photo> photos;

@OneToMany(mappedBy="person", fetch = FetchType.EAGER)
private List<TravelDocument> travelDocuments;

4. 使用DTO(数据传输对象)封装返回结果

创建一个PersonDTO类,只包含你需要返回给前端的字段,在服务层把Person实体转换成DTO,这样序列化时不会访问懒加载的集合。这种方式最推荐,因为可以精准控制返回的数据,避免不必要的关联加载:

首先创建PersonDTO(根据你的实际字段调整):

import java.util.List;
import java.util.stream.Collectors;

public class PersonDTO {
    private Long id;
    private String name;
    private String surname;
    // 如果需要返回otherNames,创建对应的DTO
    private List<PersonOtherNameDTO> otherNames;

    // 构造方法
    public PersonDTO(Person person) {
        this.id = person.getId();
        this.name = person.getName();
        this.surname = person.getSurname();
        // 转换关联集合为DTO
        this.otherNames = person.getOtherNames().stream()
                .map(PersonOtherNameDTO::new)
                .collect(Collectors.toList());
    }

    // 对应的PersonOtherNameDTO
    public static class PersonOtherNameDTO {
        private String name;
        // 其他需要的字段

        public PersonOtherNameDTO(PersonOtherName otherName) {
            this.name = otherName.getName();
        }

        // getter方法
    }

    // getter方法
}

然后修改服务层方法返回DTO列表:

@Override
public List<PersonDTO> searchByExpression(List<Criterion> expressions) {
    Session session = entityManager.unwrap(Session.class);
    List<Person> persons = null;
    try {
        Criteria criteria = session.createCriteria(Person.class);
        for (Criterion simpleExpression : expressions) {
            criteria.add(simpleExpression);
        }
        persons = criteria.list();
    }catch (Exception e) {
        e.printStackTrace();
    }
    session.close();
    // 转换为DTO
    return persons.stream().map(PersonDTO::new).collect(Collectors.toList());
}

最后修改控制器的返回类型为ResponseEntity<List<PersonDTO>>。

5. 启用Open Session in View模式

在Spring Boot的application.properties里添加配置:

spring.jpa.open-in-view=true

这个配置会让Hibernate Session保持到视图渲染完成(也就是JSON序列化完成),这样即使Session在服务层关闭,序列化时仍然可以初始化懒加载集合。但这种方式不推荐用于高并发场景,因为Session会持有数据库连接更长时间,可能导致连接池耗尽。


内容的提问来源于stack exchange,提问作者Neritan Milaqi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:06:55