Open DaehunGwak opened 1 year ago
*_
형식) 를 제공해 Criteria를 문자열로 제공했을 때 휴먼에러가 발생하는 것을 방지할 수 있음Specification<T>
는 and
or
not
디폴트 팩토리 메서드를 제공해 스펙들을 연결할 수 있다where
메서드는 null 스펙으로 인자가 들어오면 아무 조건 없는 스펙을 만들어 주는 디폴트 팩토리 메서드Sort
로 복잡한 정렬 지정을 할 수 있음and
or
메서드 제공Pageable
사용, 리턴은 Page<T>
Page<T>
사용 시, count 쿼리 발생Specification<T>
+ Pageable
인자 조합은 count 쿼리 발생SpecBuilder
를 통해 ifHasText
, ifTrue
와 같은 기능으로 선택적으로 스펙을 연결할 수 있다
and
로 엮임@Subselect
사용@Subselect
@Immutable
@Synchronize
를 통해 조회 모델 엔티티를 만들 수 있음
@Subselect
: jpql 쿼리 작성으로 해당 엔티티에 맵핑@Immutable
: 변경 쿼리 생략@Synchronize
: 엔티티 매니저에 지정된 테이블의 변경분이 플러쉬되지 않았다면 플러쉬원본 보러 가기
전반적으로 Criteria 라는 기술을 사용하고 있다.
그러나 이 기술은 실무에 적용하기엔 너무나 번잡하고 더 좋은 대안들이 있다. (by 김영한님)
QueryDSL을 사용하도록 하자.
Repository의 메서드를 생략하기 위해서 Spec을 생성하는데... 이럴 바엔 차라리 Spec을 생성하는게 나을 것 같다.
그나마 괜찮다고 생각한 기능이 있지만
Sort sort = Sort.by("number").ascending().and(Sort.by("orderDate").decending())
이것마저도 QueryDsl은 더 간단하게 기술할 수 있다.
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(100))
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
PageRequest pageReq = PageRequest.of(1, 10);
List<Xxx> Xxxs = XxxDao.findBy("사용자", pageReq);
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1) //0부터 시작(zero index)
.limit(2) //최대 2건 조회
.fetch();
@Test
public void XxxService_BooleanBuilder() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam, ageParam);
}
private List<Member> searchMember1(String usernameCond, Integer ageCond) {
BooleanBuilder builder = new BooleanBuilder();
if (usernameCond != null) {
builder.and(member.username.eq(usernameCond));
}
if (ageCond != null) {
builder.and(member.age.eq(ageCond));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
public void XxxService_WhereParam() throws Exception {
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember2(usernameParam, ageParam);
}
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond) : null;
}
private BooleanExpression ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond) : null;
}
JPQL 일반 조인
+ DTO 반환
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
RDBMS의 View 기능을 JPA에서 구현한 것으로 보인다.
그러나 나라면
일반 Join
+ DTO
+ @Tx ReadOnly
를 사용할 것 같다.
헛소리 주의
추천의 글
에 범균
님이 있음
제가 영한님을 좋아하는 것은 사실입니다만
아무래도 JPA만큼은 영한님 말씀을 ...
Page<Member> findByUsed(Pageable pageable);
List<Member> findByUsed(Pageable pageable);
List<Member> findAll(Pageable pageable);
Order order = orderRepository.findById(1L);
order.changeShippingInfo(newInfo); // 상태 변경
// 변경 내역이 DB에 반영되지 않았는데 조회
List<OrderSummary> summaries = orderSummary.findByOrdererId(1L);
SpringDataJPA를 사용하는 경우
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order>{
//공백
}
위 소스와 같이 JpaSpecificationExecutor
을 상속하는 것만으로 Specification
인자를 파라미터로 갖는 메소드를 갖게된다.
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);
스펙 정의코드
public class OrderSpec{
public static Specification<Order> memberName(final String memberName){
return new Specification<Order>(){
pulbic Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder){
//Order엔티티에서 시작
if(StringUtils.isEmpty(memberName))return null;
Join<Order, Member> m = root.join("member", JoinType.INNER);//Member엔티티 조인
return builder.equal(m.get("name"), memberName);//입력값 memberName을 갖는 엔티티만 필터링
}
};
}
public static Specification<Order> isOrderStatus(){
return new Specification<Order>(){
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder builder){
return builder.equal(root.get("status"), OrderStatus.ORDER);
}
}
}
}
스펙을 사용하는 서비스코드
import static OrderSpec.*;
//OrderService
public List<Order> findOrders(String name){
Specification<Order> spec = where(memberName(name)).and(isOrderStatus());
List<Order> result = orderRepository.findAll(spec);
}
Criteria
에 의존하기 보다 QueryDSL 로 구현하는게 더 나을듯
Repository
는 애그리거트
를 저장, 조회, 삭제하는 것에 초점을 둔 저장소이다.
여러 애그리거트를 로딩해야 하는 경우 => Repository를 이용하는 것은 부적절
ID로 참조할 때 장점?이 사라짐
동적인스턴스
Query query = em.createQuery("SELECT m.username, m.age FROM Member m");//두번쨰 인자로 명확한 지정 아닐때
List<Object[]> resultList = query.getResultList();
//방법1.
List<UserDTO> userDTOs = new ArrayList<UserDTO>();
for(Object[] row : resultList){
UserDTO userDTO = new UserDTO((String)row[0], (Integer)row[1]);
userDTOs.add(userDTO);
}
//방법2 (더 나은 방법)
TypedQuery<UserDTO> query = em.createQuery(
"SELECT new 패키지.UserDTO(m.username, m.age) FROM Member m", UserDTO.class);
List<UserDTO> resultList = query.getResultList();
하이버네이트의 @SubSelect (DB의 View)
진도
일정