월 17,600원
5개월 할부 시다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
[질문] 디버깅시, 콘솔에 쿼리 로그
안녕하세요? 1번 강의 소스파일의 yaml 파일 설정을 그대로 사용하고 있습니다(소스도) 근데, api/v1/members 호출할때마다 INSERT 등의 쿼리 로그가 보이지 않네요. 또 MEMBER 테이블에 호출하면서 저장되는 데이터 확인이 안되는데, yml 파일 설정을 어떻게 변경해야할까요? [현재설정] spring: output: ansi: enabled: always datasource: url: jdbc:h2:tcp://localhost/~/jpashop username: sa password: driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: update properties: hibernate:# show_sql: true format_sql: true default_batch_fetch_size: 1000 show-sql: truelogging.level: org.springframework: info org.hibeernate.SQL: debug# org.hibernate.type: trace
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
V5 관련 where절 java logic구현 질문
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]V5 주문조회 관련하여 질문드립니다. Order과 OrderItem은 1:N 관계이며, SELECT *FROM orderLEFT JOIN orderItem ON order.id = orderItem.id 를 다음 두번의 쿼리 형식을 이용하여 JAVA 단으로 로직을 구현한것이 V5라고 이해하고 있습니다. SELECT *FROM order; SELECT *FROM orderItem WHERE orderItem.id in ( SELECT id FROM order ) 그런데, 다음 쿼리는 위와같이 두번의 쿼리 형식을 사용하여 JAVA 단으로 로직을 구현하기 난해합니다. 이미 서브쿼리의 결과는 aggregate 된 값이기 때문입니다. SELECT *FROM order LEFT JOIN orderItem ON order.id = orderItem.id WHERE order.name = orderItem.itemName // -> 상품명과 주문이름이 같은 경우만 fetch 1. 위와 같은 경우(where절에 양쪽 field가 모두 존재하는 경우)를 JAVA 로직으로 해결 할 방법이 있는지 JAVA 로직으로 해결할 방법이 올려놓으신 강의중에 있다면 대략적인 그 강의명을 알려주시면 찾아보겠습니다. 만약, 이미 올라와 있는 강의중에 해당 내용이 없다면, 평소 사용하시는 방식을 알려주시면 감사하겠습니다. 혹은 위와같은 경우엔 그냥 V6 형식을 평소 사용하시는지 2. 1번에 대한 해결책이 없더라도 혹시 LEFT OUTER JOIN이 아닌 INNER JOIN을 JAVA로 구현할 방법이 있는지 JAVA 로직으로 해결할 방법이 올려놓으신 강의중에 있다면 대략적인 그 강의명을 알려주시면 찾아보겠습니다. 만약, 이미 올라와 있는 강의중에 해당 내용이 없다면, 평소 사용하시는 방식을 알려주시면 감사하겠습니다. 혹은 위와같은 경우엔 그냥 V6 형식을 평소 사용하시는지 질문 드립니당
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
조회 궁금한 점
api 작성할 때 service 말고 repository를 주입하신 이유가 궁금합니다. orderservice에도 repository에서 위임받은 메서드가 존재하는데 repository를 주입받은 특별한 이유가 있을까요? JPA 활용1편에서는 조회할때 성능 문제가 발생하지 않나요? Order가 Member와 Delivery에 xToOne으로 매핑되있는건 똑같아서 질문드립니다!
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
V2 CreateMemberRequest객체의 유효성 체크
안녕하세요 강의 잘 듣고 있습니다. V2 CreateMemberRequest DTO 객체를 사용하여 요청을 받을 때 유효성 체크가 동작하지 않는 것 같아서 문의드립니다. @Data static class CreateMemberRequest { @NotEmpty private String name; } @NotEmpty를 추가해도 API 호출 시 name 을 입력하지 않아도 적재가 되는데 왜 체크가 되지 않는지 궁금합니다!
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
grouping과 Collectors.toList의 차이가 무엇이고 emtrySet()은 무슨 기능인가요?
grouping과 Collectors.toList의 차이가 무엇이고 emtrySet()은 무슨 기능인가요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
default_batch_fetch_size 원리 질문입니다.
안녕하세요. batch size에 관해서 질문드립니다. 예를들어, 강의처럼 member와 order엔티티로 하겠습니다. (연간관계는 일대다) batchsize=3인 상태이구용 member.orders는 lazy상태이고, db에는 member1~6까지 6개가 들어있다고 가정하겠습니다. (기본키id도 1~6) jpql: select m from Member m 의 결과로는 List<Member> results 이고, size = 6인객체가 오겠네요 results.foreach(m -> m.getOrders().size)) 이런식으로 results를 전체순환하면서 사용하는게 아닌 아래처럼 사용하면 results.get(3).getOrders().size()results.get(5).getOrders().size() in절에는 get3과 5에 해당하는 외래키 id가 4, 6 두개가 나갈것으로 예상했는데, 쿼리를 보니 4, 5, 6 이렇게 나가더라구요. results.get(3).getOrders().size() 이렇게만 해도 in절에 4 하나만 나가는게 아니라 위와 동일하게 4, 5, 6 으로 되고.. results.get(0).getOrders().size()으로 하게 되면 1,2,3 으로, results.get(5).getOrders().size()으로 하게되면 6,5,4로 되더라구요. 그래서 일단 "초기화 안된 콜렉션 타입의 proxy를 실제 db에서 조회하려고 할때는, 무조건 batchsize에 설정된 사이즈만큼 동시에 읽어오고, 엔티티화해서 영속성컨텍스트에 미리 올려놓는다" 라고 생각하려 합니당 제가 궁금한 점은 in절로 내보내야할 id들을 가져오는 과정입니다. orders는 member의 콜렉션 프록시 이므로(=orders테이블에 memberid가 외래키)영속성컨텍스트에 이미 로딩되어있는 member엔티티들의 키를 대상으로 in절로 내보낼 id들을 찾아온다 라고 생각하면 맞을까요? 제일 의문이었던게 results.get(5).getOrders().size()를 했을때 in절에 6,7,8이 아니라 자동으로 6,5,4로 되는것이 궁금했습니다. 어떻게 6번이 마지막인지 알고 범위내의 in절을 생성하는지.. em.detach(members.get(1)) 을 한 상태에서 results.get(0).getOrders().size() 하게 되면 1,3,4 로 나가는것을 보아 영속성 컨텍스트에 존재하는 엔티티를 참조하는게 맞는것 같다는 생각인데 확실치 않아서 문의드립니다. (더 정확히는, 영속성 컨텍스트에 있으면서 proxy가 초기화 되지 않은 상태인 것들을 대상으로 in절에 보낼 id생성인듯 합니다 시나리오 -> results.get(0).getOrders().size()를 할 경우 프록시 사용 코드에서 (.size())프록시가 초기화 되지 않은 상태임을 확인. batchsize=3옵션이 있음. sql을 만들기 위해 in절로 내보낼 값들도 만들어야 함. results.get(0)의 반환타입이 Member임(= 프록시를 담고있는 객체가 Member) 그러므로 영속성컨텍스트에 로딩되어 있는 Member타입의 엔티티들을 찾아 id를 모아와야 함. 실제 member엔티티내의 orders프록시 객체가 초기화 되어 있는지 확인 초기화 되어있지 않은 것들만 골라 in절에 해당하는 값을 만들어서 db에 쿼리날림. ) 감사합니다!
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
책에 페치 조인 대상에는 별칭을 줄수 없다. 라고 나와서 궁금증이 생겼어여
책 381쪽에 보면 페치 조인 대상에는 별칭을 줄 수 없다라고 나오고, 대신 하이버네이트같은 몇몇 구현체는 지원해 준다고 나와있더라구요 "select distinct o from Order o" + " join fetch o.member m" + " join fetch o.delivery d" + " join fetch o.orderItems oi" + " join fetch oi.item i", 위와 같은 JPQL에서 o.orderItems 과 페치조인하고 oi로 별칭을 주었는데, 이 별칭을 활용하여 join fetch oi.item i 처럼 또 페치조인을 하는데... JPA에서 공식 스펙은 아니지만 하이버네이트가 잘 지원해주는 것 맞나요? 최종 질문은 저렇게 페치조인 대상에 별칭주고 거기서 또 페치조인해서... 잘 써도 되는지 궁금합니다. JPA의 공식스펙이 아닌것 같은데 문제없이 동작하는지.. (위 같은 경우는 당연 일대일이나 다대일에서만 쓸것입니다)
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
학습 관련 내용은 아니지만 ㅎ..
강의를 보다가 먼 미래에 나올 jpa 책은 혹시 지금 집필하고 계신지 궁금해서 댓글 남깁니다 ㅎㅎ..
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
build.gradle에 querydsl 추가하고나서 Application 실행이 실패합니다
안녕하세요 강의 항상 잘 듣고 있습니다. MVC 강의 부터 듣다가 최근에 회사에서 JPA 프로젝트를 진행 할 일이 있어서 작년에 결제해두었던 JPA 강의를 급하게 듣고 있습니다. 다름이 아니라 강의를 거의 다 듣고 querydsl 설정을 추가하고 나서 어플리케이션 실행했더니 실패하는 현상이 발생합니다. build/generated/querydsl/ 이하경로에 파일도 잘 생성되었는데 lombok getter/setter와 관련된컴파일 에러 can,t not find symbol 가 발생하는 이유를 잘 모르겠습니다. 구글링해보나까 Lombok & Querydsl 같이 사용할때 발생할 수 있는 에러이고 아래와 같은 설정을 추가하면 해결될수 있다고 해서 추가해보았지만 해결되지 않아서 질문을 남깁니다. project.afterEvaluate { project.tasks.compileQuerydsl.options.compilerArgs = [ "-proc:only", "-processor", project.querydsl.processors() + ',lombok.launch.AnnotationProcessorHider$AnnotationProcessor'
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
1번 강의 소스코드
안녕하세요? JPA 강의 2번 부터 수강하고 있습니다. 실제 코드를 작성하면서 따라하는데, 1번 강의의 소스코드를 이어서 실습을 하다보니 제한이 되는 부분이 있네요. 이전 강의 자료(pdf)에 없는 코드도 있는거 같은데 이부분은 제공이 불가능할까요? 당장 회원등록 API 부터 MemberService.등이 없으니 진행이 안되네요ㅠ
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
dto join 과 일반 fetch join 차이점에 대해 질문 있습니다
em.createQuery( "select new jpadook.jpashop.repository.OrderSimpleQueryDto(o.id, m.name, o.orderDate, o.status, d.address)" + " from Order o" + " join o.member m" + " join o.delivery d" , OrderSimpleQueryDto.class) .getResultList(); createQuery문은 결국 fetch조인이 내부적으로 된다고 생각해도 됩니까?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
N+1 문제를 맞게 이해한 것인지 잘 모르겠습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 이번 강의를 통해 공부하면서 N+1문제를 아래와 같이 이해하였습니다. ex) 모든 주문 내역을 가져오는 경우 -> 실제로 나가는 쿼리는 select * from orders 쿼리 한방이지만, 1) 이때 지연로딩인 경우 , 조회한 order에 대해 member나 delivery를 필요로 하는 경우에 각 order별로 member나 delivery 조회 쿼리가 N번씩 나가게 되고 2) 즉시 로딩인 경우, select * from orders 라는 모든 주문 조회 쿼리 한방이 나간 후, 곧바로 조회한 order들과 연과된 member와 delivery 조회 쿼리가 이어서 나가게 된다. (단 이떄 예측할 수 없는 쿼리가 나갈 수 있음.) 즉 정리하면 즉시로딩과 지연로딩 모두 N+1 문제가 발생할 수 있지만, 즉시로딩은 예측할 수 없는 쿼리가 나갈 수도 있어 최적화 시키기 어려운 반면, 지연 로딩은 페치 조인을 사용하여 order를 조회하면서 동일 쿼리 상에서 member나 delivery도 함께 가져오게 하는방법으로 최적화 시킬 수 있으니, 지연로딩을 사용하는것이 옳다. 라고 이해하였는데, 맞게 이해한 것인지 잘 모르겠습니다. 감사합니다.
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
Batch Size에서 두 번째 Order의 ID가 IN 쿼리에 들어가는 이유
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용] 처음 OrderItem을 조회할 때 두 번째 Order의 ID(11)와 함께 IN 쿼리를 통해 조회됩니다. 여기서 IN 쿼리에 두 번째 Order의 ID가 들어가는 이유에 대해 생각해보았습니다. 1. 현재 Order 엔티티를 통해 OrderItem을 조회하고 있다. 2. batch가 설정되어 있기 떄문에 1차 캐시에 들어있는 Order들을 통해 최대 batch_size개 까지 IN 쿼리를 통해 조회한다. 3. 조회 대상이 된 1차 캐시에 들어있던 Order들 중에 두 번째 Order가 포함되어있었기 때문에 IN 쿼리에 두 번째 Order의 ID가 함께 들어갔다. 4. member와 delivery를 fetch하지 않았을 때도 위와 같은 이유로 한번에 조회되었다. 이런 과정을 거쳐서 같이 조회가 되지 않았나 생각해보았습니다. 제가 생각한 이유가 맞거나 혹시 틀린 부분이 있는지 궁금합니다. 감사합니다.
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
영속성 컨텍스트 분산환경 질문
안녕하세요 짧게 질문하나 드리겠습니다 예를 들어 강의처럼 order와 member가 연결 되었다고 가정할때 order1 - member1 order2- member2 order3- member1 과 같이 데이터가 있다고 가정하겠습니다. order1이 member를 한번 불러오면 영속성 컨텍스트에 남아있기 때문에 마지막에 order3이 member1을 다시불러오면 디비가 아닌 영속성 컨텍스트에서 member1의 정보를 불러올텐데 그 사이에 다른 서버 (scale out된 3개가 서버가 하나의 db를 공유할때)가 member1의 이름을 수정했을 경우에는 어떻게 구동이 될까요? 저는 JPA 기본강의를 수강했을때 당시에 영속성 컨텍스트는 application level이라고 이해했는데 분산환경인 경우 서버가 3개면 영속성 컨텍스트도 3개가 아닌가 싶어서 질문드립니다
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
질문있습니다
안녕하세요 강사님 API 파라미터 변조에 대한 질문인데요 현재 프로젝트 구조는 프론트앤드 Vuejs, 백앤드는 spring boot로 API 제공 역할을 하고 있습니다 현재 인증방식은 세션 인증방식을 사용중이고 최초 인증 후 세션에 사용자 정보를 담아, 이후 API 요청에 대해서 인터셉터를 적용하여 인증된 사용자만 API에 접근할 수 있도록 하고 있습니다 하지만 특정 사용자가 포스트맨 같은 툴을 사용하여 인증을 완료후 API에 요청을 날릴때 특정 파라미터를 변조하거나 할 경우는 어떻게 보안처리를 할 수 있나요? 현재 통신 프로토콜은 HTTP 사용중인데, HTTPS를 사용하면 이러한 문제를 해결할 수 있나요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
콘솔 쿼리 모양
7분39초에나오는 jpa쿼리처럼 줄바꿈에서 콘솔에서 보려면 어떻게 하나요? 저는 1렬로 나와서 쿼리를 보기 불편해서요. 혹시나해서 제 설정파일 남길게요 spring: datasource: url: jdbc:h2:tcp://localhost/~/jpashop username: sa password: driver-class-name: org.h2.Driver jpa: hibernate: ddl-auto: create properties: hibernate: show_sql: true format_sql: truelogging: level: org.hibernate.SQL: debug # ?????? ?? org.hibernate.type: trace
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
페치조인 쿼리시 쿼리가 두번 나갑니다.
강의에서 보면 쿼리가 한번 나가는데 저는 두번이 나갑니다 ㅠㅠ http://localhost:8080/api/v3/simple-orders 호출 2022-06-26 01:02:45.805 DEBUG 5062 --- [nio-8080-exec-6] org.hibernate.SQL : select order0_.order_id as order_id1_6_0_, member1_.member_id as member_i1_4_1_, delivery2_.delivery_id as delivery1_2_2_, order0_.delivery_id as delivery4_6_0_, order0_.member_id as member_i5_6_0_, order0_.order_date as order_da2_6_0_, order0_.status as status3_6_0_, member1_.city as city2_4_1_, member1_.street as street3_4_1_, member1_.zipcode as zipcode4_4_1_, member1_.name as name5_4_1_, delivery2_.city as city2_2_2_, delivery2_.street as street3_2_2_, delivery2_.zipcode as zipcode4_2_2_, delivery2_.status as status5_2_2_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id Hibernate: select order0_.order_id as order_id1_6_0_, member1_.member_id as member_i1_4_1_, delivery2_.delivery_id as delivery1_2_2_, order0_.delivery_id as delivery4_6_0_, order0_.member_id as member_i5_6_0_, order0_.order_date as order_da2_6_0_, order0_.status as status3_6_0_, member1_.city as city2_4_1_, member1_.street as street3_4_1_, member1_.zipcode as zipcode4_4_1_, member1_.name as name5_4_1_, delivery2_.city as city2_2_2_, delivery2_.street as street3_2_2_, delivery2_.zipcode as zipcode4_2_2_, delivery2_.status as status5_2_2_ from orders order0_ inner join member member1_ on order0_.member_id=member1_.member_id inner join delivery delivery2_ on order0_.delivery_id=delivery2_.delivery_id 전체 코드 Order 엔티티 package jpabook.jpashop.domain.entity;import lombok.Getter;import lombok.Setter;import javax.persistence.*;import java.time.LocalDateTime;import java.util.ArrayList;import java.util.List;@Entity@Table(name = "orders")@Getter @Setterpublic class Order { @Id @GeneratedValue @Column(name = "order_id") private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) private List<OrderItem> orderItems = new ArrayList<>(); @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "delivery_id") private Delivery delivery; private final LocalDateTime orderDate = LocalDateTime.now(); @Enumerated(EnumType.STRING) private OrderStatus status; //==연관관계 메서드==// public void setMember(Member member) { this.member = member; member.getOrders().add(this); } public void addOrderItem(OrderItem orderItem) { orderItems.add(orderItem); orderItem.setOrder(this); } public void setDelivery(Delivery delivery) { this.delivery = delivery; delivery.setOrder(this); } //==생성 메서드==// public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) { Order order = new Order(); order.setMember(member); order.setDelivery(delivery); for (OrderItem orderItem : orderItems) { order.addOrderItem(orderItem); } order.setStatus(OrderStatus.ORDER); return order; } //==비즈니스 로직==/ /** * 주문 취소 */ public void cancel() { if (delivery.getStatus() == DeliveryStatus.COMP) { throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능합니다."); } this.setStatus(OrderStatus.CANCEL); for (OrderItem orderItem : orderItems) { orderItem.cancel(); } } //==조회 로직==// /** * 전체 주문 가격 조회 */ public int getTotalPrice() { return orderItems.stream().mapToInt(OrderItem::getTotalPrice).sum(); }} controller package jpabook.jpashop.api;import jpabook.jpashop.domain.entity.Address;import jpabook.jpashop.domain.entity.Order;import jpabook.jpashop.domain.entity.OrderStatus;import jpabook.jpashop.domain.repository.OrderRepository;import jpabook.jpashop.domain.repository.OrderSearch;import lombok.Data;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;import java.util.List;import java.util.stream.Collectors;/** * xToOne(ManyToOne, OneToOne) * Order * Order -> Member * Order -> Delivery */@Slf4j@RestController@RequiredArgsConstructorpublic class OrderSimpleApiController { private final OrderRepository orderRepository; @GetMapping("/api/v1/simple-orders") public List<Order> ordersV1() { List<Order> all = orderRepository.findAllByString(new OrderSearch()); all.stream().forEach(o -> { o.getMember().getName(); // Lazy 강제 초기화 o.getDelivery().getAddress(); // Lazy 강제 초기화 }); return all; } @GetMapping("/api/v2/simple-orders") public List<SimpleOrderDto> ordersV2() { List<Order> orders = orderRepository.findAllByString(new OrderSearch()); return orders.stream() .map(SimpleOrderDto::new) .collect(Collectors.toList()); } @GetMapping("/api/v3/simple-orders") public List<SimpleOrderDto> ordersV3() { List<Order> orders = orderRepository.findAllWithMemberDelivery(new OrderSearch()); return orders.stream() .map(SimpleOrderDto::new) .collect(Collectors.toList()); } @Data static class SimpleOrderDto { private Long orderId; private String name; private LocalDateTime orderDate; private OrderStatus orderStatus; private Address address; public SimpleOrderDto(Order order) { orderId = order.getId(); name = order.getMember().getName(); orderDate = order.getOrderDate(); orderStatus = order.getStatus(); this.address = order.getDelivery().getAddress(); } }} repository package jpabook.jpashop.domain.repository;import org.springframework.stereotype.Repository;import org.springframework.util.StringUtils;import javax.persistence.EntityManager;import javax.persistence.TypedQuery;import javax.persistence.criteria.*;import java.util.ArrayList;import java.util.List;import jpabook.jpashop.domain.entity.Order;@Repositorypublic class OrderRepository { private final EntityManager em; public OrderRepository(EntityManager em) { this.em = em; } public void save(Order order) { em.persist(order); } public Order findOne(Long id) { return em.find(Order.class, id); } 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(); } /** * JPA Criteria */ 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(); } public List<Order> findAllWithMemberDelivery(OrderSearch orderSearch) { return em.createQuery("select o from Order o join fetch o.member m join fetch o.delivery d", Order.class) .getResultList(); }}
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
Dto Json 출력 오류 질문
Address 필드가 Response 시 빈 객체로 나옵니다. [ { "orderId": 4, "name": "userA", "orderDate": "2022-06-26T00:43:08.07663", "orderStatus": "ORDER", "address": {} }, { "orderId": 11, "name": "userB", "orderDate": "2022-06-26T00:43:08.155045", "orderStatus": "ORDER", "address": {} } ] log를 찍어 봤는데 아래와 같이 dto에 address는 잘 들어가있습니다. 2022-06-26 00:43:11.618 INFO 4953 --- [nio-8080-exec-2] j.jpashop.api.OrderSimpleApiController : dto address = Address(city=서울, street=1, zipcode=1111) 2022-06-26 00:43:11.618 INFO 4953 --- [nio-8080-exec-2] j.jpashop.api.OrderSimpleApiController : dto address = Address(city=진주, street=2, zipcode=2222) 뭐가 문제일까요 ㅠㅠㅠ 전체코드 package jpabook.jpashop.api;import jpabook.jpashop.domain.entity.Address;import jpabook.jpashop.domain.entity.Order;import jpabook.jpashop.domain.entity.OrderStatus;import jpabook.jpashop.domain.repository.OrderRepository;import jpabook.jpashop.domain.repository.OrderSearch;import lombok.Data;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.time.LocalDateTime;import java.util.List;import java.util.stream.Collectors;/** * xToOne(ManyToOne, OneToOne) * Order * Order -> Member * Order -> Delivery */@Slf4j@RestController@RequiredArgsConstructorpublic class OrderSimpleApiController { private final OrderRepository orderRepository; @GetMapping("/api/v1/simple-orders") public List<Order> ordersV1() { List<Order> all = orderRepository.findAllByString(new OrderSearch()); all.stream().forEach(o -> { o.getMember().getName(); // Lazy 강제 초기화 o.getDelivery().getAddress(); // Lazy 강제 초기화 }); return all; } @GetMapping("/api/v2/simple-orders") public List<SimpleOrderDto> ordersV2() { List<Order> orders = orderRepository.findAllByString(new OrderSearch()); List<SimpleOrderDto> result = orders.stream() .map(SimpleOrderDto::new) .collect(Collectors.toList()); result.forEach(dto -> log.info("dto address = {}", dto.getAddress())); return result; } @Data static class SimpleOrderDto { private Long orderId; private String name; private LocalDateTime orderDate; private OrderStatus orderStatus; private Address address; public SimpleOrderDto(Order order) { orderId = order.getId(); name = order.getMember().getName(); orderDate = order.getOrderDate(); orderStatus = order.getStatus(); address = order.getDelivery().getAddress(); } }}
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
영속성 컨텍스트 관련 질문
안녕하세요! 영한님 먼저 양질의 강의를 제공해주셔서 감사의 말씀 드립니다. 갑자기 영속성 컨텍스트에 대한 개념이 헷갈려서 질문드립니다. 😭 만약에 order테이블에서 member의 id가 동일한 경우 ordersV2()를 사용하면 쿼리가 총 4번 나가는데 이때 영속성 컨텍스트에서 관리되어(1차 캐시에서 조회하기 때문으로 이해했습니다.) 기존에 있는 것을 가져온다고 말씀하셨습니다. 그런데 @Transactional 안에서 DB를 조회해야 영속성 컨텍스트에서 관리되는 것 아닌가요? 🤔 감사합니다.
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
질문드립니다
항상 친절한 강의와 답변 모두 감사드립니다! 강의듣다가 두 가지 질문이있는데요. MemberService.update Method 에서 return 값으로 Member를 넘기게 되면 영속성이 끊긴다고 하셨는데 그러면 Controller 쪽에서 Member updateMember = memberService.update(id, name); 라고 하면 이 updateMember 는 준영속 상태인가요 ? 그리고 일단 기본적으로 독립된 트랜잭션마다 각각 다른 영속성 컨텍스트가 생성되는 게 맞죠? (OSIV 는 추후 듣도록 하겠습니다!)