-
Notifications
You must be signed in to change notification settings - Fork 3
로그인 기능 구현 #3
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
로그인 기능 구현 #3
Changes from 41 commits
267d34c
9aded40
60b3ea6
0068a07
c2f58aa
b049194
bf4a443
62a50aa
202c1c5
4171948
b7a6fa4
7bf9a3c
3f4a4e8
cd16eac
6a2ed2e
9f144e9
e37677d
b4b1994
f7aadcb
220f688
ba4e971
9073e15
5dba48e
48f1ed3
31c4f55
a9ffff9
4134905
dd2f2ac
789346c
9e927f6
e92f3d4
9f1d15d
3220a02
716c695
b4750fd
2be08da
9fe5274
6b73e9e
54f7ecd
3222659
8903b10
033ff4f
6ee53df
eb5a18f
e53038a
ade2ea1
1863ac5
e020c30
081b988
d340ec5
61851c0
60ba393
b0143ca
7e7d75d
62ccae5
d86f2bc
be43658
777513c
968c682
56ac26a
6e2f6b8
440b7fe
6fdc792
27b564b
2b4281d
70341ff
5fe8be7
affab50
11f7510
51b24de
148058a
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 | ||
|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||
| package com.board.board.controller; | ||||
|
|
||||
| import com.board.board.domain.Article; | ||||
| import com.board.board.dto.ArticleCreateRequest; | ||||
| import com.board.board.dto.ArticleResponse; | ||||
| import com.board.board.dto.ArticleUpdateRequest; | ||||
| import com.board.board.service.BlogService; | ||||
| import jakarta.validation.Valid; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.springframework.data.domain.PageRequest; | ||||
| import org.springframework.data.domain.Pageable; | ||||
| import org.springframework.http.HttpStatus; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|
|
||||
| import java.util.List; | ||||
|
|
||||
| @RequiredArgsConstructor | ||||
| @RestController | ||||
| @RequestMapping("/articles") | ||||
| public class BlogApiController { | ||||
|
|
||||
| private final BlogService blogService; | ||||
|
|
||||
| @PostMapping("") | ||||
| public ResponseEntity<ArticleResponse> addArticle(@Valid @RequestBody ArticleCreateRequest request) { | ||||
| Article savedArticle = blogService.save(request); | ||||
|
|
||||
| return ResponseEntity.status(HttpStatus.CREATED) | ||||
| .body(new ArticleResponse(savedArticle)); | ||||
| } | ||||
|
|
||||
| @GetMapping("") | ||||
| public ResponseEntity<List<ArticleResponse>> findAllArticles(@RequestParam(defaultValue = "0") int page, | ||||
| @RequestParam(defaultValue = "10") int size) { | ||||
|
|
||||
|
||||
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.
Http Method에서 PUT, PATCH의 차이점은 무엇일까요?
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.
데이터를 수정함에 있어
전체 수정 : put
부분 수정 : patch 를 의미합니다.
현 메서드의 경우 제목과 내용을 모두 변경하는것이
가능하므로
PUT이 적절한 것 같습니다!
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package com.board.board.domain; | ||
|
|
||
| import com.board.member.entity.MemberEntity; | ||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.FetchType; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.JoinColumn; | ||
| import jakarta.persistence.ManyToOne; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Entity | ||
| @NoArgsConstructor | ||
| @Getter | ||
| public class Article { | ||
|
||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| @Column(name = "id", updatable = false) | ||
| private Long id; | ||
|
|
||
| @Column(name = "title", nullable = false) | ||
| private String title; | ||
|
|
||
| @Column(name = "content", nullable = false) | ||
| private String content; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
|
Member
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. Lazy와 Eager의 차이는 무엇일까요? 단순 지연, 즉시로딩이 아닌 어떤 원리로 JPA에서 조회가 되는 걸까요? 학습하시면 관련 키워드로 프록시~~ 를 보실텐데, 그러면 JPA에서 관련 문제로는 어떤 게 생길 수 있을까요?!
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. 연관된 엔티티 가져오는 방법 비교 LAZY : 연관된 엔티티를 프록시 객체로 가지고있음(프록시객체가 실제 엔티티를 찾아올 수는 있어야하므로 실제 엔티티의 식별자를 가지고있음) EAGER : 연관 데이터들까지 모두 즉시 로딩 ex) 작가를 불러오는 쿼리 (작가 테이블과 책 테이블은 연관되어있는 경우) LAZY : 작가만 불러옴(책 데이터들은 프록시 객체로 가지고있음)
속도 떄문에 실무에서는 대부분 LAZY 전략 채택
N + 1문제 : 쿼리가 내가 원하는것보다 N번 더 발생하는것 ex) 작가 불러오는 쿼리 1번 + 작가에 딸린 책 불러오는 쿼리(작가 갯수만큼) N번 발생 N+1 해결 방법 : JOIN FETCH JOIN FETCH란 JPA에서 사용하는 작성방법입니다. |
||
| @JoinColumn(name = "member_id") | ||
| private MemberEntity member; | ||
|
|
||
| @Builder | ||
| public Article(String title, String content, MemberEntity member) { | ||
| this.title = title; | ||
| this.content = content; | ||
| this.member = member; | ||
| } | ||
|
|
||
| public Article(Long id, String title, String content, MemberEntity member) { | ||
| this.id = id; | ||
| this.title = title; | ||
| this.content = content; | ||
| this.member = member; | ||
| } | ||
|
||
|
|
||
| public void update(String title, String content) { | ||
| this.title = title; | ||
| this.content = content; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.board.board.dto; | ||
|
||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @Setter | ||
| public class ArticleCreateRequest { | ||
|
|
||
| @NotBlank | ||
| private String title; | ||
| @NotBlank | ||
| private String content; | ||
| private Long memberId; | ||
|
||
|
|
||
| public ArticleCreateRequest(String title, String content) { | ||
| this.title = title; | ||
| this.content = content; | ||
| } | ||
|
|
||
| @Builder | ||
| public ArticleCreateRequest(String title, String content, Long memberId) { | ||
| this.title = title; | ||
| this.content = content; | ||
| this.memberId = memberId; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.board.board.dto; | ||
|
|
||
| import com.board.board.domain.Article; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Getter | ||
| public class ArticleResponse { | ||
|
|
||
| private final Long id; | ||
| private final String title; | ||
| private final String content; | ||
| private final Long memberId; | ||
|
|
||
| public ArticleResponse(Article article) { | ||
| this.id = article.getId(); | ||
| this.title = article.getTitle(); | ||
| this.content = article.getContent(); | ||
| this.memberId = article.getMember().getId(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.board.board.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public class ArticleUpdateRequest { | ||
|
|
||
| @NotBlank | ||
| private final String title; | ||
| @NotBlank | ||
| private final String content; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.board.board.repository; | ||
|
|
||
| import com.board.board.domain.Article; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| public interface BlogRepository extends JpaRepository<Article, Long> { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| package com.board.board.service; | ||
|
|
||
| import com.board.board.domain.Article; | ||
| import com.board.board.dto.ArticleCreateRequest; | ||
| import com.board.board.dto.ArticleUpdateRequest; | ||
| import com.board.board.repository.BlogRepository; | ||
| import com.board.config.auth.AuthUtil; | ||
| import com.board.exception.custom.DifferentOwnerException; | ||
| import com.board.exception.custom.MyEntityNotFoundException; | ||
| import com.board.member.entity.MemberEntity; | ||
| import com.board.member.service.MemberService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| @RequiredArgsConstructor | ||
| @Service | ||
| @Transactional | ||
|
||
| public class BlogService { | ||
|
|
||
| private final BlogRepository blogRepository; | ||
| private final MemberService memberService; | ||
|
Member
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. MemberService와 MemberRepository를 가져다 쓰는 것과 어떤 차이가 있을까요? (사실 정답은 없지만) 동진님이 생각하시는 차이가 궁금합니다 ㅎㅎ
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. MemberService 를 사용하는것이 좋다고 생각합니다. Service : 기능 구현
Member
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. 여기 보시면 많은 분들이 토론하신 게 있는데 보시면 좋을 것 같아요~
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. 의존성을 끊는다는 것이 이것 참 어렵네요 @Transactional
public ArticleEntity save(ArticleCreateRequest request, Long memberId) {
return blogRepository.save(ArticleEntity.builder()
.title(request.getTitle())
.content(request.getContent())
.member(memberService.findById(memberId))
.build());
}저번에 실무에서는 @manytoone 을 별로 사용하지 않고
Member
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. ㅎㅎ 네 모든 상황이 다 다르니깐요! https://www.youtube.com/watch?v=dJ5C4qRqAgA 이 내용 관련해서 진짜 좋은 영상인데 좀 긴데 꼭 보셨으면 좋겠습니다! 어떤 내용인지 확 와닿으실거에요!
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. 영상도 찾아주시고 정말 정말 감사합니다!!
Member
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. ㅎㅎ 넵 영상에서 결국 참조관계는 양방향보다 단방향인 경우 유지보수도 그렇고 여러 장점이 있다는 것이고 그러기에 간접참조 얘기가 나온 거에요! 아마 지금은 힘들 수 있지만, 천천히 보시고 직접 느껴보면서 관련 자료 찾아보시다보면 금방 이해하실 것 같습니다! |
||
| private final AuthUtil authUtil; | ||
|
|
||
| public Article save(ArticleCreateRequest request) { | ||
| String email = authUtil.getMemberEmail(); | ||
| MemberEntity member = memberService.findByEmail(email); | ||
|
|
||
| return blogRepository.save(Article.builder() | ||
| .title(request.getTitle()) | ||
| .content(request.getContent()) | ||
| .member(member) | ||
| .build()); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public Page<Article> findAll(Pageable pageable) { | ||
| return blogRepository.findAll(pageable); | ||
| } | ||
|
|
||
| @Transactional(readOnly = true) | ||
| public Article findById(long id) { | ||
| return findArticle(id); | ||
| } | ||
|
|
||
| public void delete(long id) { | ||
| compareAuthors(id); | ||
| blogRepository.deleteById(id); | ||
| } | ||
|
|
||
| public Article update(long id, ArticleUpdateRequest request) { | ||
| Article article = compareAuthors(id); | ||
| article.update(request.getTitle(), request.getContent()); | ||
| return article; | ||
| } | ||
|
|
||
| private Article compareAuthors(long articleId) { | ||
|
||
| String email = authUtil.getMemberEmail(); | ||
| MemberEntity member = memberService.findByEmail(email); | ||
| Article article = findArticle(articleId); | ||
| if (!Objects.equals(member.getId(), article.getMember().getId())) { | ||
| throw DifferentOwnerException.from(article.getMember().getEmail()); | ||
| } | ||
| return article; | ||
| } | ||
|
|
||
| private Article findArticle(long id) { | ||
| return blogRepository.findById(id) | ||
| .orElseThrow(() -> MyEntityNotFoundException.from(id)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.board.config; | ||
|
|
||
| import com.board.board.repository.BlogRepository; | ||
| import com.board.member.repository.MemberRepository; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.annotation.Configuration; | ||
|
|
||
| @Configuration | ||
| @RequiredArgsConstructor | ||
| public class DataInitializer { | ||
|
|
||
| private final MemberRepository memberRepository; | ||
| private final BlogRepository blogRepository; | ||
| /* 테스트에 방해되서 주석처리 | ||
| @Bean | ||
| public CommandLineRunner initData() { | ||
| return args -> { | ||
| // 기본 회원 데이터 생성 | ||
| MemberEntity member1 = MemberEntity.builder() | ||
| .email("aa@aa.com") | ||
| .password("1234") | ||
| .nickName("aa") | ||
| .build(); | ||
|
|
||
| MemberEntity member2 = MemberEntity.builder() | ||
| .email("aa@aa.com2") | ||
| .password("1234") | ||
| .nickName("aa2") | ||
| .build(); | ||
|
|
||
| memberRepository.save(member1); | ||
| memberRepository.save(member2); | ||
|
|
||
| // 기본 게시글 데이터 생성 | ||
| Article article1 = Article.builder() | ||
| .title("First Article") | ||
| .content("Content of the first article") | ||
| .member(member1) | ||
| .build(); | ||
|
|
||
| Article article2 = Article.builder() | ||
| .title("Second Article") | ||
| .content("Content of the second article") | ||
| .member(member1) | ||
| .build(); | ||
|
|
||
| Article article3 = Article.builder() | ||
| .title("Third Article") | ||
| .content("Content of the third article") | ||
| .member(member2) | ||
| .build(); | ||
|
|
||
| blogRepository.save(article1); | ||
| blogRepository.save(article2); | ||
| blogRepository.save(article3); | ||
| }; | ||
| }*/ | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.board.config.auth; | ||
|
|
||
| import static com.board.config.jwt.JwtAuthFilter.AUTHENTICATED_USER; | ||
|
|
||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.web.context.request.RequestAttributes; | ||
| import org.springframework.web.context.request.RequestContextHolder; | ||
|
|
||
| @Component | ||
| public class AuthUtil { | ||
| public String getMemberEmail() { | ||
| return (String) RequestContextHolder.getRequestAttributes() | ||
| .getAttribute(AUTHENTICATED_USER, RequestAttributes.SCOPE_REQUEST); | ||
|
||
| } | ||
|
|
||
| public boolean isAuthenticated() { | ||
| return getMemberEmail() != null; | ||
| } | ||
| } | ||
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.
오 요건 왜 다신걸까요?!
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.
며칠간 서버가 켜질때 오류가 발생하고 서버 구동이 안되었는데
원인을 알고 보니
이 코드가
@ConfigurationProperties("jwt")어노테이션을 통해yml 값을 읽어와서 해당 클래스의 값을 자동으로 넣어주는 부분인데
yml을 값을 넣어주기 위해서는
@ConfigurationPropertiesScan 어노테이션을 달아줘야된다고 봐서 추가했습니다!
(귀신같이 이걸 붙여주니 서버 구동이 잘 되었습니다)
지금 다시 검색해보니
@Component어노테이션이 있으면@ConfigurationPropertiesScan어노테이션이 굳이 필요가 없다고는 하지만..!그래도 붙여놓는것이 앞으로
@ConfigurationProperties를 추가할 경우가 있으면 편할 것 같습니다!