实现Serializable时如何启用LAZY FETCH?JPA懒加载失效问题
JPA懒加载失效问题排查与解决方案
问题背景
现有三个实体类A、B、BAudits,代码如下:
实体A
@Entity ... @BatchSize(size = 1000) @Table(name = "TABLE_A") @Getter @Setter @NoArgsConstructor public class A implements Serializable { ... @ManyToOne(fetch = FetchType.LAZY) @Cascade({ CascadeType.MERGE }) @JoinColumn(name = "B_CODE", referencedColumnName = "B_CODE", insertable = false, updatable = false) @JsonBackReference private B b; ... }
实体B
@Entity ... @BatchSize(size = 1000) @Table(name = "TABLE_B") @Getter @Setter @NoArgsConstructor public class B implements Serializable { ... @OneToMany(mappedBy = "b", fetch = FetchType.LAZY) @Cascade({ CascadeType.MERGE }) @JsonManagedReference @NotAudited private Set<BAudits> bAudits; ... }
BAudits为普通实体类,B_CODE(String类型)是A与B的外键关联字段。DAO层继承自
JpaRepository<A, Long>。
问题现象
执行以下查询时:
SELECT * FROM A a where a.id IN (**); -- ** 为具体的ID列表
明明给A的b字段设置了FetchType.LAZY,但大量B实体仍被强制加载,懒加载未生效。已知问题由实体实现Serializable接口导致,尝试多种方法未解决,寻求可行方案。
解决方案
1. 改用DTO传输数据(推荐)
实体实现Serializable后,序列化过程会触发JPA代理对象的强制初始化(序列化需要获取真实对象数据)。最稳妥的方式是避免直接序列化实体:
- 定义A的DTO类,仅包含业务需要的字段,不包含关联的B对象:
public class ADTO implements Serializable { private Long id; // 仅保留需要暴露的字段,排除B属性 // getter、setter、构造方法 }
- 使用Spring Data JPA的投影查询直接返回DTO:
// DAO层方法定义 List<ADTO> findByIdIn(List<Long> ids);
或者在服务层手动将实体转换为DTO:
List<A> entities = aRepository.findByIdIn(ids); List<ADTO> dtos = entities.stream() .map(a -> { ADTO dto = new ADTO(); dto.setId(a.getId()); // 设置其他需要的字段 return dto; }) .collect(Collectors.toList());
2. 配置序列化框架支持懒加载代理
如果必须序列化实体,可通过序列化框架的配置避免触发懒加载:
- 若使用Jackson,引入Hibernate模块并配置:
// Spring Boot中配置Bean @Bean public Module hibernateJacksonModule() { Hibernate5Module module = new Hibernate5Module(); // 关闭懒加载对象的自动初始化 module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, false); return module; }
该模块会自动识别Hibernate代理对象,序列化时不会触发懒加载初始化。
3. 调整查询方式
- 替换原生SQL为JPQL查询,明确仅加载A实体:
@Query("SELECT a FROM A a WHERE a.id IN :ids") List<A> findByIdIn(@Param("ids") List<Long> ids);
原生SQL可能绕过JPA的懒加载代理机制,导致关联对象被直接加载。
- 确保查询操作处于EntityManager会话范围内:懒加载代理仅在会话打开时有效,若查询后会话提前关闭,访问B字段会触发异常,但如果会话未关闭时被其他操作触发初始化,也会导致B被加载。需检查业务流程中是否有额外触发代理初始化的操作。
4. 自定义序列化逻辑(不推荐)
若必须序列化实体,可通过自定义序列化方法跳过关联字段的初始化:
private void writeObject(ObjectOutputStream out) throws IOException { // 仅序列化非关联字段,跳过b属性 out.writeLong(this.getId()); // 手动序列化其他必要字段 // 避免调用defaultWriteObject()触发代理初始化 }
此方式需手动维护序列化逻辑,耦合性高,仅在特殊场景下使用。
内容的提问来源于stack exchange,提问作者CHL
相关产品推荐
相关产品推荐

