묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결
스프링 인터셉터가 동작하지 않아 질문드립니다.
안녕하세요, 스프링 MVC 강의를 완강이후 프로젝트를 수행중인 학부생입니다.로그인 기능 구현과 관련하여 강의 예제 코드를 참고하며 구현하던 중이해할 수 없는 현상이 발생하여 질문 드립니다. 코드LoginInterceptor.javapackage Alchole_free.Cockpybara.interceptor; import Alchole_free.Cockpybara.constant.SessionLoginConst; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @Slf4j //@Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); log.info("session = {}", session); if(session==null || session.getAttribute(SessionLoginConst.LOGIN_MEMBER)==null){ log.info("로그인되지 않은 사용자"); response.sendRedirect("/login"); return false; } log.info("정상 요청"); return true; } } WebConfig.javapackage Alchole_free.Cockpybara.config; import Alchole_free.Cockpybara.interceptor.LoginInterceptor; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; //@Slf4j @Configuration //@RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { // private final LoginInterceptor loginInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .order(1) .addPathPatterns("/**") .excludePathPatterns("/", "/join", "/login", "/css/**", "/*.ico", "/error"); } } 문제위와 같이 코드를 구성하고 애플리케이션을 동작시켰는데, 인터셉터가 아예 로그에 남지않는(생성되지 않는 것으로 보이는) 현상이 발생하였습니다. 관련하여 구글링을 하다보니인터셉터 클래스를 빈으로 등록해주는 형태가 아니면 동작하지 않을 수 있다고 하여,빈으로 등록하고 WebConfig 클래스에서 생성자를 통해 주입받는 형태로 구현도 해보았는데여전히 같은 문제가 발생하더군요. 도대체 어느 부분에서 문제가 발생하는 것인지파악하기가 힘들어 고민끝에 질문드립니다. 혹시 몰라 아래 빌드, 설정 파일도 첨부합니다. 문제 실행 화면/hello 로 Controller @GetMapping 메서드를 구현해놓고 요청을 보냈으나 인터셉터 관련 로그가기록되지 않는 모습입니다.build.gradleplugins { id 'java' id 'org.springframework.boot' version '2.7.13' id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'Alchole_free' version = '0.0.1-SNAPSHOT' java { sourceCompatibility = '11' } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' //swagger 설정 implementation group:'io.springfox', name:'springfox-swagger2', version:'2.8.0' implementation group:'io.springfox', name:'springfox-swagger-ui', version:'2.8.0' } tasks.named('test') { useJUnitPlatform() } application.properties# ?????? ?? ?? spring.datasource.url=jdbc:mariadb://localhost:3306/cockpybara spring.datasource.username=root spring.datasource.password=cockpybara spring.datasource.driver-class-name=org.mariadb.jdbc.Driver # JPA ?? spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=create spring.jpa.properties.hibernate.format_sql=true //JPA ???? Hibernate? ????? ???? SQL? formating?? ?? spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect #show parameter binding logging.level.org.hibernate.type.descriptor.sql=DEBUG logging.level.org.hibernate.SQL=DEBUG
-
미해결Practical Testing: 실용적인 테스트 가이드
테스트 동시성 관련 질문드립니다
안녕하세요 강의 다 듣고 기능 추가해 가며 공부를 하고 있습니다. 그러다 막히는 부분이 있어 질문드리는데 강의 내용을 조금 벗어 나는거 같아 질문을 드려도 될지 모르겠는데 괜찮으시다면 답변 주시면 감사하겠습니다. @Transactional public OrderResponse createOrder(OrderCreateServiceRequest request, LocalDateTime registeredDateTime) { List<String> productCodes = request.getProductCodes(); List<Product> products = findProductsBy(productCodes); Member member = memberRepository.findByPhoneNumber(request.getPhoneNumber()).get(); deductStockQuantities(products); Order order = Order.create(products, member, registeredDateTime); return OrderResponse.of(orderRepository.save(order)); }order를 생성하는 부분에서 재고 감소 되는 부분을 동시성 처리를 해보려 하는데 테스트 코드에선 deductStockQuantities로 넘어가서 findAllByProductCodeIn 만 한번 돌고 롤백이 되더라구요. @Test public void create_order_with_concurrent_5_request() throws InterruptedException { //given createProducts(); OrderCreateServiceRequest request1 = OrderCreateServiceRequest.builder() .productCodes(List.of("A002","A003")) .phoneNumber("010-1111-2222") .build(); OrderCreateServiceRequest request2 = OrderCreateServiceRequest.builder() .productCodes(List.of("A002","A003")) .phoneNumber("010-1111-2222") .build(); OrderCreateServiceRequest request3 = OrderCreateServiceRequest.builder() .productCodes(List.of("A002","A003")) .phoneNumber("010-1111-2222") .build(); int numberOfThreads = 3; ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads); CountDownLatch latch = new CountDownLatch(numberOfThreads); //when executorService.submit(() -> { try { orderService.createOrder(request1, LocalDateTime.now()); }finally { latch.countDown(); } }); executorService.submit(() -> { try { orderService.createOrder(request2, LocalDateTime.now()); }finally { latch.countDown(); } }); executorService.submit(() -> { try { orderService.createOrder(request3, LocalDateTime.now()); }finally { latch.countDown(); } }); latch.await(); //then List<Stock> stocks = stockRepository.findAllByProductCodeIn(List.of("A002","A003")); assertThat(stocks).hasSize(2) .extracting("productCode", "quantity") .containsExactlyInAnyOrder( tuple("A002", 7), tuple("A003", 7) ); } @Lock(LockModeType.PESSIMISTIC_WRITE) List<Stock> findAllByProductCodeIn(List<String> productCodes);테스트 코드는 구글링해서 넣어보았는데 이런 부분 관련해서 따로 배운게 없어 잘 안되더라구요. 락도 걸어보고 했는데 어디서 안되는지 잘 모르겠어서 질문드립니다.
-
미해결스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
지금까지 정리한 부분이 맞는지 궁금합니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]제가 지금까지 정리한 부분인데 맞는 이야기인지 확인 및 피드백 부탁 드려도 될까요? 1. 서블릿 컨테이너에 의해 요청된 값이 담긴 Request 객체와 Response 객체가 생성됨. 생성된 Request 객체와 Response 객체는 Dispatcher Servlet의 service() 함수를 호출하면서 전달됨.2. Dispatcher Servlet가 HttpServlet의 service() 함수를 실행함으로써 Dispatcher Servlet의 핵심 로직인 doDispatch()가 호출됨.3. 구현된 HandlerMapping에서 요청 URL에 매핑된 핸들러(컨트롤러)를 찾기 시작함. 만약 핸들러에 @Controller가 적용되어 있고, 요청 URL과 매핑된 @RequestMapping 메소드가 있다면 RequestMappingHandlerMapping는 해당 핸들러를 Dispatcher Servlet에 반환됨.4. HandlerMapping은 전달받은 핸들러를 처리할 수 있는 HandlerAdapter를 조회함. 예를 들어, @RequestMapping 기반의 핸들러를 처리할 수 있는 RequestMappingHandlerAdapter를 찾음.5. HandlerAdapter는 실제 핸들러(컨트롤러)를 호출하기 전에 필요한 매개변수 정보(@RequestParam인지, @ModelAttribute인지, @RequestBody인지, HttpEntity인지)를 ArgumentResolver에 제공함.6. 핸들러 어댑터는 ArgumentResolver의 supportsParameter()를 호출하여 컨트롤러의 매개변수를 처리할 수 있는 ArgumentResolver를 찾음.7. 해당 ArgumentResolver의 resolveArgument()를 호출하여 필요한 데이터를 생성하고, 이를 HandlerAdapter에 반환함.7-1. 만약 핸들러(컨트롤러)가 메서드 레벨에서 @RequestBody, HttpEntity(RequestEntity)를 사용한다면 ArgumentResolver는 HTTP 메시지 컨버터의 canRead() 메소드를 호출하여 매개변수 타입과 미디어 타입(Content-Type)을 처리할 수 있는 HTTP 메시지 컨버터를 찾음.8. canRead() 조건을 만족하면 ArgumentResolver는 해당 HTTP 메시지 컨버터의 read()를 호출하여 필요한 데이터를 생성하고, 이를 HandlerAdapter에 반환함.9. HandlerAdapter는 생성된 파라미터의 값이 모두 준비되면 핸들러(컨트롤러)를 호출하면서 값을 넘겨주고 핸들러(컨트롤러)의 로직을 수행함.10-1. 메시지 바디에 직접 응답하지 않는 경우예를 들면 핸들러(컨트롤러)가 String 타입으로 뷰 이름을 반환하는 경우, HandlerAdapter는 ReturnValueHandler의 supportsReturnType()을 호출하여 지원하는 리턴 타입인지 확인함.지원한다면 HandlerAdapter는 해당 ReturnValueHandler의 handleReturnValue()를 호출하고 Model과 뷰 이름을 가지고 ModelView를 생성함. 그리고 이를 HandlerAdapter, Dispatcher Servlet 순으로 반환함.Dispatcher Servlet은 ModelView를 ViewResolver에 전달함.ViewResolver가 동작하고 뷰를 반환함. 그리고 Dispatcher Servlet가 Model에 담겨져 있는 뷰의 render()를 호출하면 HTML이 생성되고 이를 클라이언트에게 응답함.10-2. 메시지 바디에 직접 응답하는 경우@ResponseBody를 사용하면 뷰 템플릿 대신 HTTP 메시지에 직접 응답 데이터를 출력함.ArgumentResolver는 HTTP 메시지 컨버터의 canWrite() 메소드를 호출하여 리턴 타입과 미디어 타입(Accept-Type)을 처리할 수 있는 HTTP 메시지 컨버터를 찾음.canWrite() 조건을 만족하면 ArgumentResolver는 해당 HTTP 메시지 컨버터의 write()를 호출하여 필요한 데이터를 HTTP 응답 Body 부분에 기록함.흐름이 다시 Dispatcher Servlet으로 돌아가고, ViewResolver를 거치지 않고 바로 클라이언트에게 응답함. 감사합니다.
-
미해결스프링 핵심 원리 - 기본편
CoreApplication 실행이 안돼요....!
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 안녕하세요! 선생님따라서 인텔리제이에 core파일 open해서 CoreApplication실행하려고 하는데 run 버튼도 꺼져있고 실행이 안되는데 어떻게 해결해야할까요....??
-
미해결Practical Testing: 실용적인 테스트 가이드
이 경우, @BeforeEach에 fixture를 구성해도 될까요?
지금 OrderServiceTest 클래스의 createOrder(), createOrderWithStock(), ... 메서드들은 주문을 생성하기 위해서 given절에서 모두 동일하게 product1, 2, 3을 먼저 생성해주는 fixture가 있습니다. @Test void createOrder() { // given LocalDateTime registeredDateTime = LocalDateTime.now(); Product product1 = createProduct(HANDMADE, "001", 1000); Product product2 = createProduct(HANDMADE, "002", 3000); Product product3 = createProduct(HANDMADE, "003", 5000); productRepository.saveAll(List.of(product1, product2, product3)); OrderCreateServiceRequest request = OrderCreateServiceRequest.builder() .productNumbers(List.of("001", "002")) .build(); // when OrderResponse orderResponse = orderService.createOrder(request, registeredDateTime); // then assertThat(orderResponse.getId()).isNotNull(); assertThat(orderResponse) .extracting("registeredDateTime", "totalPrice") .contains(registeredDateTime, 4000); assertThat(orderResponse.getProducts()).hasSize(2) .extracting("productNumber", "price") .containsExactlyInAnyOrder( tuple("001", 1000), tuple("002", 3000) ); }product를 생성해주는 부분을 @BeforeEach로 빼내려고 하는데, 적절하다고 판단되시는지 궁금합니다. OrderServiceTest의 모든 메서드가 동일한 product를 생성하고는 있지만,각 테스트 메서드에서 "001"이라는 상품이 있는지는 @BeforeEach 메서드까지 스크롤을 왔다갔다하면서 확인해야될거라 생각합니다.(만약 없는 상품을 주문한다면 given절에서 예외가 발생하는 것이기 때문에, 이 부분은 당연히 있는 상품을 주문한다는 것을 보장하고 테스트 코드를 작성해야 할까요?) order에 관한 테스트이기 때문에, product에 대한 부분은 분리해도 될거라 생각하지만, 우빈님의 말씀대로 스크롤을 왔다갔다하면서 @BeforeEach의 메서드를 수시로 보는 것은 안좋다고 느껴져서.. product 생성에 대한 fixture를 분리괜찮다고 생각하시나요?
-
미해결Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트
jpa의 더티체킹 사용에 대해서
해당 강의에서 도메인 모델과 영속성 객체를 구분하는 리팩토링을 진행하셨는데요.리팩토링 이후 영속성 컨텍스트를 통한 더티체킹을 활용하지 않고, 대신 save 메서드를 매번 호출해 주시는 방법으로 변경하셨더라구요.도메인 모델과 영속성 객체의 의존성을 제거하기 위해서 더티체킹 기능도 사용하지 않는건가요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
isEqualTo를 isSameAs로 바꿔야하는것이 아닌가요??
@RunWith(SpringRunner.class) @SpringBootTest public class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Test @Transactional @Rollback(false) public void testMember() throws Exception { //given Member member = new Member(); member.setUsername("memberA"); //when Long saveId = memberRepository.save(member); Member findMember = memberRepository.find(saveId); //then assertThat(findMember.getId()).isEqualTo(member.getId()); assertThat(findMember.getUsername()).isEqualTo(member.getUsername()); assertThat(member).isEqualTo(findMember); System.out.println("findMember == member: " + (findMember == member)); } }위 코드에서 디비에 넣은 멤버와 찾은 멤버가 같은지를 비교를 isEqualTo를 사용을 하셨는데 이것은 제가 알기론 value부분만 같다면 true라는것으로 알고 있습니다. 같은 영속성 컨텍스트 안에서의 객체가 같은지 확인하려면 isSameAs가 맞지 않은지 의문이 생겼습니다!
-
해결됨JPA & Spring Data JPA 기초
도메인과 JPA 엔티티
도메인과 JPA 엔티티를 구분하려고 코드를 작성해봤습니다.@Repository@RequiredArgsConstructorpublic class OrderRepositoryImpl implements OrderRepository {private final OrderJpaRepository orderJpaRepository;@Override public void save(final OrderJpaEntity entity) {orderJpaRepository.save(entity);}@Overridepublic Optional<OrderJpaEntity> findById(final long orderId) {return orderJpaRepository.findById(orderId)}}이러한 방식으로 JpaRepository를 주입받고 OrderRepository를 구현하는 방식입니다.public interface OrderRepository {void save(final OrderJpaEntity entity);Optional<OrderJpaEntity> findById(final long orderId);} 그런데 이때 변경감지를 사용하려면 Order가 아닌 OrderJpaEntity를 반환해야하는데 이렇게 하면 결국 Repository에도 JPA 엔티티에 의존하기 때문에 분리를 잘 하지 못했다는 생각이 듭니다..혹시 도메인과 JPA 엔티티를 분리한다면 어떠한 방식으로 하시나요?
-
해결됨Java/Spring 주니어 개발자를 위한 오답노트
DB 엔티티와 도메인 분리
어디까지 추상화 해야 하는가? 강의에서 2분 17초에toDomain()으로 바꿔서 반환을 했는데이렇게하면 JPA 엔티티가 아니라서 변경감지를 사용하지 못하는거 아닌가요? 이러한 방식으로 작성한다면 업데이트가 필요할 때는 어떤식으로 처리하나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
controller, service용 dto를 분리시키는 것에 대한 질문
항상 잘 듣고 있습니다. 감사합니다. controller layer와 service layer의 dto를 서로 분리시켜서 service layer가 상위 레이어를 모르도록 한다는 것은 이해가 되었습니다.질문 드리겠습니다! dto를 분리할 때, 중복된 코드가 복잡성을 증가시킬 수도 있고 운영 시, 두 dto 간의 변환 과정의 비용이 어느 정도 성능에 영향을 미칠 수 있다고도 생각합니다.2개로 분리하는 방법은 일반적인 설계 패턴은 아니라고 생각되는데, 혹시 현업에서도 자주 사용하는 방식이신지가 궁금합니다. service에서 생성된 response 데이터에 대해서도 controller만의 response dto를 따로 생성할 필요가 있는지 궁금합니다. 클라이언트로 내려주는 응답 객체로 controller 클래스에서 ResponseEntity는 사용하지 않으시는지,주로 현업에서도 강의에서처럼 응답 객체(ApiReponse)를 커스텀해서 내려주는 방식을 선호하시는지 궁금합니다.
-
미해결더 자바, 코드를 조작하는 다양한 방법
gradle 사용하시는 분 도움
일단 새로운 프로젝트 만들고 premain 추가해주는 것 까진 강의를 그대로 따라하시면 됩니다. manifest plugin 부터 조금 차이가 있어서 거기부터 설명하면, build gradle 에 다음과 같이 추가한다.tasks.named('jar') { manifest { attributes( 'Implementation-Title': project.name, 'Implementation-Version': project.version, 'Premain-Class' : "com.java.magicianAgent.MagicianAgent", 'Can-Redefine-Classes' : true, 'Can-Retransform-Classes' : true) } }터미널에서 다음 명령어를 통해 build 한다./gradlew clean buildbuild.libs file 안에 있는 jar file 을 확인한다. (옵션)강의에서와 마찬가지로 zip file 로 변경하면 확인가능합니다. 저같은 경우 SNAPSHOT.jar 과 SNAPSHOT-plain.jar 이렇게 2개가 생겼는데 SNAPSHOT.jar 은 제가 spring boot 로 실행서 그런지 관련 설정들이 보이고 SNAPSHOT-plain.jar 이 맞는거 같더라구요. jar file 의 절대 경로를 복사해 VM option 에 추가한다. 여기서부터는 다시 강의와 같습니다. VM option 이 안보이시면 오른쪽에 Modify options 클릭하면 add vm options 라고 보이실 겁니다. 이상한거나 궁금한거 있으시면 말씀해주세요. gradle 을 쓰시는 모든 분들도 마술을 성공시킵시다 하하
-
해결됨스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
세션은 요청 당 하나만 만들어지는건가요?
SessionManager에서 직접 개발한 세션은 createSession() 메서드로 여러 개의 세션을 만들어서 sessionStore에 저장할 수 있는 것으로 보이는데요 HttpSession을 이용하기 위해 HttpServletRequest의 getSession() 메서드 호출 시 기존 세션이 있다면 생성하지 않고 기존 세션을 가져오는 것으로 이해했습니다.그러면 2개 이상의 세션을 생성할 수 없고 요청 당 하나의 세션만 만들수 있는 건가요?? 질문 하나 더 드리자면 HttpServletRequest의 getSession() 호출 시 세션 ID는 자동으로 생성되고, 세션 Name과 Value는 setAttribute로 설정해야 하는 것이 맞나요? 그렇다면 세션과 세션 ID는 1개만 생성되지만 세션 Name과 Value를 여러 개 설정하는 것이 맞는 것인지요?? 머리 속이 복잡해서 질문드립니다. 읽어주셔서 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
추상클래스 IntegrationTestSupport을 통한 테스트수행 비용단축에 관한 질문
안녕하세요 강의 테스트 수행도 비용이다. 환경 통합하기 를 듣다가 궁금한 점이 생겨 질문드립니다.강의에서는 추상클래스 IntegrationTestSupport에 어노테이션 @SpringBootTest을 달아서 통합테스트가 필요한 테스트클래스가 상속하여 스프링컨테이너가 반복 실행되는걸 단축하는게 인상깊었는데요.원리가 궁금한 점이 있어 질문드립니다.어떤 원리로 마치 스프링컨테이너가 전파되듯이 상속받은 클래스로 설정한 컨테이너가 작동하는걸까요? 그리고 어떻게 상속받은 클래스가 또 실행될 줄 알고 그 환경(컨테이너)가 어느시점까지 종료되지 않고 지속되고 있는건지 궁금합니다
-
해결됨스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
API인터셉터 경로
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]만약에 API로 개발할경우 인터셉터 경로는 어떻게 설정해야하죠?ex 유저API@RestController("api/user")@postmapping유저등록api @Deletemapping유저 삭제 api 이때만 인터셉터 실행 이렇게 있으면 .addPathPatterns("/**")할시 둘다 등록이 되기 때문에 @Deletemapping 이때만 등록하고 싶은데 방법이 있나요? 유저등록일때만@postmapping("/join")으로 설정해야 하나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
팩토리메서드를 지양하고 생성자 혹은 Builder 패턴을 쓰라는 말씀에 관하여
팩토리메서드를 지양하고 생성자 혹은 Builder 패턴을 쓰라는 말씀에 관한 질문입니다.강의 테스트 환경의 독립성을 보장하자 에 6분대 가량에서 나온 설명입니다. OrderServiceTest.java 에서 createOrderWithNoStock() 메서드입니다하나의 로직을 가지고 있는 팩토리메서드 보다는 생성자 혹은 빌더패턴을 이용하여 다른 로직의 개입으로부터 격리하여 테스트 환경의 독립성을 보장하자라는 말씀으로 이해했는데요하지만 처음 강의를 해주실 때 Builder패턴이 가독성을 방해해서 테스트 코드 안에 팩토리메서드를 만드셨는데요. 이번에는 다시 독립성을 위해 Builder 패턴을 쓰는게 좋다고 하시니 헷갈립니다. 결국 가독성 vs 독립성의 트레이드오프 관계로 이해해야하나요? 아니면 여기서 슬기롭게 풀어나갈 수 있는 방법이 있나요?
-
해결됨Practical Testing: 실용적인 테스트 가이드
yml 프로파일 구분 관련 질문이 있습니다.
강의 잘 보고 있습니다. 감사합니다. 지금 설정해두신 yml 파일을 보면,아무런 프로파일을 지정하지 않으면 local 프로파일을 이용한다고 되어 있고, local 프로파일에는 ddl-auto 설정이 create으로 되어 있습니다.spring: profiles: default: local datasource: url: jdbc:h2:mem:~/cafeKioskApplication driver-class-name: org.h2.Driver username: sa password: jpa: hibernate: ddl-auto: none --- spring: config: activate: on-profile: local jpa: hibernate: ddl-auto: create show-sql: true properties: hibernate: format_sql: true defer-datasource-initialization: true # (2.5~) Hibernate ??? ?? data.sql ?? h2: console: enabled: true 기본 프로파일에는 ddl-auto 설정이 none으로 되어 있는데, 이 설정은 어떨 때 적용이 되는 건가요?어차피 아무런 프로파일을 지정하지 않으면, local 프로파일대로 ddl-auto 설정은 create을 따라갈텐데 ddl-auto: none을 적어두신 이유가 궁금합니다. 결론은 아무런 프로파일을 지정하지 않았을 때,local 파일의 ddl-auto 속성인 create이 되는지,아니면 default 프로파일로 설정한 ddl-auto: none이 적용이 되는지가 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
환경 통합하기 관련 질문이 있습니다.
WebMvcTest 를 위해 하나의 추상 클래스를 만들고 거기에 필요한 컨트롤러와 모킹한 서비스, 레포지토리들을 추가하셨는데요. 통합 테스트의 관점에서 보면 테스트 컨텍스트가 한번만 뜨기 때문에 좋겠지만 컨트롤러 하나에 대한 단위 테스트를 고려할땐 불필요하게 모킹한 서비스, 레포지토리, 컨트롤러들까지 띄우는데 시간이 더 걸리는데 이건 어떻게 해결 할순 없을까요?강의 해서 제안 하신 것처럼 현업에서 쓰고 있긴 한데 컨트롤러가 100개가 넘어가니... 하나의 컨트롤러 테스트를 위해 테스트 컨텍스트를 띄우는데 걸리는 시간도 상당히 오래 걸리더라구요. 혹시 좋은 방법이 있다면 알려주시면 감사하겠습니다.
-
미해결스프링부트 시큐리티 & JWT 강의
로그인 올바르게 해도 login?error로 갑니다
SecurityConfig.java@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig extends WebSecurityConfigurerAdapter { private final SecurityDetailsService securityDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http // .csrf().disable() .authorizeRequests() .antMatchers("/user/**").authenticated() .antMatchers("/admin/**").access("hasRole('ADMIN')") .anyRequest().permitAll() .and() .formLogin() .loginPage("/login") .loginProcessingUrl("/login") .defaultSuccessUrl("/home") .usernameParameter("userEmail") .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .logoutSuccessUrl("/login") ; } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } SecurityDetails.java@RequiredArgsConstructor public class SecurityDetails implements UserDetails { private final UserEntity userEntity; @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new GrantedAuthority() { @Override public String getAuthority() { return userEntity.getAuthority().toString(); } }); return authorities; } @Override public String getPassword() { return userEntity.getUserPassword(); } @Override public String getUsername() { return userEntity.getUserEmail(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } SecurityDetailsService.java@Service @RequiredArgsConstructor public class SecurityDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String userEmail) throws UsernameNotFoundException { UserEntity userEntity = userRepository.findByUserEmail(userEmail); if(userEntity != null) return new SecurityDetails(userEntity); return null; } } SecurityDetails에서 return값이 boolean인 override 받는 메소드들 다 true로 해줬고login페이지나 home페이지에는 이미지도 없어서 문제될 게 없다고 생각되는데도통 이유를 모르겠습니다ㅠㅠㅠ
-
미해결스프링 핵심 원리 - 기본편
MyLogger 를 Interceptor 로 구현 시 질문
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예[질문 내용]안녕하세요? 강의를 듣다가 requesURL 같은 경우 Interceptor 에서 멤버 변수로 저장하는 것이 좋다고 해서 구현을 해보려고 했는데 문제가 생겨서 질문 드립니다.## 앞으로 강좌를 계속 듣긴 할 거라서 혹시 뒤의 강좌를 듣고 나면 해결될 수 있는 문제라면 어느 강좌인지 알려주셔도 될 것 같습니다. MyLogger.javapackage hello.core.common; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.UUID; @Component @Scope(value = "request") public class MyLogger { private String uuid; private String requestURL; public void setRequestURL(String requestURL) { this.requestURL = requestURL; } public void log(String message) { System.out.println("[" + uuid + "]" + "[" + requestURL + "] " + message); } @PostConstruct public void init() { uuid = UUID.randomUUID().toString(); System.out.println("[" + uuid + "] request scope bean create: " + this); } @PreDestroy public void close() { System.out.println("[" + uuid + "] request scope bean close: " + this); } } CoreInterceptor.javapackage hello.core.common; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Component @RequiredArgsConstructor public class CoreInterceptor implements HandlerInterceptor { private final ObjectProvider<MyLogger> myLoggerProvider; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("CoreInterceptor.preHandle"); String requestURL = request.getRequestURL().toString(); MyLogger myLogger = myLoggerProvider.getObject(); myLogger.setRequestURL(requestURL); return HandlerInterceptor.super.preHandle(request, response, handler); } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } } WebMvcConfig.javapackage hello.core.common; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @RequiredArgsConstructor public class WebMvcConfig implements WebMvcConfigurer { private final ObjectProvider<MyLogger> myLoggerProvider; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new CoreInterceptor(myLoggerProvider)).excludePathPatterns("/css/**", "/images/**", "/js/**"); } } LogDemoController.javapackage hello.core.web; import hello.core.common.MyLogger; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; @Controller @RequiredArgsConstructor public class LogDemoController { private final LogDemoService logDemoService; private final ObjectProvider<MyLogger> myLoggerProvider; @RequestMapping("log-demo") @ResponseBody public String logDemo(HttpServletRequest request) { String requestURL = request.getRequestURL().toString(); MyLogger myLogger = myLoggerProvider.getObject(); myLogger.log("controller test"); logDemoService.logic("testId"); return "OK"; } } LogDemoService.javapackage hello.core.web; import hello.core.common.MyLogger; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.ObjectProvider; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class LogDemoService { private final ObjectProvider<MyLogger> myLoggerProvider; public void logic(String id) { MyLogger myLogger = myLoggerProvider.getObject(); myLogger.log("service id = " + id); } } 위와 같이 이렇게 구현을 해보니 문제가 생기는데...일단, 인터셉터를 스프링 빈으로 등록하면서 MyLogger 를 활용하려면 알려주신 ObjectProvider 를 사용해야 할 것 같은데... WebMvcConfig 에서 인터셉터를 등록하는 경우, 위의 경우처럼 처음 서버 구동 시에 처리되어야 하는데 이 때는 reqeust 를 사용할 수 없는 문제입니다. 앞서 이 문제를 ObjectProvider 로 해결했는데 Interceptor 의 경우, 나중에 등록이 안 될 것 같은데 이런 경우 어떻게 해결해야 될까요?저렇게 인터셉터를 null 로 호출하니 실행해 보면 WebMvcConfig.java -> registry.addInterceptor(new CoreInterceptor(null))...java.lang.NullPointerException: Cannot invoke "org.springframework.beans.factory.ObjectProvider.getObject()" because "this.myLoggerProvider" is null위와 같은 에러가 발생합니다.관련된 소스 첨부하였습니다.도움 부탁 드립니다. 위의 소스는 제대로 된 걸로 다시 첨부하였습니다.registry.addInterceptor(new CoreInterceptor(null))...->private final ObjectProvider<MyLogger> myLoggerProvider; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new CoreInterceptor(myLoggerProvider)).excludePathPatterns("/css/**", "/images/**", "/js/**"); } 위와 같이 수정하였습니다. 이런 저런 버그들과 강의에서 가르쳐 주신 ObjectProvider 를 사용해서 설정파일에서 request 가 들어오는 시점에 로그를 주입해서 처리되도록 수정하였습니다.Interceptor 에서는 왜 ObjectProvider 를 사용하지 못할 거라고 생각했는지 모르겠네요. 답변 글 보고 질문을 수정하다가 생각이 나서 소스를 수정해 보니 잘 되는 것 같네요.감사합니다.
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
orderItems 에 null이 들어가는 이유가 궁금합니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]강의를 듣다 의문점이 생겨 질문 드립니다.OrderDto는 JPA에서 생성된 객체가 아니고 직접 생성자로 만들어서 사용한 객체인데 왜 Hibernate5Module이 프록시 객체로 생각해서 null값을 반환하는지 모르겠습니다.제가 긴시간 찾아보면서 생각한 이유는 이렇습니다.강의 5분경 쯤에 api 호출시 Json에서 orderItems 가 null로 반환되는것을 보여주십니다.orderItems가 null로 반환되는것은 스프링이 string으로 응답을 보낼때 messageConverter가 json으로 변환해서 반환을합니다.이때 orderItems가 JPA(Hibernate)에서 만든 프록시 객체로 되어있어 전강의에서 설정한 Hibernate5Module이 프록시 객체를 null로 바꾸기 때문에 orderItems가 null로 나타낸것으로 생각됩니다.하지만 제가 디버깅 모드로 확인해본 결과 OrderDto안에 있는 orderItems는 프록시 객체가 아닌 실제로 생성된 객체였습니다.(OrderDto는 단순히 JPA가 관리 하지 않아서 당연하다고 생각합니다.)강의 기준으로 orderItems 안에 있는 orderItem객체가 2개 존재하는데 orderItem객체는 JPA를 통해 생성된 객체이고 자세히 살펴보면 orderItem 안에 Item은 LAZY로딩으로 프록시 객체가 생성되어있습니다. 혹시몰라 Hibernate5Module을 끄고 실행한 결과 Item이 @JsonIgnore가 있으면 orderItems가 제대로 결과 값이 나오고 @JsonIgnore이 없으면 프록시 객체를 Json으로 변환하지 못해서 오류가 발생합니다.그럼 Hibernate5Module는 엔티티 안에 객체의 값중 하나라도 프록시 객체가 있다면 다른 객체의 값이 있든 말든 그냥 null을 반환하는 걸까요??제가 이해한것이 맞는지 궁금해서 질문드립니다.긴글 읽어주셔서 감사합니다.