티스토리 뷰
prior post : [Spring + JPA] Spring Data JPA 란? (1)
| 명세(specification)
- 명세를 이해하기 위한 핵심 단어는 술어(predicate),
ㄴ 이것은 단순히 참이나 거짓으로 평가
ㄴ AND, OR 같은 연산자로 조합 가능
ㄴ 데이터를 검색하기 위한 제약 조건 하나하나를 술어라고 할 수 있음
- 술어를 Spring Data JPA는 org.springframework.data.jpa.domain.Specification 로 정의
- Specification은 composite pattern 으로 구성되어 여러 specification 조합 가능 (SQL Where)
- Specification 기능 사용을 위해 org.springframework.data.jpa.repository.JpaSpecificationExecutor 인터페이스 상속
1 2 3 | public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> { ... } | cs |
- JpaSpecificationExecutor 인터페이스 내부
ㄴ Specification을 파라미터로 받아 검색 조건으로 사용
1 2 3 4 5 6 7 8 | 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); } | cs |
|| 사용과 정의
ㅇ 명세 사용
- Specifications는 명세들을 조립할 수 있도록 도와주는 클래스
ㄴ where(), and(), or(), not() 메소드 제공
- MemberName과 isOrderStatus를 and로 조합해서 검색 조건으로 사용
1 2 3 4 5 6 7 8 9 10 11 | import static org.springframework.data.jpa.domain.Specifications.*; // where() import static jpabook.jpashop.domain.spec.Orderspec.*; public List<Order> findOrders (String name) { List<Order> result = orderRepository.findAll( where(memberName(name)).and(isOrderStatus()) ); return result; } | cs |
ㅇ 명세 정의
- 명세 정의는 Specification Interface를 구현
- 명세 정의 시 toPredicate(...) 메소드만 구현하면 되는데
JPA Criteria의 Root, CriteriaQuery, CriteriaBuilder 클래스가 모두 파라미터로 주어짐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import org.springframework.data.jpa.domain.Specification; import org.springframework.util.StringUtils; import javax.persistence.criteria.*; public class OrderSpec { public static Specification<Order> memberNameLike(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.like(m.<String>get("name"), "%" + memberName + "%"); } }; } public static Specification<Order> isOrderStatus(final OrderStatus orderStatus) { return new Specification<Order>() { public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder) { if (orderStatus == null) return null; return builder.equal(root.get("status"), orderStatus); } }; } } | cs |
| 사용자 정의 리포지토리 구현
- Spring Data JPA로 Repository를 개발하면 인터페이스만 정의하고 구현체는 만들지 않음
- 하지만, 메소드를 직접 구현해야 할 경우, 세 가지 방법을 제시
(Repository를 직접 구현하면 공통 인터페이스가 제공하는 기능까지 모두 구현해야 함..)
1. 사용자 정의 인터페이스
- 직접 구현할 메소드를 위한 사용자 정의 인터페이스 작성
1 2 3 | public interface MemberRepositoryCustom { public List<Member> findMemberCustom(); } | cs |
2. 사용자 정의 구현 클래스
- 사용자 정의 인터페이스를 구현한 클래스 작성
- 클래스 이름 규칙 : Repository Interface Name + Impl
- 클래스 이름 규칙을 지키면 Spring Data JPA가 사용자 정의 구현 클래스로 인식
1 2 3 4 5 6 7 | public class MemberRepositoryImpl implements MemberRepositoryCustom { @Override public List<Member> findMemberCustom() { // .. } } | cs |
3. Repository Interface에서 사용자 정의 인터페이스를 상속
1 2 3 | public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom { // .. } | cs |
+//
+. 사용자 정의 구현 클래스 이름 끝에 Impl 대신 다른 이름을 붙이고 싶다면
repository-impl-postfix 속성을 변경
1 | <repositories base-package="jpabook.jpashop.repository" repository-impl-postfix="impl" /> | cs |
- JavaConfig 사용 시
1 | @EnableJpaRepositories(basePackages = "jpabook.jpashop.repository", repositoryImplementationPostfix = "impl") | cs |
| Web 확장
--
Spring Data Project는 도메인 클래스 컨버터 기능, 페이징과 정렬 기능 제공
|| 설정
Spring Data가 제공하는 Web 확장 기능을 활성화하기 위해
org.springframework.data.web.config.SpringDataWebConfiguration 을 스프링 빈으로 등록
1 | <bean class="org.springframework.data.web.config.SpringDataWebConfiguration" /> | cs |
JavaConfig 사용 시
org.springframework.data.web.config.EnableSpringDataWebSupport 어노테이션 사용
1 2 3 4 5 6 | @Configuration @EnableWebMvc @EnableSpringDataWebSupport public class WebAppConfig { // .. } | cs |
//
설정을 완료하면 도메인 클래스 컨버터, 페이징과 정렬을 위한 HandlerMethodArgumentResolver가
스프링 빈으로 등록
|| 도메인 클래스 컨버터 기능
org.springframework.data.repository.support.DomainClassConverter
HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩
ㅇ 사용 전
- 파라미터로 넘어온 회원 아이디로 회원 엔티티를 찾고
찾아온 회원 엔티티를 model을 사용해 뷰로 넘겨줌
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Controller public class MemberController { @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"; } } | cs |
ㅇ 도메인 클래스 컨버터 기능 사용
- 파라미터로 회원 아이디를 받지만 도메인 클래스 컨버터가 중간에 동작해서
아이디를 회원 엔티티 객체로 변환해서 넘겨줌
- 도메인 클래스 컨버터는 해당 엔티티와 관련된 Repository를 사용해서 엔티티를 찾음
- 도메인 클래스 컨버터를 통해 넘어온 회원 엔티티를 컨트롤러에서 직접 수정해도 실제 데이터베이스에는 반영되지 않음.
(영속성 컨텍스트의 동작 방식과 관련)
1 2 3 4 5 6 7 8 9 10 | @Controller public class MemberController { @RequestMapping("member/memberUpdateForm") public String memberUpdateForm(@RequestParam("id") Long id, Model model) { model.addAttribute("member", member); return "member/memberSaveForm"; } } | cs |
|| 페이징과 정렬
--
페이징 기능 : org.springframework.data.web.PageableHandlerMethodArgumentResolver
정렬 기능 : org.springframework.data.web.SortHandlerMethodArgumentResolver
c. 페이징과 정렬 예제
1 2 3 4 5 6 7 | @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"; } | cs |
ㅇ Pageable의 요청 파라미터
> page : 현재 페이지 (0부터 시작)
> size : 한 페이지에 노출할 데이터 건수
> sort : 정렬 조건을 정의 (ASC | DESC ..)
/members?page=0&size=20&sort=name,desc&sort=address.city
||| 접두사
- 사용해야 할 페이징 정보가 둘 이상하면 접두사를 사용하여 구분
- @Qualifier 어노테이션을 사용하고 "{접두사명}_"로 구분
Ex) /members?member_page=0&order_page=1
1 2 3 | public String list( @Qualifier("member") Pageable memberPageable, @Qualifier("order") Pageable orderPageable, ...) | cs |
||| 기본값
- Pageable의 기본값은 page=0, size=20
- 기본값 변경 시 @PageableDefault 어노테이션 사용
1 2 3 4 5 6 | @RequestMapping(value = "members_page", method = RequestMethod.GET) public String list(@PageableDefault(size = 12, sort = "name", direction = Sort.Direction.DESC) Pageable pageable) { // .. } | cs |
| Spring Data JPA가 사용하는 구현체
--
Spring Data JPA가 제공하는 공통 인터페이스는
org.springframework.data.jpa.repository.support.SimpleJpaRepository 클래스가 구현
c. 코드 일부 분석
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | @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(entityInformaion.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } } // .. } | cs |
@Repository : JPA 예외를 스프링이 추상화한 예외로 반환
@Transactional : JPA의 모든 변경은 트랜잭션 안에서 이루어져야 함
- Spring Data JPA가 제공하는 공통 인터페이스를 사용하면 데이터를 변경(등록, 수정, 삭제)하는 메소드에
@Transactional로 트랜잭션 처리가 되어 있음
@Transactional (readOnly = true) : 데이터를 변경하지 않는 트랜잭션에서 사용 시 플러시를 생략해서 약간의 성능 향상
save() : 저장할 엔티티가 새로운 엔티티면 저장(persist), 이미 있는 엔티티면 병합(merge) 수행
(엔티티의 식별자로 판단)
+ c. Persistable Interface를 구현하여 판단 로직 변경 시
1 2 3 4 5 | public interface Persistable<ID extends Serializable> extends Serializable { ID getId(); boolean isNew(); } | cs |
출처 : 자바 ORM 표준 JPA 프로그래밍
'Books' 카테고리의 다른 글
[JPA] 영속성(persistence) 관리 (OSIV) (0) | 2020.12.28 |
---|---|
[Spring Data JPA] Make Web Application (3) (0) | 2020.12.28 |
[Spring + JPA] Spring Data JPA 란? (1) (2) | 2020.12.24 |
[Spring + JPA] Make Web Application (2) (0) | 2020.12.24 |
[Spring + JPA] Make Web Application (1) (0) | 2020.12.23 |