2.3. Spring Data JPA, Querydsl, MySQL, @Query, @NamedQuery 실습예제
2.3.1. 새 프로젝트 생성
File > New > Spring Starter Project >
프로젝트 명: chapter2-3 > Next >
디펜던시 선택: Web, MySQL, JPA, Lombok > Finish
2.3.2. 프로젝트 환경 설정
pom.xml
# datasource
데이터베이스 연결정보를 설정합니다.
# JPA
JPA 환경설정을 추가합니다.
# Logging
로깅 환경설정 정보를 담고 있는 파일을 지정합니다.
log4jdbc.log4j2.properties
로거가 참조하는 정보를 담고 있는 파일을 resources 폴더에 생성합니다.
logback-spring.xml
로깅 환경설정 파일입니다.
2.3.3. 엔티티 클래스 생성
Lombok 설치
"부록 4. Lombok" 을 참고하여 롬복을 설치한 후에 다음을 진행합니다.
Dept.java
@Data
롬복이 제안하는 @Data 어노테이션을 클래스에 설정하면 다음 어노테이션을 붙인 것과 같습니다.
@Getter, @Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode
롬복은 컴파일 시 메소드들을 작성합니다. 따라서 @Data 어노테이션을 설정하는 것으로 해당 클래스가 Value Object로 작동하도록 만든 것과 같으며 롬복을 사용하면 코드가 짧아져서 개발이 빠르고 더불어 코드 관리가 쉽다는 장점이 있습니다.
@ToString(exclude={"staff"})
@Data 어노테이션은 @ToString 어노테이션을 포함하고 있으므로 @Data 어노테이션만 클래스에 설정해도 롬복이 클래스의 toString 메소드를 오버라이딩해서 구현합니다. 오버라이딩 된 toString 메소드는 해당 모델클래스가 객체일 때 취급하는 값을 문자열로 출력합니다. toString 메소드를 사용하여 표시할 때 객체 그래프 탐색에 의해서 무한루프에 빠지는 것을 막기 위해 staff 변수가 가리키는 값은 출력하지 않도록 배제하는 설정입니다.
@JsonIgnore
@ToString(exclude={"staff"}) 설정을 한 것과 비슷한 이유로 사용합니다. 다만 이 설정은 Jackson 라이브러리를 사용하여 객체를 JSON 포맷의 문자열로 바꿀 때 객체 그래프 탐색을 하다가 무한루프에 빠지는 것을 막기 위한 설정입니다.
Emp.java
Salgrade.java
2.3.4. 테스트 용 더미 데이터 입력
data.sql
파일 위치: src/main/resources/data.sql
2.3.5. 테스트
프로젝트를 기동한 후 로그를 살펴봅니다. 테스트 내용은 다음과 같습니다.
n 환경 설정에 문제가 있는지 여부
n 엔티티 클래스를 정상적으로 인식하여 테이블을 제대로 생성하는지 여부
HeidiSQL을 사용하여 데이터베이스에 접속합니다. 설치는 "부록 3. HeidiSQL"을 참고하세요.
testdb DB를 새로고침하여 emp, dept, salgrade 테이블들의 상태를 확인합니다.
EMP 테이블 스키마
EMP 테이블 데이터
DEPT 테이블 스키마
DEPT 테이블 데이터
SALGRADE 테이블 스키마
SALGRADE 테이블 데이터
2.3.6. Persistence Layer
스프링은 JpaRepository 인터페이스를 상속한 인터페이스 DeptRepository를 발견하면 구현 객체를 만들면서 JpaRepository 인터페이스와 부모 인터페이스에 정의된 메소드들을 만들어 넣고 대상 객체를 빈으로 관리합니다.
DeptRepository.java
EmpRepository.java
SalgradeRepository.java
2.3.7. 테스트
JUnit으로 테스트 시에도 프로젝트가 기동하면서 처리가 이루어지기 때문에 원하지 않는 경우 설정을 변경할 필요가 있습니다.
application.properties
spring.datasource.initialize=false
schema.sql, data.sql 파일을 처리하지 않도록 요청할 수 있습니다. DDL, DML 작업을 수행하고 싶지 않을 때 "false"로 설정합니다. 앞의 "5. 테스트" 과정에서 처리했기 때문에 다시 수행할 필요가 없습니다. 또는 DDL 쿼리문에 IF NOT EXISTS, DML 쿼리문에 IGNORE 같은 키워드를 설정하고 사용하는 방법도 있습니다.
spring.jpa.hibernate.ddl-auto=update
엔티티 클래스와 DB 테이블 사이에 차이점이 발견되면 테이블에 이를 반영하기 위해서 수정작업을 진행합니다. 엔티티 클래스를 건드리지 않았기 때문에 이 설정으로도 아무런 작업이 진행되지 않을 것입니다. "none" 옵션을 사용할 수도 있습니다.
JUnit 테스트 클래스 생성
테스트 코드를 배포대상에서 제외하기 위해서 별도 클래스 파일에 작성해서 사용하는 것이 좋습니다. test 패키지 하부에 배치하면 Maven으로 배포파일을 만들 때 자동적으로 제외됩니다.
DeptRepository.java 선택 > 마우스 오른쪽 클릭 > New > JUnit Test Case
New JUnit Test Case > Source folder > Browse 버튼 클릭
Source Folder Selection > src/test/java 선택 > OK 클릭
다음 화면에서 하단에 Finish 버튼을 클릭하여 작업을 마칩니다. 테스트 대상인 DeptRepository 인터페이스에 추가한 추상메소드가 없기 때문에 테스트 대상 메소드를 선택하는 작업은 수행하지 않습니다.
작업결과로 다음 클래스 파일이 생성됩니다.
DeptRepositoryTest.java
import static org.hamcrest.CoreMatchers.is;
매처 라이브러리의 메소드를 임포트하는 설정을 추가합니다. 사용할 때 짧게 메소드명만으로 사용할 수 있도록 임포트 시 static 키워드를 사용했습니다.
테스트 실행 : DeptRepositoryTest.java 선택 > 마우스 오른쪽 클릭 > Run As > JUnit Test 클릭
테스트 실행결과 : 로그
테스트 실행결과 : JUnit View
EmpRepository.java, SalgradeRepository.java 파일의 테스트 클래스는 직접 만들어서 추가해 보시기 바랍니다. Spring Data JPA가 지원하는 메소드들 중 findAll 메소드만 테스트해 보았는데 다른 메소드들도 테스트하면서 기본적인 CRUD 메소드 사용법을 살펴보시기 바랍니다.
API 문서나 책을 보는 것 만으로도 새로운 기술을 익힐 수 있겠지만 코드로 테스트하면서 학습하게 되면 기술의 이해도를 높일 수 있습니다. 스프링 기반으로 JUnit을 사용할 때 @Transactional 어노테이션을 클래스 또는 @Test 어노테이션이 붙어 있는 메소드에 설정하면 테스트가 끝난 후 자동으로 roll-back을 해주므로 부담없이 여러 테스트를 진행할 수 있어서 새로운 기술을 학습할 때 매우 편리합니다. API 문서나 책만으로 모든 궁금증을 해소할 수 없습니다. 따라서 테스트 코드를 사용하여 직접 궁금증을 해소해 나가면서 학습하는 방법을 권장합니다.
2.3.8. Service Layer
DeptRepository.java
우선 앞서 만든 DeptRepository.java에 추상 메소드 2개를 추가합니다.
Query Method
"findBy"+"모델 클래스의 필드변수명"으로 명명하는 메소드 작성방법은 Spring Data JPA가 제안하는 쿼리메소드 기술을 사용하기 위함입니다. 이 규칙을 따르면 별도로 이 메소드가 사용해야 하는 쿼리를 개발자가 따로 알려줄 필요가 없습니다. 스프링은 관습을 적용해서 메소드명만으로 메소드가 사용해야 하는 쿼리를 판단합니다. 쿼리메소드를 사용하면 추상메소드 선언만으로 개발자의 작업은 끝난 것이 됩니다.
자세한 쿼리메소드 작성법은 온라인 API 문서를 참조하시기 바랍니다.
DeptService.java
DeptServiceImpl.java
스프링 부트로 프로젝트를 만들고 디펜던시에 JDBC 또는 JPA가 선언되어 있다면 자동적으로 부트가 트랜잭션 처리를 위한 빈 설정을 진행합니다. 따라서 트랜잭션 처리를 원하는 클래스에 @Transactional 어노테이션을 붙이는 것만으로 트랜잭션 설정이 완료됩니다.
selectByDname, selectByLoc 메소드는 개발자가 추가한 쿼리메소드들과 연동합니다. 나머지 다른 메소드들은 JpaRepository 인터페이스와 그 부모 인터페이스에 선언된 메소드들과 연동합니다.
페이징 처리를 위한 로직입니다. page는 0베이스 인덱스 값을 사용합니다. 즉 첫 번째 페이지를 얻고자 하면 0값을 설정해야 합니다. size는 한 화면에 표시할 로우의 개수입니다. size값에 따라 페이지 개수가 변경될 것입니다. findAll 메소드는 Page<Dept> 객체를 리턴합니다. 이 객체 안에 있는 실제 데이터만 얻고 싶다면 getContent 메소드로 얻을 수 있습니다. 하지만 페이징 처리를 하는 것은 프리젠테이션 레이어이고 페이징 처리를 위한 부가적인 정보가 Page<Dept> 객체 안에 존재하기 때문에 그 상태 그대로 리턴하는 것이 좋습니다.
2.3.9. Presentation Layer
DeptController.java
public Object getDeptsLimitForPage(Pageable pageable)
page, size 를 키 값으로 원하는 값을 서버에 파라미터로 전달하면서 "/depts" URL로 접근하면 스프링에 의해서 자동적으로 파라미터 값들이 Pageable 객체에 담기고 메소드에 파라미터로 전달됩니다.
DeptServiceImpl 클래스에 많은 메소드들이 정의되어 있지만 컨트롤러에서는 테스트로 간단히 몇 개의 메소드만 사용해 보았습니다.
2.3.10. Paging
테이블에 데이터가 많을 경우 한 화면에 다 표시하는 것 보다는 사용자의 편의를 위해서 나누어서 보여주는 처리가 필요합니다. 이러한 작업을 페이징 처리라고 합니다.
CustomWebMvcConfigurer.java
configurer.enable()
WAS가 기본적으로 제공하는 정적리소스 및 JSP를 위한 URL 핸들러를 활성화 합니다.
@ImportResource("classpath:config/pageable-context.xml")
스프링 부트에서 XML 설정파일을 사용하는 방법입니다.
스프링 3.x 버전에서 설정은 주로 XML을 사용했으므로 레거시 프로젝트를 스프링 부트 프로젝트로 업그레이드 하고자 할 때 한번에 모든 설정을 스프링 부트에 맞게 바꾸는 것은 부담이 될 수 있습니다. 따라서 일단 @ImportResource 어노테이션으로 기존 XML을 그대로 사용하고 점진적으로 하나씩 application.properties나 Java-config 설정으로 변경해 나가면 편리할 것입니다.
pageable-context.xml
파일 위치: src/main/resources/config/pageable-context.xml
<property name="oneIndexedParameters" value="true" />
이 설정으로 0-base 인덱스가 아니라 1-base 인덱스를 사용하여 페이지를 요청할 수 있습니다.
그러나 주의할 점은 page=1로 첫 페이지를 요청할 수 있지만 내부적으로 0-based index로 처리하는 방식이 변한 것은 아니므로 fallbackPageable 설정에서 page=0으로 설정해야 따로 pageable 객체를 알려주지 않는 경우 첫 페이지를 표시하게 설정한 것이 됩니다. 이 설정은 URL에 page 값이 노출되는데 만약 사용자가 직접 URL을 조작해서 접근할 때 사용자의 혼란을 막기 위한 설정입니다.
2.3.11. 테스트
프로젝트를 재 시작하고 다음 URL들을 사용하면서 접속하여 테스트를 진행합니다.
SQL 로그
SQL 로그
SQL 로그
SQL 로그
DeptServiceImpl.selectByLimit 메소드를 살펴 보면 PageRequest 객체를 개발자가 직접 new 키워드로 만들어서 사용하고 있습니다. 이 경우 pageable-context.xml에 설정한 1-based기반으로 페이지 번호를 요청하는 기능은 적용되지 않습니다. 따라서 기본 정책인 0-based로 페이지번호를 사용해야 합니다.
DeptServiceImpl.selectByLimit
다음은 page 값을 보정하는 로직을 추가한 예제입니다.
2.3.12. @Query, @NamedQuery
개발자가 필요한 경우 JpaRepository 인터페이스를 상속한 인터페이스 Emp-Repository 클래스에 추상메소드들을 추가할 수 있습니다. 이 때 추상메소드가 사용해야 하는 쿼리를 설정하는 다양한 방법을 살펴봅니다.
Emp.java
EmpRepository.java
#1 : Query Method, JPQL
메소드명을 findBy로 시작하고 이어서 엔티티 클래스의 필드변수명을 사용하면 스프링은 관습적으로 판단하여 사용해야 쿼리를 작성합니다. 이렇게 사용하는 방법을 쿼리메소드라고 부릅니다. 사용하는 쿼리는 JPQL입니다.
#2 : @Query, JPQL, Index Place Holder
@Query 어노테이션을 사용하여 메소드가 사용해야 하는 쿼리를 지정합니다. 부가 설정을 하지 않으면 사용하는 쿼리는 JPQL입니다. "?1" 문자열을 사용하여 메소드의 첫 번째로 넘어오는 파라미터를 쿼리 문자열의 해당 위치에 대입하여 쿼리를 완성합니다. 두 번째로 넘어오는 파라미터를 지정하고 싶은 경우 "?2"라고 지정하면 됩니다.
#3 : @Query, JPQL, Name Place Holder
@Param("job") 으로 지정된 파라미터의 값을 쿼리문자열에서 ":job" 으로 지정된 해당 위치에 대입하여 쿼리를 완성합니다. 파라미터가 많은 경우 순서 기반으로 파라미터를 쿼리 문자열에 대입하는 작업이 직관적이지 못 할 때 사용합니다.
#4 : @Query, SQL, Index Place Holder
@Query 어노테이션 설정으로 nativeQuery=true를 지정하면 사용하는 쿼리는 SQL입니다. 옵션을 설정하지 않으면 기본값은 false이므로 JPQL 쿼리를 사용합니다. 여기서 사용되는 쿼리는 SQL이므로 from 다음 사용된 단어는 테이블명입니다.
#5 : @NamedQuery, JPQL, Name Place Holder
selectByJob4 메소드가 사용해야 하는 쿼리를 엔티티 클래스에 @NamedQuery 어노테이션으로 설정해 놓고 사용하는 방식입니다. @NamedQuery의 옵션 중에 name이 가리키는 값을 추상메소드의 이름 "selectByJob4"로 지정하면 스프링은 추상메소드가 사용해야 하는 쿼리가 무엇인지 알게 됩니다.
EmpRepositoryTest.java
테스트 클래스를 실행해 사용된 쿼리를 살펴보시기 바랍니다.
2.3.13. 정리
JPA와 이를 지원하는 Spring Data JPA 기술을 사용하면 데이터베이스와 대화하는 로직을 빠르게 작성할 수 있습니다. 간단한 CURD 메소드들은 스프링이 제안하는 JpaRepository 인터페이스를 상속하는 설정만으로 개발자가 해야 할 작업이 완료됩니다. 반복적이고 뻔한 작업은 스프링이 계속해서 자동화하는 기술을 선보이고 있으므로 가까운 미래에는 인터페이스 생성작업도 환경설정만으로 처리되지 않을까 기대해 봅니다.
스프링과 JPA를 함께 사용하면 개발자의 주된 업무는 뻔하지 않은 메소드들을 추가하는 일입니다. 이 또한 스프링이 개발자 대신 최대한의 업무를 처리하고자 하는 서비스의 의지를 보여주고 있습니다. 인터페이스에 추상메소드를 선언하고 이 메소드가 사용해야 하는 쿼리를 알려주면 나머지는 스프링이 처리합니다. 메소드가 사용해야 하는 쿼리를 알려주는 방법은 다음과 같습니다.
n 스프링이 정리한 관습적인 메소드 작성법을 따르면 스프링이 쿼리를 알아서 사용한다.
n 어노테이션 @Query 또는 @NamedQuery 를 사용하여 쿼리를 알려준다.
n JPQL과 SQL중에 어느 것을 사용할지 개발자가 선택할 수 있다.
그런데 Querydsl을 사용해서 쿼리를 메소드기반으로 작성해서 사용하고자 하는 경우 앞서 살펴 본 스프링이 제공하는 자동화 기술을 이용하지 못한다는 문제가 발생합니다. 왜냐하면 Querydsl의 기술을 사용하는 경우 개발자가 직접 메소드의 로직을 기술해야 하는데 Spring Data JPA의 기술은 인터페이스를 중심으로 구현하기 때문입니다. 덧붙여서 설명하자면 개발자가 코드를 기술할 공간을 확보하려면 구체 클래스가 존재해야 하므로 인터페이스만을 사용해서는 이를 구현할 수 없기 때문입니다.
이와 같은 경우에도 Spring Data JPA의 자동화 기술을 이용할 수 있는 방법을 스프링은 제공하고 있습니다. 간단히 설명해 드리자면 EmpRepository 인터페이스가 JpaRepository 인터페이스를 상속하고 있는 상황에서 추가로 개발자가 "A"라는 이름의 인터페이스를 상속하는 관계로 설정합니다. 그리고 EmpRepositoryImpl이라는 이름으로 구현클래스를 개발자가 작성합니다. 이 클래스가 개발자가 만든 인터페이스 "A"를 구현하는 관계로 설정합니다. EmpRepositoryImpl 클래스의 구현 메소드에 Querydsl 기술을 사용하는 코드를 개발자가 직접 작성합니다. 이러한 구조로 구현 클래스를 만들면 스프링은 EmpRepository 인터페이스의 구현체를 만들 때 EmpRepositoryImpl에 작성된 메소드들을 추가합니다. 따라서 Spring Data JPA가 제공하는 자동화 기술을 이용하면서 추가로 개발자가 직접 작성한 메소드들을 추가할 수 있습니다. 인터페이스 이름이 EmpRepository인 경우 구현 클래스의 이름은 EmpRepositoryImpl로 작성해야 스프링이 작업대상을 파악합니다.
다음 장에서 실습을 통해 구현하는 방법을 살펴보겠습니다.
댓글 없음:
댓글 쓰기