시나몬 브레드
개발과 관련된 이야기를 하는 곳입니다. 쉽게 찾을 수 있는 소개 형식의 글 보다는 실무를 통해 얻어진 깊이있는 정보와 전체 흐름을 이해할 수 있는 지식을 다루려고 노력중입니다.
spring-data-jpa + querydsl 로 개발하기 - 2


spring-data-jpa + querydsl 로 개발하기 - 1 에서 이어지는 글입니다.



3단계 - 네임드 쿼리

@Query, @NamedQuery 어노테이션


다른 테이블과의 조인이 필요하거나 where 절이 꽤 복잡한 쿼리라면 method 이름만 가지고 모두 표현하기에는 무리가 있다.

3단계는 복잡한 쿼리를 만드는 가장 쉬운 방법으로 저장소 인터페이스에 method를 생성하고 선언부에 @Query 어노테이션을 사용하는 방법이다. @Query 어노테이션을 사용하는 method의 이름은 2단계 쿼리 메서드규칙의 영향을 받지 않으므로 원하는데로 지으면 된다.



여기서 작성되는 쿼리는 JPQL 로 JPA의 쿼리 문법이다. 옵션을 통해 native SQL로 작성도 가능하다.



이 단계도 쿼리가 잘못 작성되었을 경우에 spring로딩에 실패하면서 프로그램이 실행되지 않는다. 그러나 편집기 상에서의 유효성 분석은 해주지 않는다. 그래서 필자는 쿼리 어노테이션은 type safe 하지 않은 것으로 보고 잘 사용하지는 않는 편이다.


@NamedQuery 어노테이션은 repository 인터페이스의 상단 선언부에 쿼리를 작성한후 이름을 부여하는 방식이다. 메서드 선언부에서는 쿼리의 이름을 지정해서 실행할 쿼리를 알려준다.





4단계 - predicate

predicate (specifications) 사용


3단계의 @Query 어노테이션은 쿼리가 너무 길어지면 코드관리가 어려워 질 수 있고, type safe 한 방식도 아니다. 또한 조건에 따라 쿼리가 틀려지는 동적쿼리는 작성할 수가 없다. 이제부터는 queryDsl을 이용한 자바코드 쿼리를 작성하는 방법을 사용한다.


4단계는 쿼리를 작성하는 객체를 통해서 복잡한 쿼리를 생성하는 방식이다. 프로그램 구조상에 predicate라는 하나의 단계가 더 추가되었다고 생각하면된다.

기존에는 controller -> service -> repository 의 3단계 구조로 개발을 했다면 controller -> service -> predicate -> repository 의 4단계로 바뀌는 것으로 생각해야 할듯 하다. repository 티어를 단순하게 유지하고 쿼리 코드만 따로 관리할 수 있는 방법이다.



queryDsl 설정

JPA 프로젝트에서 queryDsl을 사용하려면 아래와 같은 코드를 maven의 pom.xml 파일에 추가해주어야 한다.






위 코드는 Qdomain이라고 부르는 자바 코드를 생성(generate) 하는 플러그인이다.

QueryDsl을 통해 쿼리를 생성할때 바로 이Qdomain 객체를 사용한다. 생성된 QDomain 객체를 보면 알겠지만 실제 도메인 객체의 모든 필드들에 대해서 사용가능한 모든 operatoin 을 호출할 수 있는 메서드들이 정의 되어 있다.


처음에 이부분에 대해서 알았을때 필자는 queryDsl에 대해서 상당한 거부감이 있었다. 내가 작성하지 않은 코드가 내 프로젝트 안에 들어간다는 것도 이해하기 어렵고, 도메인객체가 수정될때마다 다시 생성을 해주어야 하는것인가? 에 대한 의문 때문이었다. 그러나 거의 걱정할 필요가 없다고 말할 수 있다. 위와 같이  설정해 놓으면 이클립스에서 도메인 객체를 수정하고 저장하면 곧바로 Qdomain 객체가 다시 만들어진다. 처음 설정이후에는 따로 뭘 할 필요가 없어서 개발에 추가적인 오버해드가 없다. 그리고 Qdomain 자바 소스 파일(.java) 자체가 위와 같이 target/generated-source 폴더 안에 따로 만들어지기 때문에 내가 개발하는 코드와 겹처지거나할 일이 없다. 그무엇보다 이런 작은 어색함을 통해 개발생산성을 끌어올리고 일찍 퇴근 할 수 있다면 감수할만하다.




이제 만들어져 있던 repository 를 querydsl을 사용할 수 있도록 수정해보자.


1. 만들어져 있던 Repository 인테페이스가 QueryDslPredicateExecutor를 상속하도록 수정




2. 쿼리 조건을 지정하는 predicate 객체 작성



이번 4단계만으로도 거의 대부분의 복잡한 쿼리도 작성이 가능할것이다. 다만 queryDsl의 predicate는 조인(join)을 직접 저장하는 것이 불가능하다. manyToOne의 관계같은 대부분의 상황에서는 문제가 없지만 아주 복잡한 join이 필요한 쿼리를 완벽하게 작성할 수 있는지는 필자도 확인중이다.

위 샘플 코드와 같이 다대다(ManyToMany)와 같은 관계일때는 userGrop.users.any()... 와 같이 하여 쿼리가 안되는 것은 아니나 실행을 시켜 보면 실제로 생성되는 SQL 문은 원하는 형태로 join이  작성되지 안았다. 쿼리 퍼포먼스에 민감한 프로그램 이라면 잘 고려해봐야 할듯하다. 몇가지 복잡한 상황만 제외하면 predicate 객체를 이용해서 아주 깔끔하게 데이터 접근코드 작성이 가능해 진다.



작성한 predicate 를 이용해서 쿼리 결과를 가져오는 코드



findAll() 이라는 메서드를 통해 쿼리 결과를 가져온다. pageable 도 사용가능해서 정렬 페이징 모두 가능하다.


표준 JPA 스팩를 통해 criteria 쿼리를 작성하는 방법

표준 JPA에도 자바 코드로 쿼리를 작성할 수 있는 Criteria라는 방식이 있다. spring data jpa에서 이 Criteria를 통해 만든 쿼리도 JpaRepository.findAll() 메서드를 이용해서 전달할 수 있는 Specifications라는 이름의 규격이 있다. 이 Criteria를 실제 로 사용해보면 queryDSL 보다 복잡하고 이해하기 어려웠다. 그리고 결정적으로 위에서도 지적했듯이 JPA criteria는  type safe 하지가 않다.



JPA 표준 Criteria와 queryDsl 방식을 비교한 글:

http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl




5단계 - 사용자 정의 쿼리


4단계와 5단계는 어느정도 취향에 따라 한가지 방식만 사용하는 것이 바람직할것으로 본다.

5단계는 spring-data-jpa 없이 표준적 JPA방식의 Repository를 개발하는것과 비슷하게 쿼리문을 직접 작성하고 EntityManager를 통해 전달하고 결과를 원하는 형태로 리턴하는 모든 과정을 직접 개발해야 한다. queryDslRepositorySupport 를 이용해서 좀더 편하게 작성할 수도 있다.


필자는 처음 spring-data-jpa를 도입한 프로젝트에서 JpaRepository 인터페이스의 도움을 받지 않고  repository 코드를 작성하였다.

JpaRepository 인터페이스를 상속받는 방법으로는 개발자가 직접 쿼리구현을 할 방법이 없다고 봤기때문이다. spring의 레퍼런스문서를 자세히 읽지 않으면 필자가 같은 실수를 할 수도 있다.


구현법을 보기 전에 아래 문제 상황을 해결할 방법을 생각해보면 구현방법의 이해가 빠를것이다.
프로젝트의 개발초기에는 위 단계와 같이 기본 CRUD 기능과 필요에 따라 Query emthod 만 추가하여 개발을 하다가, 복잡한 쿼리를 직접 개발해야만 하는 상황이 되었다. 기존 코드를 건드리지 않으면서 어떻게 특정 메서드만 queryDsl등을 이용한 직접 쿼리 코드작성을 할 수 있을까?JpaRepository 인터페이스를 상속 받은 domain repository를 구현하는 클래스를 만드는 방법은 직접해보면 알겠지만 불가능하다.



과정이 약간 복잡한데

spring에서 제시하는 방법을 아래와 같이 일단 클래스다이어그램으로 이해해보자.


필자의 경우 스프링의 레퍼런스 문서에 나온 내용만 보고는 위의 구조를 바로 이해하기가 힘들었다.


위와 같이 domain repository custom 인터페이스와 domain repository impl 클래스를 추가하는 구조로 기존의 domain repository 인터페이스와 그를 이용해서 개발된 코드를 전혀 수정하지 않고 원하는 메서드만 추가되게 할 수 있다.




1. 먼저 사용자 정의 쿼리를 작성할 메서드를 정의하는 인터페이스 생성


이단계에서 생성하는 인터페이스의 이름은 반드시 이전에 만들어져 있던 도메인 repository 와 같은 이름으로 시작해야 한다.  CompanyRepository 에 사용자 정의  쿼리 메서드를 작성하려면 CompanyRepository~로 시작해야 한다.




2. JpaRepository 인터페이스를 상속하는 repository 인터페이스가 첫단계에서 만든 사용자 정의 인터페이스를 상속하도록 수정





3. 첫단계에서 만든 사용자 정의 인터페이스를 구현하는 객체 생성및 queryDsl을 이용한 쿼리문 작성


여기서도 주의 할점은 사용자 정의 메서드를 구현하는 클래스의 이름이 첫단계에서 만들었던 인터페이스이름 + Impl로 끝나야 한다는 점이다. (이 문자는 설정파일에서 변경가능)

위 코드와 같이 따로 @Repository 어노테이션을 달지 않아도 스프링이 클래스 이름을 보고 해당 클래스가 domain repository의 사용자 정의 메서드를 구현하고 있다는 것을 알고 bean으로 등록해준다.




queryDslRepositorySupport 상속

사용자 정의 쿼리 메서드를 작성할때 queryDslRepositorySupport 상속하면 코드를 줄여줄만한 헬퍼 기능들을 제공한다.


유용한 메서드 소개


메서드명 

기능 

 from()

 query 객체를 생성해서 돌려준다.

 applyPagination()

 web 프로젝트 서포트 기능인 pageable 객체를 쿼리에 적용할 수 있다. pageable 객체에 http 요청을 통해서 입력받은 페이징 정보와 정렬 정보를 쿼리 에 적요시켜준다.





기존 query mehtod 와 사용자정의로 만들어진 메서드를 차례로 실행하는 코드 샘플 이미지 추가



위의 샘플 코드에서 기존에 CompanyReposotiry 만들어서 사용되던 코드들에 전혀 수정사항이 없이 search() 라는 메서드만 직접 작성할 수 있는 멋진 방법이라고 할 수 있다.


스프링 레퍼런스의 사용자정의 쿼리 메서드 만드는 법 소개


http://docs.spring.io/spring-data/jpa/docs/1.10.1.RELEASE/reference/html/#repositories.custom-implementations




샘플 소스코드

아래 필자의 개인 저장소에 이글에서 소개하는 샘플 프로젝트 소스를 볼 수 있다.

https://github.com/adrenalinee/sample/tree/master/sample-spring-data-jpa




결론

필자가 spring-data-jpa를 도입하면서 격었던 시행착오를 다른 개발자들은 줄여주고 JPA를 도입하기를 주저하는 개발자들이 있다면 다시 생각해보기를 바라는 마음으로 글을 쓰기 시작했는데 꽤길고 복잡한 글이 되어 버렸다. 어쨋든 필자가 강조하고 싶은 부분은 spring-data-~ 시리즈를 통해 점진적 개발을 해나갈 수 있다는 점이다. 개발초기에는 기본 repository를 통해 적은 코드만으로 빠르게 기본기능을 개발해나가다가 본격적으로 비지니스로직을 개발해나가는 시점이되면 repository 확장을 통해 점차로 복잡한 기능을 개발해나갈 수 있도록 설계가 되어 있다. 



  Comments,   0  Trackbacks
  • 쿼리자체가 복잡해지면 JPA방식이 xxBatis방식보다 난이도가 급상승해 보이는건 제가 아직 익숙치 않아서겠죠? ;;
    작년에 했던 플젝에서 JPA와 JDBCTemplete을 섞어서 사용했었는데, 나중에 보면 다들 JDBCTemplete만 사용하던 기억이 나네요;;
  • nayasis
    쿼리 자체가 복잡해지면... JPA 진영에서는 쿼리 자체가 복잡하면 도메인 설계가 잘못됬다고 이야기하죠. ^-^

    쿼리를 복잡하게 짜는 게 좋다는 것은 아닙니다만... 그렇게 할 수 밖에 없는 니즈에 대해서는 JPA로는 대응이 어렵다고 저도 공감하고 있습니다.

    해외에서 JPA로 많이 넘어간 까닭은, 데이터모델이 단순해진 탓이라고 봅니다. ^-^ (대신 데이터 흐름이 매우 정교해졌고요.)

    데이터 모델을 직접 핸들링할 수 있는 환경에서는 JPA를 마다할 이유가 없겠지만, 이런 선택의 자유가 없는 곳에서는 JPA가 오히려 독이라고 생각합니다. 특히 SI 프로젝트에서는.... @_@
  • 현수
    querydsl하고 spring-data가 연동이 되었었군요... querydsl 설정 까먹어서 찾고 있었는데.. 다음 프로젝트에서는 spring-data을 적극 활용해야겠네요. ㅎㅎ. 그리고 저도 도메인 모델이 점점 작아지고 SOA 같은 걸로 가면서 복잡한 쿼리에 대한 필요성이 감소하고 있다고 봅니다..
댓글 쓰기