묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨실전! 스프링 데이터 JPA
코드를 실행해도 DB에 데이터가 추가되지 않습니다
이 테스트를 실행하면 스프링 데이터 JPA의 save 메서드를 사용했으니 바로 DB에 member 하나가 추가되어야 할 것 같은데, DB를 보니 추가된 데이터가 없습니다.application.yml이 문제인가 해서 ddl-auto 설정을 보니 create로 잘 되어있고, 로그를 보면 sql문은 잘 날아가는데 왜 DB에 저장이 안 되는지를 도저히 모르겠어서 프로젝트 압축 파일을 남깁니다 ㅠㅠhttps://drive.google.com/file/d/1tyWEF3_l80u8Vban3No5q59y-x8sjWqF/view?usp=sharing테스트 코드들은 MemberRepositoryTest 클래스에다가 전부 넣어놨습니다. 감사합니다.
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
JSON으로 리턴하는 경우에도 MVC인가요?
저는 대략적으로 컨트롤러에서 view를 리턴해주는 경우 SSR, restful api를 사용해 json을 리턴해주는 경우 CSR이라고 이해하고있습니다그런데 json을 리턴해주는 경우, 컨트롤러에서 모델을 따로 사용하지 않고, 뷰를 리턴해주는것도 아닌데 이 경우에도 여전히 MVC 패턴을 사용한다고 말할 수 있는지 궁금합니다
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
타임리프를 사용하지 않는다면
타임리프를 사용하지 않는 경우 bindingResult를 view에 어떻게 전달해주나요?타임리프인 경우에만 view에 전달되는것 아닌가요?
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
version 5. 어댑터 handle 메서드 인자 관련
안녕하세요, 강의를 수강하고 있는 학생입니다. 이번 강의를 들으면서 궁금한 점이 생겨 글을 작성하게 되었습니다. version 2에서 version 3로 넘어갈 때 해결한 문제 중 하나인 HttpServletRequest와 Response에 종속되어 있다와 관련하여 궁금한 점이 생겼습니다. version 5에서 해당 핸들러(컨트롤러)에 맞는 어댑터를 찾아서 handle 메서드를 부를 때 인자로 핸들러를 제외하고도 HttpServletRequest 타입의 request, Response 타입의 response를 전달하는데, 그 이후에 딱히 이 두 인자에 종속될 필요가 없다고 느꼈습니다. 따라서, FrontController 서블릿에서 creatParamMap 메서드를 통해 파라미터를 담은 Map 타입 객체를 생성하여 인자로 전달하면 어떨까라는 생각이 들었습니다. 이러한 부분은 구현하는 사람의 재량인지, 제가 위에 설명한 방법에 문제가 있어서 그렇게 하지 않는 것인지 궁금합니다. 질문글 읽어주셔서 감사합니다.
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
도메인 객체 / entity 객체 / requset, response 객체 간 mapping 시 mapper 사용
안녕하세요. 관련 내용을 듣다가 궁금한 것이 생겨 질문드립니다.해당 강의를 듣다가 궁금한 것이 생겨 질문드립니다.찾아보니 DDD나 헥사고날 아키텍처에서 request / response 객체 <-> 도메인 객체 <-> entity 객체 간의 mapping이 일어나는 것을 볼 수 있는데요..이런 경우 controller, service, infral layer가 모두 mapper 관련 라이브러리 객체나 직접 구현한 mapper 객체를 들고 변환시켜주는 구조는 별로 좋지 못한 구조일까요?제 개인적인 의견으로는 결국 지금 구조에서 response / request 객체나 entitiy 객체가 domain 객체로 변환시켜주기위해 domain 객체에 의존성이 생기는 구조인데 mapper 객체를 쓰면 이런 객체간의 의존성을 최소화시킨다는 점에서 장점이 있을 것 같은데요.. 물론 정답은 없겠지만, 의견이 어떠신지 궁금하여 질문드립니다.
-
미해결스프링 핵심 원리 - 기본편
StatefulService.class
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]package time.onemore.singleton; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import time.onemore.AppConfig; import static org.junit.jupiter.api.Assertions.*; class StateFulServiceTest { @Test void StateFulService() { ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class); StateFulService stateFulService1 = ac.getBean(StateFulService.class); StateFulService stateFulService2 = ac.getBean(StateFulService.class); // ThreadA : A사용자 10000원 주문 stateFulService1.order("userA", 10000); // ThreadB : B사용자 20000 주문 stateFulService2.order("userB",20000); int price = stateFulService1.getPrice(); System.out.println("price = " + price); Assertions.assertThat(price).isEqualTo(20000); } static class TestConfig { @Bean public StateFulService stateFulService() { return new StateFulService(); } } }에서 StateFulService stateFulService2 = ac.getBean(StateFulService.class);@Bean은 StateFulServiceTest에 있는데, 왜 StateFulService.class 를 쓰나요??
-
미해결스프링 시큐리티 완전 정복 [6.x 개정판]
로그아웃 매핑 핸들러 질문
스프링 시큐리티는 필터 기반으로 보안 관련 기능을 처리하도록 도와주는 것으로 이해하고 있습니다.여기서 로그아웃을 컨트롤러에 작성한 이유가 있나요?로그아웃은 필터보다는 컨트롤러에서 진행하는 것이 좋나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
@Transactional 차이로 인해 재고의 quantity 가 감소되지 않는 이유에 대해 질문 드립니다.
안녕하세요, 강사님 테스트에 관심이 생겼고, 강사님 덕분에 테스트에 대해 하나하나 알아가는 재미를 느끼는 중입니다!좋은 강의 감사드립니다! 강의를 진행하던 도중 의문이 있어서 질문 드립니다. 문제 상황입니다.stock.deductQuantity(quantity) 부분에서 stock 의 this.quantity 가 파라미터로 들어온 quantity 만큼 갯수가 감소되는 것을 확인하였습니다. (아래 사진에 빨간줄로 밑줄 그었습니다) 그러나, 테스트의 결과는 실패로 떴습니다.그 이유는 감소된 재고의 수량(Stock 의 quantity)이 아래 사진처럼 테스트에 반영되지 않았습니다.해당 테스트 코드입니다. 강사님의 테스트 코드와 일치하게 짰습니다.@DisplayName("재고와 관련된 상품이 포함되어 있는 주문번호 리스트를 받아 주문을 생성한다.") @Test void createOrderWithStock() { // given Product product1 = createProduct(BOTTLE, "001", 1000); Product product2 = createProduct(BAKERY, "002", 3000); Product product3 = createProduct(HANDMADE, "003", 5000); productRepository.saveAll(List.of(product1, product2, product3)); Stock stock1 = Stock.create("001", 2); Stock stock2 = Stock.create("002", 2); stockRepository.saveAll(List.of(stock1, stock2)); OrderAddRequest request = OrderAddRequest.builder() .productNumbers(List.of("001", "001", "003", "002")) .build(); LocalDateTime registeredDateTime = LocalDateTime.now(); // when OrderResponse orderResponse = orderService.createOrder(request, registeredDateTime); // then assertThat(orderResponse.getId()).isNotNull(); assertThat(orderResponse) .extracting("registeredDateTime", "totalPrice") .contains(registeredDateTime, 10000); assertThat(orderResponse.getProducts()).hasSize(4) .extracting("productNumber", "price") .containsExactlyInAnyOrder( tuple("001", 1000), tuple("001", 1000), tuple("002", 3000), tuple("003", 5000) ); List<Stock> stocks = stockRepository.findAll(); assertThat(stocks).hasSize(2) .extracting("productNumber", "quantity") .containsExactlyInAnyOrder( tuple("001", 0), tuple("002", 1) ); } 왜 테스트가 실패하는지 한참 헤매다가 OrderService 에 @Transactional 을 추가하였더니 Stock의 감소된 quantity 가 테스트에 반영이 되었고, 테스트가 성공하게 되었습니다.// OrderService 중 일부 발췌 @Transactional @RequiredArgsConstructor @Service public class OrderService { private final OrderRepository orderRepository; private final ProductRepository productRepository; private final StockRepository stockRepository; 저의 얕은 지식으로는 @Transactional 이 왜 테스트에 영향을 주게 되었는지 이해가 도통되지 않아 강사님께 질문을 드립니다
-
미해결스프링 시큐리티 OAuth2
client 의 token 발행 가능 여부
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. keycloak 을 authorization server 로 사용하는데요.키클록에 등록되는 유저와 유저별 권한과 별개로,서비스의 메뉴별 권한을 상세 설정하기 위해서 client 에서 서비스 권한 관리를 하고 메뉴 접근 권한 관련된 token 을 발행한다고 하면, resource-server 에 jwt-set-uri 를 client 서버 endpoint 로 하고, client 에서 jwkset 을 반환하는 방식이 가능한지 궁금합니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
MemberServiceTest 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.MemberServiceTest 하려고 하는데 처음부터 오류뜨고 안돼요Cannot resolve symbol 'junit', Cannot resolve symbol 'Test', Identifier or type expected 이렇게 오류가 3개가 뜹니다. @Test 자체도 안되네요.. 사진첨부 같이 하겠습니다!
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
302 리다이렉트 대신에 302 응답코드로 뷰 템플릿 부르기
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]```java@PostMapping("/add") @ResponseStatus(HttpStatus.FOUND) public String addItemV7(Item item) { Item savedItem = itemRepository.save(item); return "basic/item"; }위와 같이 302 응답코드를 가지고 보내면 왜 안되는 건가요? (사이트에서는 302로 응답이 오는게 확인됨)302가 redirect 관련 응답코드라서 redirect location이 없어서 그런건가요?그렇다면 302가 요청 메서드를 Get으로 바꿔주는건 오직 redirect location으로 이동했을 때만 인가요?사이트에서 확인을 해보니
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
안녕하세요
유연한 컨트롤러1 - v5 강의를 듣다가 중간에 코드가 많아져서 이 강의에서 한 전체 코드를 보고 중간에 제가 이해를 제대로 "완벽" 하게 했는지 좀 의문이 드는데 이건 저만 그런건 아닐까요?
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
ArgumentResolver 구현 시 supportsParameter 메서드 관련 질문
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]우리가 직접 만든 LoginMemberArgumentResolver 를 등록하게 되면 스프링에서는 @Login 을 처리할 수 있는 ArgumentResolver 를 찾기 위해 supportsParameter 라는 메서드를 통해 지원 여부를 확인할 것으로 예상이 됩니다.public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver { @Override public boolean supportsParameter(MethodParameter parameter) { log.info("supportsParameter 실행"); boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class); boolean hasMemberType = Member.class.isAssignableFrom(parameter.getParameterType()); return hasLoginAnnotation && hasMemberType; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { log.info("resolveArgument 실행"); HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); HttpSession session = request.getSession(false); if (session == null) { return null; } return session.getAttribute(SessionConst.LOGIN_MEMBER); } }그럼 실제 resolve 하기 전에 supportsParameter 가 먼저 호출될 것이므로 로그에 "supportsParameter 실행" 이라는 것이 남아야 할 것 같은데 실제 콘솔창에는 남지 않습니다. 원인이 무엇인가요?
-
미해결스프링 핵심 원리 - 고급편
예외 처리 부분 질문 있습니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]항상 강의 잘 보고 있습니다. 감사합니다.강사님 자료 중에 "throw e: 예외를 꼭 다시 던져주어야 한다. 그렇지 않으면 여기서 예외를 먹어버리고, 이후에 정상 흐름으로 동 작한다. 로그는 애플리케이션에 흐름에 영향을 주면 안된다. 로그 때문에 예외가 사라지면 안된다"라는 문장에서예외를 다시 던져준다와 예외를 먹어버린다 등의 표현이 잘 이해가 안되는데 설명 부탁드립니다. 감사합니다
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
윈도우 빌드 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.계속 빌드 오류가 뜹니다....자바 버전도 17 이상인데 뭐가 문제인걸까요...
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
양방향 @OneToOne 조회에 대해 질문 있습니다.
@Entity public class Delivery { @Id @Column(name = "delivery_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(fetch = FetchType.LAZY, mappedBy = "delivery") private Order order; private Address address; @Enumerated(EnumType.STRING) private DeliveryStatus status; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } public DeliveryStatus getStatus() { return status; } public void setStatus(DeliveryStatus status) { this.status = status; } }@Entity @Table(name = "orders") public class Order { @Id @Column(name = "order_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "delivery_id") private Delivery delivery; private LocalDateTime orderDate; @Enumerated(EnumType.STRING) private OrderStatus status; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public Delivery getDelivery() { return delivery; } public void setDelivery(Delivery delivery) { this.delivery = delivery; } public LocalDateTime getOrderDate() { return orderDate; } public void setOrderDate(LocalDateTime orderDate) { this.orderDate = orderDate; } public OrderStatus getStatus() { return status; } public void setStatus(OrderStatus status) { this.status = status; } }public class Test2 { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); Delivery delivery = new Delivery(); delivery.setStatus(DeliveryStatus.READY); delivery.setAddress(new Address("city", "street", "zipcode")); em.persist(delivery); Order order = new Order(); order.setOrderDate(LocalDateTime.now()); order.setStatus(OrderStatus.ORDER); order.setDelivery(delivery); em.persist(order); // Order 에 fetch = FetchType.LAZY 설정 , delivery 실제 값 사용 { em.flush(); em.clear(); Order findOrder = em.find(Order.class, order.getId()); Delivery findDelivery = findOrder.getDelivery(); System.out.println("findDelivery : " + findDelivery.getStatus()); } tx.commit(); }finally { em.close(); } } }실행결과Hibernate: /* insert for com.mycom.myapp.ex.Delivery */insert into Delivery (city, street, zipcode, status) values (?, ?, ?, ?) Hibernate: /* insert for com.mycom.myapp.ex.Order */insert into orders (delivery_id, orderDate, status) values (?, ?, ?) Hibernate: select o1_0.order_id, o1_0.delivery_id, o1_0.orderDate, o1_0.status from orders o1_0 where o1_0.order_id=? Hibernate: select d1_0.delivery_id, d1_0.city, d1_0.street, d1_0.zipcode, d1_0.status from Delivery d1_0 where d1_0.delivery_id=? Hibernate: select o1_0.order_id, o1_0.delivery_id, o1_0.orderDate, o1_0.status from orders o1_0 where o1_0.delivery_id=? findDelivery : READY 강의에서 진행한 Order와 Delivery를 가지고 조회 테스트를 해봤습니다. 우선 Order와 Delivery를 양방향으로 OneToOne 관계를 설정했습니다. 그리고 둘다 각각 지연로딩으로 설정했습니다.em.flush(); em.clear(); Order findOrder = em.find(Order.class, order.getId()); Delivery findDelivery = findOrder.getDelivery(); System.out.println("findDelivery : " + findDelivery.getStatus());그리고 이 부분에 대해 아래와 같이 이해를 했습니다.맨처음 em.find()를 해서 Order에 대해 DB에 select문을 보냅니다. 이때 delivery와는 지연로딩이므로 추가 select가 발생하지 않습니다. 그리고 findDelivery.getStatus()); 에서 delivery의 실제 값을 사용하므로 이때 delviery에 대한 select문이 발생합니다. 정리하면 처음에는 order에 대한 select문 그리고 실제 delevery 값을 사용할때 delivery에 대한 select문 해서 총 두번의 select문이 발생할꺼라고 예상을 했습니다.근데 실제 실행결과를 보니 총 세번의 select문이 발생했습니다. 실행결과에서 첫번째 select문과 두번째 select문이 출력된거는 이해가 되는데 마지막 select문은 왜 발생했는지 모르겠습니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
MemberRepositoryTest 실행함에 있어 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]application.ymlspring: 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: true logging.level: org.hibernate.SQL: debug # org.hibernate.type:trace org.hibernate.orm.jdbc.bind: tracebuild.gradleplugins { id 'java' id 'org.springframework.boot' version '3.2.0' id 'io.spring.dependency-management' version '1.1.4' } group = 'jpabook' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '17' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' //JUnit4 추가 testImplementation("org.junit.vintage:junit-vintage-engine") { } test { useJUnitPlatform() }MemberRepositoryTest.javapackage jpabook.jpashop; import jpabook.jpashop.Entity.Member; import jpabook.jpashop.Repository.MemberRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(SpringExtension.class) @SpringBootTest class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Test @Transactional @Rollback(false) public void testMember() throws Exception { //given Member member = new Member(); member.setUsername("memberA"); //when Long savedId = memberRepository.save(member); Member findMember = memberRepository.find(savedId); //then assertThat(findMember.getId()).isEqualTo(member.getId()); assertThat(findMember.getUsername()).isEqualTo(member.getUsername()); System.out.println("findMember == member: " + (findMember == member)); } }테스트 실행 시 No matching tests found in any candidate test task. Requested tests: Test pattern jpabook.jpashop.Repository.MemberRepositoryTest in task :test* Try:> Run with --stacktrace option to get the stack trace.> Run with --info or --debug option to get more log output.> Run with --scan to get full insights.> Get more help at https://help.gradle.org.Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.For more on this, please refer to https://docs.gradle.org/8.8/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.BUILD FAILED in 1s4 actionable tasks: 1 executed, 3 up-to-date오류가 발생합니다.
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
java.lang.NullPointerException: Cannot invoke "java.util.Optional.ifPresent(java.util.function.Consumer)" because the return value of "..."is null
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 회원테스트 기술 부분을 학습하고있는데 테스트케이스 오류가 났습니다 어떻게 해결해야할지 몰라 질문글을 적었습니다아래는 테스트 코드입니다import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; class MemberServiceTest { MemberRepository memberRepository = new MemoryMemberRepository(); MemberService memberService = new MemberService(); //테스트는 과감하게 한글로 작성 가능 //테스트할땐 given,when,then 틀 사용 @Test void 회원가입() { //given Member member = new Member(); member.setName("hello"); //when Long saveId = memberService.join(member); //then Member findMember = memberService.findOne(saveId).get(); org.assertj.core.api.Assertions.assertThat(member.getName()).isEqualTo(findMember.getName()); } @Test void findMembers() { } @Test void findOne() { } }그리고 MemberService코드입니다package hello.hello_spring.service; import hello.hello_spring.domain.Member; import hello.hello_spring.repository.MemberRepository; import hello.hello_spring.repository.MemoryMemberRepository; import java.util.List; import java.util.Optional; public class MemberService { private final MemberRepository memberRepository = new MemoryMemberRepository(); /*회원가입*/ public Long join(Member member){ validateDuplicateMember(member); //중복회원검증 memberRepository.save(member); return member.getId(); } private void validateDuplicateMember(Member member){ memberRepository.findByName(member.getName()) .ifPresent(m -> { throw new IllegalArgumentException("이미 존재하는 회원입니다"); }); } /*전체회원 조회*/ public List<Member> findMembers() { return memberRepository.findAll(); } public Optional<Member> findOne(Long memberId){ return memberRepository.findById(memberId); } }아래는 오류 메시지 입니다java.lang.NullPointerException: Cannot invoke "java.util.Optional.ifPresent(java.util.function.Consumer)" because the return value of "hello.hello_spring.repository.MemberRepository.findByName(String)" is nullat hello.hello_spring.service.MemberService.validateDuplicateMember(MemberService.java:22)at hello.hello_spring.service.MemberService.join(MemberService.java:15)at hello.hello_spring.service.MemberServiceTest.회원가입(MemberServiceTest.java:24)at java.base/java.lang.reflect.Method.invoke(Method.java:568)at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)Process finished with exit code -1 어떻게 해결하면 좋을까요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
order.cancel();의 더티체킹에 대해 질문 있습니다.
@Entity @Getter @Setter @DiscriminatorColumn(name = "dtype") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class Item { @Id @Column(name = "item_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private int price; private int stockQuantity; @ManyToMany(mappedBy = "items") private List<Category> categories = new ArrayList<>(); //== 비즈니스 로직 ==// public void addStock(int quantity) { this.stockQuantity += quantity; } public void removeStock(int quantity) { int restStock = this.stockQuantity - quantity; if (restStock < 0) { throw new NotEnoughStockException("need more stock"); } this.stockQuantity = restStock; } }@Entity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) public class OrderItem { @Id @Column(name = "order_item_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id") private Order order; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "team_id") private Item item; private int orderPrice; private int count; //==생성 메서드==// public static OrderItem createOrderItem(Item item, int orderPrice, int count) { OrderItem orderItem = new OrderItem(); orderItem.setItem(item); orderItem.setOrderPrice(orderPrice); orderItem.setCount(count); item.removeStock(count); return orderItem; } //==비즈니스 로직==// /* * 재고 수량을 복구한다. * */ public void cancel() { getItem().addStock(count); } //==조회 로직==// public int getTotalPrice() { return getOrderPrice() * getCount(); } }@Entity @Getter @Setter @Table(name = "orders") @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Order { @Id @Column(name = "order_id") @GeneratedValue(strategy = GenerationType.IDENTITY) 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 LocalDateTime orderDate; @Enumerated(EnumType.STRING) private OrderStatus status; //== 연관관계 메서드 ==// public void addMember(Member member) { this.member = member; member.getOrders().add(this); } public void addOrderItem(OrderItem orderItem) { orderItems.add(orderItem); orderItem.setOrder(this); } public void addDelivery(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); order.setOrderDate(LocalDateTime.now()); 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() { int totalPrice = 0; for (OrderItem orderItem : orderItems) { totalPrice += orderItem.getTotalPrice(); } return totalPrice; } }@Service @Transactional(readOnly = true) @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; private final MemberRepository memberRepository; private final ItemRepository itemRepository; // 주문 @Transactional public Long order(Long memberId, Long itemId, int count) { // 엔티티 조회 Member member = memberRepository.findOne(memberId); Item item = itemRepository.findOne(itemId); // 배송정보 생성 Delivery delivery = new Delivery(); delivery.setAddress(member.getAddress()); // 주문상품 생성 OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count); // 주문 생성 Order order = Order.createOrder(member, delivery, orderItem); // 주문 저장 orderRepository.save(order); return order.getId(); } // 취소 @Transactional public void cancelOrder(Long orderId) { Order order = orderRepository.findOne(orderId); order.cancel(); } // 검색 // public List<Order> findOrders() { // return orderRepository.f // } }OrderService에서 cancelOrder()을 할 경우 @Transactional에 의해 자동으로 트랜잭션을 시작하고 커밋을 보내고 커밋을 보낼때 플러시가 발생하고 더티체킹이 발생해 직업 update를 안해도 알아서 update 쿼리가 날라가는 부분에 대해서는 이해를 했습니다.궁금한 부분은 현재 Order order = orderRepository.findOne(orderId); 를 통해 order 엔티티만 조회 했으니 영속성 컨텍스트에는 order 엔티티만 존재하므로 Item 엔티티는 영속성 컨텍스트에 없는데 어떻게 더티체킹에 들어갈까가 저의 의문입니다.이 부분에 대해서 아래와 같이 생각해봤는데 맞게 생각한건지 궁금합니다. 잘못된 부분에 대해서 알려주시면 감사하겠습니다.order.cancel();의 cancel() 메서드를 보면 orderItem.cancel(); 부분이 존재한다.orderItem.cancel() 의 cancel() 메서드를 보면 getItem().addStock(count); 부분이 존재한다.여기서 getItem()을 사용해서 (현재 Order와 OrderItem은 지연로딩 관계) 실제 orderItem 값을 사용하므로 DB에서 orderItem 엔티티를 조회해서 영속성 컨텍스트에 저장한다.다시 orderItem.cancel() 의 cancel() 메서드에서 addStock(count) 메서드를 들어가 보면 Item의 addStock() 메서드로 이동한다. 여기서 this.stockQuantity += quantity; 를 통해 Item의 실제 값을 사용하므로 Item 엔티티도 DB에서 조회해서 영속성 컨텍스트에 저장한다.결과적으로 OrderService의 cancelOrder() 메서드에서 order.cancel();가 호출되면 Item과 OrderItem 엔티티도 영속성 컨텍스트에 저장되서 더티체킹 범위에 들어가게 된다.
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
핸들러매핑
이전에 V5예시에서 URL에 대한 핸들러 매핑은 객체가 된것을 확인할 수 있습니다 @Controller public class SpringMemberFormControllerV1 { @RequestMapping("/springmvc/v1/members/new-form") public ModelAndView process() { return new ModelAndView("new-form"); . } }이후 스프링 MVC에서 RequestMappingHandlerMapping은 스프링 중에서 @RequestMapping또는 @Controller가 클래스 레밸에 붙어 있는 경우에 매핑 정보로 인식한다라고 하셨는데 그렇다면 URL 정보가 스프링빈(객체)로 매핑되는것이 핸들러 매핑이 되는 것인가요?? @RequestMapping이 클래스 단위가 아니라 메서드 단위에 적용된다는 것은 핸들러 어뎁터조회, 핸들러 어뎁터 실행이 끝나고 실제 핸들러가 실행되는 부분이라고 이해했습니다..