스터디/JPA 스터디
[JPA] 스프링 데이터 JPA
jpa-study에서 스터디를 진행하고 있습니다.
스프링 데이터 JPA 소개
- 스프링 데이터 JPA는 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트이다.
- CRUD를 처리하기 위한 공통 인터페이스를 제공하고, Repository를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성하여 주입해 준다.
- 일반적인 CRUD 메소드는
JpaRepository
인터페이스가 공통으로 제공하고,findByUsername()
과 같이 공통으로 처리할 수 없는 메소드는 스프링 데이터 JPA가 메소드를 분석하여 JPQL로 변환한다.
스프링 데이터 프로젝트
- 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트이다.
스프링 데이터 JPA 작성
- spring-data-jpa 라이브러리를 gradle 혹은 maven을 이용하여 설치한다.
- 사용할 Repository를
JpaRepository
를 상속한 인터페이스로 정의하면 끝이다.
- 위에서 언급했듯이, 인터페이스만 만들어 두면 스프링 데이터 JPA가 자동으로 실행 시점에 구현 클래스를 만들어 주입해 준다.
공통 인터페이스 기능
- 스프링 데이터 JPA를 사용하는 가장 단순한 방법은
JpaRepository
를 상속받는 것이다. JpaRepository
의 계층 구조는 다음과 같다.
쿼리 메소드 기능
- 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능은 크게 3가지가 있다.
- 메소드 이름으로 쿼리 생성
- 메소드 이름으로 JPA NamedQuery 호출
- @Query 어노테이션을 사용하여 Repository 인터페이스에 쿼리 직접 정의
메소드 이름으로 쿼리 생성
- 스프링 데이터 JPA에서 정해진 규칙에 따라서 메소드 이름을 지으면 내부적으로 알아서 JPQL로 변경한다. 예를 들어,
findByEmailAndName()
같이 메소드를 정의하면 된다.
JPA NamedQuery
- JPA Named 쿼리는 쿼리에 이름을 부여해서 사용하는 방법이다.
- @NamedQuery 어노테이션을 사용하거나, XML에 쿼리를 정의할 수 있다. 주로 전자의 방식을 많이 사용한다.
// NamedQuery 정의
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member m where m.username = :username")
public class Member {
...
}
// NamedQuery 호출
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
- 스프링 데이터 JPA는 “도메인 클래스 +
.
+ 메소드 이름”으로 Named 쿼리를 알아서 찾아서 실행한다.
@Query, Repository 메소드에 쿼리 정의
- 실행할 메소드 위에 @Query 어노테이션을 통해 정적 쿼리를 직접 작성할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = ?1")
List<Member> findByUsername(String username);
}
- 만약 네이티브 쿼리를 사용하고 싶다면, nativeQuery 옵션에 true를 주고 네이티브 쿼리 문법을 지켜서 작성하면 된다.
파라미터 바인딩
- 스프링 데이터 JPA는 이름 기반 파라미터 바인딩(
:username
)과 위치 기반 파라미터 바인딩(?1
)을 모두 지원한다. 코드 가독성을 위해 전자의 방식을 사용하는 것을 추천한다.
기타 기능
- 벌크 연산
- 페이징과 정렬
- JPA 쿼리 힌트
- SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트임.
- Lock
명세
- 명세를 이해하기 위한 핵심 단어는 술어이다. 술어는 데이터를 검색하기 위한 제약 조건 하나 하나를 의미한다.
- 스프링 데이터 JPA는 Specification 클래스로 정의하였는데, 다양한 검색 조건을 조립하여 새로운 검색 조건을 쉽게 만들 수 있다.
// 인터페이스 정의
public interface OrderRepository extends JpaRepository<Order, Long>,
JpaSpecificationExecutor<Order> {
}
// 명세 정의
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() 구현
}
// 명세 사용
List<Order> result = orderRepository.findAll(
where(memberName(name)).and(isOrderStatus())
);
- JPA Criteria 방식으로 명세를 위와 같이 정의하고 사용할 수 있다.
사용자 정의 Repository 구현
- 스프링 데이터 JPA로 Repository를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는데, 특정 메소드를 직접 구현하기 위해 구현체를 만들어야 하는 경우가 있다.
- 이때 Repository를 직접 구현하면 공통 인터페이스가 제공하는 기능까지 모두 구현한다는 문제가 있다. 스프링 데이터 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 {
}
- Repository 구현 클래스 이름 끝에 반드시 Impl을 붙여 주어야 스프링 데이터 JPA가 사용자 정의 Repository로 인식한다.
Web 확장
설정
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppConfig {
}
- 위 설정을 완료하면 도메인 클래스 컨버터와 페이징과 정렬을 위한
HandlerMethodArgumentResolver
가 스프링 빈으로 등록된다.
도메인 클래스 컨버터 기능
- 도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해 준다.
@Controller
public class MemberComtroller {
@RequestMapping("member/memberUpdateForm")
public String memberUpdateForm(@ReqeustParam("id") Member member, Model model) {
model.addAttribute("member", member);
return "member/memberSaveForm";
- @RequestParam을 보면 도메인 클래스 컨버터가 중간에 동작해서 아이디를 회원 엔티티로 변환하여 넘겨주는 것을 볼 수 있다.
페이징과 정렬 기능
@RequestMapping(value = "/members", method = RequestMethod.GET)
public String list(Pageable pageable, Model model) {
Page<Member> page = memberService.findMembers(pageable);
model.addAttribute("members", page.getContent());
return "members/memberList";
}
- page, size, sort 요청 파라미터를 파싱하여 자동으로 Pageable 객체를 만들어서 넘겨준다.
- ex)
/members?page=0&size=20&sort=name,desc&sort=address.city
스프링 데이터 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 트랜잭션 적용
- save() 메소드: 저장할 엔티티가 새로운 엔티티면 저장하고, 이미 있는 엔티티면 병합(merge)한다.
- 식별자가 객체일 때는 null, 기본 타입일 때는 숫자 0 값이면 새로운 엔티티로 판단한다.
스프링 데이터 JPA와 QueryDSL 통합
- QUeryDslPredicateExecutor 또는 QueryDslRepositorySupport를 사용하여 QueryDSL을 사용할 수 있다.
출처
김영한 - 자바 ORM 표준 JPA 프로그래밍
'스터디 > JPA 스터디' 카테고리의 다른 글
[JPA] 컬렉션과 부가 기능 (0) | 2022.03.01 |
---|---|
[JPA] 웹 애플리케이션과 영속성 관리 (0) | 2022.02.09 |
[JPA] QueryDSL, 네이티브 SQL, 객체지향 쿼리 심화 (1) | 2022.01.30 |
[JPQ] 객체 지향 쿼리 소개, JPQL (0) | 2022.01.20 |
[JPA] Qna 미션을 통해 새롭게 배운 내용 정리 (0) | 2022.01.17 |
댓글