Spring Boot에서 사용되는 Spring Data JPA에 대해서 알아보자.
Spring Data JPA
- 쿼리 메서드 : 메서드의 이름 자체가 쿼리의 구문으로 처리되는 기능
- @Query(JPQL) : SQL과 유사하게 엔티티 클래스의 정보를 이용해서 쿼리를 작성하는 기능
- Querydsl : 동적쿼리 기능
대표적으로 위의 3가지 방식을 사용한다고 한다.
쿼리 메서드(Query Methods)
쿼리 메서드는 말 그대로 '메서드의 이름 자체가 질의(query)문'이다. 쿼리 메서드는 주로 'findBy나 getBy...'로 시작하고 이후에 필요한 필드 조건이나 And, Or와 같은 키워드를 이용해서 메서드의 이름 자체로 질의 조건을 만들어낸다.
쿼리 메서드 관련 키워드는 주로 SQL에서 사용되는 키워드와 동일하게 작성되어 있는데
자세한 내용은 Spring Data JPA Reference을 이용하여 찾아보자
Spring Data JPA - Reference Documentation
Example 108. Using @Transactional at query methods @Transactional(readOnly = true) public interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") v
docs.spring.io
쿼리 메서드의 예제
//Spring Data JPA 에서 제공하는 인터페이스로 설계하는데 스프링 내부에서 자동으로 객체를 생성하고 실행하는 구조
public interface MemoRepository extends JpaRepository<Memo, Long> {//JpaRepository의 key 엔티티 객체, value PK
//쿼리 메소드 : 메서드의 이름 자체가 질의문
List<Memo> findByMnoBetweenOrderByMnoDesc(Long from, Long to);
Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);//정렬기능을 추가하기 위해 pageable 인터페이스 사용 Page<Memo> 가 반환 클래스
void deleteMemoByMnoLessThan(Long num);
}
- 쿼리 메서드의 장점: 메서드 이름 자체가 질의문이기 때문에 따로 SQL을 작성하지 않고 직관적이게 메서드를 이용하여 바로 원하는 결과를 사용할 수 있도록 만드는 것이다.
- 쿼리 메서드의 단점 : 쿼리가 복잡해지면 메서드 이름 자체가 너무 길어지고, 구현하기 어렵다는 것이다.
@Query(JPQL)
Spring Data JPA가 제공하는 쿼리 메서드는 검색과 같은 기능을 작성할 때 편리함을 제공하기는 하지만 나중에 조인이나 복잡한 조건을 처리해야 하는 경우에는 'And, Or' 등이 사용되면서 불편함이 많을 것이다.
때문에 일반적인 경우에는 간단한 처리만 쿼리메서드를 이용하고, @Query를 이용하는 경우가 더 많다.
- @Query의 경우는 메서드의 이름과 상관없이 메서드에 추가한 어노테이션을 통해서 원하는 처리가 가능하다.
- @Query의 value는 JPQL(Java Persistence Query Lauguage)로 작성하는데 흔히 '객체지향 쿼리'라고 불리는 구문들이다.
객체지향쿼리는 테이블 대신에 엔티티 클래스를 이용하고, 테이블의 칼럼 대신에 클래스에 선언된 필드를 이용해서 작성한다. JPQL은 SQL과 상당히 유사하기 때문에 간단한 기능을 제작하는 경우에는 추가적인 학습 없이도 적용이 가능하다.
//@Query : 복잡한 조건을 사용할 때 직접 SQL문을 넣어 사용할 수 있는 것
// JPQL (Java Persistence Query Language) : 객체지향 쿼리, 쿼리 메소드안의 value로 사용
@Query("select m from tbl_memo m order by m.mno desc")
List<Memo> getListDesc();
@Query의 파라미터 바인딩
- '?1, ?2' : 1부터 시작하는 파라미터의 순서를 이용하는 방식
- ':xxx' : ':파라미터 이름'을 활용하는 방식
- ':#{ }' : 자바 빈 스타일을 이용하는 방식
//:xxx -> :파라미터 이름 활용
@Transactional
@Modifying
@Query("update tbl_memo m set m.memo_text = :memoText where m.mno = :mno")
int updateMemoText(@Param("mno") Long mno, @Param("memoText") String memoText);
//#{ } -> 자바 빈 스타일을 이용
@Transactional
@Modifying
@Query("update tbl_memo m set m.memo_text = :#{#param.memoText} where m.mno = :#{#param.mno}")
int updateMemoText(@Param("param") Memo memo);
@Query Object[] 리턴
- 쿼리 메서드의 경우에 엔티티 타입의 데이터만을 추출
- @Query를 이용하는 경우에는 현재 필요한 데이터만을 Object[]의 형태로 선별적으로 추출할 수 있음
- JPQL을 이용하여 JOIN, GROUP사용할 시 적당한 엔티티 타입이 존재하지 않은 경우가 많음 이럴 때 유용한 Object[] 리턴 타입
//@Query 의 장점중의 하나는 쿼리 메소드의 경우 엔티티타입만 추출가능하지만, @Query는 Object타입도 추출 가능함
@Query(value = "select m, CURRENT_DATE from tbl_memo m where m.mno > :mno",
countQuery = "select count(m) from tbl_memo m where m.mno > :mno")
Page<Object[]> getListWithQueryObject(Long mno, Pageable pageable);
Native SQL 처리
- 데이터베이스 고유의 SQL구문을 그대로 활용
- 복잡한 JOIN 구문 등을 처리하기 위해서 어쩔 수 없는 선택을 하는 경우에 사용
//NATIVE SQL 처리 : 쿼리가 너무 복잡한 경우에 데이터베이스 고유의 SQL을 그대로 사용
@Query(value = "select m from tbl_memo m where m.mno > 0", nativeQuery = true)
List<Object[]> getNativeResult();
Querydsl
복잡한 조합을 이용하는 경우가 많은 상황에서 동적으로 쿼리를 생성하여 처리할 수 있는 기능을 제공해준다.
Querydsl을 이용하면 코드 내부에서 상황에 맞는 쿼리를 생성할 수 있지만 이를 위해서는 작성된 엔티티 클래스를 그대로 이용하는 것이 아닌 'Q도메인'이라는 것을 이용해야만한다.
Q도메인을 사용하면 기존에 생성한 엔티티에 대하여 'Q엔티티'이런 형식의 클래스가 추가된다.
엔티티 클래스에 많은 변수들이 선언되어 있다면 조합의 수는 수도 없이 많아짐
-> 이런 상황을 대비해서 상황에 맞게 쿼리를 처리할 수 있는 Querydsl이 필요함
Querydsl 사용법
- BooleanBuilder를 생성
- 조건에 맞는 구문은 Querydsl 에서 사용하는 Predicate타입의 함수를 생성
- BooleanBuilder에 작성된 Predicate를 추가하고 실행
@Test
public void testQuery2(){
Pageable pageable = PageRequest.of(0, 10, Sort.by("gno").descending());
// 1. Q도메인 클래스를 얻어옴 -> Q도메인 클래스를 이용하면 엔티티 클래스에 선언된 title, content와 같은 필드들을 변수로 사용할 수 있음
QGuestBook qGuestBook = QGuestBook.guestBook;
String keyword = "1";
// 2. BooleanBuilder where문에 들어가는 조건들을 넣어주는 컨테이너
BooleanBuilder builder = new BooleanBuilder();
// 3. 원하는 조건은 필드값과 같이 결합해서 생성
BooleanExpression exTitle = qGuestBook.title.contains(keyword);
BooleanExpression exContent = qGuestBook.content.contains(keyword);
BooleanExpression exAll = exTitle.or(exContent);
//4. 만들어진 조건은 where문에 and나 or같은 키워드와 결합
builder.and(exAll);
builder.and(qGuestBook.gno.gt(0));
// 5. BooleanBuilder은 GuestbookRepository에 QuerydslPredicateExecutor가 추가되어서 findAll에 같이 사용할 수 있음
Page<GuestBook> result = guestbookRepository.findAll(builder, pageable);
result.stream().forEach(guestbook -> {
System.out.println(guestbook);
});
}
- BooleanBuilder : where문에 들어가는 조건들을 넣어주는 컨테이너를 생성
- BooleanExpression : Predicate타입으로 where문에 들어가는 조건
BooleanBuilder를 먼저 정의하고 해당 조건에 맞는 BooleanExpression들을 연결하여
최종적으로 BooleanBuilder에 결합시킨다. findAll함수에 최종 builder를 매개변수로 넣어 결과값을 리턴받으면 된다.
Spring Data JPA에 대하여 정리해보았는데 나는 개인적으로 @Query가 편하다는 생각이 들었다....
Spring에서 Mybatis Mapper를 너무 많이 사용하다보니까 @Query의 JPQL이 익숙하고 좋은 것 같다.
Querysql은 BooleanBuilder, BooleanExpression 사용하는게 너무 번거롭고
코드가 길어지는 면에서 별로라는 느낌을 너무 많이 받았다.
다음번엔 QueryFactory를 공부해봐야겠다.
'Spring Boot' 카테고리의 다른 글
[Spring Boot]ResponseEntity (0) | 2021.11.28 |
---|---|
[Spring Boot] JPQL, Repository 확장 (0) | 2021.11.07 |
[Spring Boot] RedirectAttributes, FlashAttributes (0) | 2021.10.01 |
[Spring Boot] Entity VS DTO (0) | 2021.09.25 |