【问题标题】:Spring Data JPA Specification for a ManyToMany Unidirectional Relationship多对多单向关系的 Spring Data JPA 规范
【发布时间】:2015-10-28 18:11:50
【问题描述】:

我有一个设置,猫可以由多个主人拥有,每个主人可以拥有几只猫。鉴于此,我想编写一个规范来帮助我找到具有给定所有者名称的所有猫。

这是一个简单的类设置。

@Entity
public class Cat extends AbstractEntity {
  @Column
  private String name;
}

* 为简洁起见,没有 getter/setter。 id 字段在超类中。

@Entity
public class Owner extends AbstractEntity {
  @Column
  private String name;

  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(name = "OWNER_2_CATS", 
        joinColumns = @JoinColumn(name = "OWNER_ID"),
        inverseJoinColumns = @JoinColumn(name = "CAT_ID"))
  @OrderColumn(name = "order_column")
  private List<Cat> cats = Lists.newArrayList();
}

* 为简洁起见,没有 getter/setter。 id 字段在超类中。

这是一个存储库,其中包含一个有效的查询和一个无效的规范。

public interface CatRepository extends AtomicsRepository<Cat, Long> {

  // This query works.
  @Query("SELECT c FROM Owner o INNER JOIN o.cats c WHERE o.name = ?")
  List<Cat> findAllByOwner(String ownerName);

  // But how do I accomplish this in a specification?
  public static class Specs {
    static Specification<Cat> hasOwnerName(final String ownerName) {
      return (root, query, cb) -> {
        // These next lines don't work! What do I put here?
        Root<Owner> owner = query.from(Owner.class);
        owner.join("cats");
        return cb.equal(owner.get("name"), ownerName);
      };
    }
  }
}

请帮我写规范。

我遇到的问题是这种关系似乎需要违背关系的表达方式:所有者有一个cats 列表,但猫没有owners 列表。

【问题讨论】:

  • 你想达到什么目的?
  • catRepository.findAll(CatRepository.Specs.hasOwnerName("Bob") 应该返回 Bob 拥有的所有猫。

标签: java spring jpa


【解决方案1】:

概述

此规范的棘手之处在于您查询的是与 Owner 没有直接关系的 Cat。

总体思路是:

  • 抓住所有者。
  • 使用 Owner.cats 关系成员关系将 Owner 与 Cat 关联起来。我们可以只使用实体来做到这一点,并且像 JPA 一样为我们处理实体 @Id 相关性。
  • 将Cat 上的查询标记为distinct。为什么?因为这是一个@ManyToMany 关系,根据所使用的Owner 标准,任何给定的Cat 都可能有多个匹配的Owner。

方法 1 - 子查询

我的首选方法是使用子查询来引入 Owner:

// Subquery using Cat membership in the Owner.cats relation
public static class Specs {
    static Specification<Cat> hasOwnerName(final String ownerName) {
        return (root, query, cb) -> {
            query.distinct(true);
            Root<Cat> cat = root;
            Subquery<Owner> ownerSubQuery = query.subquery(Owner.class);
            Root<Owner> owner = ownerSubQuery.from(Owner.class);
            Expression<Collection<Cat>> ownerCats = owner.get("cats");
            ownerSubQuery.select(owner);
            ownerSubQuery.where(cb.equal(owner.get("name"), ownerName), cb.isMember(cat, ownerCats));
            return cb.exists(ownerSubQuery);
        };
    }
}

Hibernate 4.3.x 会生成如下 SQL 查询:

select cat0_.id as id1_1_
from cat cat0_
where 
    exists (
        select owner1_.id from owner owner1_ where
            owner1_.name=?
            and (cat0_.id in (
                select cats2_.cat_id from owner_2_cats cats2_ where owner1_.id=cats2_.owner_id
            ))
    )

方法 2 - 笛卡尔积

另一种方法是使用笛卡尔积来介绍所有者:

// Cat membership in the Owner.cats relation using cartesian product
public static class Specs {
    static Specification<Cat> hasOwnerName(final String ownerName) {
        return (root, query, cb) -> {
            query.distinct(true);
            Root<Cat> cat = root;
            Root<Owner> owner = query.from(Owner.class);
            Expression<Collection<Cat>> ownerCats = owner.get("cats");
            return cb.and(cb.equal(owner.get("name"), ownerName), cb.isMember(cat, ownerCats));
        };
    }
}

Hibernate 4.3.x 会生成如下 SQL 查询:

select cat0_.id as id1_1_
from cat cat0_
cross join owner owner1_
where
    owner1_.name=?
    and (cat0_.id in (
        select cats2_.cat_id from owner_2_cats cats2_ where owner1_.id=cats2_.owner_id
    ))

【讨论】:

  • 如果你有分页怎么办?如何实现这个功能?
【解决方案2】:

您可以创建一个 IN 谓词,通过子查询获取猫的 id 值。

public static class Specs {
  static Specification<Cat> hasOwnerName(final String ownerName) {
    return (root, query, cb) -> {
      //EntityType<Cat> Cat_ = root.getModel();
      final Subquery<Long> queryOwner = query.subquery(Long.class);// Check type of the Cat's ID attribute
      final Root<Owner> aliasOwner = queryOwner.from(Owner.class);
      //EntityType<Owner> Owner_ = aliasOwner.getModel();
      //final Join<Owner, Cat> aliasCatsOwner = aliasOwner.join(Owner_.cats);
      final Join<Owner, Cat> aliasCatsOwner = aliasOwner.join("cats");
      //queryOwner.select(aliasCatsOwner.<Long> get(Cat_.id)));
      queryOwner.select(aliasCatsOwner.<Long> get("id")));// Check type and name of the Cat's ID attribute
      queryOwner.where(cb.equal(Owner.<String> get("name"), ownerName));
      //return cb.in(root.get(Cat_.id).value(queryOwner);
      return cb.in(root.get("id").value(queryOwner);//check the name of ID attribute!
    };
  }
}

【讨论】:

    【解决方案3】:

    简短的回答是这样的:

     Page<Cat> findByOwner_Name(String ownerName, Pageable p);
    

    但难点在于如何查询那些不属于任何人的猫,等于

    select * from cat where cat.id NOT IN (select cat.id from owner_has_cat);
    

    请问有人知道吗?

    【讨论】:

      猜你喜欢
      • 2017-01-09
      • 2017-08-19
      • 2018-05-07
      • 2017-10-28
      • 2018-04-14
      • 1970-01-01
      • 1970-01-01
      • 2020-09-12
      • 1970-01-01
      相关资源
      最近更新 更多