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

使用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,后续调用又能获取值,是因为第一次调用时代理初始化未完成,后续调用时已完成数据库查询。

解决方案

  1. 替换为findById方法:findById会立即查询数据库并返回真实实体对象,避免懒加载代理的初始化问题:
    FileMetadata fileMetadata = fileMetadataRepository.findById(id).orElseThrow(() -> new EntityNotFoundException());
    
  2. 主动初始化代理对象:在使用代理对象前,调用Hibernate.initialize()强制完成初始化:
    FileMetadata fileMetadata = fileRawData.getFileMetadata();
    Hibernate.initialize(fileMetadata); // 强制初始化
    if (fileMetadata.getNumberOfLines() == null) {
        // 业务逻辑
    }
    
  3. 修改关联的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 05:25:32