为什么 Hibernate 在 OneToMany 集合中返回一些空项?

问题描述 投票:0回答:1

从数据库中获取实体及其集合包含空对象。这与 postgresql 查询形成对比,后者不返回空对象。

我尝试在处理器中进行 JPQL 查询

select pa from PreAlignment pa left join fetch pa.preAlignmentSources order by pa.id asc
,它发现空集合项。同时,如果我在控制台中运行相同的查询
select * from pre_alignment_sources where pre_alignment_id = 4151;
,它只会返回两行没有任何空对象的好行。

这是我运行 Spring Batch Job 并在处理器中的第 36 行停止时在调试窗口中的样子。

这是在控制台中运行

select * from pre_alignment_sources where pre_alignment_id = 4151;
的结果。顺序栏被截掉了,但分别写着0和1。

文件

预对齐实体

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();
    }
}

预对齐源实体

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();
    }
}

removeNullSourcePreAlignments 进入 BatchConfiguration 内的 cleanDatabaseJob 内部

@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();
    }

空源项目处理器

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;
        }

    }
}
spring postgresql spring-batch jpql
1个回答
0
投票

这是设计使然。如果没有找到匹配,Left Join 应该返回 null 值。

这是 JPA 查询:

select pa from PreAlignment pa left join fetch pa.preAlignmentSources order by pa.id asc

它被翻译成原生 SQL,如下所示:

select pa.id, pas.pre_alignment_id from pre_alignments pa left join pre_alignment_sources pas on pa.id = pas.pre_alignment_id order by pa.id asc

如果 pa.id 没有与 pas.pre_alignment_id 对应的列,则仍返回 pa.id,但 pas.pre_alignment_id 将为 null。

例如,如果 pre_alignments 具有以下值:

id
1
2

并且 pre_alignment_sources 具有以下值:

id pre_alignment_id
1      1
2      1

那么查询的结果将是:

pa.id     pas.pre_alignment_id
1          1
1          1
2          null

这就是结果中存在空元素的原因

© www.soinside.com 2019 - 2024. All rights reserved.