用于ManyToMany单向关系的Spring Data JPA规范

MrQ*_*rrt 4 java spring jpa

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

这是一个简单的类设置.

@Entity
public class Cat extends AbstractEntity {
  @Column
  private String name;
}
Run Code Online (Sandbox Code Playgroud)

*没有getters/setters的简洁.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();
}
Run Code Online (Sandbox Code Playgroud)

*没有getters/setters的简洁.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);
      };
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

请帮我写规范.

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

Rya*_*son 14

概观

使用此规范的棘手问题在于您查询Cat所有者没有直接关系.

一般的想法是:

  • 抓住所有者.
  • 使用Owner.cats关系成员资格将所有者Cat相关联.我们可以仅使用实体来完成此操作,并且像JPA一样为我们处理实体@Id相关性.
  • Cat上的查询标记为不同.为什么?因为这是@ManyToMany关系,并且根据所使用的所有者标准,任何给定的Cat可能有多个匹配的所有者.

方法1 - 子查询

我首选的方法是使用子查询来介绍所有者:

// 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);
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个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
            ))
    )
Run Code Online (Sandbox Code Playgroud)

方法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));
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

哪个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
    ))
Run Code Online (Sandbox Code Playgroud)