티스토리 뷰
Spring Data JPA 적용
- 환경설정
- Repository 리팩토링
- 명세 적용
| 환경설정
ㅇ pom.xml에 spring-data-jpa 라이브러리 추가
1 2 3 4 5 6 7 | <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa --> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.8.0.RELEASE</version> </dependency> | cs |
ㅇ appConfig.xml에 <jpa:repositories> 추가 후 base-package 속성에 repository 위치 지정
1 | <jpa:repositories base-package="jpabook.jpashop.repository" /> | cs |
| Repository Refactoring
기존 Repository들이 Spring Data JPA를 사용하도록 Refactoring
||| Member Repository
c. 기존 Repository
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 30 31 32 33 | import jpabook.jpashop.domain.Member; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.List; @Repository public class MemberRepository { @PersistenceContext EntityManager em; public void save(Member member) { em.persist(member); } public Member findOne(Long id) { return em.find(Member.class, id); } public List<Member> findAll() { return em.createQuery("select m from Member m", Member.class) .getResultList(); } public List<Member> findByName(String name) { return em.createQuery("select m from Member m where m.name = :name", Member.class) .setParameter("name", name) .getResultList(); } } | cs |
c. 리펙토링 후
- 클래스를 인터페이스로 변경
- Spring Data JPA가 제공하는 JpaRepository를 상속받고 제네릭 타입 지정
- JpaRepository가 제공하는 기본 메소드 save(), findOne(), findAll() 제거
1 2 3 4 5 6 7 8 9 10 | import jpabook.jpashop.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface MemberRepository extends JpaRepository<Member, Long> { List<Member> findByName(String name); } | cs |
||| Item Repository
c. 기존 Repository
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 30 | import jpabook.jpashop.domain.item.Item; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.List; @Repository public class ItemRepository { @PersistenceContext EntityManager em; public void save(Item item) { if (item.getId() == null) { em.persist(item); } else { em.merge(item); } } public Item findOne(Long id) { return em.find(Item.class, id); } public List<Item> findAll() { return em.createQuery("select i from Item i",Item.class).getResultList(); } } | cs |
c. 리펙토링 후
- ItemRepository가 제공하는 모든 기능은 JpaRepository가 제공하는 공통 인터페이스로
1 2 3 4 5 6 | import jpabook.jpashop.domain.item.Item; import org.springframework.data.jpa.repository.JpaRepository; public interface ItemRepository extends JpaRepository<Item, Long> { } | cs |
||| Order Repository
c. 기존 Repository
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 30 31 32 33 34 35 36 37 38 39 40 | @Repository public class OrderRepository { @PersistenceContext EntityManager em; public void save(Order order) { em.persist(order); } public Order findOne(Long id) { return em.find(Order.class, id); } public List<Order> findAll(OrderSearch orderSearch) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Order> cq = cb.createQuery(Order.class); Root<Order> o = cq.from(Order.class); List<Predicate> criteria = new ArrayList<Predicate>(); //주문 상태 검색 if (orderSearch.getOrderStatus() != null) { Predicate status = cb.equal(o.get("status"), orderSearch.getOrderStatus()); criteria.add(status); } //회원 이름 검색 if (StringUtils.hasText(orderSearch.getMemberName())) { Join<Order, Member> m = o.join("member", JoinType.INNER); //회원과 조인 Predicate name = cb.like(m.<String>get("name"), "%" + orderSearch.getMemberName() + "%"); criteria.add(name); } cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()]))); TypedQuery<Order> query = em.createQuery(cq).setMaxResults(1000); //최대 검색 1000 건으로 제한 return query.getResultList(); } } | cs |
c. 리펙토링 후
- findAll()의 복잡한 로직은 명세 기능을 사용
- 명세 기능 사용을 위해 JpaSpecificationExecutor 추가 상속
1 2 3 4 5 6 7 8 | import jpabook.jpashop.domain.Order; import jpabook.jpashop.repository.custom.CustomOrderRepository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order>, CustomOrderRepository { } | cs |
| 명세(Specification) 적용
c. 명세를 작성하기 위한 OrderSpec 클래스 생성
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 30 | 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> orderStatusEq(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 |
c. 검색 조건을 가지고 있는 OrderSearch 객체에 Specification을 생성하도록 코드 추가
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class OrderSearch { private String memberName; //회원 이름 private OrderStatus orderStatus; //주문 상태 // Getter, Setter public Specifications<Order> toSpecification() { return where(memberNameLike(memberName)) .and(orderStatusEq(orderStatus)); } } | cs |
c. Repository 검색 코드가 명세를 파라미터로 넘기도록 Order Service 수정
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Service @Transactional public class OrderService { @Autowired MemberRepository memberRepository; @Autowired OrderRepository orderRepository; @Autowired ItemService itemService; // 주문, 주문 취소 /** * 주문 검색 */ public List<Order> findOrders(OrderSearch orderSearch) { // return orderRepository.findAll(orderSearch); // 리펙토리 전 return orderRepository.findAll(orderSearch.toSpecification()); // Specification 사용 //return orderRepository.search(orderSearch); //QueryDSL 사용 } } | cs |
| QueryDSL 통합
Spring Data JPA는 두 가지 방법으로 QueryDSL 지원
- org.springframework.data.querydsl.QuerydslPredicateExecutor
- org.springframework.data.jpa.repository.support.QuerydslRepositorySupport
|| QuerydslPredicateExecutor
QuerydslPredicateExecutor 는 Spring Data JPA에서 편리하게 QueryDSL을 사용할 수 있지만 기능에 한계
- 예로 join, fetch는 사용할 수 없음
- QueryDSL이 제공하는 다양한 기능을 사용하려면 JPAQuery를 직접 사용하거나 QuerydslRepositorySupport 사용
c. Repository에서 QuerydslPredicateExecutor 상속
1 2 3 4 | public interface itemRepository extends JpaRepository<Item. Long>, QuerydslPredicateExecutor<Item> { } | cs |
c. QueryDSL 사용 예시
1 2 3 4 | QItem item = QItem.item; Iterable<Item> result = itemRepository.findAll ( item.name.contains("장난감").and(item.price.between(10000, 20000)) ); | cs |
QueryDSL을 검색조건으로 사용하면 Spring Data JPA가 제공하는 페이징과 정렬 기능도 함께 사용 가능
c. QuerydslPredicateExecutor Interface
1 2 3 4 5 6 7 8 | 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); } | cs |
|| QuerydslRepositorySupport
--
QueryDSL의 모든 기능을 사용하려면 JPAQuery 객체를 직접 생성해서 사용
QuerydslRepositorySupport를 상속받아 사용하면 더 편리하게 QueryDSL 사용 가능
c. 사용자 정의 Repository
- 공통 인터페이스는 직접 구현할 수 없기 떄문에 사용자 정의 Repository 생성
1 2 3 4 5 6 7 8 9 10 11 | import jpabook.jpashop.domain.Order; import jpabook.jpashop.domain.OrderSearch; import java.util.List; public interface CustomOrderRepository { public List<Order> search(OrderSearch orderSearch); } | cs |
c. QuerydslRepositorySupport 사용
- 검색 조건에 따라 동적으로 쿼리 생성 (line 25~32)
- 생성자에서 QuerydslRepositorySupport에 엔티티 클래스 정보를 넘겨주어야 함 (line 13~15)
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 30 31 32 33 34 35 36 | import com.mysema.query.jpa.JPQLQuery; import jpabook.jpashop.domain.Order; import jpabook.jpashop.domain.OrderSearch; import jpabook.jpashop.domain.QMember; import jpabook.jpashop.domain.QOrder; import org.springframework.data.jpa.repository.support.QueryDslRepositorySupport; import org.springframework.util.StringUtils; import java.util.List; 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.list(order); } } | cs |
||| QuerydslRepositorySupport 핵심 기능
--
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 | @Repository public abstract class QuerydslRepositorySupport { // 엔티티 매니저 반환 protected EntityManager getEntityManager() { return entityManager; } // from 절 반환 protected JPQLQuery from(EntityPath<?>... paths) { return querydsl.createQuery(paths); } // QueryDSL delete 절 반환 protected DeleteClause<JPADeleteClause> delete(EntityPath<?> path) { retirn new JPADeleteClause(entityManager, path); } // QueryDSL update 절 반환 protected UpdateClause<JPAUpdateClause> update(EntityPath<?> path) { return new JPAUpdateClause(entityManager, path); } // Spring Data JPA가 제공하는 QueryDSL을 편하게 사용하도록 돕는 헬터 객체 반환 protected Querydsl getQuerydsl() { return this.querydsl; } } | cs |
출처 : 자바 ORM 표준 JPA 프로그래밍
https://github.com/holyeye/jpabook/tree/master/ch12-springdata-shop
'Books' 카테고리의 다른 글
[JPA] 엔티티 그래프 (Entity Graph) (0) | 2020.12.29 |
---|---|
[JPA] 영속성(persistence) 관리 (OSIV) (0) | 2020.12.28 |
[Spring + JPA] Spring Data JPA 란? (2) (0) | 2020.12.28 |
[Spring + JPA] Spring Data JPA 란? (1) (2) | 2020.12.24 |
[Spring + JPA] Make Web Application (2) (0) | 2020.12.24 |