티스토리 뷰
이전 글: https://gojs.tistory.com/54
객체지향 쿼리 언어
객체지향 쿼리JPA에서 하나의 식별자로 하나의 엔티티를 조회할 수 있고, 조회한 엔티티를 기준으로 삼아 연관된 엔티티를 찾을 수 있다.- 식별자로 조회: EntityManager.find(id)- 객체 그래프 탐색으
gojs.tistory.com
JPA를 사용해서 개발을 진행하는 경우에도 중복적으로 코드를 입력하게된다.
Spring Data JPA는 이에 대해 보다 간편하게 개발할 수 있도록 지원하는 프로젝트이다.
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>3.0.6</version>
</dependency>
// https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa
implementation group: 'org.springframework.data', name: 'spring-data-jpa', version: '3.0.6'
Spring Data JPA를 사용하기 위해서는 아래와 같이 디펜던시를 설정해주어야 한다.
그렇다면 Spring Data JPA에서 추가적으로 제공하는 기능들에 대해서 일단 전반적으로 간략하게만 알아보자.
공통 인터페이스 기능
Spring Data JPA를 사용하는 가장 단순한 방법은 JpaRepository를 상속받는 것이다.
부모 클래스의 제네릭 타입에 엔티티 클래스와 식별자 타입을 지정한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
}
public interface JpaRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID> {
...
}
위 의존성 다이어그램을 보면 Spring Data 모듈에 Repository, CrudRepository, PagingAndSortingRepository를 포함하고 있다.
Spring Data JPA 모듈에 포함된 JpaRepository는 JPA에 특화된 기능들을 추가적으로 제공한다.
- save(S): 새로운 엔티티는 저장하고 이미 있는 엔티티는 수정
- delete(T): 엔티티 하나 삭제 (EntityManager.remove())
- findOne(ID): 엔티티 하나 조회 (EntityManager.find())
- getOne(ID): 엔티티를 프록시로 조회 (EntityManager.getReference())
- findAll(...): 모든 엔티티 조회 (정렬, 페이징 조건을 파라미터로 전달)
쿼리 메서드 기능
Spring Data JPA는 여러가지 쿼리 메서드 기능을 제공한다.
- 메서드 이름으로 쿼리 생성
- 메서드 이름으로 JPA NamedQuery 호출
- @Query를 사용해서 쿼리 직접 정의
메서드 이름으로 쿼리 생성
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByEmailAndName(String email, String name);
}
select m from Member m where m.email = ?1 and m.name = ?2
위와 같이 정의한 메서드를 실행하면 Spring Data JPA는 메서드 이름을 분석해서 이에 알맞는 JPQL을 실행시킨다.
Keyword | Sample | JPQL snippet |
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is, Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull, Null | findByAge(Is)Null | … where x.age is null |
IsNotNull, NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
위 표를 참고하여 쿼리 이름 규칙을 지으면 된다.
JPA NamedQuery
쿼리에 이름을 부여해서 사용하는 방법이다.
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username"
)
public class Member {
...
}
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
@Query를 사용하여 Repository 메서드에 쿼리 정의
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
Member findByUsername(String username);
}
벌크성 수정 쿼리
@Modifying
@Query("update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount")
int bulkModifyPrice(@Param("stockAmount") String stockAmount);
벌크성 수정, 삭제 쿼리는 @Modifying 어노테이션을 활용한다.
반환 타입
Spring Data JPA는 조회 결과가 단 건인 경우 지정한 타입으로 반환하고, 한 건 이상인 경우에는 컬렉션 타입으로 반환한다.
단 건 조회 시 조회 결과가 없는 경우 null을 반환하고 다 건 조회 시 조회 결과가 없는 경우에는 빈 컬렉션을 반환한다.
페이징과 정렬
public interface MemberRepository extends JpaRepository<Member, Long> {
Page<Member> findByNameStartingWith(String name, Pageable pageable);
}
Page 타입으로 반환하는 경우에는 count 쿼리가 추가로 실행된다.
public interface Page<T> extends Iterable<T> {
// 현재 페이지
int getNumber();
// 페이지 크기
int getSize();
// 전체 페이지 수
int getTotalPages();
// 현재 페이지에 나올 데이터 수
int getNumberOfElements();
// 전체 데이터 수
long getTotalElements();
// 이전 페이지 여부
boolean hasPreviousPage();
// 현재 페이지가 첫 번째 페이지인지 여부
boolean isFirstPage();
// 다음 페이지 여부
boolean hasNextPage();
// 현재 페이지가 마지막 페이지인지 여부
boolean isLastPage();
// 다음 페이지 객체 (다음 페이지 없으면 null)
Pageable nextPageable();
// 이전 페이지 객체 (이전 페이지 없으면 null)
Pageable previousPageable();
// 조회된 데이터
List<T> getContent();
// 조회된 데이터 존재 여부
boolean hasContent();
// 정렬 정보
Sort getSort();
}
위 Page 타입을 참고하여 페이징 처리를 해보자.
PageRequest pageRequest = new PageRequest(0, 10, new Sort(Direction.DESC, "name"));
Page<Member> result = memberRepository.findByNameStaringWith("김", pageRequest);
List<Member> members = result.getContent();
int totalPages = result.getTotalPages();
boolean hasNextPage = result.hasNextPage();
위와 같이 손쉽게 페이징 및 정렬에 대해 처리할 수 있다.
쿼리 힌트
JPA 쿼리 힌트를 사용하려면 @QueryHint를 사용하면 된다. (SQL 힌트가 아닌 JPA 힌트이다)
@QueryHints(
value = {
@QueryHint(name = "org.hibernate.readOnly", value = true)
},
forCounting = true
)
Page<Member> findByName(String name, Pageable pageable);
forCounting 속성을 Page 반환 타입 메소드에서 자동으로 실행되는 count 쿼리에도 힌트를 적용할지에 대한 옵션이다.
락
@Lock(LockModeType.PESSIMISTIC_WRITE)
List<Member> findByName(String name);
@Lock을 활용해서 락을 걸 수 있다.
다음 글: https://gojs.tistory.com/57
영속성 관리 전략
트랜잭션 범위의 영속성 컨텍스트스프링 컨테이너는 기본적으로 트랜잭션 범위의 영속성 컨텍스트 전략을 가진다. 트랜잭션이 시작되면 영속성 컨텍스트가 생성되고, 트랜잭션이 끝나면 영속
gojs.tistory.com
'공부 > JPA' 카테고리의 다른 글
JPA에서 컬렉션과 여러 기능 활용하기 (0) | 2024.10.03 |
---|---|
JPA에서 트랜잭션을 고려한 영속성 관리 전략 (4) | 2024.10.02 |
객체지향 쿼리 언어 알아보기 (0) | 2024.08.26 |
JPA 값 타입 구현하기 (0) | 2024.07.27 |
JPA 프록시와 로딩 전략 (0) | 2024.07.27 |