| 명세(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 |
출처: https://data-make.tistory.com/622 [Data Makes Our Future]
'Study > 내가 정리하는 개념들' 카테고리의 다른 글
맥에서 homebrew 설치 (0) | 2022.06.21 |
---|---|
Homebrew(홈브류) 설치 및 사용법, MacOS에서 프로그램을 쉽게 다운로드 및 삭제할 수 있는 패키지 관리자 (0) | 2022.06.21 |
[자바] 자주 사용되는 Lombok 어노테이션 (0) | 2022.05.09 |
[Swagger UI] Annotation 설명 (0) | 2022.05.09 |
HTTP 메소드 PUT , PATCH 차이 (0) | 2022.04.14 |
댓글