Spring Boot+Hibernate集合序列化时如何禁止懒加载关联自动加载?
我正在学习Spring Boot,想要实现关联对象的懒加载,避免页面渲染生成JSON时自动加载关联数据。我定义了带有自关联father的Person实体类,也配置了Jackson的Hibernate5Module。现在遇到的问题是:调用接口获取单个Person时,father为null符合预期,但获取所有Person列表时,father却被自动加载出来了。试了好几种配置方式都没解决,请问该怎么处理?
实体类代码
@Entity(name = "Person") @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long idPerson; private String Name; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "idFather") private Person father; /*Getters and setters ommited*/ }
尝试过的配置
配置代码1
@Bean public Jackson2ObjectMapperBuilder configureObjectMapper() { return new Jackson2ObjectMapperBuilder() .modulesToInstall(Hibernate5Module.class); }
配置代码2
@Configuration @EnableWebMvc public class Configuration implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(jacksonMessageConverter()); WebMvcConfigurer.super.configureMessageConverters(converters); } public MappingJackson2HttpMessageConverter jacksonMessageConverter() { MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new Hibernate5Module()); messageConverter.setObjectMapper(mapper); return messageConverter; } }
接口返回示例
获取单个Person
//localhost:9000/person/2 {"idPerson":2,"name":"Luke Skywalker","father":null}
获取所有Person
//localhost:9000/person [ { "idPerson":1, "name":"Darth Vader", "father":null }, { "idPerson":2, "name":"Luke Skywalker", "father":{ "idPerson":1, "name":"Darth Vader", "father":null } } ]
问题分析与解决办法
这种情况我之前也碰到过,大概率是Hibernate5Module的配置没有完全生效,或者查询列表时Session的状态导致懒加载被意外触发了。咱们一步步来排查解决:
1. 关闭OpenSessionInViewFilter(关键步骤)
Spring Boot默认会自动启用OpenSessionInViewFilter,这个过滤器会让Hibernate Session保持打开直到视图渲染完成。这就意味着Jackson序列化JSON的时候,Hibernate可以随时初始化懒加载的关联对象,直接绕过了你的懒加载配置。
你只需要在application.properties(或application.yml)中添加以下配置关闭它:
spring.jpa.open-in-view=false
2. 优化Hibernate5Module的配置
虽然你已经注册了Hibernate5Module,但可以显式配置它的特性,确保未初始化的懒加载对象被序列化为null:
// 以配置代码2为例修改 public MappingJackson2HttpMessageConverter jacksonMessageConverter() { MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); ObjectMapper mapper = new ObjectMapper(); // 显式配置模块,强制处理未初始化的懒加载对象 Hibernate5Module hibernateModule = new Hibernate5Module(); hibernateModule.enable(Hibernate5Module.Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS); mapper.registerModule(hibernateModule); messageConverter.setObjectMapper(mapper); return messageConverter; }
这个特性会让未加载的懒加载对象只序列化ID,或者如果不需要ID的话,也可以保持默认(默认会序列化为null),但显式配置能确保模块按预期工作。
3. 检查Repository层的查询逻辑
确保你的列表查询方法没有强制加载father关联。比如如果用了自定义的JPQL查询,有没有写join fetch p.father这种语句?如果有,那会直接触发Eager加载,完全忽略FetchType.LAZY的设置。
如果是用Spring Data JPA的默认findAll()方法,那没问题,但如果有自定义查询,一定要检查是否存在意外的Fetch Join。
4. 优化实体类的Jackson注解
你已经加了@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}),这个可以保留。另外针对自关联的场景,建议给father字段加上@JsonBackReference来避免潜在的循环序列化问题,同时也能辅助Jackson识别懒加载关联:
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "idFather") @JsonBackReference private Person father;
5. 验证配置是否生效
可以在配置ObjectMapper的时候,加一行日志或者打印语句,确认Hibernate5Module确实被注册成功了:
System.out.println("Hibernate5Module已注册: " + mapper.getRegisteredModuleIds().contains(Hibernate5Module.class.getName()));
如果输出true,说明模块已经正确加载;如果是false,那就要检查配置类是否被Spring扫描到了。
按照上面的步骤逐一排查,应该就能解决列表查询时father被自动加载的问题了。
内容的提问来源于stack exchange,提问作者marcellorvalle

