본문 바로가기
개발/JPA

[JPA] Specification을 이용하여 쿼리 조건 처리하기 - 외래키

by ynzu🤍 2022. 1. 7.
반응형

 

Specification

Repository에 JpaSpecificationExecutor<T> 인터페이스를 상속받으면 Specification을 사용할 수 있다.

@Repository
public interface GrandChildRepository extends JpaRepository<GrandChild, GrandChildId>, JpaSpecificationExecutor<GrandChild> {
}

 

※참고 : 복합키이기 때문에 JpaRepository<T, ID>를 상속받을 때 ID부분에 Id class를 넣어주면 된다. 관련 내용은 아래 포스팅을 참고하자

 

[JPA] 복합키, 외래키 Entity 설정하기(@IdClass를 사용하여 식별관계 매핑)

올해 초 복합키, 외래키 사용이 많은 테이블을 JPA Entity로 구현했어야 했는데, JPA 사용이 처음이였어서 익숙하지 않아 복잡하게 느껴졌었다. 나같은 사람을 위해 정리해 본다! Entity에 복합키

ynzu-dev.tistory.com

 

예제1

먼저 간단하게 grand_child_name 칼럼에 대해 조회하는 Specification을 작성해보면 아래와 같다.

(람다식은 Specification의 toPredicate() 메소드를 구현한 것)

public GrandChildSpecification {

  public static Specification<GrandChild> searchName(String name) {
      return (Specification<GrandChild>) ((root, query, builder) -> 
                builder.equal(root.get("GrandChildName"), name)
      );
  }
  
}

root.get("GrandChildName")을 통해 GrandChild 엔티티의 GrandChildName이 매개변수 name 일치하는지 확인하고 해당하는 GrandChild 객체를 반환한다.

 

이렇게 작성한 Specification을 이용하여 원하는 조건에 따라 조회가 가능하다.

grandChildRepository.findAll(GrandChildSpecification.searchName("홍길동"));

 

 

예제2

하지만 검색조건은 하나가 아니라 여러개이고, 외래키도 조회할 수 있어야 했기에 아래와 같이 작성하였다.

public class GrandChildSpecification {

    public enum SearchKey {
        PARENTID("parentId"),
        CHILDID("childId"),
        GRANDCHILDID("grandChildId"),
        GRANDCHILDNAME("grandCildName");

        private final String value;

        SearchKey(String value) {
            this.value = value;
        }

        public String getValue() {
            return value;
        }
    }

    public static Specification<GrandChild> searchWith(Map<SearchKey, Object> searchKeyword) {
        return (root, query, builder) -> {
            List<Predicate> predicate = getPredicateWithKeyword(searchKeyword, root, builder);
            return builder.and(predicate.toArray(new Predicate[0]));
        };

    }

    private static List<Predicate> getPredicateWithKeyword(Map<SearchKey, Object> searchKeyword, Root<GrandChild> root, CriteriaBuilder builder) {
        List<Predicate> predicate = new ArrayList<>();
        for (SearchKey key : searchKeyword.keySet()) {

            //Like 절 사용 시
            String likeValueString = "%" + searchKeyword.get(key) + "%";

            switch (key) {
                case GRANDCHILDID:
                    predicate.add(builder.like(
                            root.get(key.value), likeValueString
                    ));
                    break;
                case GRANDCHILDNAME:
                    predicate.add(builder.like(
                            root.get(key.value), likeValueString
                    ));
                    break;
                case PARENTID:
                    predicate.add(builder.equal(root.<CHILD>get("child").<PARENT>get("parent"), searchKeyword.get(key)
                    ));
                    break;
                case CHILDID:
                    predicate.add(builder.equal(root.<CHILD>get("child"), searchKeyword.get(key)
                    ));
                    break;
            }
        }
        return predicate;
    }

}

여기서 주의깊게 봐야할 부분은 PARENTID와 CHILDID이다. 

parent_id와 child_id는 GrandChild 엔티티의 외래키이며  GrandChild 엔티티에서 parent_id 데이터를 가져오기 위해서는 grandChild.getChild().getParent().getParentId() , child_id 데이터를 가져오기 위해서는 grandChild.getChild().getChildId()를 해야한다.

엔티티에서 데이터를 가져올 때의 depth를 생각하면서  세팅해주자

 

 

화면에서 받은 조회 조건을 searchRequest map에 세팅하였고, null인 조건을 제외하면서 searchWith 메소드의 매개변수에 맞는 map을 생성하여 조건에 해당하는 데이터를 조회했다.

//검색 조건 세팅
Map<String, Object> searchRequest = new HashMap<>();
searchRequest.put("grandChildId", vo.getSrcGrandChildId());
searchRequest.put("grandCildName", vo.getSrcGrandChildName());
searchRequest.put("parentId", vo.getSrcParentId());
searchRequest.put("childId", vo.getSrcChildId());

// where 조건 Parameter Map
Map<DeviceSpecification.SearchKey, Object> searchKeys = new HashMap<>();
// Parameter 순차적으로 세팅
for (String key : searchRequest.keySet()) {
	String value = String.valueOf(searchRequest.get(key));
	if (value != null && !value.isEmpty() && !"null".equals(value)) {
		searchKeys.put(GrandChildSpecification.SearchKey.valueOf(key.toUpperCase()), searchRequest.get(key));
	}
}

this.grandChildSpecificationRepository.findAll(GrandChildSpecification.searchWith(searchKeys));

 

 

728x90
반응형

댓글