코드를 설명하기전에 우선 나는 이것을 수업으로만 들을때는 이것이 왜 필요한지 전혀 감이 잡히지가 않았다. 상품 주문에 관련된 repository를 구현했었다. 처음에는 순수 jpa repository를 사용하여 직접 영속성 컨텍스트를 관리하는 메소드를 제작했었다. 하지만 spring data JPA를 학습하면서 기존의 순수 JPA Repository를 spring data JPA로 리펙토링과정을 진행해야했다. 여기에서 문제가 생긴과정이 리펙토링을 마쳤지만 상품주문 검색에 사용한 동적쿼리 메소드를 전혀 구현할 수 가 없었다. 왜냐면 Spring data JPA가 제공하는 인터페이스에서는 동적쿼리나 QueryDSL을 재정의하거나 구현할 수 없기 때문이다. 그래서 사용자 정의 인터페이스가 필요하고 사용된다는 것을 깨달았다.
public interface OrderRepository extends JpaRepository<Order,Long> {
}
위 코드는 Order에 관련된 Repository이다 .이 Repository에서는 동적 쿼리나 QueryDSL을 전혀 사용 할 수 없다. 그래서 필요한 것이 사용자 정의 인터페이스이다.
이제 규칙을 알아보았으니 코드로 보자!
다음의 코드는 순수 jpa나 동적쿼리나 queryDsl 등등 spring data jpa에서 제공하는 메소드가 아닌것들을 구현할 메서드들을 정의하는 인터페이스이다.
public interface OrderRepositoryCustom {
List<Order> findAll();
List<Order> findAllByString(OrderSearch orderSearch);
List<Order> findAllByCriteria(OrderSearch orderSearch);
}
다음의 코드는 코드가 굉장히 길지만 코드의 내용이나 해석이 이 블로그글에서 중요한 것은 아니라고 생각한다. 여기서 알아차려야 할점은 우선 entitymanager를 직접 주입 받아 사용하는 순수 JPA Repository라는점 그리고 검색에 필요한 동적쿼리를 재정의 한 메소드가 존재한다는 점! 이러한 것들은 Spring data JPA에서는 사용할 수 없다. 그래서 사용자 정의 인터페이스가 필요한 것이다. 그리고 앞서 사용자 정의 인터페이스의 가장 중요한 규칙인 인터페이스를 구현할 클래스의 이름은 OrderRepositoryImpl이다 이 명명 규칙은 매우 중요하다. 그래야 Spring data JPA가 인색해서 스프링 빈으로 등록을 해준다
@RequiredArgsConstructor
public class OrderRepositoryImpl implements OrderCustomRepository {
private final EntityManager em;
public List<Order> findAll() {
return em.createQuery("select o from Order o join fetch o.member m " +
"join fetch o.delivery d")
.getResultList();
}
public List<Order> findAllByString(OrderSearch orderSearch) {
String jpql = "select o from Order o join o.member m";
boolean isFirstCondition = true;
if (orderSearch.getOrderStatus() != null) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " o.status = :status";
}
if (StringUtils.hasText(orderSearch.getMemberName())) {
if (isFirstCondition) {
jpql += " where";
isFirstCondition = false;
} else {
jpql += " and";
}
jpql += " m.name like :name";
}
TypedQuery<Order> query = em.createQuery(jpql, Order.class)
.setMaxResults(1000);
if (orderSearch.getOrderStatus() != null) {
query = query.setParameter("status", orderSearch.getOrderStatus());
}
if (StringUtils.hasText(orderSearch.getMemberName())) {
query = query.setParameter("name", orderSearch.getMemberName());
}
return query.getResultList();
}
public List<Order> findAllByCriteria(OrderSearch orderSearch) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> o = cq.from(Order.class);
Join<Object, Object> m = o.join("member", JoinType.INNER);
List<Predicate> criteria = new ArrayList<>();
if (orderSearch.getOrderStatus() != null) {
Predicate status = cb.equal(o.get("status"), orderSearch.getOrderStatus());
criteria.add(status);
}
if (StringUtils.hasText(orderSearch.getMemberName())) {
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);
return query.getResultList();
}
}
앞에서 소개한 Spring data JPA의 Repository인터페이스는 제공받는 JpaRepository<>만을 상속받고 있었다. 하지만 이제 사용자 정의 인터페이스 또한 상속받아서 사용 할 수 있다.
//spring data JPA외에 사용자 정의 Repository까지 상속 받음
public interface OrderRepository extends JpaRepository<Order,Long>, OrderCustomRepository {
}