[Spring data JPA]사용자 정의 인터페이스

김건우·2022년 12월 3일
0

Spring Data JPA

목록 보기
4/11
post-thumbnail

Spring Data JPA[사용자 정의 인터페이스]

사용자 정의 인터페이스가 왜 필요한가?

  1. Spring Data JPA에서 제공하는 JpaRepository 인터페이스는 스프링이 구현체를 자동으로 생성해준다.Spring Data JPA가 제공하는 인터페이스를 직접 구현하면 구현해야 하는 메서드들이 너무 많다.
  2. 기본적인 네이밍 메서드 , @Query 어노테이션 안에 간단한 쿼리문이아닌 복잡한 동적 쿼리나 - ▪순수 JPA 직접 사용 ▪스프링 JDBC Template 사용 ▪MyBatis 사용 ▪데이터베이스 커넥션 직접 사용 ▪Querydsl 사용이 필요로 할 때 사용자 정의 인터페이스가 필요하다.

코드를 설명하기전에 우선 나는 이것을 수업으로만 들을때는 이것이 왜 필요한지 전혀 감이 잡히지가 않았다. 상품 주문에 관련된 repository를 구현했었다. 처음에는 순수 jpa repository를 사용하여 직접 영속성 컨텍스트를 관리하는 메소드를 제작했었다. 하지만 spring data JPA를 학습하면서 기존의 순수 JPA Repository를 spring data JPA로 리펙토링과정을 진행해야했다. 여기에서 문제가 생긴과정이 리펙토링을 마쳤지만 상품주문 검색에 사용한 동적쿼리 메소드를 전혀 구현할 수 가 없었다. 왜냐면 Spring data JPA가 제공하는 인터페이스에서는 동적쿼리나 QueryDSL을 재정의하거나 구현할 수 없기 때문이다. 그래서 사용자 정의 인터페이스가 필요하고 사용된다는 것을 깨달았다.

Order Repository ( Interface )

public interface OrderRepository extends JpaRepository<Order,Long> {
}

위 코드는 Order에 관련된 Repository이다 .이 Repository에서는 동적 쿼리나 QueryDSL을 전혀 사용 할 수 없다. 그래서 필요한 것이 사용자 정의 인터페이스이다.

사용자 정의 인터페이스의 과정과 규칙

  1. 사용자 정의 인터페이스 제작(이 인터페이스의 이름은 아무것이나 상관없다. 하지만 관례상 Order에 관한 사용자정의 repository가 필요하다면 OrderRepositoryCustom이라고 명명하자)
  2. 사용자 정의 인터페이스에 구현할 추상메서드를 정의한다.
  3. 사용자 정의 인터페이스를 구현할 클래스를 제작한다.
    여기서 가장 중요한 규칙인데 이 클래스의 이름은 매우 중요하고 이 규칙을 따라야 spring이 인식한다. [ 구현 할 인터페이스의 이름 + Impl ] 로 해야한다.
    4.Spring data JPA가 제공하는 인터페이스 Repository에서 사용자 정의 인터페이스를 상속받는다.

이제 규칙을 알아보았으니 코드로 보자!

1.사용자 정의 인터페이스 제작하기

다음의 코드는 순수 jpa나 동적쿼리나 queryDsl 등등 spring data jpa에서 제공하는 메소드가 아닌것들을 구현할 메서드들을 정의하는 인터페이스이다.

public interface OrderRepositoryCustom {

     List<Order> findAll();

     List<Order> findAllByString(OrderSearch orderSearch);

     List<Order> findAllByCriteria(OrderSearch orderSearch);
}

2.사용자 정의 인터페이스를 구현할 클래스 제작

다음의 코드는 코드가 굉장히 길지만 코드의 내용이나 해석이 이 블로그글에서 중요한 것은 아니라고 생각한다. 여기서 알아차려야 할점은 우선 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();
    }

}

3. Spring data JPA Repository에서 사용자 정의 인터페이스 상속받기

앞에서 소개한 Spring data JPA의 Repository인터페이스는 제공받는 JpaRepository<>만을 상속받고 있었다. 하지만 이제 사용자 정의 인터페이스 또한 상속받아서 사용 할 수 있다.

//spring data JPA외에 사용자 정의 Repository까지 상속 받음
public interface OrderRepository extends JpaRepository<Order,Long>, OrderCustomRepository {
}
profile
Live the moment for the moment.

0개의 댓글