API Development Basics
Membership Registration API
왜 이런식으로 분리를 하냐면
api 랑 화면이랑 좀 다르다.
@Controller @ResponseBody = @RestController@ResponseBody = JSON으로 주고 받자.
JSON -> Member
postman
위치 오류
내용 빼도 저장이됨
@Valid 하고 싶으면
@NotEmpty원하시면 컨트롤러 어드바이스 같은거 잡으시고
심각한 문제가 많습니다.
이거 자체가 나쁜건 아닌데 화면 벨리데이션 의 로직이 엔티티에 들어가 있다. = 엔티티가 순수하지 못하다.
만약에 username으로 바꾸면 = api 스펙 자체가 바뀜
엔티티는 바뀔수 있다.
@Data
static class CreateMemberResponse{
private Long id;
public CreateMemberResponse(Long id) {
this.id = id;
}
}
큰 장애가 발생됨.
소셜로그인, 그냥 로그인, 정보 조회등등 많은 api가 사용한다.
항상 엔티티를 파라미터로 받지 마라.
@PostMapping("/api/v2/members")
public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request){
Member member = new Member();
member.setName(request.getName());
Long id = memberService.join(member);
return new CreateMemberResponse(id);
}
장점 - 스펙이 바뀌지않고 값이 변경되면 컴파일러는 오류를 잡아줌.
답답한게 모냐면 문서 안까보면 파라미터 뭐가 넘어오는지 모름.
검증 값은 이렇게 넣어라.
@Data
static class CreateMemberRequest{
@NotEmpty
private String name;
}
엔티티가 순수하지 못하면 api 별로 검증값이 달라서 매번 문제됨.
실무에서는 절대로 노출하거나 바로 받으면 안된다.
가져다 쓰는 타팀들 열받고... ㅠㅠ
DTO를 통해서 받거나 주자.
Member Modification API
rest api 내용.
등록과 수정은 스펙이 다름
별도에 DTO를 만들자
내부 클래스 만들기
롬복에서 쓰는것이 정해져 있음.
그러나 DTO에는 막써요.
@Data
@AllArgsConstructor
static class UpdateMemberRequest {
private String name;
}
@Data
static class UpdateMemberResponse {
private Long id;
private String name;
public UpdateMemberResponse(Long id, String name) {
this.id = id;
this.name = name;
}
}
update를 만들어야 되겠네요.
@PutMapping("/api/v2/members/{id}")
public UpdateMemberResponse updateMemberResponse(@PathVariable("id") Long id,
@RequestBody @Valid UpdateMemberRequest request){
memberService.update(id, request.getName());
Member findMember = memberService.findOne(id);
return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}
수정할때 뭐가 좋다 그랫죠?
가급적이면 변경감지.
영속 상태
맴버에서 반환해줘도됨. 근데 좀 애매해짐
커맨드와 쿼리를 철저하게 분리한다.
디버깅 : 함부로 어노테이션 쓰지 말것
@Data
@AllArgsConstructor - 오류
static class UpdateMemberRequest {
간단한 pk 정도는..
유지 보수가 좋아짐.
분석
Membership Inquiry API
ddl-auto : none
데이터 넣기 귀차느니까
1.단순하게 = 가장 안좋은 버전
@GetMapping("/api/v1/members")
public List<Member> membersV1(){
return memberService.findMembers();
}
몇가지 문제가 있음.
계속 강조하는거
주문 탭이 나오네?
@JsonIgnore = 잭슨
여기까지는 해결이 되는데.
또 화면 로직이 들어갓죠.
엔티티로 의존이 들어와야되는데 나가는 모양새
또 이름 문제.
번외 문제
배열이 넘어옴. 근데 갑자기 count를 넣어달라고함. 배열에?
오브젝트 {
count : 4,
배열: [{}, {}, {}]
}
@Data
@AllArgsConstructor
static class Result<T>{
// private int count;
private T data;
}
@Data
@AllArgsConstructor
static class MemberDto{
private String name;
}
JAVA8
@GetMapping("/api/v2/members")
public Result membersV2(){
List<Member> findMembers = memberService.findMembers();
List<MemberDto> collect = findMembers.stream()
.map(m->new MemberDto(m.getName()))
.collect(Collectors.toList());
return new Result(collect);
}
언젠가 수정사항 들어옴 ㅋㅋ
여담이지만
단축키 : 인라인
결과값 확인
제발 필요한것만 노출 하세요.
랩핑 클래스 수정하면 끝남
return new Result(collect.size(), collect);
추천이 아니라 강제 드립니다.
Advanced API Development - Preparation
Enter sample data for query
A 가 책 두권을 삿다.
B 가 다른책 두권을 삿다.
주문은 두건
@Component
@RequiredArgsConstructor
public class InitDb {
@Component
@Transactional
@RequiredArgsConstructor
static class InitService{
private final EntityManager em;
public void dbInit1(){
}
}
}
라이브 로 보여드릴게요
public void dbInit1(){
Member member = new Member();
member.setName("userA");
member.setAddress(new Address("서울", "1", "1111"));
em.persist(member);
}
@PostConstruct
/**
* 총 주문 2개
* * userA
* * JPA1 BOOK
* * JPA2 BOOK
* * userB
* * SPRING1 BOOK
* * SPRING2 BOOK
*/
단축키 : 일괄 변경
...으로 했던 이유 = 배열
public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems){
그냥 넣으면 잘 안되요. 라이프 사이클 때문에
public void dbInit1(){
Member member = new Member();
member.setName("userA");
member.setAddress(new Address("서울", "1", "1111"));
em.persist(member);
Book book1 = new Book();
book1.setName("JPA1 BOOK");
book1.setPrice(10000);
book1.setStockQuantity(100);
em.persist(book1);
Book book2 = new Book();
book2.setName("JPA2 BOOK");
book2.setPrice(20000);
book2.setStockQuantity(100);
em.persist(book2);
OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, 1);
OrderItem orderItem2 = OrderItem.createOrderItem(book2, 20000, 2);
Delivery delivery = new Delivery();
delivery.setAddress(member.getAddress());
Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);
em.persist(order);
}
@PostConstruct
public void init(){
initService.dbInit1();
}
ddl-auto: create
2개중에 대표상품만 띄우게
갠지가 안난다.
단축키 : 메소드 추출
뭐 해줌 - replace
다 해줌.
단축키 : 변수 파라미터화
public void init(){
initService.dbInit1();
initService.dbInit2();
}
Advanced API Development - Lazy Loading and Query Performance Optimization
Simple Order Lookup V1: Exposing Entities Directly
원망하지 말자 ㅋㅋ
이번 장에서는 @XToOne 을 다룸
장애 케이스
order->member->order->->->
member.orders
누구하나를 @JsonIgnore
orderItem.order
delivery.order
= mappedBy 위치랑 맞추는것도 좋을듯
2번째 문제
private Member member= new ProxyMember();
private Member member = new ByteBuddyInterceptor();
잭슨 라이브러리가 봣는데 순수 객체가 아니네..오류
잭슨 라이브러리를 막기.
모듈 설치
버전을 지우면 알아서 맞는 버전을 찾아줌
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-hibernate5'
@Bean
Hibernate5Module hibernate5Module(){
return new Hibernate5Module();
}
드디어 나왔다.
지연 로딩이라 null 파티
지연로딩을 무시하라고 해서 이렇게 된거임.
로딩되게 할수 있음.
@Bean
Hibernate5Module hibernate5Module(){
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
return hibernate5Module;
}
데이터 다 가져옴.
노출 = 피곤한일 + 성능문제
로그에서 레이지 를 다 불러옴
안좋습니다.
사실 이런거 쓰시면 안되요.
모듈 막 등록해서 쓰는게...이거 때문에 쓰기에는 적절하지 않음.
내가 원하는 애만 골라서 하는 방법이 있습니다.
강제 레이지 로딩 = 강제 디비 조회.
order.getMember().getName();
// order.getMember() <<프록시 디비>> getName();
for (Order order : all) {
order.getMember().getName();
order.getDelivery().getAddress();
}
잘 나옴
좀 안깔끔하죠.
이렇게 복잡하면 운영이 망합니다.
핵심은 DTO
EAGER를 하지마세요.
1. JPA를 어떻게든 되는데 JPQL은 그대로 날라가서 오더만 가져오고 다시 날리기 = N + 1
2. 그리고 다른 데서 불러도 다 긁어옴.
Simple Order Lookup V2: Converting Entities to DTOs
Dto가 파라미터 받는건 문제가 안됨.
java8
@GetMapping("/api/v2/simple-orders")
public List<SimpleOrderDto> ordersV2(){
return orderRepository.findAllByString(new OrderSearch()).stream()
.map(SimpleOrderDto::new)
.collect(Collectors.toList());
}
엔티티에서 스팩에 맞게 딱 뽑아서
Address = valueObject = 엔티티가 아니다.
절대적으로 다 DTO로 바꿔서
문제 LAZY로딩으로 인한 쿼리 호출이 너무 많다.
order -> 루프( LAZY.member, LAZY.address)
# org.hibernate.type : trace
3번의 쿼리가 나갔다. 아니다 5번이다.
ORDER -> 1회 = 주문 2개 = 루프 2개
1번 루프 ORDER 2개
맴버 쿼리, 딜리버리 쿼리
2번 루프 ORDER 2개
맴버 쿼리, 딜리버리 쿼리
소위 N + 1 = 1 + N(회원2+배송2) 쿼리
한번 해보십쇼~ 그래도 한번 보여드릴까요.
order -> member -> delivery -> delivery
Simple Order Lookup V3: Converting Entities to DTOs - Fetch Join Optimization
Simple Order Lookup V4: Lookup directly from JPA to DTO
소리 작음
public List<OrderSimpleQueryDto> findOrderDtos(){
return em.createQuery("select o from Order o" +
" join o.member m" +
" join o.delivery d", OrderSimpleQueryDto.class)
.getResultList();
}
o객체는 Dto에 매핑 될수가 없어요.
entity나 embadable 정도만 가능
new 쓰셔야됨
o를 넣으면 id 값으로 적용
Column Selection mode
select 절 변화 확인
Order를 건드리지 않은 상태 = 재사용 가능
대신 재사용성이 없음
Dto를 조회한건 JPA 에서 손댈수 있는게 없음
코드도 좀 지저분 하고
정리
생각보다 영향이 미비
그런데 리파지토리가 api 스펙에 최적화됨.
그래서 LV3 까지는 괜찮다고봄.
LV4는 논리적으로 계층이 다 깨져 있는것
뒤에서 더 조언을 드릴게요.
대부분의 성능은 FROM 절 이후에서 결정된다.
리파지토리는 엔티티를 조회하는데 써야되요
쿼리용을 별도 패키지로 뽑는다.
이렇게 위치를 옮김
리파지토리는 순수한 엔티티만을 조회
권장을 드립니다.
정리
기본 LV2, 필요하면 LV3-4, 이후 SQL조회
여기까지 ToOne 맴버-딜리버리
Advanced API Development - Optimizing Collection Lookups
Order Lookup V1: Direct Exposure of Entities
컬랙션조회 = 1:다 조회
어렵다.
내가 주문한 내역이랑 상품이랑 같이 뽑아보는 방식
LV1 엔티티 직접 노출
Order.orderItems 에서 @JsonIgnore 삭제
그냥 뿌리면 문제
단축키 : iter
하이버네이트 내용
@GetMapping("/api/v1/orders")
public List<Order> ordersV1(){
List<Order> all = orderRepository.findAllByString(new OrderSearch());
for (Order order : all) {
order.getMember().getName();
order.getDelivery().getAddress();
List<OrderItem> orderItems = order.getOrderItems();
orderItems.stream().forEach(o-> o.getItem().getName());
}
return all;
}
정리
Order Lookup V2: Converting Entities to DTOs
@GetMapping("/api/v1/orders")
public List<OrderDto> ordersV2(){
}
static class OrderDto{
}
@GetMapping("/api/v1/orders")
public List<OrderDto> ordersV2(){
List<Order> orders = orderRepository.findAllByString(new OrderSearch());
List<OrderDto> collect = orders.stream()
.map(o -> new OrderDto(o))
.collect(Collectors.toList());
return collect;
}
static class OrderDto{
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private List<OrderItem> orderItems;
public OrderDto(Order order) {
orderId = order.getId();
name = order.getMember().getName();
orderDate = order.getOrderDate();
orderStatus = order.getStatus();
address = order.getDelivery().getAddress();
orderItems = order.getOrderItems();
}
}
디버그 : uri 중복
그래도 에러남
property가 없다. = getter setter 말하는거임
@Data 뭐 되긴하는데 @Getter도 괜찬음.
아이템들이 안나옴 = 엔티티니까
order.getOrderItems().stream().forEach(o->o.getItem().getName());라고 하면 안되요.
dto 안에 엔티티가 있으면 안되요. 감싸는것도 안되요.
컬렉션은 다 바꾸셔야되요. = api 스팩 바뀌면 또 다 수정 하셔야 되요.
이름만 필요하면
@Getter
static class OrderItemDto{
private String itemName;
private int orderPrice;
private int count;
public OrderItemDto(OrderItem orderItem) {
itemName = orderItem.getItem().getName();
orderPrice = orderItem.getOrderPrice();
count = orderItem.getCount();
}
}
딱 강사님이 원하던데로
단순히 껍데기가아니라 정말 데이터를 Dto로 처리해라.
정리
단축키 : 인라인
@GetMapping("/api/v2/orders")
public List<OrderDto> ordersV2(){
return orderRepository.findAllByString(new OrderSearch()).stream()
.map(OrderDto::new)
.collect(toList());
}
orderItems = order.getOrderItems().stream()
.map(o -> new OrderItemDto(o))
.collect(toList());
order 2개->member 1개->delivery1개->orderItem 2개루프->item 이름 1개
Order Lookup V3: Converting Entities to DTOs - Fetch Join Optimization
오더 2개 아이템4개 = 4개
1오더 2개, 2오더 2개
public List<Order> findAllWithItem() {
return em.createQuery(
"select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" +
" join fetch oi.item i", Order.class)
.getResultList();
}
JPA는 ROW수 만큼 작업을 수행한다. =
쿼리 데이터가 많아지면 성능이 떨어진다.
4개 날라옴
고대로 복사
for (Order order : orders) {
System.out.println("order ref=" + order + " id=" + order.getId());
}
JPA에서는 주소값이 같으면 같다고 판단한다.
order ref=jpabook.jpashop.domain.Order@1f967b5b id=4
order ref=jpabook.jpashop.domain.Order@1f967b5b id=4
JPA는 데이터를 섣부르게 판단하지 않음.
distinct = DB + JPA
그런데 문제가 있다. DB는 완전히 같아야 중복 제거가 됨.
될때도 있지만 안되고 안되는게 맞다.
애플리케이션 단계에서 처리를 해주는거임.
정리
코드가 같다. 쿼리는 10분의1
앞에서의 LV3 와 다른점을 설명하겠습니다.
어마어마한 단점이 하나 있다. 페이징 불가능
.setFirstResult(1)
.setMaxResults(100)
limit offset 없음
firstResult/maxResults specified with collection fetch; applying in memory!
다 가져온다음에 메모리에서 소팅했다.
잘못쓰면 큰일난다.
페치 조인부분에서 설명함.
1:M:N
Order Lookup V3.1: Converting Entities to DTOs - Paging and Breaking Limits
페이징
꿀팁 대방출
//페이징 영향 안주는 쿼리, 페이징 가능
public List<Order> findAllWithMemberDelivery() {
옵션을 줍니다.
여기서 필살기
띄어쓰기 조심
default_batch_fetch_size: 100where
orderitems0_.order_id in (
?, ?
)
(4, 11);
조회쿼리 -> size만큼 값추출 -> 조작된 값이 주입된 인쿼리
where
item0_.item_id in (
?, ?, ?, ?
)
(2, 3, 9, 10);
이정도면 왠만하면 나옴.
더빠를 거면 레디스 써야지
V3 단점 : 중복데이터를 다 전송.
깔끔하게 나옴
하지만 중복을 제거한다는데 의의가 있다.
페이징 하려면 방법이 없다.
xToOne 관계도 바꾸면됨.
그러나 네트워크는 많이탐(쿼리가 더 날라감)
그래서 패치 조인을 미리 세팅해주는것이 좋다.
디테일하게 적용
@BatchSize(size = 100)
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
컬랙션이 아닌경우
OrderItem은 ToOne이라서 적으면 안되고
@ManyToOne(fetch = LAZY)
@JoinColumn(name="item_id")
private Item item;
여기다 적으면 된다.
@BatchSize(size = 100)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="dtype")
@Getter @Setter
public abstract class Item {}
그런데 상황에 따라 다 달라서 그냥 디폴트가 편하다.
정리
열변을 토하는 이유
= 개발시간을 엄청 줄일 수 있다.
가장 큰 장점 : 페이징이 가능하다
min < ? < 1000 = 인쿼리 한계
고민의 방향이 다름
루프를 끝까지 돌리니 결국 다 가져옴.
애매하면 100~500으로 쓰세요.
여기서 90퍼 해결됨
Order Lookup V4: Direct DTO Lookup in JPA
패키지 나눈 이유
화면에 의존하는 Dto
이런걸 관심사 분리라고함.
라이프 사이클이 다를때
단축키 : 다음에러 F2
별도로 만든 이유
컬랙션을 빼는 이유
컬랙션은 바로 넣을수 없음.
= 데이터를 한줄밖에 못넣음.
일단 끝을 냅니다.
stream 안쓰고 바로 foreach
안넣어도 되지만
oi.order.id키값으로 식별하는거니 탐색안하고 그냥 넣어줌.
public List<OrderQueryDto> findOrderQueryDtos() {
List<OrderQueryDto> result = findOrders();
result.forEach( o -> {
List<OrderItemQueryDto> orderItems = findOrderItems(o.getOrderId());
o.setOrderItems(orderItems);
});
return result;
}
이런식으로 빼도 되죠? Dto라 대충대충
@JsonIgnore
private Long orderId;
정리
그냥 조인임. 패치조인 아님
2개의 OrderItem에 1개의 Item을 붙인것이라
갯수가 늘지 않음. M : 1 = xToOne
1 + N X 1
Order Lookup V5: Direct DTO Lookup in JPA - Optimized Collection Lookup
컬랙션 상황의 최적화
findOrderQueryDtos의 단점 loop를 돌음
= 한방에 가져올 예정
List<OrderItemQueryDto> orderItems = em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id in :orderIds", OrderItemQueryDto.class)
.setParameter("orderIds", orderIds)
.getResultList();
너무 좋아
List<Long> orderIds = result.stream().map(o -> o.getOrderId()).collect(Collectors.toList());
Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));
result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));
1.주문정보 쿼리 -> 리스트 화 -> 리스트 값을 넣은 조작쿼리 -> 쿼리값을 (주문번호, DTO)로 맵핑
2.주문정보 쿼리를 루프 -> 돌면서 Dto 맵에 주문 Id를 주입하여 벨류인 DtoList를 주문정보에 추가한다.
public List<OrderQueryDto> findAllByDto_optimization() {
List<OrderQueryDto> result = findOrders();
//List<Long> orderIds = toOrderIds(result);
Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(toOrderIds(result));
result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));
return result;
}
private Map<Long, List<OrderItemQueryDto>> findOrderItemMap(List<Long> orderIds) {
List<OrderItemQueryDto> orderItems = em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
" from OrderItem oi" +
" join oi.item i" +
" where oi.order.id in :orderIds", OrderItemQueryDto.class)
.setParameter("orderIds", orderIds)
.getResultList();
Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));
return orderItemMap;
}
private List<Long> toOrderIds(List<OrderQueryDto> result) {
List<Long> orderIds = result.stream()
.map(o -> o.getOrderId())
.collect(Collectors.toList());
return orderIds;
}
JPQL DTO가 마냥 편하지 만은 않구나.
장점 : 데이터 셀렉트의 양이 줄어듬
Order Lookup V6: Direct Lookup from JPA to DTO, Flat Data Optimization
1방에 가져오기
디버깅: 괄호, 멤버 변수 명
public List<OrderFlatDto> findAllByDto_flat() {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)" +
" from Order o" +
" join o.member m" +
" join o.delivery d" +
" join o.orderItems oi" +
" join oi.item i", OrderFlatDto.class)
.getResultList();
}
@GetMapping("/api/v6/orders")
public List<OrderFlatDto> ordersV6(){
return orderQueryRepository.findAllByDto_flat();
}
이거의 장점 : 쿼리 한번
단점 : 페이징은 되나 오더를 기준으로 페이징이 될리가 있나
OrderItems가 기준이 되서 어려움.
스펙이 안맞음 = 노가다
복잡한 코드를 넣어주시면 됩니다.
내가 하면 된다.
망했습니다.
@EqualsHashCode 기준으로 묶어주기
@EqualsAndHashCode(of="orderId")
@GetMapping("/api/v6/orders")
public List<OrderFlatDto> ordersV6(){
List<OrderFlatDto> flats = orderQueryRepository.findAllByDto_flat();
//노가다
return flats;
}
정리
장점 쿼리1번
단점 노가다, 페이징 불가능
Advanced API Development - Practical Essential Optimization
OSIV and Performance Optimization
장애 가능성
차이점
치명적 단점
커넥션이 마른다.
설정값 false
해결 하는 방법
1.옵션을 킨다.
2.트랜잭션
3.패치조인
단순한 경우
OrderQueryService를 만들어라
"쿼리용 서비스를 별도 분리 중이구나"
도메인이 분리 되어있으면 더 좋다.
private OrderQueryService orderQueryService;
@GetMapping("/api/v3/orders")
public List<jpabook.jpashop.service.query.OrderDto> ordersV3(){
return orderQueryService.ordersV3();
}
이름이 겹치면 경로명을 다 써준다.
쿼리를 위한 서비스를 만드는것을 선호
끈 상태로 개발을 해라?
대부분의 성능 이슈는 조회에서 발생
핵심 비즈니스 5개, 복잡한거 20~30개
생명 주기가 다름
화면용 기능은 자주 바뀜
내부 비즈니스는 변경 안됨
앱이 작으면 OrderService로 유지
커지면 분리
아 그래서 ㅋㅋㅋ
앱이 작으면 키고 써라.
Next
Introduction to Spring Data JPA
소개
찍먹
{
return memberRepository.findAll();
}짱구 굴릴수 있는건 만들어져있음
구현체를 알아서 만들어서 넣어줌.
안되는 이유
놀라운게 있습니다. = 여기서 끝이에요
findBy이름
//select m from Member m where m.name = ?
List<Member> findByName(String name);
정리 - 좋다.
그런데
장애나기 딱 좋다.
기본세트
스프링Data JPA + queryDsl
JPA를 공부해야되
너어무 좋아 - 그런데 쓰면 안되
꿀도 있음
조심해서 써도 되는것이 있음.
큰그림 이었다.
Introducing QueryDSL
소개
너무 좋아
앞선 강의 쿼리
고민
해결 안되는것 = 동적 쿼리
디버그 :
open-in-view: true
코드 설명
설정 문제 해결
Gradle 설정 PDF 42P 참조
구글링 하지말고 pdf 에 있음
뭔가 들어옴
설정
이제 시작
뭐가 보이나요.
직관적인 문법
컨디션 주입
com.querydsl.core.types.dsl
public List<Order> findAll(OrderSearch orderSearch){
JPAQueryFactory query = new JPAQueryFactory(em);
QOrder order = QOrder.order;
QMember member = QMember.member;
return query
.select(order)
.from(order)
.join(order.member, member)
.where(statusEq(orderSearch.getOrderStatus()))
.limit(1000)
.fetch();
}
private BooleanExpression statusEq(OrderStatus statusCond){
if(statusCond == null){
return null;
}
return QOrder.order.status.eq(statusCond);
}
이렇게 하면 정적 쿼리
.where(order.status.eq(orderSearch.getOrderStatus()))
.where(order.status.eq(
orderSearch.getOrderStatus()),
member.name.like(orderSearch.getMemberName())
)
단축키 :
동적
return query
.select(order)
.from(order)
.join(order.member, member)
.where(statusEq(
orderSearch.getOrderStatus()),
nameaLike(orderSearch.getMemberName())
)
.limit(1000)
.fetch();
}
private BooleanExpression nameaLike(String memberName) {
if(!StringUtils.hasText(memberName)){
return null;
}
return QMember.member.name.like(memberName);
}
private BooleanExpression statusEq(OrderStatus statusCond){
if(statusCond == null){
return null;
}
return QOrder.order.status.eq(statusCond);
}
가장 큰 장점 컴파일 시점에 오타가 다잡힘
디버그 : !
generated .gitignore 로 빼주세요.
코드도 줄고 동적 쿼리도 되고
스태틱 임포트 가능
@Repository
public class OrderRepository {
private final EntityManager em;
private final JPAQueryFactory query;
public OrderRepository(EntityManager em) {
this.em = em;
this.query = new JPAQueryFactory(em);
}
public List<Order> findAll(OrderSearch orderSearch){
return query
.select(order)
.from(order)
.join(order.member, member)
.where(statusEq(
orderSearch.getOrderStatus()),
nameaLike(orderSearch.getMemberName())
)
.limit(1000)
.fetch();
}
new 오퍼레이션의 축약
정리
컴파일 시점 디버깅
정적쿼리 장점
미리 만들어둔 컨디션들 재사용하기가 쉬움
마무리





