1. 스프링 데이터 JPA
- 스프링 데이터 JPA는 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트다.
- CRUD를 처리하기 위한 공통 인터페이스
JpaRepository
를 제공하고, 리포지토리를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입해준다. - 일반적인 CRUD 메소드는
JpaRepository
인터페이스가 공통으로 제공하며, 직접 작성한 메소드는 스프링 데이터 JPA가 이름을 분석해서 JPQL을 실행한다 MemberRepository.findByUsername(String username); //JPQL select m from Member m where username=:username;
스프링 데이터 프로젝트
- 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트 중 하나다.
- 스프링 데이터 프로젝트는 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여준다.
3. 공통 인터페이스 기능
- 스프링 데이터 JPA를 사용하는 가장 단순한 방법은 공통 인터페이스를 상속받는 것이다.
- 상속받은 후 제네릭에 엔티티 클래스와 엔티티 클래스가 사용하는 식별자 타입을 지정
// JpaRepository 공통 기능 인터페이스
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
...
}
// JpaRepository를 사용하는 인터페이스
public interface MemberRepository extends JpaRepository<Member, Long>{
}
JpaRepository
인터페이스의 계층 구조
Repository
,CrudRepository
,PagingAndSortingRepository
는 스프링 데이터 JPA가 공통으로 사용하는 인터페이스JpaRepository
는 JPA에 특화된 기능을 추가로 제공한다
save(S)
: 새로운 엔티티는 저장하고 이미 있는 엔티티는 수정EntityManger.persist()
orEntityManger.merge()
호출
delete(T)
: 엔티티 하나를 삭제 -EntityManger.remove()
호출findOne(Id)
: 엔티티 하나를 조회 -EntityManger.find()
호출getOne(Id)
: 엔티티를 프록시로 조회 -EntityManger.getReference()
호출findAll(...)
: 모든 엔티티를 조회, 정렬이나 페이징 조건을 파라미터로 제공할 수 있음
4. 쿼리 메소드 기능
- 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능은 크게 3가지가 있다.
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
@Query
어노테이션을 사용하여 Repository 인터페이스에 쿼리 직접 정의
4.1 메소드 이름으로 쿼리 생성
- 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행한다.
- 엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 함께 변경해야 한다.
- 쿼리 생성 기능 - 스프링 데이터 JPA 공식문서를 참조
더보기
Keyword | Sample | JPQL snippet |
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 | findByAgeIsNull | … 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> age) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
4.2 JPA Named Query
- 스프링 데이터 JPA는 메소드 이름으로 JPA Named 쿼리를 호출하는 기능을 제공한다.
NamedQuery 정의
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
public class Member {
...
}
스프링 데이터 JPA로 NamedQuery 호출
- 메소드 이름만으로 Named 쿼리 호출 가능
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
- 스프링 데이터 JPA는 선언한 “도메인 클래스 + . + 메소드 이름”으로 Named 쿼리를 찾아서 실행한다.
- 만약 실행할 Named쿼리가 없으면 메소드 이름으로 쿼리 생성 전략을 사용한다.
4.3 @Query
, Repository 메소드에 쿼리 정의
- 리포지토리 메소드에 직접 쿼리를 정의하려면
@Query
어노테이션을 사용한다. - 메소드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있다.
- 어플리케이션 실행 시점에 문법 오류를 발견할 수 있다.
@Query
어노테이션에nativeQuery = true
를 설정하면 네이티브 SQL을 사용할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
List<Member> findByUsername(String username);
//네이티브 SQL
@Query("SELECT * FROM MEMBER WHERE USERNAME = ?0", nativeQuery = true)
List<Member> findByUsername(String username);
}
4.4 파라미터 바인딩
- 스프링 데이터 JPA는 위치 기반 파라미터 바인딩(
?1
)과 이름 기반 파라미터 바인딩(:name
)을 지원한다. - 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하는 것이 좋다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :name")
List<Member> findByUsername(@Param("name") String username);
}
4.5 벌크성 수정 쿼리
- 벌크성 수정, 삭제 쿼리는
@Modifying
어노테이션을 사용한다. - 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트를 초기화하고 싶으면
clearAutomatically
옵션을true
로 설정하면 된다.
@Modifying(clearAutomatically = true)
@Query("update Member m set m.age = m.age + 1 where m.age >= :age")
int bulkAgePlus(@Param("age") int age);
4.6 반환 타입
- 스프링 데이터 JPA는 결과가 한 건 이상이면 컬렉션 인터페이스를 사용하고 단건이면 반환타입을 지정한다.
- 조회 결과가 없으면 컬렉션은 빈 컬렉션을 반환하고, 단건은 null을 반환
- 반환타입이 단건일 때 조회 결과가 2건 이상이면 예외 발생
List<Member> findByName(String name); //컬렉션
Member findByEmail(String email); // 단건
4.7 페이징과 정렬
- 스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 2가지 특별한 파라미터를 제공한다.
Sort
: 정렬 기능Pageable
: 페이징 기능(내부에 Sort 포함) - 반환 타입으로 List나 Page 사용 가능
- 반환타입으로 Page를 사용하면 스프링 데이터 JPA는 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.
//count 쿼리 사용
Page<Member> findByUsername(String name, Pageable pageable);
//count 쿼리 사용 안함
List<Member> findByUsername(String name, Pageable pageable);
List<Member> findByUsername(String name, Sort sort);
4.8 힌트
@QueryHints
어노테이션을 사용하여 JPA 쿼리 힌트를 사용할 수 있다.- 명세(Specification)를 이해하기 위한 핵심 단어는 술어(predicate)인데, 술어는 데이터를 검색하기 위한 제약 조건 하나하나를 의미한다.
- 여러 검색 조건을 조립해서 새로운 검색조건을 만들 수 있다.
JpaSpecificationExecutor
인터페이스를 상속받아 사용한다.
// 인터페이스 정의
public interface OrderRepository extends JpaRepository<Order, Long>,
JpaSpecificationExecutor<Order> {
}
// 명세 정의 - Specification 인터페이스 구현
// toPredicate() 메소드 구현하여 적절한 검색조건 반환
public class OrderSpec {
public static Specification<Order> memberName(String memberName) {
return new Specification<Order>() {
public Predicate toPredicate(Root<Order> root,
CriteriaQuery<?> query, CriteriaBuilder builder) {
if (StringUtils.isEmpty(memberName)) return null;
Join<Order, Member> m = root.join("member", JoinType.INNER);
return builder.equal(m.get("name"), memberName);
}
}
};
// 비슷한 방식으로 isOrderStatus() 구현
}
// 명세 사용 - 이름 명세와 주문 상태 명세를 and로 조합하여 검색 조건으로 사용
List<Order> result = orderRepository
.findAll(where(memberName(name)).and(isOrderStatus())
);
6. 사용자 정의 리포지토리 구현
- 스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는다. 하지만 다양한 이유로 메소드를 직접 구현하기 위해 구현체를 만들어야 하는 경우가 있다.
- 아래와 같은 방법으로 사용자 정의 리포지토리를 구현할 수 있다.
//사용자 정의 인터페이스
public interface MemberRepositoryCustom {
public List<Member> findMemberCustom();
}
//사용자 정의 구현 클래스
public class MemberRepositoryImpl implements MemberRepositoryCustom {
@Override
public List<Member> findMemberCustom() {
...
}
}
//사용자 정의 인터페이스 상속
public interface MemberRepository extends JpaRepository<Member, Long>,
MemberRepositoryCustom {
}
7. Web 확장
- 스프링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 기능을 제공한다
- 도메인 클래스 컨버터 기능
- 페이징과 정렬 기능
7.1 설정
SpringDataWebConfiguration
을 스프링 빈으로 등록하면 됨- JpaConfig를 사용하면
@EnableSpringDataWebSupport
어노테이션을 사용
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppConfig {
}
7.2 도메인 클래스 컨버터 기능
- HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해줌
- HTTP 요청으로 회원 아이디를 받지만 도메인 클래스 컨버터가 중간에 동작해서 아이디를 회원 엔티티 객체로 변환해서 넘겨줌
@Controller
public class MemberController{
@RequestMapping("/member/memberUpdateForm")
public String memberUpdateForm(@RequestParam("id") Member member, Model model) {
model.addAttribute("member", member);
return "member/memberSaveForm";
}
}
7.2 페이징과 정렬 기능
- 스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있도록
HandlerMethodArgumentResolver
제공 - 페이징 :
PageableHandlerMethodArgumentResolver
- 정렬 :
SortHandlerMethodArgumentResolver
@RequestMapping(value = "/member", method = RequestMethod.GET)
public String list(Pageable pageable, Model model) {
Page<Member> page = memberService.findMembers(pageable);
model.addAttribute("member", pageable.getContent());
return "member/memberList";
}
요청 파라미터
- page : 현재 페이지, 0부터 시작
- size : 한 페이지에 노출할 데이터 건수
- sort : 정렬 조건 정의
접두사
- 사용해야 할 페이징 정보가 둘 이상이면
@Qualifier
어노테이션을 사용해서 접두사를 붙일 수 있다
기본값
Pageable
의 기본값은 page=0, size=20이다.- 변경하고 싶으면
@PagealbeDefault
어노테이션을 사용한다
8. 스프링 데이터 JPA가 사용하는 구현체
- 스프링 데이터 JPA가 제공하는 공통 인터페이스는
SimpleJpaRepository
클래스가 구현한다.
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, Id extends Serializable> implements
JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isnew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
...
}
@Repository
적용 : JPA 예외를 스프링이 추상화한 예외로 변환한다@Transactional
트랜잭션 적용 : JPA의 모든 변경은 트랜잭션 안에서 이루어진다.@Transactional(readOnly=true)
: 데이터를 변경하지 않는 메소드에서는readOnly=true
를 설정해 약간의 성능 향상을 얻을 수 있다.save()
메소드 : 저장할 엔티티가 새로운 엔티티면 저장(persist)하고, 이미 있는 엔티티면 병합(merge)한다.- 식별자가 객체일 때는 null, 기본 타입일 때는 숫자 0 값이면 새로운 엔티티로 판단한다.
10. 스프링 데이터 JPA와 QueryDSL 통합
10.1. QueryDslPredicateExecutor 사용
- 리포지토리에서
QueryDslPredicateExecutor
를 상속받으면 QueryDSL을 사용할 수 있다. - join, fetch 등은 사용할 수 없다.
10.2. QueryDslRepositorySupport 사용
- QueryDSL의 모든 기능을 사용하려면 JPAQuery 객체를 직접 생성해서 사용한다.
- 리포지토리에서
QueryDslRepositorySupport
를 상속받으면 편리하게 QueryDSL을 사용할 수 있다.
'자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 14장 - 컬렉션과 부가 기능 (0) | 2023.07.27 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 13장 - 웹 애플리케이션과 영속성 관리 (0) | 2023.07.26 |
[자바 ORM 표준 JPA 프로그래밍] 10.4 - QueryDSL (0) | 2023.07.22 |
[자바 ORM 표준 JPA 프로그래밍] 10.2 - JPQL (0) | 2023.07.22 |
[자바 ORM 표준 JPA 프로그래밍] 9장 - 값 타입 (0) | 2023.07.22 |