Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8주차]Cow_mvc_practice(0711kc) #6

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open

Conversation

0711kc
Copy link

@0711kc 0711kc commented May 14, 2024

추가 Q & A

🤔 잘 모르겠거나, 더 궁금한 내용이 있었나요?

저는 @transactional이 중간에 예외가 발생한 경우 전체 작업을 취소하기 위해 사용하는 것으로 이해하고 있습니다. 그러면 읽기만 하는 경우에는 작업을 취소할 필요가 없을 것 같은데 사용하는 이유가 궁금합니다!

집중적으로 리뷰 받고싶은 것이 있나요?

COW - 2024-05-14 12_08_20

Postman API 링크
https://documenter.getpostman.com/view/34669193/2sA3QngtSU

@0711kc 0711kc requested review from Hoya324 and KoSeonJe as code owners May 14, 2024 14:37
@TaetaetaE01 TaetaetaE01 self-requested a review May 15, 2024 14:45
Copy link
Collaborator

@TaetaetaE01 TaetaetaE01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전반적으로 많이 찾아보고 하신 거 같습니다!! 조금 더 세부적인 거랑 comment 엔티티 관계 한번 더 고민해보시면 좋을 거 같습니다!!

import com.cow.cow_mvc_practice.member.service.MemberService;

import lombok.RequiredArgsConstructor;

@RestController
@Controller
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Controller 어노테이션으로 바꾼 이유가 있으신가요??

@@ -40,23 +41,32 @@ public class MemberController {

/* MemberResponse dto 적용 */
@PostMapping("/new")
public MemberResponse create(@RequestBody final MemberRequest memberRequest) {
public @ResponseBody MemberResponse create(@RequestBody final MemberRequest memberRequest) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RestController을 사용하게되면 ResponseBody를 포함하고 있어 각 메소드마다 안 붙혀도 적용됩니다!

@GetMapping()
public MemberResponse findMemberQuery(@RequestParam final Long memberId) {
public @ResponseBody MemberResponse findMemberQuery(@RequestParam("id") final Long memberId) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

같은 행위를 하는 두 개의 메소드가 보이는데 따로 이유가 있으신가요? 또 메소드 명이 의미하는 바가 무엇인가요?

return memberService.findOne(memberId);
}

@DeleteMapping("/{memberId}/delete")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteMaping을 통해 http 메소드 중 delete를 사용 중입니다. url에 한번 더 delete를 언급하는 것은 rest 스럽지 못 한 거 같습니다!

https://velog.io/@yewo2nn16/Spring-%EC%98%88%EC%A0%9C%EC%99%80-%ED%95%A8%EA%BB%98-REST-API-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

@@ -19,6 +18,6 @@ private MemberRequest(final String name) {
}

public static Member toEntity(String name) {
return Member.of(name);
return Member.from(name);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

builder를 이용해서 객체를 해당 클래스에서 리턴해주는 것이 의미있어 보입니다


private final PostService postService;

@PostMapping("/members/{memberId}/new")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

restful api url에 대한 고민해보세요!

return postService.findOne(postId);
}

@GetMapping()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url에 대한 고민이 필요합니다

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime date;

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lazy 키워드와 cascade 속성에 대해 알고 계신가요?

Copy link
Owner

@Hoya324 Hoya324 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다! 전반적으로 열심히 하셨다는 생각이 드는 코드라 정말 감사하다는 말씀드리고 싶어요!
다만 코드 컨벤션이 지켜지지 않은 부분이 있으니 해당 자료 참고하셔서 수정 해보시면 좋을거 같습니다!
코드 컨벤션 적용법

.build();
}

}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EOL 참고해주세요!

Comment on lines 49 to 54
public static Comment of(Long id, String content) {
return Comment.builder()
.id(id)
.content(content)
.build();
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment를 정적팩토리 메서드로 사용하신 점은 너무 잘하신거 같아요!! 하지만 id를 직접 받아서 Comment의 id에 삽입할 일이 필요한가? 를 고민해보셔야합니다!
만약 저 메서드가 있다면 같이 협업하는 개발자가 있다고 할 때 "어? id를 수정할 수 있고, 해도 괜찮은거구나?"라는 오해를 일으킬 수 있습니다!

@@ -39,24 +42,44 @@ public class MemberController {
//

/* MemberResponse dto 적용 */
@ResponseBody
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 공부하거나 정리할 때 이런식으로 분리해서 그냥 해본 경험이 있어요ㅎㅎㅎ 헷갈리기도 하고 @controller + @responsebody 를 한게 @RestController라는걸 자꾸 생각할 수 있으니 좋습니다!

다만 이게 익숙해지신다면 @RestController를 사용하시는걸 추천드려요! 만약 @controller를 쓰게 된다면 왜 쓰게 될지 고민해보시는 것도 좋습니다! (@controller 가 객체를 return하는 것이 아닌 뷰를 return한다는 차이를 두고 고민해보셔요!)

public class UpdateMemberRequest {

private String name;
final String name = null;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name default를 null로 설정한 이유가 궁금하고 private를 제거한 이유도 궁금합니다!
이러면 해당 값을 접근할 수 있지 않을까요?? 만약 요청된 값을 실수로 변경해버리면 어떨까요?

Comment on lines 17 to 25
private MemberResponse(final Long id, final String name, final List<String> posts) {
this.id = id;
this.name = name;
this.posts = posts;
}

public static MemberResponse of(final Member member) {
public static MemberResponse from(final Member member) {
List<String> posts = new ArrayList<>();
member.getPosts().forEach((p) -> posts.add(p.getTitle()));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Post를 title만 받아서 MemberResponse의 필드값으로 가지고 싶으셨던거 같아요!
그럼 필드값을 PostResponse와 같은 dto로 가지도록 변경해보시는걸 추천드립니다.

}

@Transactional(readOnly = true)
@Override
public MemberResponse findOne(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new EntityNotFoundException("[Error] 사용자를 찾을 수 없습니다."));
return MemberResponse.of(member);
return MemberResponse.from(member);
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 어떤 피드백인가요??

}

@PostMapping("/{memberId}/new")
public PostResponse create2(@PathVariable final Long memberId, @RequestBody final PostRequest postRequest) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create2는 어떤 의미인가요??

@0711kc 0711kc requested a review from 0702Yoon as a code owner May 18, 2024 11:51
Copy link
Collaborator

@KoSeonJe KoSeonJe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q. 저는 @transactional이 중간에 예외가 발생한 경우 전체 작업을 취소하기 위해 사용하는 것으로 이해하고 있습니다. 그러면 읽기만 하는 경우에는 작업을 취소할 필요가 없을 것 같은데 사용하는 이유가 궁금합니다!

A.
@transactional의 기능은 해당 메소드의 로직이 하나의 Transaction임을 명시하는 어노테이션입니다. 중간에 예외가 발생하면 전체 작업을 취소해주는 것은 Transaction의 기능이 맞습니다. 하지만 �조회만 하는 작업은 데이터를 변경할 일이 없기에 @transactional을 그대로 사용하는 것보다 @transactional(readOnly=true)로 조건을 변경해주는 것이 좋습니다. 원래 default 값은 readOnly =false 입니다.

조회만 하는데 왜 굳이 Transaction을 선언하는가? 사용하지 않아도 되는 것이 아닌가 생각하실 수 있습니다. 하지만 몇 가지 이점이 있습니다.

먼저, 데이터의 변경되는 것을 방지해줍니다.
이 부분을 설명하려면 JPA에 대해 공부가 필요할 것 같아서 간단히 설명하자면, readOnly = true 조건이 달려있는 메소드 내에서 데이터를 변경하려 시도하면 예외가 발생시켜줍니다.
자세히 설명하면 JPA의 쿼리(save, delete)를 사용하면 쿼리는 데이터베이스에 그대로 반영하는 것이 아닌 어떤 저장소에 저장되어있습니다. 트랜잭션 명령이 잘 수행된 후 커밋이 되면 자동으로 flush()라는 메소드를 호출하여 저장소에 있던 쿼리가 한꺼번에 DB에 반영됩니다. readOnly = true라는 조건은 자동으로 flush()메소드가 호출되는 것을 막기에 변경점을 DB에 반영하지 않습니다.
위와 같이 데이터의 일관성을 보장해줍니다.

이 뿐만 아니라, 격리성 레벨, 더티체킹 시도x, 스냅샷 보관x 등 다양한 성능 이점을 줄 수 있기에 사용합니다.

하지만 @transactional을 무조건 사용해야 한다는 것은 아닙니다. 예를 들어 JPA 메소드는 기본적으로 @transactional이 붙어있는데, save()와 같은 메소드 한줄이 있는데 이미 @transactional이 붙어있기에 Transactional을 걸어줄 필요가 없기 때문입니다.
더 자세한 여러가지 이유가 있어 궁금하다면 따로 공부해보시길 바랍니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants