使用JpaRepository.getReferenceById返回含空值实体的问题排查
Spring Data JPA getReferenceById 获取实体属性偶尔为null的问题
问题场景
通过getReferenceById获取FileMetadata实体并关联到未持久化的FileRawData实体后,在标注@Transactional的handle方法中,偶尔出现无法读取FileMetadata的numberOfLines属性值的情况,但数据库中该字段确实存在有效值。实体关系为FileMetadata(一对多)关联FileRawData,数据库file_metadata表存在对应记录。
实体代码
FileMetadata类
@Setter @Getter @Entity(name = "FileMetadata") @Table(name = "file_metadata") @DynamicUpdate public class FileMetadata { private static final long serialVersionUID = -6174939830390750530L; @Id @GeneratedValue(generator = "UUID", strategy = GenerationType.AUTO) @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @Column(columnDefinition = "UUID") private UUID id; @NotNull @Column(name = "file_name") private String fileName; @OneToMany(mappedBy = "fileMetadata") private List<FileRawData> fileRawData; // 注:实体中应包含numberOfLines字段,此处用户代码可能遗漏 private Integer numberOfLines; }
FileRawData类
@Setter @Getter @Entity(name = "FileRawData") @Table(name = "file_raw_data") @DynamicUpdate public class FileRawData { private static final long serialVersionUID = 8073845379702147924L; @Id @GeneratedValue(generator = "UUID", strategy = GenerationType.AUTO) @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") @Column(columnDefinition = "UUID") private UUID id; @Column(name = "raw_data") private String rawData; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "file_metadata_id", nullable = false) private FileMetadata fileMetadata; }
业务方法代码
@Transactional public void handle(FileRawData fileRawData) { FileMetadata fileMetadata = fileRawData.getFileMetadata(); if (fileMetadata.getNumberOfLines() == null) { log.info("NumberOfLines is null"); log.info("{}", fileRawData.getFileMetadata().getId()); log.info("{}", fileRawData.getFileMetadata().getFileName()); log.info("{}", fileRawData.getFileMetadata().getNumberOfLines()); } }
控制台日志
2023-03-06 21:35:03.897 INFO 19424 --- [onverter-exec-4] c.t.d.m.e.PlanConvertTaskExecutor : NumberOfLines is null 2023-03-06 21:35:03.898 INFO 19424 --- [onverter-exec-4] c.t.d.m.e.PlanConvertTaskExecutor : 96fd27d1-61d6-40cf-b9f9-941664952763 2023-03-06 21:35:03.898 INFO 19424 --- [onverter-exec-4] c.t.d.m.e.PlanConvertTaskExecutor : \tmp\2023-03-06T14_34_27\tmp_file.csv 2023-03-06 21:35:03.898 INFO 19424 --- [onverter-exec-4] c.t.d.m.e.PlanConvertTaskExecutor : 40
使用版本
- spring-boot-starter-parent: 2.7.8
- spring-boot-starter-data-jpa: 2.7.8
- hibernate-core: 5.6.14.Final
问题原因及解决方案
原因
getReferenceById返回的是Hibernate懒加载代理对象,而非真实实体。代理对象仅在首次访问非ID属性时才会触发数据库查询完成初始化。日志中第一次判断numberOfLines为null,后续调用又能获取值,是因为第一次调用时代理初始化未完成,后续调用时已完成数据库查询。
解决方案
- 替换为findById方法:
findById会立即查询数据库并返回真实实体对象,避免懒加载代理的初始化问题:FileMetadata fileMetadata = fileMetadataRepository.findById(id).orElseThrow(() -> new EntityNotFoundException()); - 主动初始化代理对象:在使用代理对象前,调用
Hibernate.initialize()强制完成初始化:FileMetadata fileMetadata = fileRawData.getFileMetadata(); Hibernate.initialize(fileMetadata); // 强制初始化 if (fileMetadata.getNumberOfLines() == null) { // 业务逻辑 } - 修改关联的Fetch策略:将
FileRawData中@ManyToOne的fetch属性改为FetchType.EAGER(不推荐,会导致关联实体被立即加载,影响性能,仅适合关联数据必然被使用的场景):@ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "file_metadata_id", nullable = false) private FileMetadata fileMetadata;
内容的提问来源于stack exchange,提问作者Minh Trần
相关产品推荐
相关产品推荐

