Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8. 프록시와 연관관계 관리
9. 값 타입
9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션
10. 객체지향 쿼리 언어
11. 웹 애플리케이션 제작
11.1 프로젝트 환경설정
11.2 도메인 모델과 테이블 설계
11.3 애플리케이션 구현
12. 스프링 데이터 JPA
12.5 명세
- 책 도메인 주도 설계는 명세라는 개념을 소개, 스프링 데이터 JPA는 JPA Criteria로 개념을 지원
- 명세를 이해하기 위한 핵심 단어는 술어이며, 단순히 참이나 거짓으로 평가
- AND, OR 같은 연산자로 조합이 가능
- 예를 들면, 데이터를 검색하기 위한 제약 조건 하나하나가 술어 스프링 데이터 JPA는 Specification클래스로 정의
- 명세 기능을 사용하려면 레포지토리에
JpaSpecificationExecutor
인터페이스를 상속public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> { }
JpaSpecificationExecutor 인터페이스
public interface JpaSpecificationExecutor<T> { T findOne (Specification<T> spec); List<T> findAll(Specification<T> spec); Page<T> findAll(Specification<T> spec, Pageable pageable); List<T> findAll(Specification<T> spec, Sort sort); long count(Specification<T> spec); }
- JpaSpecificationExecutor의 메소드들은 Specification파라미터로 검색 조건으로 사용
public List<Order> findOrders(String name) { List<Order> result = orderRepository.findAll( where(memberName(name)).and(isOrderStatus()) ); return result; }
- Specifications는 명세들을 조립할 수 있도록 도와주는 클래스
where(), and(), or(), not() 메소드들 제공
public class OrderSpec { public static Specification<Order> memberName(final 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) } } } public static Specification<Order> isOrderStatus() { return new Specification<Order>() { public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) { return builder.equal(root.get("status"), OrderStatus.ORDER); } } } }
- JpaSpecificationExecutor의 메소드들은 Specification파라미터로 검색 조건으로 사용
12.6 사용자 정의 리포지토리 구현
- 스프링 데이터 JPA로 레파지토리로 개발하면 인터페이스만 정의하고 구현체는 만들지 않음
- 리포지토리를 직접 구현하면 제공하는 기능을 모두 구현해야하는 문제가 발생
- 스프링 데이터 JPA는 필요한 메소드만 구현할 수 있는 방법을 제공
- 사용자 정의 인터페이스를 작성, 인터페이스 이름은 자유롭게 정의
public interface MemberRepositoryCustom { public List<Member> findMemberCustom(); }
- 인터페이스를 구현한 클래스를 작성, 이름 짓는 규칙이 존재
리포지토리 인터페이스 이름 + impl (
MemberRepositoryImpl.java
)public class MemberRepositoryImpl implements MemberRepositoryCustom { @Override public List<Member> findMemberCustom() { ...//사용자 정의 구현 } }
- 레파지토리 인터페이스에 사용자 정의 인터페이스를 상속
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom { }
- 구현 클래스 이름 끝에 Impl 대신 다른 이름을 붙이고 싶은 경우
repository-impl-postfix 속성 변경, 참고로 Impl이 기본값
<repositories base-package="jpabook.jpashop.repository" repository-impl-postfix="impl" /> @EnableJpaRepositories(basePackages = "jpabook.jpashop.repository", repositoryImplementationPostfix = "Impl")
- 사용자 정의 인터페이스를 작성, 인터페이스 이름은 자유롭게 정의
- 스프링 데이터 JPA는 필요한 메소드만 구현할 수 있는 방법을 제공
12.7 Web 확장
- 스프링 데이터 프로젝트는 스프링 MVC에 사용할 수 있는 편리한 기능을 제공
- 식별자로 도메인 클래스를 바인딩해주는 도메인 클래스 컨버터 기능, 페이징과 정렬 기능 제공
12.7.1 설정
1. 스프링 데이터가 제공하는 Web 확장 기능을 활성화하려면
org.springframework.data.web.config.SpringDataWebConfiguration
스프링 빈을 등록
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
- JavaConfig를 사용하려면 @EnableSpringDataWebSupport 어노테이션을 사용
@Configuration @EnableWebMvc @EnableSpringDataWebSupport public class WebAppConfig { ... }
- 설정을 완료하면 도메인 클래스 컨버터와 페이징과 정렬을 위한
HandlerMethodArgumentResolver
가 스프링 빈으로 등록
- 등록되는 도메인 클래스 컨버터명
DomainClassConverter
12.7.2 도메인 클래스 컨버터 기능
- 도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티 아이디로 엔티티 객체를 찾아 바인딩
- 수정화면 요청 URL :
/member/memberUpdateForm?id=1
- 기존 사용 방법
@Contorller public class MemberContorller { @Autowired MemberRepository memberRepository; @RequestMapping("member/memberUpdateForm") public String memberUpdateForm(@RequestParam("id") Long id, Model model) { Member member = memberRepository.findOne(id); //회원 조회 model.addAttribute("member", member); return "member/memberSaveForm"; } }
- 도메인 클래스 컨버터를 적용한 예제
@Contorller public class MemberController { @RequestMapping("member/memberUpdateForm") public String memberUpdateForm(@RequestParam("id") Member member, Model model) { model.addAttribute("member", member); return "member/memberSaveForm"; } }
- 도메인 클래스 컨버터가 중간에 동작해 아이디를 회원 엔티티 객체로 변환해서 전달
- 도메인 클래스 컨버터는 해당 엔티티와 관련된 레파지토리를 사용해 엔티티를 탐색
- 도메인 클래스 컨버터를 통해 넘어온 회원 엔티티를 직접 수정해도 DB 반영 X
- 영속성 컨텍스트의 동작 방식 때문에 발생 (OSIV에 관련 자세한 내용 13장)
- 기존 사용 방법
- 수정화면 요청 URL :
12.7.3 페이징과 정렬 기능
- 스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용
HandlerMethodArgumentResolver
를 통해 제공- 페이징 기능:
PageableHandlerMethodArgumentResolver
- 정렬 기능:
SortHandlerMethodArgumentResolver
@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"; }
- 파라미터로 Pageable을 받고 Pageable은 다음 요청 파라미터 정보로 생성
page
: 현재 페이지, 0부터 시작
size
: 한 페이지에 노출할 데이터 건수
sort:
정렬 조건을 정의 (예: 정렬 속성, 정렬 속성...(ASC | DESC)/members?page=0&size=20&sort=name,desc&sort=address.city
- 페이지를 1부터 시작하고 싶으면
PageableHandlerMethodArgumentResolver
를 스프링 빈으로 직접 등록 후setOneIndexedParameters
를 true로 설정
- 페이지를 1부터 시작하고 싶으면
- 페이징 기능:
접두사
- 사용해야 할 페이징 정보가 둘 이상이면 접두사를 사용해서 구분
- 접두사는 스프링 프레임워크가 제공하는 @Qualifier 어노테이션을 사용 "{접두사명}_"로 구분
public String list ( @Qualifier("member") Pageable memberPageable, @Qualifier("order") Pageable orderPageable, ... 예: /members?member_page=0&order_page=1
기본값
- Pageable의 기본값은 page=0, size=20
- 기본 값을 변경하고 싶으면 @PageableDefault 어노테이션을 사용
@RequestMapping(value = "/members_page", method = REquestMethod.GET) public String list(@PageableDefault(size = 12, sort = "name", direction = Sort.Direction.DESC) Pageable pageable) { ... }
12.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(entityInfomation.isNew(entity) { em.persist(entity; return entity; } else { return em.merge(entity); } } }
@Repository 적용
: JPA 예외를 스프링이 추상화한 예외로 변환
@Transactional 트랜잭션 적용
: JPA의 모든 변경은 트랜잭션 안에서 처리 됌 데이터를 변경(등록, 수정, 삭제)하는 메소드에 @Transactional로 트랜잭션 처리가 되어 있음 서비스 계층에서 트랜잭션을 시작했으면 레포지토리도 해당 트랜잭션을 전파받아서 사용
@Transactional(readOnly = true)
: 데이터를 조회하는 메소드에는readOnly = true
옵션이 적용, 이 옵션을 사용하면 플러시를 생략해서 약간의 성능 향상 효과 (15.4.2절 참고)
save() 메소드
: 저장할 엔티티가 새로운 엔티티면 저장(persist)하고 이미 있는 엔티티면 병합 (merge) 처리 새로운 엔티티를 판단하는 기본 전략은 식별자가 객체일 때 null, 숫자 일땐 0으로 판단 필요하면 Persistable 인터페이스를 구현해서 판단 로직 변경 가능public interface Persistable<ID extends Serializable> extends Serializable { ID getId(); boolean isNew(); }
12.10 스프링 데이터 JPA와 QueryDSL 통합
- 스프링 데[이터 JPA는 2가지 방법으로 QueryDSL을 지원
org.springframework.data.querydsl.QueryDslPredicateExecutor
org.springframework.data.querydsl.QueryDslRepositorySupport
12.10.1 QueryDslPredicateExecutor 사용
- 레포지토리에서
QueryDslPredicateExecutor
를 상속받아서 사용public interface ItemRepository extends JpaRepository<Item, Long>, QueryDslPredicateExecutor<Item> { }
QueryDSL
사용 예제QItem item = QItem.item; Iterable<Item> result = itemRepository.findAll( item.name.contains("장난감").and(item.price.between(10000, 20000)) );
QueryDslPredicateExecutor
인터페이스public interface QueryDslPredicateExecutor<T> { T findOne(Predicate predicate); Iterable<T> findAll(Predicate predicate); Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders); Page<T> findAll(Predicate predicate, Pageable pageable); long count(Predicate predicate); }
QueryDslPredicateExecutor
는 편리하게 QueryDSL을 사용할 수 있지만 한계가 존재- 예를 들어 join, fetch를 사용 못 함
QueryDSL
이 제공하는 다양한 기능을 사용하려면 JPAQuery를 직접 사용 또는 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 사용
12.10.2 QueryDslRepositorySupport 사용
- QueryDSL의 모든 기능을 사용하려면 JpaQuery 객체를 직접 사용하면 가능
- 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 상속받아 사용하면 좀 더 편리
public interface CustomOrderRepository { public List<Order> search(OrderSearch orderSearch); }
- 스프링 데이터 JPA가 제공하는 공통 인터페이스는 직접 구현할 수 없기 떄문에
CustomOrderRepository
라는 사용자 정의 레포지토리를 생성QueryDslRepositorySupport
핵심 기능은 (p. 575 참고)public class OrderRepositoryImpl extends QuerydslRepositorySupport implements CustomOrderRepository{ public OrderRepositoryImpl() { super(Order.class); } @Override public List<Order> search(OrderSearch orderSearch) { QOrder order = QOrder.order; QMember member = QMember.member; JPQLQuery query = from(order); if (StringUtils.hasText(orderSearch.getMemberName())) { query.leftJoin(order.member, member) .where(member.name.contains(orderSearch.getMemberName())); } if(orderSearch.getOrderStatus() != null) { query.where(order.status.eq(orderSearch.getOrderStatus())); } return query.fetch(); } }
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 13.2 준영속 상태와 지연 로딩 (0) | 2021.09.21 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 13.1 트랜잭션 범위의 영속성 컨텍스트 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 12.4 쿼리 메소드 기능 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 12.1~3 스프링 데이터 JPA 소개, 공통 인터페이스 기능 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 10.6 객체지향 쿼리 심화 (0) | 2021.09.13 |