묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
JPA 순환참조 질문 드립니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. EventGroupInformation.java@NotFound(action = NotFoundAction.IGNORE)@JsonBackReference@ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)@JoinColumn(name="CATEGORY_ID", insertable = false, updatable = false)private CategoryInformation categoryInformation;@BatchSize(size = 1000)@JsonManagedReference@OneToMany(mappedBy = "eventGroupInformation", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)@Where(clause = "DBSTS = 'O' AND FEED_DISPLAY_YN = 'Y' AND START_DATE <= SYSDATE AND END_DATE >= SYSDATE ")@Column(insertable = false, updatable = false)private List<EventInformation> eventInformations; CategoryInformation.java@BatchSize(size = 1000)@JsonManagedReference@OneToMany(mappedBy = "categoryInformation", fetch = FetchType.LAZY)@Where(clause = "DBSTS = 'O' AND START_DATE <= SYSDATE AND END_DATE >= SYSDATE ")@Column(insertable = false, updatable = false)//@Access(value = AccessType.PROPERTY)public List<EventGroupInformation> eventGroupInformations; 여러개의 oneToMany가 걸려 있는 entitiy들을 강의 V5예제를 참고하여 개선중에 있습니다.CategoryInformation(Order)ㄴ EventGroupInformation(OrderItem)ㄴ EventInformation(Item)이런 식에 구조인데요, 각각 oneToMany 입니다. EventGroupInformation과 EventInformation을 fetchJoin을 통해 쿼리를@Query(value = "SELECT distinct eg FROM EventGroupInformation eg " +"LEFT JOIN FETCH eg.eventInformations e " +"WHERE eg.dbsts = :dbsts " +"AND eg.categoryId IN :categoryIds" ) 호출하면 해당 fetchjoin 쿼리 하나만 나갈꺼라고 생각을했는데,갑자기 상위 entity인 CategoryInformation 테이블에 category_id(PK)로 category_id 개수만큼의N+1 이슈가 발생 하였습니다. JsonBackReference, JsonManagedReference 해당 옵션을 주면 역참조가 안되어야 하는게 맞지 않나요?? 추가로 ManyToOne 쪽에 @JsonIgnore 도 추가 해보았으나 마찬가지입니다 ㅠ제가 놓치고 있는게 있을까요? 김영한 선생님 강의 에서는OrderItem-Item 관계에서 IN쿼리를 호출했을때 IN절에 포함되는 orderId기준의 상위 엔티티인 Order 테이블에 역으로 쿼리가 나가는건 못봤는데 말이죠..(물론, 양방향 관계를 끊고 단방향 설정시 N+1 쿼리는 나가지 않습니다.)
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
파라미터 받는 방법에 대하여
컨트롤러의 메서드에 파라미터로 VO나 도메인 객체의 경우ex) Item item만 적더라도 @ModelAttribute가 자동으로 붙기떄문에 생략해도 된다고 이해하였는데요 String이나 Integer의 경우 생략시 @RequestParam이 자동으로 붙으나 Map같은 경우에는 왜 @RequestParam을 강제로 붙여야지만 나오는지 혹시 아실까요?..
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
어떻게 생성자 없이 number1과 number2에 값이 할당된건지 모르겠습니다.(DTO관련)
안녕하세요 강사님먼저 첫번째 질문은 DTO (CalculatorMultiplyRequest request)를 매개변수로 받는 데, 자동으로 request 인스턴스가 생성되는 것이 이해가 가지 않습니다. 클래스는 생성자를 호출하기 위해서는 new키워드로 생성해야 하는 것으로 알고있는데, 이해가 잘 되지 않습니다.두번째 질문은 GET요청과는 다르게 POST요청은 Request 클래스 내부에 생성자 없이도 number1과 number2에 값이 할당되는 점입니다.어떻게 이게 가능할 수 있죠..?
-
미해결스프링 핵심 원리 - 기본편
[섹션 7 - 옵션 처리] 전체 테스트 중 CoreApplicationTests 클래스의 contextLoads 테스트 실패 질문입니다.
안녕하세요.게시판을 둘러보니 비슷한 오류가 나시는 분들이 계신것 같은데 해결 되신 분이 없는 것 같아 질문드립니다.개발 환경Spring Boot : 3.2.0운영체제 : Mac OS XIDE : IntelliJ IDEA Ultimate 2023.2.5JDK : JDK 17빌드 툴 : Gradle 8.4문제강의를 따라가던 도중 전체 테스트를 진행하는 과정에서 CoreApplicationTests 클래스의 contextLoads 테스트가 NoUniqueBeanDefinitionException 오류를 발생시키며 실패합니다.org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.member.MemberRepository' available: expected single matching bean but found 2: memoryMemberRepository,memberRepository특이한점으로 GitHub에 올려놓은 코드를 내려받은 후 실행하면 테스트가 통과하고, 지금까지 했던 프로젝트를 실행하면 테스트가 실패합니다.테스트가 성공한 프로젝트도 아래처럼 컨텍스트를 주입받아 MemberRepository를 getBean으로 받아오는 테스트를 해보면 오류가 납니다.@SpringBootTest class CoreApplicationTests { @Autowired ApplicationContext ac; @Test void contextLoads() { MemberRepository bean = ac.getBean(MemberRepository.class); } }org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.member.MemberRepository' available: expected single matching bean but found 2: memoryMemberRepository,memberRepository로그를 살펴보면 아래와 같습니다.expected single matching bean but found 2: memoryMemberRepository,memberRepository컴포넌트 스캔으로 등록한 빈과, AppConfig를 통해 등록한 빈이 겹치는 것 같습니다.아래는 유추한 내용입니다.컴포넌트 스캔이름을 변경해서 확인해봤습니다.다시 테스트를 돌려보면 로그가 아래처럼 찍힙니다.expected single matching bean but found 2: 메모리멤버레포지토리,memberRepository@Bean이름을 변경해서 확인해봤습니다.테스트를 돌려보면 로그가 아래처럼 찍힙니다.expected single matching bean but found 2: 메모리멤버레포지토리,앱콘피그에있는메모리레포지토리컴포넌트 스캔을 이용하여 MemberRepository 빈을 등록했는데 AppConfig 에서 @Bean 어노테이션이 붙은 메서드의 반환 객체도 빈으로 중복 등록 되어 발생한 것으로 생각됩니다.의문점AutoAppConfig 에서 Configuration 어노테이션이 붙은 클래스는 스캔의 대상에서 제외를 했는데 왜 중복해서 등록이 된 것일까요?검증을 위해 스프링 부트 통합 테스트를 진행해보았더니 AppConfig 가 빈으로 등록되어 있습니다.@SpringBootTest class CoreApplicationTests { @Autowired ApplicationContext ac; @Test void contextLoads() { AppConfig bean = ac.getBean(AppConfig.class); System.out.println(bean); } }hello.core.AppConfig$$SpringCGLIB$$0@37df14d1AppConfig 클래스의 코드입니다.@Configuration public class AppConfig { @Bean public MemberService memberService() { System.out.println("Call - AppConfig.memberService"); return new MemberServiceImpl(memberRepository()); } @Bean public MemberRepository memberRepository() { System.out.println("Call - AppConfig.memberRepository"); return new MemoryMemberRepository(); } @Bean public OrderService orderService() { System.out.println("Call - AppConfig.orderService"); return new OrderServiceImpl(memberRepository(), discountPolicy()); } @Bean public DiscountPolicy discountPolicy() { // return new FixDiscountPolicy(); return new RateDiscountPolicy(); } }(+) 컴포넌트 스캔을 CoreApplication 으로 옮겨도 똑같이 오류가 발생합니다.추가 질문만약 위 의문이 해결되어 AppConfig 에서 생성한 객체들이 빈으로 등록되지 않고, 컴포넌트 스캔을 통하여 빈을 등록한다면 MemoryMemberRepository 는 memoryMemberRepository 이름으로 빈으로 등록됩니다. 그렇다면 MemberServiceImpl 에서는 memberRepository 를 주입받아야 하는데, 빈의 이름이 달라 주입이 불가능할 것 같습니다. 이 경우에는 @Component("memberReository") 로 수정해줘야 할까요? 감사합니다.
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
@PathVariable 변수명 같을때 생략시 오류 (빌드 설정을 gradle로 하면 해결되는 것 같습니다)
/** * PathVariable 사용 * 변수명이 같으면 생략 가능 * @PathVariable("userId") userId -> @PathVariable String userId */ @GetMapping("/mapping/{userId}") public String mappingPath(@PathVariable ("userId") String data){ log.info("mappingPath userId={}",data); return "ok"; } 다음 코드에서 영상에서 알려주신데로 변수명 중복시 생략하였을때 @GetMapping("/mapping/{userId}") public String mappingPath(@PathVariable String userId){ log.info("mappingPath userId={}",userId); return "ok"; }아래와 같이 사용하였으니 실행시에 500에러가 뜹니다{ "timestamp": "2023-11-29T03:47:55.458+00:00", "status": 500, "error": "Internal Server Error", "path": "/mapping/userA" }java.lang.IllegalArgumentException: Name for argument of type [java.lang.String] not specified, and parameter name information not found in class file either.
-
해결됨스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
@PathVariable name 생략 질문 드립니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.@GetMapping("/{itemId}")public String item(@PathVariable("itemId") Long itemId, Model model) { Item item = itemRepository.findById(itemId);model.addAttribute("item", item);return "basic/item";}제가 알기론 Mapping 의 경로와 변수명이 같다면 name을 생략 가능한걸로 알아 원래 예제에선 PathVariable 뒤에 ("itemId") 를 생략하여도 잘 작동하지만, 따라해보니 name을 생략하면 아래와 같은 오류가 발생합니다.java.lang.IllegalArgumentException: Name for argument of type [java.lang.Long] not specified, and parameter name information not found in class file either왜 이런걸까요>?
-
미해결스프링 핵심 원리 - 기본편
static관련 질문드립니다.
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]"static method에 @Bean을 사용하게 되면 싱글톤 보장을 위한 지원을 받지 못한다" 그 이유에 대해서 자세히 알 수 있을까요? 비슷한 질문글들을 봐도 이유에 대한 정확한 설명은 없는 것 같아서요. 제가 찾아본 바로는 bean의 라이프사이클관리, 의존성 주입, 프록시 등등 있는데 무엇이 정확한 이유인지 잘 모르겠어서 질문남깁니다!!
-
해결됨실전! 스프링 데이터 JPA
flush() 와 clear()
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]1. 스프링 데이터 JPA 가 지원하는 save를 호출하면 flush()가 나가는건가요 아니면 save를 하고 bulkAgePlus()를 호출하면서 bulkAgePlus() 에 있는 JQPL이 실행되서 flush() 가 나가는건가요 ? bulkAgePlus()를 실행하면 JPQL 실행이되고, JPQL 이 실행이 되면 flush() 가 호출이 되는데 이때 SQL쿼리문이 DB에 전송이대서 DB에는 업데이트가 되어있지만 영속성 컨텍스트는 업데이트가 안되기 때문에 clear()를 해주는게 맞는건가요 ?
-
미해결스프링부트 시큐리티 & JWT 강의
Spring Boot 최신 버전(3.1.5)에 대하여..
질문은 아니지만 최근에 이 강의를 들으시는 분들에게 조금이나마 팁이 될까 적어봅니다.강사님께서 강의하신 3년전에는 2.3.* 버전이고,강의자료 github version 3에서도 2.7.* 버전이라 최신 버전인 3.1.*에는 안맞는 것들이 조금은 많았습니다.대부분 Spring Boot 3.*대로 업데이트 되면서 많은게 바뀌었더라고요. 그래서 작업하면서 최신 버전에서는 이렇게 하면 오류가 해결되는구나에 대해서 기억나는대로 설명해드리고자 합니다.<강사님 github Version3 SecurityConfig.java에서 filterChain 발췌>@Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .httpBasic().disable() .apply(new MyCustomDsl()) // 커스텀 필터 등록 .and() .authorizeRequests() .antMatchers("/api/v1/user/**") .access("hasRole('USER') or hasRole('MANAGER') or hasRole('ADMIN')") .antMatchers("/api/v1/manager/**") .access("hasRole('MANAGER') or hasRole('ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ADMIN')") .anyRequest().permitAll() .and().build(); } 람다 표현식 사용 권장httpServlet 오브젝트에 처음 적용시키는 csrf부터 빨간줄이 떴습니다. 설명줄을 확인해보니 스프링 시큐리티 6.1 버전부터 deprecated되었다고 하네요.자동완성을 확인해보니 밑에 있던 기존 csrf는 밑줄이 그어져있고 대신 안에 파라미터를 넣어줘야 한다고 되어있네요. 이런식으로 파라미터를 요구하는 식에는 모두 람다식 표현을 사용했습니다. http.csrf(cs-> cs.disable()) .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()) .apply(new MyCustomDs1())람다식 표현은 매개변수->{매개변수 표현식} 으로 표현할 수가 있습니다. 자세한 내용은 구글링 하시면 잘 나오실 겁니다. .and() Method 삭제http의 csrf, sessionManagement, formLogin, httpBasic을 disable로 하고 .and()로 한번 끊고 나서 다음 설정을 하는 구문입니다.자동완성을 확인해보니 and() 메소드는 완전히 삭제가 된 모양이군요.and()의 역할은 다중 보안 설정 시에 사용하는 메소드입니다. SecurityConfig.java 파일에서는 처음으로 보안 설정을 한 후에 권한 설정을 하는 방식으로 진행되었습니다. 이에 따라 and() 구문으로 보안설정과 권한설정을 나누었으나, and() 메소드가 삭제됨에 따라 나누는 방법에 대해서 많은 고민을 했던 것 같습니다. <수정 내용>인프런 AI 인턴으로부터 받은 답변의 내용을 살펴보면apply() 메소드 뒤에 메소드 체이닝으로 붙여서 람다 표현식으로 하면 권한이 생성된다고 되어있습니다.한 번 해보시죠.빨간색 줄을 보면 authorizeHttpRequests 메소드가 'MyCustomDs1' 이라는 커스텀 필터 클래스의 메소드라고 인식하고, MyCustomDs1 필터 내에 authorizeHttpRequests라는 메소드가 존재하지 않음으로 오류를 띄워주는 것이라고 할 수 있겠습니다. 이에 대해, 제 방식이 정답인지는 모르겠으나 이런식으로 해결했습니다. http.csrf(cs-> cs.disable()) // 보안 설정 .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()) .apply(new MyCustomDs1()); http.authorizeHttpRequests(authorize-> { // 권한 부여 // authorizeRequests가 deprecated됨에 따라 authorizeHttpRequests 사용 권장 ... // /user, /manager, /admin으로 들어가도 /loginForm으로 접근하도록 return http.build();① 보안 설정에 대한 내용을 HttpServlet 오브젝트인 http에 한번 추가를 시키고,② 메소드 체이닝을 끊어낸 다음,③ 권한 부여에 대한 내용을 추가했습니다.권한 부여 방식① authorizeRequests deprecatedauthorizeRequests 메소드도 설명란을 보면 어노테이션으로 Deprecated가 걸린 것을 볼 수 있습니다.자동완성으로 확인을 해보자면authorizeHttpRequests() 메소드를 람다식 표현으로 쓰라고 되어 있네요.② antMatchers deprecated antMatchers는 흔적도 없이 사라졌나 봅니다..requestMatchers() 메소드를 사용하시면 되겠습니다.③ hasAnyRole() 내의 parameter format 변경기존에는 hasAnyRole() 파라미터로 ROLE_USER, ROLE_ADMIN 이런식으로 앞에 ROLE_을 붙여서 권한을 부여했지만, Spring Security가 업데이트 되면서 hasAnyRole 메소드에서 권한을 부여할 때 각 role마다 앞에 자동으로 'ROLE_'을 붙여줍니다. 즉, 기존 방식대로 사용하다 보면 httpServlet 입장에서는 'ROLE_ROLE_USER', 'ROLE_ROLE_ADMIN' 이런식으로 인식하게 되어서 권한을 부여받지 못하는 부분이 있었습니다. 대략 이렇게 정리를 마치겠습니다.Spring같은 Framework의 큰 장점이자 단점은 업데이트가 수시로 된다는 점인데요.업데이트가 되면서 사용하는 데에 조금 더 편안해지겠지만, 이에 대해 인지하지 못한다면 사용할 수 없다는 점이 아닐까 생각됩니다.긴 글 읽어주셔서 감사하고, 저와 이 글을 읽으신 모든 분들의 코딩 실력이 한 발자국 더 앞설 수 있기를 기도하겠습니다.제가 작업한 프로젝트도 github에 올려놨으니 확인이 필요하시다면 한번씩 방문해주세요~http://github.com/msun0215/jwt.git 오늘의 결론 ① Spring 공식 홈페이지 업데이트 될때마다 찾아가서 확인해보자② 영어 공부 열심히 하자
-
해결됨스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
webDataBinder
ArgumentResolver가 파라미터를 만들 때 WebDataBinder에 존재하는 validator를 순차적으로 찾아서 적용시키는 건가요??
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
CRUD에 따른 ResponseDto를 보내는 유형 및 ResponseDto 구성 방식에 대한 질문
안녕하십니까 영한님.영한님 덕분에 JAVA Spring 기반 백엔드 개발의시작을 할 수 있었고,이제는 5개월 차 스타트업 백엔드 개발자로써 커리어를 시작하게 되었습니다. 현업에 와서는 오히려 취준생 때 보다 더 많은 고민을 하게 되느데요, 질문 1. 그중 최근에 CRUD의 각 상황별 API 응답을 어떻게 보내는게 적절할지, 질문 2. 그리고 ResponseDto를 어떻게 구성하는것이 적절할지에 대한 고민을 팀장님과 함께 하고있어서, 영한님의 생각을 여쭙고자 질문글을 올리게 되었습니다.답변 해주시면 정말 감사할 거 같습니다! Q1. 생성, 조회 , 수정, 삭제 API의 응답을 각각 어떻게 보내시는지 여쭤보고 싶습니다. 가장 먼저 조회의 경우는 말 그대로 path의 Entity 및 관련된 Entity 정보를 조합하여 응답 DTO로 변환하여 보내고 있습니다. 그런데 나머지 Write Operation에 대한 응답을 어디까지 보내야 하냐가 이슈 입니다.예를들면 엔티티의 생성의 경우 엔티티의 응답 DTO를 보내면 - 프론트에서 별도의 조회 API 호출 없이 바로 프론트가 화면에 뿌려줄 수 있으니깐 저는 생성의 경우에도 조회와 마찬가지로 Entity의 정보를 조합하여 응답 DTO로 변환하여 보내고 있었습니다. 그런데 이러한 부분이 Command Query Sperate 원칙에 어긋나는것 같아, 영한님께서는 혹시 생성한 Entity의 Key만 보내시는지, 아니면 Entity의 정보를 DTO로 변환하여 보내시는지 궁금합니다. 만약 Entity의 Id만 보내신다면 이후에 별도로 조회API를 호출해야 하고 그 또한 비용일텐데 이러한 부분은 어떻게 하시는지 여쭤보고 싶습니다. 이제 수정 API인데요,제가 다룬 비즈니스 로직의 경우 수정 비즈니스 로직이 다양하고 , 각 비즈니스 로직의 경우 다뤄지는 Entity의 종류가 다른 경우였습니다. (중심 Entity는 동일하지만, 연관된 Entity를 누구까지 건드리냐의 차이) 그래서 하나의 통일된 응답으로 보내기 모호한 점이 첫 번째 이유이고,애초에 수정 후에 프론트 화면에서 그 엔티티의 정보를 보여줄 필요가 없어서 라는 두번째 이유에 의해서 에초에 엔티티의 Id값도 보내지 않고 있었는데요,이 수정 API의 응답을 영한님은 어떻게 진행하지는지 그 이유가 궁금합니다. 마지막으로 삭제의 경우는 정말, 프론트에게 보낼 응답이 없어도 되는 경우 라고 생각했는데요,팀장님의 의견은 만약에 나중에 삭제한 Entity를 복구하는 요구사항이 추가되는것을 고려하여Id 정도는 넘기자는 의견을 내어주셨습니다.마찬가지로 삭제의 경우도 어떤식으로 수행하시는지 그 이유가 궁금합니다. Q3. 마지막으로 Entity의 ResponseDto의 필드를 어떤식으로 구성하시는지 궁금합니다 예를들면 저의 경우는 API는 프론트와 서버 간의 스펙이라고 생각하고, Entity의 단건조회의 경우는 단건 조회용 ResponseDto를, 전체조회의 경우는 전체 조회용 SummaryResponseDto를 별도로 만들어서 사용하고 있었습니다. 저희 팀장님 께서는 프론트쪽도 일을 해오시다가 , 백엔드쪽 분야로 전향하신 케이스 인데요,그렇다 보니 어떻게 해야 프론트의 생산성이 올라가는지를 고려하시는 분이셨고,팀장님의 생각은 서버에서 넘겨주는 응답에 일관성이 있어야 그 응답을 사용하는 프론트 측도 학습이 되고 놓치는 부분 없이 생산성이 올라간다는 의견이셨습니다. 그래서 Entity별로 당장 사용하지 않더라도 가능한 모든 필드를 담은 ResponseDto를 하나만 만들고,해당 ResponseEntity의 조합으로 각 API별 응답 Dto를 만들어서 사용하면 ,프론트 측 에서는 일관성 있는 응답값을 사용할 수 있다는 의견이셨습니다.물론 이 방법이 네트워크 패킷의 양을 쓸데없이 증가시킨다는 것을 알고 계시면서도,생산성에 큰 영향을 미치는 부분이라고 생각하셨습니다. 예를들어 다음과 같이 각 Entity의 응답 Dto의 조합별로 API의 ResponseDto를 만들 수 있습니다.ResponseDto{ UserDto{id : 1,name : “aaa”… // User엔티티의 거의 모든 필드} ItemDto{ id : 2,name : “bbb”,… // Item엔티티의 거의 모든 필드}} 저는 이러한 부분에 대해 생각해 본 적이 없이,그냥 제가 “해당 API를 호출하는 화면에서 필요한 정보들만을 담아 (혹은 여러 화면에서 쓰인다면 여러개를 고려) ResponseDto를 각각 만들어서” 넘겼는데요 영한님께서는 이러한 ResponseDto를 구성하는 부분에 있어서상황별로 필드를 재구성 하여 ResponseDto를 정의하여 사용하시는 편 인지 (SummaryResponseDto 등의 별도 Dto에 사용될 Entity의 필드들을 풀어서 정의하시는지)아니면 생산성을 고려하여 각 Entity별 Dto를 만들고, 이들을 조합하여 ReponseDto를 정의하시는 편 이신지 ,혹은 다른 규칙이 있으신지 궁금합니다. 물론 그렇다고 , 팀장님의 의견에서 전체조회시 사용하는 DTO와 단건조회시 사용하는 DTO가 동일하더라도,전체조회 후 단건조회를 할때 단건조회 API를 호출하지 말고 기존 Front가 가지고 있는 값을 쓰지는 말자는 의견 이십니다 (단건조회API는 별도로 호출해야 한다)그저 핵심은 프론트가 다루는 ResponseDto의 일관성을 위해서 입니다 (결론은 생산성을 위해) 긴글 읽어주셔서 감사합니다.항상 건강하셨으면 좋겠고,다음 강의들도 손꼽아 기다리고 있습니다!
-
해결됨스프링 DB 2편 - 데이터 접근 활용 기술
조회 및 읽기 트랜잭션 적용 문의
안녕하세요 트랜잭션 옵션 소개 강의를 시청 중에 궁금한 점이 있어 질문 드립니다. 데이터베이스에서 조회 및 읽기만 수행을 할 때 데이터의 변경이 없어 트랜잭션이 필요하지 않다고 생각하는데 굳이 트랜잭션을 거는 이유가 있을까요?? 보통은 클래스 레벨에 트랜잭션을 걸게되어 안에 있는 여러 메서드에도 트랜잭션이 자동으로 걸리게 되어 조회 혹은 읽기 메서드에도 트랜잭션을 의도하지 않게 걸리게 되는 것 일까요?
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
MemberForm은 이미 모델에 들어가 있는거죠?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]검증오류가 날 시 BindingResult에 의해 들어가는 게 아니라,저렇게 인자로 받는 건 이미 모델에 들어가 주는거죠?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
정적 팩토리 메서드에 static이 붙어야 하는 이유
안녕하세요. 강의 잘 보고 있습니다! 다른 분 질문에 궁금한 것이 해소되지 않아 질문드립니다.https://www.inflearn.com/course/lecture?courseSlug=%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1&unitId=24297&category=questionDetail&q=30892&tab=community정적 팩토리 메소드 사용 이유중에 static 메모리에 올라가기때문에 새로운 객체를 생성하지 않는 장점이 있는 거라고 알고 있습니다. 이 예제의 경우 static을 빼도 JPA가 엔티티로 관리하면서 어차피 사용할 수 있는 부분아닌가요..? 라는 질문에서 강사님께서는 static을 빼보면 이해될 거라고 답변하셨습니다.제가 생각하기로는 정적 팩토리 메서드 안에서 생성자를 통해 인스턴스를 생성하는 것은 똑같아 보이고, 호출할 때 new가 아닌 Order.createOrder()로 호출하는 것 외에는 차이점을 못 느꼈습니다.조금 더 상세한 가르침을 주시면 감사하겠습니다!
-
미해결스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
세션아이디에 대해 질문드립니다..
HttpSession session = request.getSession();을 하게되면 쿠키가 생성되고, 쿠키의 이름은 JSESSIONID, 쿠키의 값은 랜덤값이라고 이해했습니다. 하지만 이전 강의를 보면 쿠키의 값을 세션id와 동일하게 설정을하고, 쿠키의 값을 통해 세션저장소에서 세션을 조회했는데요. 하지만 코드에서는 session.setAttribute(SessionConst.LOGIN_MEMEMBER, loginMember)로 상수를 세션id(key)로 지정했는데 이렇게 하면 SessionConst.LOGIN_MEMEMBER를 통해 세션저장소에서 세션을 조회해야하지않나요? 쿠키의 value와 세션저장소의 세션id가 동일하지않은데 어떻게 조회할수 있는지 궁금합니다..
-
미해결스프링 핵심 원리 - 고급편
@target 관련
안녕하세요 강의 잘 듣고 있습니다.강의 말미에 args, @args, @target은 단독으로 사용하면 안 된다고 하셨고 다음과 같은 이유를 말씀하셨습니다. '실제 객체 인스턴스가 생성되고 런타임에 적용 여부를 알 수 있다'아규먼트의 실제 타입을 요구하는 args, @args는 비교적 직관적으로 이해했는데, @target은 어째서 런타임에 적용 여부를 알아야만 하는 걸까요? 마치 @within이 그런 것처럼 클래스 내부에 특정 어노테이션이 있으면 해당 클래스부터 부모 클래스까지 AOP를 적용하면 될 것 같은데 말이죠.
-
해결됨스프링 핵심 원리 - 고급편
포인트컷 표현식 질문
안녕하세요 강의 잘 듣고있습니다.강의에서 등장하는 AtTargetAtWithinAspect의 execution 표현식을 보면 * hello.aop..*(..)로 기재되어 있습니다.이전 강의에서 학습했던 표현식(* hello.aop..*.*(..))과 어떤 차이점이 있는건가요?
-
미해결스프링 시큐리티 OAuth2
Naver Login시 권한에 대해 질문이 있습니다
CustomAuthorityMapper 클래스에서강사님과 코드는 동일하나authorities가 넘어올시에 위와 같이 넘어옵니다.그래서 강사님 영상과 다르게ROLE_USER만이 authority에 대해서 담기게 되고위의 사진은 hasRole 권한을 해제하여 찍어본것입니다. /api/user에 진입이 불가합니다 코드는 동일한데 뭐가 문제일까요,,,? 강사님의 네이버 로그인시는 response를 안벗기고도 진입이 가능해서 질문드립니다 package io.security.oauth2.sociallogin; import lombok.RequiredArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import java.util.Collection; import java.util.HashSet; import java.util.Set; public class CustomAuthorityMapper implements GrantedAuthoritiesMapper { private String prefix = "ROLE_"; @Override public Set<GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) { HashSet<GrantedAuthority> mapped = new HashSet<>(authorities.size()); for (GrantedAuthority authority : authorities) { mapped.add(mapAuthority(authority.getAuthority())); } return mapped; } private GrantedAuthority mapAuthority(String name) { if(name.lastIndexOf(".") > 0){ int index = name.lastIndexOf("."); name = "SCOPE_" + name.substring(index+1); } if (!this.prefix.isEmpty() && !name.startsWith(this.prefix)) { name = this.prefix + name; } return new SimpleGrantedAuthority(name); } }
-
해결됨스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
ResponseStatusException
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]ResponseStatusException 예외에서 3번째 파라미터에 illeagalArgumentException 예외를 넣어주셧는데 그말은 IlleaglArgumentException 이 컨트롤러에서 터지면 500 에러인데 ResponseStatusException 에 담아줬기 떄문에 404 에러로 바뀌는건가요 ?
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
일대다 패치 조인 사용 시, 뻥튀기가 되지 않는 문제
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? 예[질문 내용]아래 처럼 print를 찍었을 때, 저는 id 1과 2가 두 개가 나오지 않습니다. 관련 코드는 아래 첨부하겠습니다. 강의대로 똑같이 했는데, 왜 1개씩만 나오는지 모르겠습니다.. distinct를 넣었을 때와 안넣었을때의 결과가 같습니다. DB에서는 4개로 나오는데 Postman이랑 콘솔에서는 2개로 난오네요..[v3 적용 시] package jpabook.jpashop2.api;import jpabook.jpashop2.domain.Address;import jpabook.jpashop2.domain.Order;import jpabook.jpashop2.domain.OrderItem;import jpabook.jpashop2.domain.OrderStatus;import jpabook.jpashop2.repository.OrderRepository;import jpabook.jpashop2.repository.OrderSearch;import lombok.Data;import lombok.Getter;import lombok.RequiredArgsConstructor;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;import static java.util.stream.Collectors.toList;@RestController@RequiredArgsConstructorpublic class OrderApiController {private final OrderRepository orderRepository;@GetMapping("/api/v1/orders")public List<Order> ordersV1(){List<Order> all = orderRepository.findAllByString(new OrderSearch());for (Order order : all) {order.getMember().getName(); // 프록시 객체 초기화order.getDelivery().getAddress();List<OrderItem> orderItems = order.getOrderItems();orderItems.stream().forEach(o -> o.getItem().getName());}return all;}@GetMapping("/api/v2/orders")public List<OrderDto> ordersV2(){List<Order> orders = orderRepository.findAllByString(new OrderSearch());List<OrderDto> collect = orders.stream().map(o -> new OrderDto(o)).collect(toList());return collect;}@GetMapping("/api/v3/orders")public List<OrderDto> ordersV3(){List<Order> orders = orderRepository.findAllWithItem();for (Order order : orders) {System.out.println("order ref= " + order + " id=" + order.getId());}List<OrderDto> collect = orders.stream().map(o -> new OrderDto(o)).collect(toList());return collect;}@Datastatic class OrderDto{private Long orderId;private String name;private LocalDateTime orderDate;private OrderStatus orderStatus;private Address address; // VO는 그대로 사용해도 되지만, (VO는 객체이기 때문에, 안에 값이 변하면 다른 객체로 판단)private List<OrderItemDto> orderItems; // 엔티티는 DTO로 다시 한번 래핑이 필요하다.public OrderDto(Order order) {orderId = order.getId();name = order.getMember().getName();orderDate = order.getOrderDate();orderStatus = order.getStatus();address = order.getDelivery().getAddress();orderItems = order.getOrderItems().stream().map(orderItem -> new OrderItemDto(orderItem)).collect(toList());}}@Datastatic class OrderItemDto { // OrderItem에서 필요한 값만 다시 추출private String itemName; // 상품명private int orderPrice; // 주문 가격private int count; // 주문 수량public OrderItemDto(OrderItem orderItem) {itemName = orderItem.getItem().getName();orderPrice = orderItem.getOrderPrice();count = orderItem.getCount();}}} public List<Order> findAllWithItem() {return em.createQuery("select o from Order o" +" join fetch o.member m" +" join fetch o.delivery d" +" join fetch o.orderItems oi" +" join fetch oi.item i", Order.class).getResultList();}