30%
61,600원
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
31:04 부분 "select o from Order o" 만 했을 경우 결과값
2023-10-09T19:14:55.542+09:00 INFO 2455 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2023-10-09T19:14:55.542+09:00 INFO 2455 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2023-10-09T19:14:55.542+09:00 INFO 2455 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 0 ms 2023-10-09T19:14:55.660+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select o1_0.order_id, o1_0.delivery_id, o1_0.member_id, o1_0.order_date, o1_0.status from orders o1_0 offset ? rows fetch first ? rows only 2023-10-09T19:14:55.662+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495662 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 offset ? rows fetch first ? rows only select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 offset 0 rows fetch first 100 rows only; 2023-10-09T19:14:55.668+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select m1_0.member_id, m1_0.city, m1_0.street, m1_0.zipcode, m1_0.name from member m1_0 where array_contains(?,m1_0.member_id) 2023-10-09T19:14:55.672+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495672 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name from member m1_0 where array_contains(?,m1_0.member_id) select m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name from member m1_0 where array_contains('ar0: ARRAY [CAST(1 AS BIGINT), CAST(2 AS BIGINT)]',m1_0.member_id); 2023-10-09T19:14:55.674+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select d1_0.id, d1_0.city, d1_0.street, d1_0.zipcode, d1_0.status from delivery d1_0 where array_contains(?,d1_0.id) 2023-10-09T19:14:55.674+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495674 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select d1_0.id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status from delivery d1_0 where array_contains(?,d1_0.id) select d1_0.id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status from delivery d1_0 where array_contains('ar1: ARRAY [CAST(1 AS BIGINT), CAST(2 AS BIGINT)]',d1_0.id); 2023-10-09T19:14:55.676+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select o1_0.order_id, o1_0.delivery_id, o1_0.member_id, o1_0.order_date, o1_0.status from orders o1_0 where o1_0.delivery_id=? 2023-10-09T19:14:55.677+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495677 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=? select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=1; 2023-10-09T19:14:55.677+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select o1_0.order_id, o1_0.delivery_id, o1_0.member_id, o1_0.order_date, o1_0.status from orders o1_0 where o1_0.delivery_id=? 2023-10-09T19:14:55.678+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495678 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=? select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=2; 2023-10-09T19:14:55.680+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select o1_0.order_id, o1_0.order_item_id, o1_0.count, o1_0.item_id, o1_0.order_price from order_item o1_0 where array_contains(?,o1_0.order_id) 2023-10-09T19:14:55.686+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495686 | took 5ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select o1_0.order_id,o1_0.order_item_id,o1_0.count,o1_0.item_id,o1_0.order_price from order_item o1_0 where array_contains(?,o1_0.order_id) select o1_0.order_id,o1_0.order_item_id,o1_0.count,o1_0.item_id,o1_0.order_price from order_item o1_0 where array_contains('ar2: ARRAY [CAST(1 AS BIGINT), CAST(2 AS BIGINT), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL]',o1_0.order_id); 2023-10-09T19:14:55.687+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select i1_0.item_id, i1_0.dtype, i1_0.name, i1_0.price, i1_0.stock_quantity, i1_0.artist, i1_0.etc, i1_0.author, i1_0.isbn, i1_0.actor, i1_0.director from item i1_0 where array_contains(?,i1_0.item_id) 2023-10-09T19:14:55.688+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495688 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select i1_0.item_id,i1_0.dtype,i1_0.name,i1_0.price,i1_0.stock_quantity,i1_0.artist,i1_0.etc,i1_0.author,i1_0.isbn,i1_0.actor,i1_0.director from item i1_0 where array_contains(?,i1_0.item_id) select i1_0.item_id,i1_0.dtype,i1_0.name,i1_0.price,i1_0.stock_quantity,i1_0.artist,i1_0.etc,i1_0.author,i1_0.isbn,i1_0.actor,i1_0.director from item i1_0 where array_contains('ar3: ARRAY [CAST(1 AS BIGINT), CAST(2 AS BIGINT), CAST(3 AS BIGINT), CAST(4 AS BIGINT)]',i1_0.item_id); 이 결과값에서 강의랑 다른 부분이 있어서 질문 드립니다하이버네이트 6.2 라서 array_contains 인거 아는데 아래 부분이 이해가 안됩니다. select o1_0.order_id, o1_0.delivery_id, o1_0.member_id, o1_0.order_date, o1_0.status from orders o1_0 where o1_0.delivery_id=? 2023-10-09T19:14:55.677+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495677 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=? select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=1; 2023-10-09T19:14:55.677+09:00 DEBUG 2455 --- [nio-8080-exec-1] org.hibernate.SQL : select o1_0.order_id, o1_0.delivery_id, o1_0.member_id, o1_0.order_date, o1_0.status from orders o1_0 where o1_0.delivery_id=? 2023-10-09T19:14:55.678+09:00 INFO 2455 --- [nio-8080-exec-1] p6spy : #1696846495678 | took 0ms | statement | connection 7| url jdbc:h2:tcp://localhost/~/jpanew select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=? select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=2;orders 를 처음에 조회하고 나서 또 추가적으로 o1_0.delivery_id=?이걸로 조회하는 결과가 왜 생기는걸까요?..(배치사이즈도 강의랑 똑같이 100입니다) OrderApiController.java@GetMapping("/api/v3.1/orders") public List<OrderDto> ordersV3_page(@RequestParam(value ="offset",defaultValue = "0") int offset, @RequestParam(value ="limit",defaultValue = "100") int limit) { List<Order> orders = orderRepository.findAllWithMemberDelivery(offset,limit); List<OrderDto> result = orders.stream() .map(o -> new OrderDto(o)) .collect(Collectors.toList()); return result; } OrderRepository.java public List<Order> findAllWithMemberDelivery(int offset, int limit) { return em.createQuery( "select o from Order o" , Order.class ).setFirstResult(offset) .setMaxResults(limit) .getResultList(); }이렇게 바꾸고 난 이후에 일어난 결과입니당..강의랑 In말고도 추가적으로 다른 부분이 있어서 질문드립니다일단 제 스프링부트 버전은 3.1.3 입니다.
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
주문 조회 최적화 테스트 중 트랜잭션에 관하여 질문
지금 주문 조회 최적화 테스트에서 @트랜잭션 어노테이션을 안붙히고 사용 중인데조회 최적화만 해서 안붙힌건가요? 만약에 insert 나 update 테스트를 했더라면 commit을 해줘야 하기 때문에 @트랙잭션을붙혀야 되는거죠.?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
Hibernate5Module 관련 질문
Hibernate5Module 애가 빈으로 등록되어 있기 때문에아래 코드에서 프록시 객체인 상태를 getUsername()로 프록시 초기화 시켜 바로 api로 반환 가능한건가요? List<Order> all = orderRepository.findAllByCriteria(new OrderSearch()); for (Order order : all) { order.getMember().getUsername(); } return all; }
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
중복 메서드 추출 안되네요 ㅠㅠ
메서드 추출은 되는데IntelliJ IDEA 2023.2.2 Ultimate(컴퓨터는 맥북)해당 버전 사용중입니다 중복된 메서드일때 intellij 에서Extract Parameters to Replace Duplicate(10:34 초부분)이건 안되네요.인텔리제이 버전 업하면서 그런거같은데혹시 해당 화면처럼 하는 팁 있을까요?package jpabook.jpashop; import jakarta.annotation.PostConstruct; import jakarta.persistence.EntityManager; import jpabook.jpashop.domain.*; import jpabook.jpashop.domain.item.Book; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; /** * 총 주문 2개 * userA * * JPA1 BOOK * * JPA2 BOOK * userB * * SPRING1 BOOK * * SPRING2 BOOK */ @Component @RequiredArgsConstructor public class initDb { private final InitService initService; @PostConstruct public void init() { initService.dbInit1(); // 여기에 코드들 다 넣는 경우는 작동하지 않는다 initService.dbInit2(); } @Component @Transactional @RequiredArgsConstructor static class InitService { private final EntityManager em; 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(book1, 20000, 2); Delivery delivery = new Delivery(); delivery.setAddress(member.getAddress()); Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);// ... 되어있으면 여러개 넘길 수 있다 em.persist(order); } public void dbInit2() { Member member = new Member(); member.setName("userB"); member.setAddress(new Address("진주", "2", "2222")); 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(book1, 20000, 2); Delivery delivery = new Delivery(); delivery.setAddress(member.getAddress()); Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);// ... 되어있으면 여러개 넘길 수 있다 em.persist(order); } } }전체코드고 아래 부분을 메서드 추출하면 중복 체크하고 매개변수 넘기는 방식으로는 안되네요.. Member member = new Member(); member.setName("userA"); member.setAddress(new Address("서울", "1", "1111")); em.persist(member);
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
처음 저장한 데이터가 조회가 안됩니다.
안녕하세요. 현재 대학생이고 김영한님 강의를 본 후 프로젝트를 진행중입니다.다름이 아니라, 현재 Tale과 Keyword의 다대다 연관관계를 피하기 위해 중간에 TaleKeyword라는 중간 테이블을 두었고, Tale은 Member 테이블과 일대일 연관관계를 설정해두었습니다.동화(Tale)를 키워드, 회원정보에 끼워넣고 생성하는 API는 완성하였고 DB에 쌓이는 것까지 확인했습니다. 하지만, memberId를 이용해서 조회하려고 하니, 맨 처음 memberId가 1인 회원의 처음 저장한 taleId 1번의 데이터와 memberId가 2인 회원의 처음 저장한 동화인 taleId가 4인 동화가 조회가 안됩니다.(나머지 taleId 2, 3, 5번 데이터는 조회가 잘됩니다.) 쿼리문은 다음과 같이 짰고 영속성 문제인 것 같은데, 왜 첫번째 데이터만 조회가 안되는 걸까요?// 동화 목록조회(페이징) public List<Tale> findTalesByMemberId(Long memberId, int offset, int limit) { return em.createQuery( "select t from Tale t" + " join fetch t.member m" + " join fetch t.image.taleImage ti" + " where m.id = :memberId" + " order by t.createdTime desc", Tale.class ) .setParameter("memberId", memberId) .setFirstResult(offset) .setMaxResults(limit) .getResultList(); }
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
Lazy 로딩 , FetchJoin 그리고 @BatchSize
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]공부를 하면서 제가 생각하는 부분이 맞는지 확인차 질문 드립니다. 모든 XToOne 은 fetch:Lazy로 되어있다는 가정Member와 Order는 양방향 참조Member를 사용할 때 Order는 가끔 사용되는경우Lazy 로 하고 Order를 사용할 때 마다 쿼리 나감자주 사용되는 경우패치조인을 사용해서 한번에 같이 불러온다.컬렉션인 경우XToOne은 패치 조인 하고 @BatchSize를 사용해서 페이징 및 최적화 까지 챙긴다 제 생각으론 동작방법만 제대로 알고 있으면 실무에서는default_batch_fetch_size 는 계속 등록해서 글로벌로 사용하면 좋아보이는데 그게 맞나요 ?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
default_batch_fetch_size: 100 으로 설정을 해줘도 쿼리가 한번에 가져오지 않습니다.
@GetMapping("api/v2/orders/{id}") // batch_fetch_size 검색용 public ResultMany findByBatchFetch(@PathVariable("id") Long id) { Customer customer = customerService.findCustomerById(id); List<Order> orders = orderRepository.findAllOrder(); return getOrderDtoList(orders); } public List<Order> findAllOrder() { return em.createQuery("select o from Order o", Order.class) .getResultList(); } private ResultMany getOrderDtoList(List<Order> orders) { List<OrderDto> orderDtos = orders.stream() .map(o -> new OrderDto(o)) .collect(Collectors.toList()); return new ResultMany<>(orders.size(), orderDtos); } @Data static class OrderDto { private Long id; private CustomerDto customer; private List<DiffuserProductRequestDto> diffuserProductRequest; public OrderDto (Order order) { id = order.getId(); customer = new CustomerDto(order.getCustomer()); diffuserProductRequest = order.getDiffuserProductRequests().stream() .map(diff -> new DiffuserProductRequestDto(diff)) .collect(Collectors.toList()); } } @Data static class DiffuserProductRequestDto { private Long id; private DiffuserDto diffuser; private int amount; private Deadline deadline; private ProductionStatus status; public DiffuserProductRequestDto (DiffuserProductRequest diffuserProductRequest) { id = diffuserProductRequest.getId(); diffuser = new DiffuserDto(diffuserProductRequest.getDiffuser()); amount = diffuserProductRequest.getAmount(); deadline = diffuserProductRequest.getDeadline(); status = diffuserProductRequest.getStatus(); } } 위에는 order클래스 연관된 클래스를 찾기위한 코드들인데 제가 27개의 오더를 만들고 get요청을 보내면 default_batch_fetch_size: 100으로 설정 해놓았기 때문에 27개를 한번에 가져올 것이라고 생각하고 있는데 결과는 그렇지 않습니다이렇게 두번의 쿼리로 찾아오는데 어떤게 문제인걸까요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
OSIV OFF시 Sercice클래스 최적화
orderService.class를 예를들면 Order Entity를 저장하거나 find하거나 등 entity위주의 코드만 뒀습니다.허나 Order Entity뿐만 아니라 OrderForm, OrderDto를 변환해주는 코드가 필요하다면 각 형태를 변화해주는 Service를 따로 만들어야하는지 아님 하나의 Service클래스안에 다 둬야 하는지 궁금합니다.예로들어 OrderEntity => OrderForm 로바꿔주는서비스, OrderEntity => OrderDto로 바꿔주는 서비스,OrderEntity를 repository에 넘겨주는(DB에 저장하는) 서비스 이런식으로 각각 나눠서 여러개의 클래스로 만들어주나요??
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
v2에서 No serializer found for class ~ 문제가 발생합니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.DTO로 변환을 해줬는데 왜 이러한 문제점이 발생하는지 잘 모르겠습니다. 감사합니다. [OrderDto][log]소스압축파일https://drive.google.com/file/d/1Kb9yLRF3-AkxyBlvC8Aum_3gumKI21Ol/view?usp=sharing
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
SimpleOrderDto에서의 Address
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.SimpleOrderDto에서의 Address를 왜 (order.getDelivery().getAddress()) 오더의 딜리버리에서 가져오는 건가요?? 오더에 멤버에서 가져올수도 있는데 딜리버리에서 가져오는 이유가 있을까요? 멤버에서 가져오면 쿼리가 3번이면 끝나고 딜리버리에서 가져오면 5번의 쿼리를 거쳐서 조금 헷갈립니다
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
@oneToMany의 @oneToMany
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니요) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예EntityA 1:N EntityBEntityB 1:N EntityC이런식으로 있을 때 ,EntityA 가 EntityB를 @oneToMany로 가지고있고,EntityB 가 EntityC를 @oneToMany로 가지고 있습니다. 배치사이즈 옵션을 넣고select a from EntityA a 했을때 , EntityA의 갯수만큼 EntityB에 in절로 EntityA의 키값을 넣어서 @oneToMany를 한번에 끌고왔다고 했을 때 ,EntityB안에있는 EntityC를 또 EntityB의 갯수만큼 EntityC에 in절로 EntityB의 키값을 넣어서 한번에 끌고 올 수 있는 방법이 있는지 궁금했습니다. 다중 페치조인 안되는걸 알지만 예를 들어서 이런걸 원한다고 생각하시면 될 것 같습니다select a from EntityA a join fetch EnitityB(List) b join fetch b.EntityC(List)이런식으로 컬럼 갯수가 일대다 일대다로 기하급수적으로 증가하는 경우인데배치사이즈를 사용하여 select a from EntityA a 를 했을 땐 a.EntityB 에 접근할때 당연히 in 절로 가져와주지만 EntityB의 일대다인 EntityC 를 조회 할 땐 N+1이 다시 터집니다엮여있는 모든 연관관계들을 IN절로 태우고 싶은데 방법이 없을까요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
v6에서 groupingBy가 아닌 distinct
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]안녕하세요! [강의] 주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화 관련해서 궁금한 점이 있습니다. findAllByDto_flat()에서 distinct를 통해 중복을 제거하는게 아니라 강의와 같이 findAllByDto_flat() 호출 후 groupingBy를 통해 중복을 제거했을 때 서로 어떤 차이가 있나요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
수업 자료 코드 오타
강의에서는 List<Order> orders = orderRepository.findAllByString(new OrderSearch()); 로 수업해주시는데 강의자료에는 List<Order> orders = orderRepository.findAll(); 로 나와있습니다.
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
6:40 샘플데이터 EntityManager 인식문제.
트랜잭션안에 쓸수있는 EntityManager 없다고 에러가 뜹니다. 설정 문제인걸까요?org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initDb': Invocation of init method failed; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:440) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:921) ~[spring-context-5.3.29.jar:5.3.29] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.29.jar:5.3.29] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.14.jar:2.7.14] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) ~[spring-boot-2.7.14.jar:2.7.14] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) ~[spring-boot-2.7.14.jar:2.7.14] at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) ~[spring-boot-2.7.14.jar:2.7.14] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) ~[spring-boot-2.7.14.jar:2.7.14] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) ~[spring-boot-2.7.14.jar:2.7.14] at jpabook.jpashop.JpashopApplication.main(JpashopApplication.java:10) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-2.7.14.jar:2.7.14] Caused by: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:299) ~[spring-orm-5.3.29.jar:5.3.29] at jdk.proxy3/jdk.proxy3.$Proxy119.persist(Unknown Source) ~[na:na] at jpabook.jpashop.InitDb$InitService.dbInit1(InitDb.java:34) ~[main/:na] at jpabook.jpashop.InitDb.init(InitDb.java:20) ~[main/:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.3.29.jar:5.3.29] at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.3.29.jar:5.3.29] ... 23 common frames omitted 제코드입니다. package jpabook.jpashop; import jpabook.jpashop.domain.*; import jpabook.jpashop.domain.item.Book; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; @Component @RequiredArgsConstructor public class InitDb { private final InitService initService; @PostConstruct public void init() { initService.dbInit1(); } @Component @Transactional @RequiredArgsConstructor static class InitService { private final EntityManager em; private 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, 1); Delivery delivery = new Delivery(); delivery.setAddress(member.getAddress()); Order order = Order.createOrder(member, delivery, orderItem1, orderItem2); em.persist(order); } } }
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
재질문 드립니다 죄송합니다
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. EntityA 1:N EntityBEntityB 1:N EntityC이런식으로 있을 때 ,EntityA 가 EntityB를 @oneToMany로 가지고있고,EntityB 가 EntityC를 @oneToMany로 가지고 있습니다. 배치사이즈 옵션을 넣고select a from EntityA a 했을때 , EntityA의 갯수만큼 EntityB에 in절로 EntityA의 키값을 넣어서 @oneToMany를 한번에 끌고왔다고 했을 때 ,EntityB안에있는 EntityC를 또 EntityB의 갯수만큼 EntityC에 in절로 EntityB의 키값을 넣어서 한번에 끌고 올 수 있는 방법이 있는지 궁금했습니다. 다중 페치조인 안되는걸 알지만 예를 들어서 이런걸 원한다고 생각하시면 될 것 같습니다select a from EntityA a join fetch EnitityB(List) b join fetch b.EntityC(List)이런식으로 컬럼 갯수가 일대다 일대다로 기하급수적으로 증가하는 경우인데배치사이즈를 사용하여 select a from EntityA a 를 했을 땐 a.EntityB 에 접근할때 당연히 in 절로 가져와주지만 EntityB의 일대다인 EntityC 를 조회 할 땐 N+1이 다시 터집니다엮여있는 모든 연관관계들을 IN절로 태우고 싶은데 방법이 없을까요?
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
확실한 답을 못얻었습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 일단 페치조인으로 둘이상의 컬렉션은 페치 조인 할 수 없다는 걸 알았습니다, 이럴땐 배치사이즈로 컬렉션들을 in 절로 끌고오면 되는것도 이해했습니다. 궁금한것은 in절로 끌고온 컬렉션들안에 또 @oneToMany로 선언된 엔티티가 존재한다면 그 컬렉션들은 @Batchsize옵션으로 in절로 끌고 올 수 가 없나요? 배치 사이즈를 줘도 컬조회할 기준 엔티티에 직접적으로 존재하지 않는 컬렉션 엔티티들은 in절로 끌고오지않고 n+1이 터지는것같습니다
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
restApi, html api 스펙
restApi를 배울땐 api스펙이 달라져서 데이터를 보낼때와 받을때 Dto로 바꿔서 했는데html에서 데이터를 보낼때는 Dto로 만들지 않고 entity로 데이터를 주고 받는데 html에서도 dto로 바꿔서 해야하는 거 아닌가요???
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
API 호출 방법
안녕하세요. JPA 활용 강의를 들으며 추가로 REST API에 대해 공부중입니다.@Controller에서 페이지 렌더링을 하고,@RestController에서 API를 작성하는 것은 이해했습니다. 그러나 model로만 데이터를 넘기다가 API에 대해 공부를하다보니 호출 방식에 대해 이해가 되지 않습니다. 만약에 회원조회 기능을 만든다고 가정하면[1]@RestController에서 /api/members에서 회원 조회하여 ResponseEntity<MemberDto>를 리턴해주고,@Controller에서 /api/members를 호출한 다음에 MemberDto로 변경하여 Model에 담은 후 /memberList로 렌더링 [2]@Controller에서 /memberList로 렌더링한 후, javascript에서 fetch()를 사용하여 /api/members를 호출하여 가져온 값을 화면에 뿌려줌 제가 고민해본 방식은 위 2가지입니다... 올바른 방식이 있는지,, 없다면 어떤 방식이 맞는 지 조언 해주시면 감사하겠습니다... ========================================앗 해당 질문글을 작성한 후 더 공부를 해보았는데요,CSR (클라이언트 사이드 렌더링)와 SSR (서버 사이드 렌더링) 방식으로 두 가지 방식 모두 존재하는 방식이었군요... (제가 고민해본 방식과 CSR, SSR가 동일한 방식인지는 모르겠습니다... 맞나요...?) 페이지 이동과 동시에 조회가 필요한 곳인 경우 (필터 없이 회원조회) = 페이지 렌더링할때 api를 호출하여 model에 담아 넘기고, 필터로 조회하는 경우 = API만 호출하여 데이터 보여주기 위에 내용으로 개발하면 될까요?? (저는 현재 백엔드 개발자를 준비하며 혼자서 포트폴리오용 프로젝트를 만들고있습니다!)
- 해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
JPA 최적화 강의 수강 후, 개인 프로젝트 수행하면서 생긴 질문입니다.
안녕하세요. JPA 최적화 강의를 수강하고, 개인적으로 DDD를 책으로 공부해본 후에 개인 토이 프로젝트를 진행하던 중, 뜻밖에 의문이 생겼는데 여쭐 곳이 없어서 이렇게 질문 올리게 되었습니다. 맨 땅에 헤딩으로 이런저런 강의, 책, 다른 분들의 소스를 참고 하려다보니 여러 개념이 뒤섞여서 혼동이 옵니다.. ㅠㅠ 현재 프로젝트에서는 크게 에그리거트를 CUSTOMER, EXTERNAL, SECURITYMEDIA 3개로 나누어 설계했는데요. 강의에서도, 책에서도 DOMAIN 계층에 있는 서비스는 해당 도메인에 대한 순수한 CRUD를 수행하는 것으로 보았습니다. DDD 책에서는 여러 에그리거트가 필요로 하는 기능을 구현할 때는 도메인 서비스로 구현하라고 이야기 했구요.처음에는 책에서 조언하는 대로 도메인 서비스로 구현해보고자 하다가, 좀처럼 구현이 안되어서 다른 분들이 구현한 소스를 참조하다 보니 application(응용)영역을 FACADE라는 상위 계층을 두는 것을 방식을 알게 되었습니다. 소스를 따라가보니 각 애그리거트의 DOMAIN 영역에 있는 서비스를 주입하여, 각 도메인 영역에 있는 서비스를 적절히 호출하기에 책에서 본 도메인 서비스와 같은 역할을 하겠구나 하여,, 해당 프로젝트 구성 방식을 따라 개발해보기로 했습니다.그런데 개발을 하다보니,, 참조하는 소스에서 메소드 단위의 트랜잭션의 적용을 facade 영역이 아닌, 도메인 영역의 서비스 구현체에서 하는 것을 알게 되었습니다. 제가 개발하고자 기능은 여러 애그리거트를 생성, 변경하는 하나의 행위가 하나의 트랜잭션으로 묶여야 하는데 말이죠.이러한 이유 때문에 현재 소스는 FACADE에서는 하나의 도메인 영역의 서비스를 주입하여 하나의 메소드를 호출하도록 되어있고, 도메인 영역에 있는 해당 서비스의 구현체에서 여러 애그리거트의 서비스, 레포지토리를 주입받아 하나의 메소드에서 트랜잭션 단위로 수행하도록 구현되어있습니다..@Service @RequiredArgsConstructor public class SecurityMediaFacade { private final SecurityMediaService securityMediaService; public SecurityMediaInfo.Main registerOtp(SecurityMediaCommand.RegisterSecurityMediaRequest req) { //디지털otp 발급 // 디지털 otp 발행 return securityMediaService.issueSecurityMedia(req, SecurityMediaType.DIGITAL_OTP); } ... }public interface SecurityMediaService { public SecurityMediaInfo.Main issueSecurityMedia(SecurityMediaCommand.RegisterSecurityMediaRequest req, SecurityMediaType type); ... }@Slf4j @Service @RequiredArgsConstructor public class SecurityMediaServiceImpl implements SecurityMediaService { private final CustomerReader customerReader; private final SecurityMediaStore securityMediaStore; private final TokenStore tokenStore; private final ExternalClientService externalClientService; @Override @Transactional public SecurityMediaInfo.Main issueSecurityMedia(SecurityMediaCommand.RegisterSecurityMediaRequest req, SecurityMediaType type) { // 요청고객 찾기 Customer customer = customerReader.findCustomerByRnn(req.getRnn()); SecurityMedia newOtp = null; if(!customer.existActiveSecurityMedia()) { // otp 신규 SecurityMedia initOtp = req.toEntity(SecurityMediaType.DIGITAL_OTP, customer); newOtp = securityMediaStore.store(initOtp); // 토큰 발급 요청 Token newToken = externalClientService.getToken(customer, newOtp); newOtp.addToken(newToken); tokenStore.store(newToken); } return new SecurityMediaInfo.Main(newOtp); }위에는 프로젝트의 구성인데.. 첫 단추부터 잘못 끼운 것도 같아서 시작 단계인 지금에서라도 좀 개선을 해보려고 하는데요.사실 도메인 서비스가 제가 의도로 하는 여러 애그리거트의 서비스 기능을 묶어서 하는 건지 아무리 읽어봐도 혼선이 옵니다. 혹시 DDD 책에서 이벤트라는 개념이 나오는데 도메인 서비스가 아니라, 이 이벤트를 통해 다른 애그리거트의 응용 서비스를 호출하도록 핸들링 하는게 올바른 방법일까요?지금과 같은 구조를 유지해도 된다면.. facade 영역의 메소드를 트랜잭션으로 묶고, 각 도메인 계층의 서비스들에서 선언된 해당 도메인에 대한 crud 메소드를 적절히 호출해가면서 facade 영역에서 비즈니스 로직을 처리해도 될까요? 너무 글이 장황하고 기네요.. ㅠㅠ 혹시 도움을 주신다면 너무나도 감사드리겠습니다.
- 미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
엔티티 그래프를 사용하지 않는 이유가 있나요?
연관 데이터를 불러와야 하는 상황에서 지연 로딩 전략을 취하고 페치조인을 사용하는 것이 N+1 문제를 해결하는 방법이자, 성능 최적화의 방법이라고 알고 있습니다. 그런데 JPA에서 엔티티 그래프를 사용해도 N+1 문제를 해결할 수 있지 않나요? 사용 방법도 더 편리하고 좋은 것 같은데, 엔티티 그래프를 사용하지 않는 이유가 있나요?