Hibernate的OneToMany集合返回空元素问题排查
问题描述
从数据库获取PreAlignment实体及其关联集合时,集合中出现空对象,但直接查询PostgreSQL数据库却无空数据,两者结果矛盾。
执行JPQL查询select pa from PreAlignment pa left join fetch pa.preAlignmentSources order by pa.id asc后,在Spring Batch处理器中发现preAlignmentSources集合存在空元素;但在控制台执行SQL语句select * from pre_alignment_sources where pre_alignment_id = 4151;,返回两条有效数据。
调试窗口截图

(Spring Batch Job运行时,在处理器第36行停顿的调试界面,显示集合包含空元素)
数据库查询结果截图

(控制台执行上述SQL的结果,order列值为0和1)
相关代码文件
PreAlignment 实体
package nathanlively.subalignercss.adapter.out.persistence; import jakarta.persistence.*; import lombok.*; import nathanlively.subalignercss.Models.BaseEntity; import org.hibernate.Hibernate; import java.io.Serial; import java.io.Serializable; import java.util.*; @Getter @Setter @Entity @ToString @NoArgsConstructor @AllArgsConstructor @Builder @Table(name = "pre_alignments") public class PreAlignment extends BaseEntity implements Serializable { @Serial private static final long serialVersionUID = 3564048730879366680L; @Id @GeneratedValue private Long id; @Builder.Default() @OneToMany(mappedBy = "preAlignment", cascade = CascadeType.ALL, orphanRemoval = true) @ToString.Exclude @OrderColumn private List<PreAlignmentSource> preAlignmentSources = new LinkedList<>(); @Builder.Default @Column(name = "is_from_manufacturer") private Boolean isFromManufacturer = false; @Builder.Default @Column(name = "is_from_user") private Boolean isFromUser = false; @Builder.Default @Column(name = "is_public", nullable = false) private Boolean isPublic = false; @Column(name = "score") private float score; @Column(name = "notes") private String notes; public void addPreAlignmentSource(PreAlignmentSource preAlignmentSource) { preAlignmentSources.add(preAlignmentSource); preAlignmentSource.setPreAlignment(this); } public void removePreAlignmentSource(PreAlignmentSource preAlignmentSource) { preAlignmentSources.remove(preAlignmentSource); if(preAlignmentSource != null) { preAlignmentSource.setPreAlignment(null); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; PreAlignment that = (PreAlignment) o; return getId() != null && Objects.equals(getId(), that.getId()); } @Override public int hashCode() { return getClass().hashCode(); } }
PreAlignmentSource 实体
package nathanlively.subalignercss.adapter.out.persistence; import jakarta.persistence.*; import lombok.*; import nathanlively.subalignercss.Models.BaseEntity; import org.hibernate.Hibernate; import java.io.Serial; import java.io.Serializable; import java.util.Objects; @Getter @Setter @Entity @ToString @NoArgsConstructor @AllArgsConstructor @Builder @Table(name = "pre_alignment_sources") public class PreAlignmentSource extends BaseEntity implements Serializable { @Serial private static final long serialVersionUID = -5343480867438042527L; @Id @GeneratedValue @Column(nullable = false) private Long id; @ToString.Exclude @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "pre_alignment_id") private PreAlignment preAlignment; @ToString.Exclude @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinColumn(name = "speaker_id", nullable = false) private Speaker speaker; @Builder.Default @Column(nullable = false) private Boolean polarity = false; // false = 0 = normal; true = 1 = inverted; @Builder.Default @Column(nullable = false) private Float delay = 0F; @Builder.Default @Column(nullable = false) private Float gain = 0F; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false; PreAlignmentSource that = (PreAlignmentSource) o; return getId() != null && Objects.equals(getId(), that.getId()); } @Override public int hashCode() { return getClass().hashCode(); } }
BatchConfiguration中cleanDatabaseJob的removeNullSourcePreAlignments步骤
@Bean public Step removeNullSourcePreAlignments(JobRepository jobRepository, PlatformTransactionManager transactionManager, JpaItemWriter<PreAlignment> preAlignmentWriter, NullSourceItemProcessor nullSourceItemProcessor) { return new StepBuilder("removeNullSourcePreAlignments", jobRepository) .<PreAlignment, PreAlignment>chunk(100, transactionManager) .reader(preAlignmentJpaItemReader()) .processor(nullSourceItemProcessor) .writer(preAlignmentWriter) .build(); } @Bean public JpaCursorItemReader<PreAlignment> preAlignmentJpaItemReader() { return new JpaCursorItemReaderBuilder<PreAlignment>() .name("preAlignmentJpaItemReader") .entityManagerFactory(entityManagerFactory) .queryString("select pa from PreAlignment pa left join fetch pa.preAlignmentSources order by pa.id asc") .build(); } @Bean public NullSourceItemProcessor nullSourceItemProcessor() { return new NullSourceItemProcessor(); } @Bean public JpaItemWriter<PreAlignment> preAlignmentWriter() { return new JpaItemWriterBuilder<PreAlignment>() .entityManagerFactory(entityManagerFactory) .build(); }
NullSourceItemProcessor
package nathanlively.subalignercss.BatchProcessing.CleanDatabase; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import nathanlively.subalignercss.adapter.out.persistence.PreAlignment; import nathanlively.subalignercss.adapter.out.persistence.PreAlignmentSource; import org.springframework.batch.item.ItemProcessor; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Objects; @Log4j2 @RequiredArgsConstructor public class NullSourceItemProcessor implements ItemProcessor<PreAlignment, PreAlignment> { private static int counter = 0; @Override @Transactional public PreAlignment process(@NotNull PreAlignment preAlignment) throws Exception { counter++; if (counter % 100 == 0) { log.info("Searching for null sources. Processing hundredth PreAlignment with id: " + preAlignment.getId()); } List<PreAlignmentSource> sourcesToRemove = preAlignment.getPreAlignmentSources().stream() .filter(Objects::isNull) .toList(); if (!sourcesToRemove.isEmpty()) { log.info("Found null source on preAlignment " + preAlignment.getId() + ". Attempting to remove."); for (PreAlignmentSource source : sourcesToRemove) { preAlignment.removePreAlignmentSource(source); } return preAlignment; } else { return null; } } }
内容的提问来源于stack exchange,提问作者Nathan Lively
相关产品推荐
相关产品推荐

