복붙노트

[SPRING] ManyToMany 단방향 관계에 대한 Spring Data JPA 사양

SPRING

ManyToMany 단방향 관계에 대한 Spring Data JPA 사양

많은 소유자가 고양이를 소유 할 수있는 설정이 있으며 각 소유자는 여러 고양이를 소유 할 수 있습니다. 이를 감안할 때 주어진 소유자 이름을 가진 모든 고양이를 찾는 데 도움이되는 사양을 작성하고 싶습니다.

다음은 간단한 클래스 설정입니다.

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

* 간결성을위한 게터 / 세터가 없습니다. 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();
}

* 간결성을위한 게터 / 세터가 없습니다. 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);
      };
    }
  }
}

사양 작성을 도와주세요.

내가 문제를 겪고있는 것은이 관계가 관계가 표현되는 방식에 맞지 않아야한다는 것입니다. 소유자는 고양이 목록을 가지고 있지만 고양이는 소유자 목록을 가지고 있지 않습니다.

해결법

  1. ==============================

    1.이 사양의 까다로운 점은 소유자와 직접적인 관계가없는 Cat을 쿼리한다는 것입니다.

    이 사양의 까다로운 점은 소유자와 직접적인 관계가없는 Cat을 쿼리한다는 것입니다.

    일반적인 아이디어는 다음과 같습니다.

    내가 선호하는 접근 방식은 하위 쿼리를 사용하여 소유자를 소개하는 것입니다.

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

    다음과 같은 SQL 쿼리를 생성하는 Hibernate 4.3.x

    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
                ))
        )
    

    대안은 직교 제품을 사용하여 소유자를 소개하는 것입니다.

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

    다음과 같은 SQL 쿼리를 생성하는 Hibernate 4.3.x

    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. ==============================

    2.하위 쿼리로 cat의 id 값을 가져 오는 IN 술어를 만들 수 있습니다.

    하위 쿼리로 cat의 id 값을 가져 오는 IN 술어를 만들 수 있습니다.

    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. ==============================

    3.짧은 대답은 다음과 같습니다.

    짧은 대답은 다음과 같습니다.

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

    그러나 어려움은 누군가 소유하지 않은 고양이를 쿼리하는 방법입니다.

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

    누구든지 아이디어가 있습니까?

  4. from https://stackoverflow.com/questions/31841471/spring-data-jpa-specification-for-a-manytomany-unidirectional-relationship by cc-by-sa and MIT license