@RestController @RequiredArgsConstructor public class MemberApiController { .. @PostMapping("/api/v1/members") public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) { Long id = memberService.join(member); return new CreateMemberResponse(id); } @Data static class CreateMemberResponse { private Long id; public CreateMemberResponse(Long id) { this.id = id; } } 일 때, memberService.join(member); 의 반환 값이 엔티티가 아니고 단순히 Long id인 값인데도 return id; 로 안하고 CreateMemberResponse 클래스를 만들어 return new CreateMemberResponse(id); 로 하는 이유는 뭔가요 ? 처음에는 Long id = memberService.join(member); return id; 로 했다가 누군가가 inline으로 합쳐버려서 return memberService.join(member); 으로 만들어 버리면 memberService.join의 반환 값이 수정될 때 컴파일이 안뜨고, api에서 스펙이 바뀌어 오류가 생길 가능성이 있어서 컴파일 단계에서 막아버릴려고 CreateMemberResponse 클래스를 만들어 return new CreateMemberResponse(id); 를 해주는 것일까 생각도 들었는데, inline으로 합쳐버리고 memberService.join의 반환값이 바뀌어도 어차피 @PostMapping("/api/v1/members") public Long saveMemberV1(@RequestBody @Valid Member member) { Long id = memberService.join(member); return id; } 인 상태에서 inline하는 거니까 public Long saveMemberV1(..) {..} 에서 return값이 Long타입이 아니게 바뀌면 컴파일 뜰 것 같아서 그것도 아닌가 싶기도 하고 .. 유지보수할 때, 추적하기 쉬우려고 그러는 건가 싶기도 하고 .. 갑자기 든 생각인데, 일반적으로는 단순히 id만 반환할 일이 없으니 일반적인 케이스를 생각해 만드신 건가 싶기도 하고.. 이 케이스만 예외적으로 Long으로 써도 가능한 건지 궁금합니다 :]
안녕하세요 강의를 보던 도중 의문이 생겨서 질문남깁니다. Layerd Architecture 에서 Controller, Service, Repository로 역할을 구분해서 레이어당 커플링을 줄이도록 하고 있는 알고 있습니다. 그런데 DTO 의 사용위치 (해당 DTO는 사용자 API)에 대해 궁금한 점이 있습니다. JPA에서 DTO로 조회하면 편하게 데이터를 가져 올수 있는데 해당 DTO가 서비스 혹은 Application Layer에 커플링이 생기는데 이럴 경우는 어떻게 해결해야 될까요..? DTO 자체를 Service DTO, 와 Controller DTO로 분리하여 컨버팅을 해주어야 되는지 아니면 Controller 에서 Repository 를 직접 사용해도 되는 예외상황을 고려해야될지가 고민입니다. 아니면 엔티티를 조회한후 필요한 부분만 DTO로 변환을 하는지 그것도 아니면 Object 객체로 조회한후 Object 안에서 데이터를 추출하는지 어떤 방법을 사용하시는지가 궁금합니다. 보통 이런 경우에는 어떻게 처리하는게 효율적인 방법일까요..? 시스템 개발을 하다가 서비스로 커리어 전환을 하고 있는데 김영한님 강의가 너무 재밌어서 계속 찾아보게 되네요 ㅎ
선생님 안녕하세요! 꾸준히 걸어가고있는 코린입니다.. 혹시 @Component @Transactional @RequiredArgsConstructor static class InitService { 이 부분에서 static이 아닌 private으로 봐꿔보면 종속성 순환에러에 걸리는데 static일때는 메모리를 공유하고 private일때는 공유 하지않아서 생기는 문제인가요?
안녕하세요, 먼저 좋은 강의 만들어주셔서 감사드립니다. 해당 강의 '프로필 수정 처리' 학습을 진행하던 중 궁금한 사항이 생겨 질문드립니다. 먼저 강의 내용에서 처럼 POST 방식으로 프로필 수정 요청을 하는 경우 @PostMapping("{URL}") 어노테이션이 설정된 메소드가 실행이 되면서 DB의 정보와 Account 객체가 업데이트 되고, 여기서 Account 객체는 세션에 담긴 객체이지만 '준영속성(detached)' 상태로 AccountService.updateProfile(Account accoutn, Profile profile) 메소드를 통해 Merge를 하는 과정을 확인할 수 있었습니다. 다만, 여기서 궁금한 점은 1. 프로필이 수정되기 전 세션에 설정된 Account 객체는 로그인을 통해 SecurityContext에 저장된 User(Principal, 인증된 사용자 정보)와 동일하겠지만, 프로필 수정 후 에는 세션의 Account 객체와 User는 서로 다른 상태가 되는게 맞을까요? 만약, 그렇다면 프로필 수정 후 User 정보도 Account 정보와 동일하게 맞추려면 어떤 방법이 있는지 궁금합니다. 2. [개인적인 질문] 만약, 관리자 페이지가 존재해서 사용자가 애플리케이션을 이용하고 있는 중에 관리자가 회원의 정보를 변경하는 경우 사용자는 자신의 정보가 변경이 되었는지 알 수 있는 방법이 있을까요? - 사용자 측면에서 세션의 Account는 계속해서 관리자가 수정하기 전의 자신의 정보만을 바라보고 있을 것 같다라는 생각이 들어서 질문을 남겨봅니다. 글이 길어 졌네요. 답변 미리 감사드립니다.
회원가입시 멤버 이름 중복으로 가입하면 IllegalStateException 창이 발생하는데 이때 Spring MVC 2편에서 배웠던 예외처리 방법을 통해 다시 홈이나 가입화면으로 넘겨주면 되는건가요? 실무에서는 보통 ID중복확인 같은 버튼을 만들어서 검증된 ID만 회원가입이 되게 하는데 , 그 부분은 백엔드개발자가 신경쓸 부분은 아니고 지금처럼 익셉션을 날리면 되는건지 궁급합니다
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://docs.google.com/document/d/1j0jcJ9EoXMGzwAA2H0b9TOvRtpwlxI5Dtn3sRtuXQas/edit#heading=h.w2tomwsznga7) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://docs.google.com/document/d/1xCQKit-1V6l6ObeCe49St33RHPzLF_P_c3o7aSDTKs0/edit#heading=h.7dhnp46ven0v) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 예 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예 [질문 내용] 여기에 질문 내용을 남겨주세요. 이전 강의 에서 Entity에서 Iteam을 Book , Movie, Ablum으로 상속하는 구조를 가지고 있습니다. 이럴 경우 querydsl로 조회 할 때 dtype을 where 절에서 사용할 수 있는지 궁금합니다. 만약 사용할 수 없다면 실무에 적용할 수 있는 다른 좋은 방법이 있는지 궁금합니다.
안녕하세요. 선생님! 회원가입, 로그인은 잘 되는데, AuthenticationHeaderFilter를 적용하고 127.0.0.1:8000/user-service/welcome 으로 get 호출하면 NoClassDefFoundError가 발생합니다. filter 코드도 github에 있는 거 그대로이고, application.yml에도 filter로 등록했는데 postman에서 응답을 받을 수 없다면서 Socket hang up 에러가 납니다. 그런데 get으로 저의ip:port/welcome 으로는 조회가 잘 되요..! 제가 h2를 선생님이 설정한 방식이 아닌 tcp 통신으로 진행하고 있었습니다. 이게 문제인 걸까요..? h2를 tcp로 연결 : ( jdbc:h2:tcp://localhost/~/user-service )
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] Order를 select할 때, 굳이 Member테이블과 조인하는 이유가 궁금합니다. 조인을 하지 않더라도, order를 조회할때, member 테이블로 따로 select문을 써서 값을 가져오고, 만약 fetchType을 LAZY로 할경우 필요할 때 Join쿼리를 추가적으로 날리는 것으로 알고 있습니다. 제가 생각했을 때는, 유일한 예외경우가 "Member가 null값인 Order가 조회되는 경우"인 것 같은데, 이 부분은 Order.createOrder 생성자에서 처리해줄 수 있을 것 같거든요. (혹은 Order의 member컬럼을 not null로 설정하거나) 이유가 궁금합니다!
Autowired 를 만약 Service에서 하게된다면 Service는 Repository를 DI해야하는것이 통상 개발하는 구조(?) 니까 자동으로 Spring은 Repository를 DI하는것같은 메소드(즉 Autowired) 된 것을 찾고 Autowired된 것들중에 Repository의 Class와 Service에서 Class가 일치하는것들 두개를 자동으로 주입시켜준다고 내부적 프로그래밍이 되어있다고 봐도 무방할까요?
안녕하세요! 야생형 커리큘럼 따라서 먼저 한번 듣고 두번째로 실전1 강의 듣고있습니다. 궁금한게 있어 질문드립니다. bootstrap같은 경우 프로젝트 내부 static 폴더에 css와 js 패키지를 다운로드 받아서 적용시키잖아요, 그런데 두 번째 시도에서 파일을 다운로드 받지도 않고 그냥 실행했는데 예쁘게 적용이 되어있는걸 확인할 수 있습니다. 아마 지난번에 만들었던 프로젝트가 영향을 준 것 같은데 어떻게 그럴수 있을까요? 혹시 캐싱 문제일까 해서 강력 새로고침을 해보기도 했는데 그 문제는 아닌 거 같습니다. 어떻게 이렇게 되는건지 혹시 가르쳐주실 수 있을까요? static 폴더가 비어있는 상태에서 localhost:8080 화면입니다!
안녕하세요. 강의 내용대로 CommentSummary를 클래스 기반 프로젝션으로 그대로 넣어서 했는데 아래와 같은 오류가 나오네요. No converter found capable of converting from type [me.whiteship.demospringdatacommonweb.post.Comment] to type [me.whiteship.demospringdatacommonweb.post.CommentSummary] org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [me.whiteship.demospringdatacommonweb.post.Comment] to type [me.whiteship.demospringdatacommonweb.post.CommentSummary] at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322) at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195) (중략) at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:56) at java.base/java.lang.Thread.run(Thread.java:829) CommentRepositoryTest > getComment() FAILED org.springframework.core.convert.ConverterNotFoundException at CommentRepositoryTest.java:38 사실 인터페이스 기반 프로젝션을 사용하면 될 문제이지만, 클래스 기반 프로젝션으로도 정상적으로 동작할거라 봤는데 동작을 안하네요. Spring 버전이 바뀌면서 클래스 기반 프로젝션을 사용할 수 없게 된걸까요?? 바쁘시겠지만 확인해주시면 감사하겠습니다.
안녕하세요, 강의잘보고있습니다. 여러가지 lombok 이나 다른 편의라이브러리를 사용하는것도 좋지만 아직은 구조잡기가 우선이라고 생각하여 최대한 필수구조를 잡아가며 짜는연습중인데 강의중에 Requirment... Annotation 을 사용하지않고 아래 방법대로 @Autowired 가 되어도 문제가없는지 질문드립니다. @Service @Transactional (readOnly = true ) public class OrderService { private final MemberRepository memberRepository ; private final OrderRepository orderRepository ; private final ItemRepository itemRepository ; @Autowired public OrderService (MemberRepository a , OrderRepository b , ItemRepository c) { this . memberRepository = a ; this . orderRepository = b ; this . itemRepository = c ; } }
안녕하세요 영한님 잘보고있습니다. 감사합니다 질문이 하나 있습니다. public List<MemberTeamDto> searchByBuilder(MemberSearchCondition condition){ BooleanBuilder builder = new BooleanBuilder(); if(StringUtils.hasText(condition.getUsername())){ builder.and(member.username.eq(condition.getUsername())); } if(StringUtils.hasText(condition.getTeamName())){ builder.and(team.name.eq(condition.getTeamName())); } if(condition.getAgeGoe() != null){ builder.and(member.age.goe(condition.getAgeGoe())); } if(condition.getAgeLoe() != null){ builder.and(member.age.loe(condition.getAgeLoe())); } return queryFactory .select(new QMemberTeamDto( member.id.as("memberId"), member.username, member.age, team.id.as("teamId"), team.name.as("teamName") )) .from(member) .leftJoin(member.team, team) .where(builder) .fetch(); } //페치조인 미 적용 테스트를 해보면 @Test public void searchTest(){ Team teamA = new Team("teamA"); Team teamB = new Team("teamB"); em.persist(teamA); em.persist(teamB); Member member1 = new Member("member1", 10, teamA); Member member2 = new Member("member2", 20, teamA); Member member3 = new Member("member3", 30, teamB); Member member4 = new Member("member4" ,40, teamB); em.persist(member1); em.persist(member2); em.persist(member3); em.persist(member4); MemberSearchCondition condition = new MemberSearchCondition(); condition.setAgeGoe(35); condition.setAgeLoe(40); condition.setTeamName("teamB"); List<MemberTeamDto> result = memberJpaRepository.searchByBuilder(condition); assertThat(result).extracting("username").containsExactly("member4"); } N+1 같은 현상이 일어나지 않습니다. 페치 조인을 사용하지 않았는데 이유가 있을까요 ?
안녕하세요! 언제나 좋은 강의 감사드립니다. 강의를 듣고 여러가지 테스트를 진행하던 중 궁금증이 생겨 질문남길게요. 테스트를 진행했던 코드들은 다음과 같습니다. 1. 엔티티를 detach 한 상태에서 커밋시 DB에 쿼리가 날리가지 않는다. (영속성 컨택스트가 비어있음으로) @Test @Rollback(value = false) void detachTest(){ Person person = new Person(); person.setId(1l); person.setName("test1"); em.persist(person); em.detach(person); } -> 예상대로 동작하였습니다. 2. clear() 를 통해 영속성 컨택스트를 비운 후 merge() 를 통해 다시 영속화하면 커밋시 반영이 될 것. @Test @Rollback(value = false) void detachTest(){ Person person = new Person(); person.setId(1l); person.setName("test1"); em.persist(person); em.clear(); em.merge(person); } -> 예상대로 동작하였습니다. 3. detach 를 통해 영속성컨택스트에서 엔티티를 준영속 상태로 변경한 후 merge() 하여 다시 영속화 하면 커밋시 반영이 될 것 @Test @Rollback(value = false) void detachTest(){ Person person = new Person(); person.setId(1l); person.setName("test1"); em.persist(person); em.detach(person); em.merge(person); } -> 제 생각과 달리 possible non-threadsafe access to session 에러가 떨어졌는데 그 이유를 곰곰히 생각해봤지만 가늠이 잘 가지 않아 질문드립니다.
개념이 헷갈려서 질문 드립니다. 1. Team의 List members를 사용하지 않고 Member에 있는 외래키를 이용해 팀의 멤버를 select하는 방법을 사용하면 안되나요? 2. 지금은 1:N 이지만 멤버도 여러 Team을 가질수 있는 N:N 을 한다고 가정하면 그 경우 1:N으로 나누어야해서 MemberTeam 같은 양 테이블의 기본키를 복합키로 가지고 있는 테이블이 필요하다고 알고 있습니다. 양방향 객체 관계를 가지면 위 테이블이 필요가 없어보이는데 중간 테이블을 만들고 select를 하는 것은 안되나요? 제가 이해를 잘못해서 그런지 굉장히 헷갈리네요
우선 관련 다른 댓글들은 읽고왔는데 더 궁금한 점이 있어 질문드립니다. Q1. baseEntity에서 abstract class를 사용하지 않고 실제 class를 사용하신 이유 softDelete 사용시 springdatajpa 레퍼런스 닥스에서는 엔티티에 delete flag를 만들어주고, @sql로 delete 실행을 delete flag를 true로 만들어주는 방식을 예제로 두고 있습니다. 그러면 baseEntity에서 - 필드 delete flag 선언 -> 전역 엔티티에 적용 - @sql 로 delete 실행 변경 -> 전역정용 - @where절 delete=false -> 전역 적용 이렇게 할 경우 장점으로는 cascade 옵션으로 delete// orphanRemoval에도 자동으로 delete 쿼리가 업데이트 쿼리로 변경되어 삭제 플레그의 연관관계를 보다 쉽게 처리할 수 있는거로 생각됩니다. 또한 필터? 옵션으로서 실제 플레그로 삭제된 엔티티도 조회 가능하도록 제공하는걸 확인했습니다. 관련 다른 댓글에서 답변해주신것을 보면 영한님은 전역 엔티티가 아닌 엔티티별로 메서드를 정의해주고 연관관계가 있을경우 직접 연관관계 메서드에서 해당 메서드를 사용하시는 것으로 추측됩니다. Q2. 그렇게 사용하시는 걸 선호하시는 이유를 알 수 있을까요? 개인적으로 전역으로 관리하는게 좋겠다 싶었던 이유는 사용자에 의해 기록된 모든 데이터가 기업의 입장에서는 엄청나게 큰 자산이고, 이것을 통해 추천 상품 알고리즘을 위한 데이터로 활용할수도 있고 (주문기록, 회원의 나이 등 정보, 관심상품 등등), 어떠한 비즈니스 정책들을 정하는데 큰 도움이 될거라 싶어 실무에서는 플래그로 삭제 하지 않을까? 해서 전역으로 관리 하는게 좋겠다 싶었습니다. 근데 전역으로 저렇게 처리할 경우, 고객 정보같은 민감한 데이터는 실제로 삭제를 해줘야 할것 같은데 (회원 탈퇴시 같은 경우) 회원과 연관관계가 있는 엔티티들이 엄청나게 엮어 있을때 이런 경우는 어떻게 처리해야 좋을지 감이 잡히지 않습니다. 그래서 추가 질문으로는 Q4. 실무에서 softDelete를 어떤 엔티티에 적용하는지 Q5. 회원은 민감한 정보라 실제 삭제를 할때 관련된 연관관계가 있는 엔티티는 어떻게 처리하는게 좋을지 예를들면 member에 null값으로 그냥 냅두는지 이런 것들이 궁금합니다. 답변주시면 정말정말 감사드리겠습니다. 쿼리dsl 강의 듣기전에 datajpa로 이것저것 토이프로젝트를 해보면서 고민해보는데 생각보다 적용함에 있어 고려할것들이 넘쳐나 질문을 많이 하게되네요