如何使用 Spring Data JPA(Hibernate) 跨映射表过滤关联实体?

Posted

技术标签:

【中文标题】如何使用 Spring Data JPA(Hibernate) 跨映射表过滤关联实体?【英文标题】:How to filter association entities across a mapping table using Spring Data JPA(Hibernate)? 【发布时间】:2021-09-02 22:09:15 【问题描述】:

有没有办法使用@OneToMany@ManyToOne 在中间实体(映射表)中过滤软删除的多对多关联?

productproduct_option_group 是 N:M 关系。我正在使用disabled_datetime 列实现软删除,并希望从Product 实体中过滤ProductOptionGroup 的集合。 This post 正在使用 @ManyToMany@Where 来实现这一点。我跟着它工作了(禁用product_option_groups 从product.getProductOptionGroups() 中过滤)。请注意 @Where 上的 ProductOptionGroup 类。

// `product` <-> `product-product_option_group` <-> `product_option_group`

@Entity
@Table(name = "product")
public class Product implements Serializable 
    ...

    @ManyToMany
    @JoinTable(name = "product-product_option_group",
            joinColumns = @JoinColumn(name = "product_id"),
            inverseJoinColumns = @JoinColumn(name = "product_option_group_id"))
    private final Set<ProductOptionGroup> productOptionGroups = new HashSet<>();

    ...


@Entity
@Table(name = "product_option_group")
@Where(clause = "disabled_datetime is null")
public class ProductOptionGroup implements Serializable 
    ...

    @Column(name = "disabled_datetime")
    private ZonedDateTime disabledDatetime;

    ...

但我想将@OneToMany 用于product-product_option_group 表,就像这样。

@Entity
@Table(name = "product")
public class Product implements Serializable 
    ...

    @OneToMany(mappedBy = "id.product")
    private final Set<ProductProductOptionGroup> productProductOptionGroups = new HashSet<>();

    ...


@Entity
@Table(name = "`product-product_option_group`")
public class ProductProductOptionGroup implements Serializable 
    @EmbeddedId
    private final ProductProductOptionGroupId id = new ProductProductOptionGroupId();

    ...


@Embeddable
public class ProductProductOptionGroupId implements Serializable 
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id", referencedColumnName = "id")
    private Product product;

    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "product_option_group_id", referencedColumnName = "id")
    @Where(clause = "disabled_datetime is null")
    private ProductOptionGroup productOptionGroup;


@Entity
@Table(name = "product_option_group")
@Where(clause = "disabled_datetime is null")
public class ProductOptionGroup implements Serializable 
    ...

但是@Where 注释将不再起作用,因此禁用的product_option_groups 也从product.getProductProductOptionGroups().stream().map(o -&gt; o.getId().getProductOptionGroup().collect(Collectors.toList()) 中选择。如何解决?

【问题讨论】:

【参考方案1】:

我想在等待答案时添加解决方法。

首先,我可以添加一个视图作为过滤器。

CREATE VIEW `product-product_option_group-enabled` AS
SELECT *
FROM `product-product_option_group` ppog
JOIN product_option_group pog ON ppog.product_option_group_id = pog.id
    AND pog.disabled_datetime IS NULL;
@Entity
@Table(name = "`product-product_option_group-enabled`") // using the view as a filter
public class ProductProductOptionGroupEnabled implements Serializable 
    @EmbeddedId
    private final ProductProductOptionGroupId id = new ProductProductOptionGroupId();

    ...

然后使用ProductProductOptionGroupEnabled 进行读取和ProductProductOptionGroup 进行任何数据修改可能会解决问题。我不喜欢这个。

一个实体的多个集合。修改时要小心。 同一张表上有 2 个不同的实体。编排这些可能很困难(equals()hashCode())。 添加视图。更改架构时最好小心。

也许我可以使用Updatable and Insertable Views,但我仍然需要ProductProductOptionGroup 来选择每一行。


其次,我可以从 Java 中过滤集合。

@Entity
@Table(name = "product")
public class Product implements Serializable 
    ...

    @OneToMany(mappedBy = "id.product")
    private final Set<ProductProductOptionGroup> productProductOptionGroups = new HashSet<>();

    public Set<ProductProductOptionGroup> getProductProductOptionGroups() 
        return productProductOptionGroups.stream()
                .filter(o -> o.getId().getProductOptionGroup().getDisabledDatetime() == null)
                .collect(Collectors.toSet());
    

    public Set<ProductProductOptionGroup> getProductProductOptionGroupsAll() 
        return productProductOptionGroups;
    
    
    ...

然后使用getProductProductOptionGroups() 获取过滤后的集合。我也不喜欢这个。

无论disabled_datetime 是什么,它都会从product-product_option_group 表中选择每一行。 创建另一个集合。修改时一定要小心。 javax.persistence.EntityNotFoundException: Unable to find with id xxx 可能会发生。可以通过join fetch解决麻烦的实体来解决

【讨论】:

【参考方案2】:

WHERE 子句将过滤功能添加到 FROM-SELECT 结构中。在任何 JPQL 查询中,您都必须从数据库中检索选择性对象。在我看来,您应该检查@where 的正确实现,请参阅以下reference。

【讨论】:

我将org.hibernate.annotations.Where 用于@Where 注释。我找不到任何其他实现。 @Where 的正确实现是什么? 遗憾的是,org.hibernate.annotations.Where 只接受 SQL,不接受 JPQL。

以上是关于如何使用 Spring Data JPA(Hibernate) 跨映射表过滤关联实体?的主要内容,如果未能解决你的问题,请参考以下文章

spring.datasource.data 拾取,但脚本从未执行

使用 spring-data-jpa 获取这些数据如何更正确?

处理 JPA 规范和 spring-data-jpa 时如何使用声明 Stream 作为返回类型

如何使用 spring4+spring-data-jpa(hibernateJpaVendorAdapter)+multidatasource+one entityManager+jpaReposit

从控制台应用程序使用带有休眠功能的spring-data-jpa时如何延迟加载收集

如何使用 Spring Data JPA 连接两个表