티스토리 뷰

반응형





| 명세(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 == nullreturn 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 <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 프로그래밍

반응형
댓글
최근에 올라온 글
최근에 달린 댓글
링크
Total
Today
Yesterday