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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:14:53