-
현재까지 구현한 Member와 Post Entity에 대한 Service, Controller, Dto내용을 정리했다.
testCode가 눈에 띄게 부실해서, 관련 모듈들을 더 공부한 후에 보충해서 따로 정리를 할 예정이다.
Member
MemberRepository
package spring.postproject.Member.Repository; import org.springframework.data.jpa.repository.JpaRepository; import spring.postproject.Member.Entity.Member; import java.util.Optional; public interface MemberRepository extends JpaRepository<Member,Long> { Optional<Member> findByUserIdAndPassword(String userId, String Password); }
JpaRepository를 상속받아 구현하였고 로그인에 사용할 findByUserIdAndPassword 만 선언해주었습니다.
MemberService
package spring.postproject.Member.Service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import spring.postproject.Excetion.ExceptionBoard; import spring.postproject.Member.Entity.Member; import spring.postproject.Member.Repository.MemberRepository; import java.util.Optional; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class MemberService { private final MemberRepository memberRepository; // 회원가입 @Transactional public Member signUp(Member member){ return memberRepository.save(member); } //계정 탈퇴 @Transactional public void withdraw (Member member, String password){ if (!member.getPassword().equals(password)){ throw ExceptionBoard.INVALID_PASSWORD.getException(); } memberRepository.delete(member); } public Member findOne(Long id){ return memberRepository.findById(id).orElseThrow(ExceptionBoard.NOT_FOUND_MEMBER::getException); } }
MemberService에는 회원가입과, member 하나만 찾는 로직을 추가 했다
LoginService
public interface LoginService { Member login(String id, String password); }
LoginService 인터페이스를 이용하여 추후에 Login 방법이 바뀌게 될 경우 구현체만 바꿀 수있도록 하기 위해 처리했다
SessionLoginServiceImpl
package spring.postproject.Member.Service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import spring.postproject.Excetion.ExceptionBoard; import spring.postproject.Member.Entity.Member; import spring.postproject.Member.Repository.MemberRepository; @Service @RequiredArgsConstructor public class SessionLoginServiceImpl implements LoginService{ private final MemberRepository memberRepository; @Override public Member login(String id, String password) { return memberRepository.findByUserIdAndPassword(id, password).orElseThrow(ExceptionBoard.NOT_FOUND_MEMBER::getException); } }
Session을 이용할 Service인데 막상 Session을 컨트롤 하지는 않는것 같아 수정이 필요하다.
MemberCreateDto
package spring.postproject.Member.dto; import lombok.Data; @Data public class MemberCreateDto { private String nickname; private String userId; private String password; }
회원가입시 전달받을 Dto입니다. setter등 여러가지를 지원하고 있는 @Data를 이용했다.
MemberLoginDto
package spring.postproject.Member.dto; import lombok.*; @Data public class MemberLoginDto { private String userId; private String password; }
로그인시 전달받을 Dto입니다. setter등 여러가지를 지원하고 있는 @Data를 이용했다.
LoginController
package spring.postproject.Member.Controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import spring.postproject.Member.Entity.Member; import spring.postproject.Member.Entity.MemberRoll; import spring.postproject.Member.Service.MemberService; import spring.postproject.Member.Service.SessionLoginServiceImpl; import spring.postproject.Member.dto.MemberCreateDto; import spring.postproject.Member.dto.MemberLoginDto; import spring.postproject.Post.Entity.Post; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.List; @Controller @RequiredArgsConstructor @Slf4j public class LoginController { private final SessionLoginServiceImpl loginService; private final String login = "login"; private final MemberService memberService; @GetMapping("/") public String loginForm(@SessionAttribute(name = login,required = false)Member member, Model model){ if(member != null){ model.addAttribute("member",member); List<Post> posts = member.getPostList(); model.addAttribute("post",posts); return "/post/home"; } model.addAttribute("memberLoginDto",new MemberLoginDto()); return "/member/loginForm"; } @PostMapping("/") public String login(MemberLoginDto memberLoginDto, HttpServletRequest request, Model model){ if (result.hasErrors()) { return "member/loginForm"; } Member loginMember = loginService.login(memberLoginDto.getUserId(), memberLoginDto.getPassword()); if (loginMember == null) { result.reject("loginFail", "아이디 또는 비밀번호가 맞지 않습니다."); return "member/loginForm"; } //로그인 성공 처리 //세션이 있으면 있는 세션 반환, 없으면 신규 세션을 생성 HttpSession session = request.getSession(); //세션에 로그인 회원 정보 보관 session.setAttribute(login, loginMember.getId()); List<Post> posts = loginMember.getPostList(); model.addAttribute("post",posts); return "/post/home"; } @GetMapping("/member/new") public String createMember(Model model){ model.addAttribute("memberCreateDto",new MemberCreateDto()); return "member/createMember"; } @PostMapping("/member/new") public String create(MemberCreateDto memberCreateDto){ Member member = Member.builder() .userId(memberCreateDto.getUserId()) .password(memberCreateDto.getPassword()) .nickName(memberCreateDto.getNickname()) .build(); member.updateRole(MemberRoll.NORMAL); memberService.signUp(member); return "redirect:/"; } @GetMapping("/member/logout") public String logout(HttpServletRequest request) { // getSession(false) 를 사용해야 함 (세션이 없더라도 새로 생성하면 안되기 때문) HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); } return "redirect:/"; } }
- 우선 로그인을 먼저 하도록 만들었기 때문에 “/” 리소스 Get요청에는 member/loginForm 리소스에 MemberLoginDto를 담아서 전달하도록 했고, Post요청시에는 MemberLoginDto에서 userId와 password만 db에서 확인하여 세션을 부여했다.
- @SessionAttribute 어노테이션을 이용하여 세션이 있을 경우에는 해당 SESSIONID의 Member로 home에 접근 하도록 했다. home의 경로가 현재는 post/home으로 되어 있는데 추후에 알맞은 네이밍과 경로로 수정할 예정이다.
- /member/new리소스의 Get 요청에는 로그인 페이지인 /member/createMember 리소스에 MemberCreateDto 담아서 전달고, Post 요청에는 id와 password로 Member 앤티티를 만들어 저장하고 로그인을 할 수 있게 로그인 페이지로 "redirect:/" 해주었다.
- logout에서는 클라이언트에서 보낸 세션을 삭제시킨 후 "redirect:/" 했다.
- 여기서 @Valid를 하나도 사용하지 않았는데 이유는 현재 앤티티에서 모두 처리할 수 있도록 만들었기 때문에 추가를 하지 않았는데 controller 단에서 처낼 수 있는 exception이 앤티티까지 밀고 들어온다는게 옳지 않은 방법인것 같은데 추후 리팩토링시 좋은 방향으로 반영할 예정이다.
- 세션 관련글은 여기를 참고
Post
PostRepository
package spring.postproject.Post.Repository; import org.springframework.data.jpa.repository.JpaRepository; import spring.postproject.Post.Entity.Post; public interface PostRepository extends JpaRepository<Post,Long> { }
PostRepostiroy도 마찬가지로 JpaRepository를 상속받아서 생성하였는데 따로 더 필요한 custom 쿼리는 없어서 추가하지 않았다.
PostService
package spring.postproject.Post.Service; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import spring.postproject.Excetion.ExceptionBoard; import spring.postproject.Member.Entity.Member; import spring.postproject.Post.Entity.Post; import spring.postproject.Post.PostDto.PostDto; import spring.postproject.Post.Repository.PostRepository; import java.util.List; @Service @RequiredArgsConstructor @Slf4j @Transactional public class PostService { private final PostRepository postRepository; public Post create(Post post, Member member){ post.setMember(member); return postRepository.save(post); } //더티체킹으로 업데이트 하도록 함 public Post update(Long id,PostDto postDto){ Post post = postRepository.findById(id).orElseThrow(ExceptionBoard.NOT_FOUND_POST::getException); post.update(postDto); return post; } public void delete(Long id){ Post post = postRepository.findById(id).orElseThrow(ExceptionBoard.NOT_FOUND_POST::getException); postRepository.delete(post); } public Post findOne(Long id){ return postRepository.findById(id).orElseThrow(ExceptionBoard.NOT_FOUND_POST::getException).counting(); } @Transactional(readOnly = true) public List<Post> findAll(){ return postRepository.findAll(); } }
create에서 post를 영속화하기전 member과 연관관계 매핑을 하도록 했다.
update에서는 주석을 달아놓은것처럼 더티체킹으로 update되도록 하기위해 post를 영속화 시킨 후 내용을 Entity단에서 update만 해주었다.
나머지는 어렵지 않은데 findOne에서 @Transactonal 옵션을 readOnly로 하지 않은 이유는 조회수 로직때문이다. readOnly를 할 경우에는 JPA에서 스냅샷을 찍지 않아 수정내용이 반영되지 않기때문에 count()로직이 실행되어도 수정이 반영되지 않는다. 현재 조회수 카운팅방법에는 문제가 있고 세션, 토큰등 다양한 방법이 있다는 것을 인지하고 있기 때문에 이 부분은 추후 업데이트 대상이 될 것 같다.
PostDto
package spring.postproject.Post.PostDto; import lombok.Data; import spring.postproject.Post.Entity.Post; @Data public class PostDto { private String title; private String content; public void toDto(Post post) { this.title = post.getTitle(); this.content = post.getContent(); } }
post 수정시 전달받을 Dto입니다. 마찬가지로 편의를 위해 @Data 어노테이션 사용했다.
PostController
package spring.postproject.Post.Controller; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import spring.postproject.Member.Entity.Member; import spring.postproject.Post.Entity.Post; import spring.postproject.Post.PostDto.PostDto; import spring.postproject.Post.Service.PostService; @RequiredArgsConstructor @Controller @Slf4j public class PostController { private final PostService postService; @GetMapping("/post/new") public String createPost(Model model){ model.addAttribute("postDto",new PostDto()); return "/post/postCreate"; } @PostMapping("/post/new") public String create(PostDto postDto, @SessionAttribute(name = "login",required = false) Member member){ if(member == null){ log.info("member session is not valid"); return "redirect:/"; } Post post = Post.builder() .title(postDto.getTitle()) .content(postDto.getContent()) .build(); postService.create(post, member); return "redirect:/"; } @GetMapping("/post/{postId}/detail") public String detail(@PathVariable("postId") Long postId, Model model){ model.addAttribute("post",postService.findOne(postId)); return "post/postDetail"; } @GetMapping("/post/{postId}/update") public String updateForm(@PathVariable("postId") Long postId, Model model){ Post post = postService.findOne(postId); PostDto postDto = new PostDto(); postDto.toDto(post); model.addAttribute("postDto",postDto); model.addAttribute("postId",postId); return "post/postUpdate"; } @PostMapping("/post/{postId}/update") public String update(@PathVariable("postId") Long postId,PostDto postDto, Model model){ Post post = postService.update(postId, postDto); return "redirect:/"; } @GetMapping("/post/{postId}/delete") public String delete(@PathVariable("postId")Long postId){ postService.delete(postId); return "redirect:/"; } }
'프로젝트 > 게시판 만들기로 배우는 Spring Data JPA' 카테고리의 다른 글
크게한걸음 - 로그인과 글쓰기 (회고, Form(타임리프)) (0) 2022.06.12 크게 한걸음 - 로그인과 글쓰기 (Entity, global Exception) (0) 2022.06.09 1. 시작하며 (0) 2022.04.03 댓글