5장은 대부분 spec을 이용하여 설명하고 있다.
처음 들어본 개념이라서 찾아보니 Specification과 Querydsl이 비교 대상으로 자주 언급된다.
결론적으로 Specification의 사용은 지양하고, Querydsl을 사용하는 것 같다.
그래서 이번 포스팅에서는 Specificaton과 Querydsl의 개념을 모두 정리해보고, 둘을 비교해보는 내용 위주로 작성하고자 한다.
🔻한번씩 읽어보면 좋을 것 같다.
Spring blog에서 비교한 글
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl
김영한님의 답변
https://www.inflearn.com/questions/16685/specification-%EB%8C%80%EB%B9%84-querydsl%EC%9D%98-%EC%9E%A5%EC%A0%90%EC%9D%B4-%EC%96%B4%EB%96%A4%EA%B2%83%EC%9D%B4-%EC%9E%88%EC%9D%84%EA%B9%8C%EC%9A%94
스프링 데이터 JPA는 Specification 인터페이스를 다음과 같이 정의하고 있다.
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
이런 식으로 스펙 생성 기능을 별도 클래스에 모아 사용할 수 있다.
public CustomerSpecifications {
public static Specification<Customer> customerHasBirthday() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.equal(root.get(Customer_.birthday), today);
}
};
}
public static Specification<Customer> isLongTermCustomer() {
return new Specification<Customer> {
public Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb) {
return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
}
};
}
}
레포지토리에서는 JpaSpecificationExecutor
를 상속한다.
public interface CustomerRepository extends JpaRepository<Customer>, JpaSpecificationExecutor {
// Your query methods here
}
그러면 다음과 같이 findAll()을 사용해 스펙을 만족하는 엔티티를 검색할 수 있다.
customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());
QueryDslPredicateExecutor
을 상속하여 사용할 수 있다.public interface QuerydslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); (1)
Iterable<T> findAll(Predicate predicate); (2)
long count(Predicate predicate); (3)
boolean exists(Predicate predicate); (4)
// … more functionality omitted.
}
interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
QueryDslRepositorySupport
를 상속하도록 한다. 생성자에서super(EntityType.class)
를 호출한다. 메서드를 구현할 때 JOIN, FETCH 사용 가능하다.@Repository
public interface UserRepository extends JpaRepository<User,Long>, CustomerUserRepository {
Optional<User> findByName(String name);
}
public interface CustomUserRepository {
List<User> findAllUserByAge(Integer age);
}
public class UserRepositoryImpl extends QueryDslRepositorySupport implements CustomUserRepository{
public UserRepositoryImpl(){
super(User.class);
}
@Override
public List<User> findAllUserByAge(Integer age){
QUser user = QUser.user;
return from(user)
.where(user.age.eq(23L))
.fetch();
}
}
스프링 데이터 JPA를 이용하면 두가지 방법으로 정렬을 지정할 수 있다.
1. 메서드 이름에 OrderBy 사용
findByOrdererIdOrderByOrderDateDescNumberAsc
List<Entity명> list명 = repository명.findAll(Sort.by(Sort.Direction.DESC/ASC, "기준컬럼명"));
PageableDefault 인터페이스
적용 예시
쿼리 결과에서 임의의 객체를 동적으로 생성할 수 있다.
아래 코드에서는 OrderView 객체를 new 키워드를 이용해 동적으로 생성한다.
public interface OrderSummaryDao extends Repository<OrderSummary,String>{
@Query("select new com.myship.order.query.dto.OrderView(o.number, o.state, m.name, m.id, p.name)
from Order o join o.orderLines ol, Member m, Product p
where o.orderer.memberId.id = m.id
and index(ol) = 0
and ol.productId.id = p.id
order by o.number.number desc")
List<OrderView> findOrderView(String ordererId);
}
@Subselect
@Immutable
@Synchronize
import org.hibernate.annotations.Immutable;
import org.hibernate.annotations.Subselect;
import org.hibernate.annotations.Synchronize;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@Immutable
@Subselect(
''''
select o.order_number as number,
o.version, o.orderer_id, o.orderer_name,
o.total_amounts, o.receiver_name, o.state, o.order_date, p.product_id, p.name as product_name
from purchase_order o inner join order_line ol
on o.order_number = ol.order_number
cross join product p
where ol.line_idx = 0 and ol.product_id = p.product_id
''''
)
@Synchronize({"purchase_order", "order_line", "product"})
public class OrderSummary {
@Id
private String number;
private long version;
@Column(name="orderer_id")
private String ordererId;
@Column(name="orderer_name")
private String ordererName;
protected OrderSummary(){
}
}