-
Notifications
You must be signed in to change notification settings - Fork 7
[Logan] Spring Data JPA 4~6단계 미션 #12
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
base: logan
Are you sure you want to change the base?
Changes from 7 commits
db45fad
d5566d2
cdffb4f
11b44d1
c6b8613
c9d4f78
96b1a20
1ae7379
1215822
5de5b63
15a68e8
9b8ad45
3ba6966
3dbf00f
901bd5e
9409618
da5e586
6ee806a
b058fd2
bddf51d
4dc79da
8fc5bc8
25e7439
e25c043
44e36f0
8ab2938
b487f68
e65be6d
d8dd999
ad56b75
d0b4b25
05739f8
df7a4d0
335cc99
e064a8d
ea79c7f
4c3e11e
75b32e6
b4c2c18
18dd7a8
f2e6f8b
b1e72b8
f3b1434
ed6767a
01ea224
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,33 @@ | ||
| package com.yourssu.roomescape.auth; | ||
|
|
||
| import com.yourssu.roomescape.auth.dto.LoginRequest; | ||
| import com.yourssu.roomescape.exception.CustomException; | ||
| import com.yourssu.roomescape.exception.ErrorCode; | ||
| import com.yourssu.roomescape.jwt.TokenProvider; | ||
| import com.yourssu.roomescape.member.Member; | ||
| import com.yourssu.roomescape.member.MemberDao; | ||
| import com.yourssu.roomescape.member.MemberRepository; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class AuthService { | ||
|
|
||
| private final MemberDao memberDao; | ||
| private final MemberRepository memberRepository; | ||
| private final TokenProvider tokenProvider; | ||
|
|
||
| public AuthService(TokenProvider tokenProvider, MemberDao memberDao) { | ||
| public AuthService(TokenProvider tokenProvider, MemberRepository memberRepository) { | ||
| this.tokenProvider = tokenProvider; | ||
| this.memberDao = memberDao; | ||
| this.memberRepository = memberRepository; | ||
| } | ||
|
|
||
| public String login(LoginRequest loginRequest) { | ||
| Member member = memberDao.findByEmailAndPassword(loginRequest.email(), loginRequest.password()); | ||
| Member member = memberRepository.findByEmailAndPassword(loginRequest.email(), loginRequest.password()) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); | ||
| return tokenProvider.createToken(member.getEmail()); | ||
| } | ||
|
|
||
| public Member checkLogin(String token) { | ||
| String payload = tokenProvider.getPayload(token); | ||
| return memberDao.findByEmail(payload); | ||
| return memberRepository.findByEmail(payload) | ||
| .orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package com.yourssu.roomescape.auth; | ||
| package com.yourssu.roomescape.auth.dto; | ||
|
|
||
| public record CheckLoginResponse(String name) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| package com.yourssu.roomescape.auth; | ||
| package com.yourssu.roomescape.auth.dto; | ||
|
|
||
| public record LoginRequest(String email, String password) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,15 @@ | ||
| package com.yourssu.roomescape.member; | ||
|
|
||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
|
|
||
| @Entity | ||
| public class Member { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
| private String name; | ||
| private String email; | ||
|
|
@@ -21,6 +30,11 @@ public Member(String name, String email, String password, String role) { | |
| this.role = role; | ||
| } | ||
|
|
||
| public Member() { | ||
|
|
||
| } | ||
|
||
|
|
||
|
|
||
| public Long getId() { | ||
| return id; | ||
| } | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.yourssu.roomescape.member; | ||
|
|
||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface MemberRepository extends JpaRepository<Member, Long> { | ||
|
|
||
| Optional<Member> findByEmailAndPassword(String email, String password); | ||
| Optional<Member> findByEmail(String email); | ||
| Optional<Member> findByName(String name); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,19 @@ | ||
| package com.yourssu.roomescape.member; | ||
|
|
||
| import com.yourssu.roomescape.member.dto.MemberRequest; | ||
| import com.yourssu.roomescape.member.dto.MemberResponse; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| @Service | ||
| public class MemberService { | ||
| private MemberDao memberDao; | ||
| private final MemberRepository memberRepository; | ||
|
|
||
| public MemberService(MemberDao memberDao) { | ||
| this.memberDao = memberDao; | ||
| public MemberService(MemberRepository memberRepository) { | ||
| this.memberRepository = memberRepository; | ||
| } | ||
|
|
||
| public MemberResponse createMember(MemberRequest memberRequest) { | ||
| Member member = memberDao.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); | ||
| Member member = memberRepository.save(new Member(memberRequest.getName(), memberRequest.getEmail(), memberRequest.getPassword(), "USER")); | ||
| return new MemberResponse(member.getId(), member.getName(), member.getEmail()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,28 +1,48 @@ | ||
| package com.yourssu.roomescape.reservation; | ||
|
|
||
| import com.yourssu.roomescape.member.Member; | ||
| import com.yourssu.roomescape.theme.Theme; | ||
| import com.yourssu.roomescape.time.Time; | ||
| import jakarta.persistence.*; | ||
|
|
||
| @Entity | ||
| public class Reservation { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
| private String name; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "member_id") | ||
| private Member member; | ||
|
Comment on lines
+15
to
+17
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fetch 설정을 LAZY로 하신 이유가 궁금해요!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ManyToOne 관계는 기본이 EAGER로 설정되어 있지만, 항상 연관된 Member 객체를 즉시 사용할 필요는 없기 때문에 LAZY로 변경했습니다. 📚 참고자료
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 유은님 코드를 보면 응답 dto를 만들 때 결국 Time이나 Theme의 값이 필요하기 때문에 추가 쿼리가 발생하고 있어요
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reservation 객체를 리스트로 조회할 때 발생하던 추가 쿼리를 방지하기 위해, 1️⃣ fetch join vs
2️⃣ ReservationRepository 내 findByStatus()를 통해 비교 기본(fetch join 적용 X)Fetch Join 쿼리EntityGraph 쿼리3️⃣ 개인 의견 |
||
|
|
||
| private String date; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "time_id") | ||
| private Time time; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "theme_id") | ||
| private Theme theme; | ||
|
|
||
| public Reservation(Long id, String name, String date, Time time, Theme theme) { | ||
| @Enumerated(EnumType.STRING) | ||
| private ReservationStatus status; | ||
|
Comment on lines
+29
to
+30
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EnumType에는 무엇이 있나요? 그 중 STRING으로 설정하신 이유가 궁금해요!
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| public Reservation(Long id, Member member, String date, Time time, Theme theme) { | ||
| this.id = id; | ||
| this.name = name; | ||
| this.member = member; | ||
| this.date = date; | ||
| this.time = time; | ||
| this.theme = theme; | ||
| } | ||
|
|
||
| public Reservation(String name, String date, Time time, Theme theme) { | ||
| this.name = name; | ||
| public Reservation(Member member, String date, Time time, Theme theme, ReservationStatus status) { | ||
| this.member = member; | ||
| this.date = date; | ||
| this.time = time; | ||
| this.theme = theme; | ||
| this.status = status; | ||
| } | ||
|
|
||
| public Reservation() { | ||
|
|
@@ -33,8 +53,8 @@ public Long getId() { | |
| return id; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| public Member getMember() { | ||
| return member; | ||
| } | ||
|
|
||
| public String getDate() { | ||
|
|
@@ -48,4 +68,8 @@ public Time getTime() { | |
| public Theme getTheme() { | ||
| return theme; | ||
| } | ||
|
|
||
| public ReservationStatus getStatus() { | ||
| return status; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,14 +4,17 @@ | |
| import com.yourssu.roomescape.exception.CustomException; | ||
| import com.yourssu.roomescape.exception.ErrorCode; | ||
| import com.yourssu.roomescape.member.Member; | ||
| import com.yourssu.roomescape.theme.ThemeDao; | ||
| import com.yourssu.roomescape.reservation.dto.ReservationFindAllResponse; | ||
| import com.yourssu.roomescape.reservation.dto.ReservationSaveRequest; | ||
| import com.yourssu.roomescape.reservation.dto.ReservationSaveResponse; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| import java.net.URI; | ||
| import java.util.List; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/reservations") | ||
| public class ReservationController { | ||
|
|
||
| private final ReservationService reservationService; | ||
|
|
@@ -20,23 +23,23 @@ public ReservationController(ReservationService reservationService) { | |
| this.reservationService = reservationService; | ||
| } | ||
|
|
||
| @GetMapping("/reservations") | ||
| public List<ReservationResponse> list() { | ||
| return reservationService.findAll(); | ||
| @GetMapping("/mine") | ||
| public List<ReservationFindAllResponse> getMyReservations(@LoginMember Member member) { | ||
| return reservationService.getMyReservations(member); | ||
| } | ||
|
|
||
| @PostMapping("/reservations") | ||
| public ResponseEntity<ReservationResponse> create(@RequestBody ReservationRequest reservationRequest, @LoginMember Member member) { | ||
| if (reservationRequest.getDate() == null || reservationRequest.getTheme() == null || reservationRequest.getTime() == null) { | ||
| @PostMapping | ||
| public ResponseEntity<ReservationSaveResponse> create(@RequestBody ReservationSaveRequest reservationSaveRequest, @LoginMember Member member) { | ||
| if (reservationSaveRequest.date() == null || reservationSaveRequest.theme() == null || reservationSaveRequest.time() == null || reservationSaveRequest.status() == null) { | ||
|
||
| throw new CustomException(ErrorCode.INVALID_RESERVATION_REQUEST); | ||
| } | ||
|
|
||
| ReservationResponse reservation = reservationService.save(reservationRequest, member); | ||
| ReservationSaveResponse reservation = reservationService.save(reservationSaveRequest, member); | ||
|
|
||
| return ResponseEntity.created(URI.create("/reservations/" + reservation.getId())).body(reservation); | ||
| return ResponseEntity.created(URI.create("/reservations/" + reservation.id())).body(reservation); | ||
| } | ||
|
|
||
| @DeleteMapping("/reservations/{id}") | ||
| @DeleteMapping("/{id}") | ||
| public ResponseEntity delete(@PathVariable Long id) { | ||
| reservationService.deleteById(id); | ||
| return ResponseEntity.noContent().build(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
토큰을 만들 때 멤버의 email 정보로 만들고 있는데 , 지금 유은님 코드 상으로는 동일한 이메일로 두 개 이상의 Member가 생길 수 있어요. 이러면 어떻게 될까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 부분에서 token에서 추출한 payload(email)가 DB에 중복으로 존재할 경우,
findByEmail()이 2개 이상의 결과를 반환하면서 IncorrectResultSizeDataAccessException 예외가 발생합니다.
👉 이메일 중복 문제가 발생하지 않도록 Member 엔티티의 email 필드에 @column(unique = true)를 적용하여 DB 레벨에서 중복을 방지하고, 회원가입 로직에서는 existsByEmail을 통해 사전 중복 검사를 수행한 뒤, 중복 시 CustomException을 발생시키도록 보완했습니다.