오늘은 사용자를 만들꺼다 계속 그렇지만 반복하는 설명은 안할꺼다.
먼저 돌아가는 구조를 정리하자면
원하는 구조는 다음과 같다.
1. 사이트에 접속하면 게시글 목록이 나오는데 누구나 확인만 가능하다.
2. 회원 가입과 로그인을 한뒤 인증과 인가를 받아서 게시글과 댓글 그리고 좋아요 등록 권한을 얻는다.
3. 게시글과 조회수는 페이지네이션 처리가 되어야 한다.
4. 좋아요를 두번 누르면 취소가 된다.(같은 POST 요청이 중복)
5. 단일 게시판을 조회하면 조회수가 1 증가한다.
우선 우리는 Member Entity를 추가해야겠다.
@Entity
@Getter @Setter
@NoArgsConstructor
public class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
@Column(unique = true)
private String email;
private String password;
@Column(unique = true)
private String nickname;
@OneToMany(mappedBy = "member",cascade = CascadeType.ALL)
List<Board> boards = new ArrayList<>();
@OneToMany(mappedBy = "member",cascade = CascadeType.ALL)
List<Reply> replies = new ArrayList<>();
@ElementCollection(fetch = FetchType.EAGER)
private List<String> roles = new ArrayList<>();
}
다른건 다 같은데 roles가 추가 되었다. 권한인데 이따가 주석 처리 하는거 보면서 확인하자. 그리고 @ElementCollection는 따로 테이블을 생성하지 않게 하는거다. 왜냐하면 나는 회원 가입한 유저들의 권한을 차등하지 않고 전부 같게 할꺼라 뭐 따로 볼필요가 없다.
@Service
@RequiredArgsConstructor
public class MemberService{
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final CustomAuthorityUtils customAuthorityUtils;
public Member findMemberId(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
}
public Member findByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
}
public Member displayNickname(String nickname) {
return memberRepository.findByNickname(nickname)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
}
@Transactional
public Long createMember(MemberPostDto memberPostDto) {
Member member = new Member();
member.setEmail(memberPostDto.getEmail());
member.setPassword(passwordEncoder.encode(memberPostDto.getPassword()));
member.setRoles(customAuthorityUtils.createRoles(memberPostDto.getEmail()));
member.setNickname(memberPostDto.getNickname());
return memberRepository.save(member).getMemberId();
}
public Long updateMember(MemberPatchDto memberPatchDto,Long memberId) {
Member member = findMemberId(memberId);
member.setNickname(memberPatchDto.getNickname());
member.setEmail(memberPatchDto.getEmail());
member.setPassword(memberPatchDto.getPassword());
return memberRepository.save(member).getMemberId();
}
public MemberResponseDto findByMemberId(Long memberId) {
Member member = findMemberId(memberId);
return MemberResponseDto.FindFromMember(member);
}
public void deleteMember(Long memberId) {
findMemberId(memberId);
memberRepository.deleteById(memberId);
}
}
member.setPassword(passwordEncoder.encode(memberPostDto.getPassword()));
member.setRoles(customAuthorityUtils.createRoles(memberPostDto.getEmail()));
서비스에서 위의 부분이 신경 쓰일꺼다. 비밀번호를 변환 해주는거다 보안성을 올려주지 이렇게.(실제로는 123123이다.)
두번째는 Role을 저장하는건데 나중에 나올꺼다.
patch 보면
public Long updateMember(MemberPatchDto memberPatchDto,Long memberId) {
Member member = findMemberId(memberId);
member.setNickname(memberPatchDto.getNickname());
member.setEmail(memberPatchDto.getEmail());
member.setPassword(memberPatchDto.getPassword());//여길 passwordEncoder 안하니깐 그냥 저장되는게 보이는가?
return memberRepository.save(member).getMemberId();
}
일부러 안바꿨다 그런데 결과를 보자.
이러면 DB 털리면 비밀번호를 바로 볼수 있는거다 그래서 암호화를 하는거다.
public Member findByEmail(String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
}
public Member displayNickname(String nickname) {
return memberRepository.findByNickname(nickname)
.orElseThrow(() -> new BusinessLogicException(ExceptionCode.MEMBER_NOT_FOUND));
}
이 두개도 신경 쓰일꺼다. 추가한 이유는 findByEmail은 좋아요 올릴때 이메일 식별해서 올릴려고 한거고.
displayNickname은 보드에 올릴때 누군지 식별할때 nickname 사용하려고 올린거다.
여기서 의문 왜 그냥 한가지로 하지 헷갈리게 하냐고?
그냥... 여러가지로 식별하는거 해보고 싶어서 추가했다... 미안하다 수정할 사람 수정해라. 어떻게 굴러가는 지 보여주면
POST 요청시 회원가입할때 사용한 nick 아니라 nickname 이라는걸 쓰면 board에 저장이 안된다. 다른 회원인 nick2을 만들어서 POST 하고 DB를 보여주겠다.
이렇게 누가 작성했는지 보여 주게 된다.
그리고 MemberRepository
@Repository
public interface MemberRepository extends JpaRepository<Member,Long> {
Optional<Member> findByEmail(String email);
Optional<Member> findByNickname(String nickname);
}
member를 이메일과 nickname으로 찾기 위해서 두개를 만들었다.
@Getter @Setter
public class MemberPostDto {
@NotEmpty @Email
private String email;
@NotEmpty
private String password;
@NotEmpty
private String nickname;
}
post dto인데 다른것도 같다.
아래는 수정된 board Service 이다.
@Service
@RequiredArgsConstructor
@Transactional
public class BoardService {
private final BoardRepository boardRepository;
private final MemberService memberService;
//아이디 찾고 없으면 Exception
public Board findBoardId(Long boardId) {
return boardRepository.findById(boardId)
.orElseThrow(()->new BusinessLogicException(ExceptionCode.BOARD_NOT_FOUND));
}
public void isPermission(Member member, String email) {
if (!member.getEmail().equals(email)) {
throw new BusinessLogicException(ExceptionCode.NO_PERMISSION);
}
}
public Long createBoard(BoardPostDto boardPostDto) {
Board board = new Board();
board.setTitle(boardPostDto.getTitle());
board.setContent(boardPostDto.getContent());
board.setMember(memberService.displayNickname(boardPostDto.getMember()));
return boardRepository.save(board).getBoardId();
}
public Long updateBoard(BoardPatchDto boardPatchDto, Long boardId,String email) {
Board board = findBoardId(boardId);
isPermission(board.getMember(),email);
board.setTitle(boardPatchDto.getTitle());
board.setContent(boardPatchDto.getContent());
board.setMember(memberService.displayNickname(boardPatchDto.getMember()));
return boardRepository.save(board).getBoardId();
}
public BoardResponseDto findByBoardId(Long boardId) {
Board board = findBoardId(boardId);
board.setBoardCount(board.getBoardCount() + 1);
// board.setMember(displayNickname(boardPatchDto.getMember()));
boardRepository.save(board); // 게시글의 조회수를 증가시킨 후 저장하는 용도
return BoardResponseDto.FindFromBoard(board);
}
public Page<BoardResponseDto> findAllBoards(Pageable pageable) {
Page<Board> boards = boardRepository.findAll(pageable);
return boards.map(BoardResponseDto::FindFromBoard);
}
public void deleteBoard(Long boardId) {
findBoardId(boardId);
boardRepository.deleteById(boardId);
}
}
여기서 문제 아래 보드는 왜 수정이 안될까요? member가 nick2라서? 아니
토큰이 없어서다. 일단 기다려달라.
토큰을 넣었는데도 오류 뜬다.
첫번째 사진 오류는 토큰을 안맞게 하면 뜨는 오류고.
두번째는 DB에 없는 유저를 집어 넣으면 뜬다.
public void isPermission(Member member, String email) {
if (!member.getEmail().equals(email)) {
throw new BusinessLogicException(ExceptionCode.NO_PERMISSION);
}
}
이건 권한이 없으면 예외 터트리는 용도이다. 유저1이 유저2의 게시글 삭제 수정 등등
이런식으로 Reply도 수정하면 된다.
솔직히 뭐 별거 없다 member를 만드는것은...
문제는 인증과 인가다 나는 Spring Security와 JWT를 사용했는데 여기서는 토큰 이라는것을 다루게 된다. 혹시 토큰을 모른다면 내가 이전에 작성한 https://acid7937.tistory.com/46 여기를 참고해도 좋고 다른 사이트에서 확인해도 좋다.
추가 팁인데 서비스에서 오류 터질때 @Transactional를 붙여주면 잘 될때가 많다.
@Transactional는 은행 송금이라고 생각해라
A가 돈을 보내고 B가 받을때 인출과 송금이 같이 진행되어야 우리는 돈을 보냈다고 할수 있다.
그런데 A가 돈을 보냈는데 B가 못받아 그러면 이건 실행을 취소해야 하는거다.
이처럼 @Transactional은 메서드의 실행을 하나로 묶어주는 역할을 하여 실행하면 전부 실행하고 실패하면 다 실패해라 라는 뜻을 담고 있다.
이게 왜 효과가 있냐?
사실 Lazy와 Eager를 들어 봤는가? 이건 DB에서 물건빼는 순서 같은건데.
{Lazy Loading (지연 로딩) Eager Loading (즉시 로딩)}
은행을 다시 예로 들면 입금을 먼저 처리하고 송금 처리하는게 말이 안되질 않는가? Lazy가 후순위 Eager가 앞순위 인데 이거 잘못쓰면 꼬일때가 많은데 수정하기 너무 번거롭다 그래서 쓰다가 에러 터지면 물론 Lazy Eager 손봐야 하겠지만 귀찮으면 @Transactional 박아라. 보통 메서드에 박는게 일반적인데 서비스에 통으로 넣어도 뭐 문제는 없다. 사수가 싫어하겠지만.
다음 포스트는 인증/인가를 보여주겠다.
'스프링boot > 정리' 카테고리의 다른 글
Spring MVC) 좋아요, 조회수를 만들어 보자. (1) | 2023.04.21 |
---|---|
Spring MVC) 사용자를 만들어 보자 2 (0) | 2023.04.21 |
Spring MVC) 댓글을 만들어 보자 (1) | 2023.04.09 |
Spring MVC) 게시판을 만들어 보자 4 (0) | 2023.04.07 |
Spring MVC) 게시판을 만들어 보자 3 (0) | 2023.04.07 |