
[ 시작하기 전! @RestController 처리 모두 다 해라!! ]

이거 처리 안 하면 static resource (뷰) 찾을 수 없다는 에러가 뜸
1. 요청 BODY 수정 (json으로 변경)
요청 body에 @RequestBody 만 붙이면 됨 -> 그럼 전부 json 요청으로 바뀐다
어떤 건 폼으로 받고, 어떤건 json으로 받고... 이러는 건 좋지 않다. 하나로 통일하자~

바디 데이터 -> post, put 요청에 전부 @RequestBody를 붙이고 다니자
2. 응답 body 수정
응답은 return에 ResponseEntity 처리.
지금은 응답 바디에 form이나 redirect 처리를 한 상태.
(응답은 상태코드 200만 날려주면 된다.)
ok = 200


1. ResponseEntity.ok(new ApiUtil(newSessionUser));
2. new ResponseEntity<>(new ApiUtil(newSessionUser), HttpStatus.OK);
이 2개는 같은 코드이나... ok라고 쓰는게 더 간결하고 보기 좋다!!
ok 자체가 200을 뜻하니까! 그러니 1번 코드를 사용하자!
UserService 응답 body 수정
User 전체 코드
[ UserService ]
package shop.mtcoding.blog.user;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.errs.exception.Exception400;
import shop.mtcoding.blog._core.errs.exception.Exception401;
import shop.mtcoding.blog._core.errs.exception.Exception404;
import java.util.Optional;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserJPARepository userJPARepository;
@Transactional
public User 회원수정(int id, UserRequest.UpdateDTO requestDTO) {
User user = userJPARepository.findById(id)
.orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다."));
user.setPassword(requestDTO.getPassword());
user.setEmail(requestDTO.getEmail());
return user;
} //더티체킹
public User 회원조회(int id) {
User user = userJPARepository.findById(id)
.orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다."));
return user;
}
//조회라 트랜젝션 안 붙여도 됨!
public User 로그인(UserRequest.LoginDTO requestDTO) {
//나중에 해시 비교하는 이런 코드 여기에 들어옴
User sessionUser = userJPARepository.findByUsernameAndPassword(requestDTO.getUsername(), requestDTO.getPassword())
.orElseThrow(() -> new Exception401("인증되지 않았습니다"));
return sessionUser;
}
@Transactional
public User 회원가입(UserRequest.JoinDTO requestDTO) {
// 1. 유효성 검사 -> 컨트롤러 책임 x
// 2. 유저네임 중복검사 (서비스 체크) - DB연결 필요
Optional<User> userOP = userJPARepository.findByUsername(requestDTO.getUsername());
//isPresent가 있으면 비정상
if (userOP.isPresent()) {
throw new Exception400("중복된 유저네임입니다.");
}
return userJPARepository.save(requestDTO.toEntity());
}
}
[ UserController ]
package shop.mtcoding.blog.user;
import jakarta.persistence.NoResultException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.errs.exception.Exception400;
import shop.mtcoding.blog._core.errs.exception.Exception401;
import shop.mtcoding.blog._core.utils.ApiUtil;
import shop.mtcoding.blog.board.Board;
import shop.mtcoding.blog.board.BoardRepository;
@RequiredArgsConstructor
@RestController
public class UserController {
private final UserService userService;
private final HttpSession session;
// TODO: 회원정보 조회 API 필요 -> @GetMapping("/api/user/{id}")
@GetMapping("/api/user/{id}")
public ResponseEntity<?> userinfo(@PathVariable Integer id) {
User user = userService.회원조회(id);
return ResponseEntity.ok(new ApiUtil(user));
}
@PutMapping("/api/users/{id}")
public ResponseEntity<?> update(@PathVariable Integer id, @RequestBody UserRequest.UpdateDTO requestDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
User newSessionUser = userService.회원수정(sessionUser.getId(), requestDTO);
session.setAttribute("sessionUser", newSessionUser);
return ResponseEntity.ok(new ApiUtil(newSessionUser));
}
@PostMapping("/join")
public ResponseEntity<?> join(@RequestBody UserRequest.JoinDTO requestDTO) {
User user = userService.회원가입(requestDTO);
return ResponseEntity.ok(new ApiUtil(user));
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody UserRequest.LoginDTO requestDTO) {
User sessionUser = userService.로그인(requestDTO);
session.setAttribute("sessionUser", sessionUser);
return ResponseEntity.ok(new ApiUtil(null));
}
@GetMapping("/logout")
public ResponseEntity<?> logout() {
session.invalidate();
return ResponseEntity.ok(new ApiUtil(null));
}
}
BoardService / BoardController - 글쓰기만 예시
[ 글쓰기 ]
@Transactional
public Board 글쓰기(BoardRequest.SaveDTO requestDTO, User sessionUser) {
Board board = boardJPARepository.save(requestDTO.toEntity(sessionUser));
return board; //나중에 board가 아니라 dto를 리턴해줘야함!!
}
@PostMapping("/api/boards")
public ResponseEntity<?> save(@RequestBody BoardRequest.SaveDTO requestDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
//권한 체크는 생략
Board board = boardService.글쓰기(requestDTO, sessionUser);
return ResponseEntity.ok(new ApiUtil(board));
}
return board를 한다고? 진짜 위험한 코드 (무한 순환 참조)
Board 전체 코드
[ BoardService ]
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.errs.exception.Exception403;
import shop.mtcoding.blog._core.errs.exception.Exception404;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardJPARepository boardJPARepository;
public Board 글조회(int boardId) {
Board board = boardJPARepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
//2. 권한 처리
// if (sessionUserId != board.getUser().getId()) {
// throw new Exception403("게시글을 수정페이지로 이동 할 권한이 없습니다");
// }
return board;
}
@Transactional
public Board 글수정(int boardId, int sessionUserId, BoardRequest.UpdateDTO requestDTO) {
//1. 더티체킹 하기 위해 조회하고 예외처리
Board board = boardJPARepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
//2. 권한 처리
if (sessionUserId != board.getUser().getId()) {
throw new Exception403("게시글을 수정할 권한이 없습니다");
}
//3. 실제 글 수정 (더티체킹 함)
board.setTitle(requestDTO.getTitle());
board.setContent(requestDTO.getContent());
return board;
}
@Transactional
public Board 글쓰기(BoardRequest.SaveDTO requestDTO, User sessionUser) {
Board board = boardJPARepository.save(requestDTO.toEntity(sessionUser));
return board;
}
@Transactional
public void 글삭제(Integer boardId, Integer sessionUserId) {
Board board = boardJPARepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다."));
if (sessionUserId != board.getUser().getId()) {
throw new Exception403("게시글 삭제 권한 없음!");
}
boardJPARepository.deleteById(boardId);
}
public List<Board> 글목록조회() {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
return boardJPARepository.findAll(sort);
}
// board, isOwner
public Board 글상세보기(int boardId, User sessionUser) {
Board board = boardJPARepository.findByIdJoinUser(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
boolean isBoardOwner = false;
if(sessionUser != null){
if(sessionUser.getId() == board.getUser().getId()){
isBoardOwner = true;
}
}
board.setBoardOwner(isBoardOwner);
board.getReplies().forEach(reply -> {
boolean isReplyOwner = false;
if(sessionUser != null){
if(reply.getUser().getId() == sessionUser.getId()){
isReplyOwner = true;
}
}
reply.setReplyOwner(isReplyOwner);
});
return board;
}
}
[ BoardController ]
package shop.mtcoding.blog.board;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.errs.exception.Exception403;
import shop.mtcoding.blog._core.errs.exception.Exception404;
import shop.mtcoding.blog._core.utils.ApiUtil;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@RestController
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
// TODO : 글 목록조회 API 필요 -> @GetMapping("/");
@GetMapping("/")
public ResponseEntity<?> main() {
List<Board> boardList = boardService.글목록조회();
return ResponseEntity.ok(new ApiUtil(boardList));
}
// TODO : 글 상세보기 API 필요 -> @GetMapping("/api/boards/{id}/detail")
@GetMapping("/api/boards/{id}/detail")
public ResponseEntity<?> detail(@PathVariable Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board board = boardService.글상세보기(id, sessionUser);
return ResponseEntity.ok(new ApiUtil(board));
}
// TODO : 글 조회 API 필요 -> @GetMapping("/api/boards/{id}")
// 글상세보기 -> 글, 유저 정보, 댓글까지 다 있음 +)동사 추가함 detail
// 글조회 -> 글만 조회 하는거라 글 수정할 때 필요
@GetMapping("/api/boards/{id}")
public ResponseEntity<?> findOne(@PathVariable Integer id) {
Board board = boardService.글조회(id);
return ResponseEntity.ok(new ApiUtil(board));
}
@PutMapping("/api/boards/{id}")
public ResponseEntity<?> update(@PathVariable Integer id, @RequestBody BoardRequest.UpdateDTO requestDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board board = boardService.글수정(id, sessionUser.getId(), requestDTO);
return ResponseEntity.ok(new ApiUtil(board));
}
@DeleteMapping("/api/boards/{id}")
public ResponseEntity<?> delete(@PathVariable Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardService.글삭제(id, sessionUser.getId());
return ResponseEntity.ok(new ApiUtil(null));
}
}
Reply 전체 코드
[ ReplyController ]
package shop.mtcoding.blog.reply;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import shop.mtcoding.blog._core.utils.ApiUtil;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@RestController
public class ReplyController {
private final ReplyService replyService;
private final HttpSession session;
@DeleteMapping("/api/replies/{id}")
public ResponseEntity<?> delete(@PathVariable Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
replyService.댓글삭제(id, sessionUser.getId());
return ResponseEntity.ok(new ApiUtil(null));
}
@PostMapping("/api/replies")
public ResponseEntity<?> save(@RequestBody ReplyRequest.SaveDTO requestDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
Reply reply = replyService.댓글쓰기(requestDTO, sessionUser);
return ResponseEntity.ok(new ApiUtil(reply));
}
}
댓글쓰기는 작성 된 댓글을 돌려줘야함. insert니까 return 바디 값이 들어감!
[ ReplyService ]
package shop.mtcoding.blog.reply;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.errs.exception.Exception403;
import shop.mtcoding.blog._core.errs.exception.Exception404;
import shop.mtcoding.blog.board.Board;
import shop.mtcoding.blog.board.BoardJPARepository;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@Service
public class ReplyService {
private final ReplyJPARepository replyJPARepository;
private final BoardJPARepository boardJPARepository;
@Transactional
public Reply 댓글쓰기(ReplyRequest.SaveDTO requestDTO, User sessionUser) {
Board board = boardJPARepository.findById(requestDTO.getBoardId())
.orElseThrow(() -> new Exception404("없는 게시글에 댓글을 작성할 수 없어요"));
Reply reply = requestDTO.toEntity(sessionUser, board);
return replyJPARepository.save(reply);
}
@Transactional
public void 댓글삭제(int replyId, int sessionUserId) {
Reply reply = replyJPARepository.findById(replyId)
.orElseThrow(() -> new Exception404("없는 댓글을 삭제할 수 없어요"));
if(reply.getUser().getId() != sessionUserId){
throw new Exception403("댓글을 삭제할 권한이 없어요");
}
replyJPARepository.deleteById(replyId);
}
}
나머지 API (TODO 처리한 것들) 만들자
[ UserController ]

@RequiredArgsConstructor
@RestController
public class UserController {
private final UserService userService;
private final HttpSession session;
// TODO: 회원정보 조회 API 필요 -> @GetMapping("/api/users/{id}")
@GetMapping("/api/users/{id}")
public ResponseEntity<?> userinfo(@PathVariable Integer id){
User user = userService.회원조회(id);
return ResponseEntity.ok(new ApiUtil(user));
}
post, put이 아닌데 왜 돌려줘요> get 요청했으니까. 달라고 했으니까요
겟 요청은 원래 요청받은 것을 돌려줘야 함.
포스트, 풋 요청의 경우 새로운 것이 입력되어서 새로운 행이 추가되거나 원래 있던 내용이 수정되어서 행이 변경되는 것이라 무엇을 돌려줄 필요가 없을 수 있으나 프론트엔드가 궁금해할 수 있어서 돌려주라고 함.
정리하자면 겟 요청은 뭔가 달라고 한 것이니라 뭘 돌려주는 것이 인지상정
[ BoardController ]

@RequiredArgsConstructor
@RestController
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
// TODO : 글 목록조회 API 필요 -> @GetMapping("/");
@GetMapping("/")
public ResponseEntity<?> main() {
List<Board> boardList = boardService.글목록조회();
return ResponseEntity.ok(new ApiUtil(boardList));
}
// TODO : 글 상세보기 API 필요 -> @GetMapping("/api/boards/{id}/detail")
@GetMapping("/api/boards/{id}/detail")
public ResponseEntity<?> detail(@PathVariable Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board board = boardService.글상세보기(id, sessionUser);
return ResponseEntity.ok(new ApiUtil(board));
}
// TODO : 글 조회 API 필요 -> @GetMapping("/api/boards/{id}")
// 글상세보기 -> 글, 유저 정보, 댓글까지 다 있음 +)동사 추가함 detail
// 글조회 -> 글만 조회 하는거라 글 수정할 때 필요
@GetMapping("/api/boards/{id}")
public ResponseEntity<?> findOne(@PathVariable Integer id) {
Board board = boardService.글조회(id);
return ResponseEntity.ok(new ApiUtil(board));
}
Share article