12-3. (실습) 스프링 V3 (유효성검사 V1)

박은서's avatar
Feb 24, 2026
12-3. (실습) 스프링 V3 (유효성검사 V1)

1. 유효성 검사 V1

1) build.gradle 에 추가

implementation 'org.springframework.boot:spring-boot-starter-validation'

2) BoardRequest.java

package com.example.boardv1.board; import jakarta.validation.constraints.NotBlank; import lombok.Data; public class BoardRequest { // 책임 : 클라이언트(브라우저)의 요청 데이터를 저장하는 클래스 @Data public static class SaveOrUpdateDTO { @NotBlank(message = "제목을 입력해주세요") private String title; @NotBlank(message = "내용을 입력해주세요") private String content; } }

3) UserRequest.java

package com.example.boardv1.user; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; public class UserRequest { @Data public static class JoinDTO { @NotBlank(message = "유저네임을 입력해주세요") @Size(min = 3, max = 20, message = "유저네임은 3~20자로 입력해주세요") private String username; @NotBlank(message = "비밀번호을 입력해주세요") @Size(min = 4, max = 20, message = "비밀번호는 4~20자로 입력해주세요") private String password; @NotBlank(message = "이메일 형식이 올바르지 않습니다") private String email; } @Data public static class LoginDTO { @NotBlank(message = "유저네임을 입력해주세요") private String username; @NotBlank(message = "비밀번호을 입력해주세요") private String password; } }

4) ReplyRequest.java

package com.example.boardv1.reply; import jakarta.validation.constraints.NotBlank; import lombok.Data; public class ReplyRequest { @Data public static class SaveDTO { private Integer boardId; @NotBlank(message = "댓글 내용을 입력하세요") private String comment; } @Data public static class DeleteDTO { private Integer boardId; } }

5) BoardController.java

package com.example.boardv1.board; import java.util.List; import org.springframework.stereotype.Controller; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.example.boardv1._core.errors.ex.Exception400; import com.example.boardv1._core.errors.ex.Exception401; import com.example.boardv1._core.errors.ex.Exception500; import com.example.boardv1.user.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor // final이 적혀있는 애로 생성자 만들어줌 @Controller // @Controller 적어야 리턴값이 파일이 됨 (외부진입점) public class BoardController { private final BoardService boardService; private final HttpSession session; // title : title=title7&content=content7 (x-www-form) @PostMapping("/boards/save") public String save(@Valid BoardRequest.SaveOrUpdateDTO reqDTO, Errors errors) { // new해서 넣어줌! 필드가 많을 때 상태만 추가하면 되기 때문에 매우 편함! 재사용 가능!(필드명 잘 적어야 함) // 유효성 검사 실패 시 if (errors.hasErrors()) { throw new Exception400(errors.getAllErrors().get(0).getDefaultMessage()); } // 인증(v) 권한(x) // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new Exception401("인증되지 않았습니다."); boardService.게시글쓰기(reqDTO.getTitle(), reqDTO.getContent(), sessionUser.getId()); return "redirect:/"; } // body : title=제목&content=내용 @PostMapping("/boards/{id}/update") public String update(@PathVariable("id") int id, @Valid BoardRequest.SaveOrUpdateDTO reqDTO, Errors errors) { // 유효성 검사 실패 시 if (errors.hasErrors()) { throw new Exception400(errors.getAllErrors().get(0).getDefaultMessage()); } // 인증(v) 권한(v) // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new Exception401("인증되지 않았습니다."); boardService.게시글수정(id, reqDTO.getTitle(), reqDTO.getContent(), sessionUser.getId()); return "redirect:/boards/" + id; } @GetMapping("/") public String index(HttpServletRequest req) { List<Board> list = boardService.게시글목록(); req.setAttribute("models", list); return "index"; } @GetMapping("/boards/save-form") public String saveForm() { // 인증(v) 권한(x) // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new Exception401("인증되지 않았습니다."); return "board/save-form"; } @GetMapping("/boards/{id}/update-form") public String updateForm(@PathVariable("id") int id, HttpServletRequest req) { // 인증(v) 권한(v) // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new Exception401("인증되지 않았습니다."); Board board = boardService.수정폼게시글정보(id, sessionUser.getId()); req.setAttribute("model", board); return "board/update-form"; } @GetMapping("/boards/{id}") public String detail(@PathVariable("id") int id, HttpServletRequest req) { User sessionUser = (User) session.getAttribute("sessionUser"); // Object타입으로 반환하기 때문에 다운캐스팅해야 함 Integer sessionUserId = sessionUser == null ? null : sessionUser.getId(); BoardResponse.DetailDTO dto = boardService.상세보기(id, sessionUserId); req.setAttribute("model", dto); return "board/detail"; // mustache 파일의 경로 } @PostMapping("/boards/{id}/delete") public String delete(@PathVariable("id") int id) { // 인증(v) 권한(v) // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new Exception401("인증되지 않았습니다."); try { boardService.게시글삭제(id, sessionUser.getId()); } catch (Exception e) { throw new Exception500("댓글이 있는 게시글을 삭제할 수 없습니다"); } return "redirect:/"; } @GetMapping("/api/boards/{id}") public @ResponseBody BoardResponse.DetailDTO apiDetail(@PathVariable("id") int id) { User sessionUser = (User) session.getAttribute("sessionUser"); Integer sessionUserId = sessionUser == null ? null : sessionUser.getId(); BoardResponse.DetailDTO dto = boardService.상세보기(id, sessionUserId); return dto; } }

6) UserController.java

package com.example.boardv1.user; import org.springframework.stereotype.Controller; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.example.boardv1._core.errors.ex.Exception400; import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Controller public class UserController { private final UserService userService; private final HttpSession session; @GetMapping("/logout") public String logout(){ session.invalidate(); // sessionKey 삭제 return "redirect:/"; } // 조회인데, 예외로 post 요청 @PostMapping("/login") public String login(@Valid UserRequest.LoginDTO reqDTO, Errors errors, HttpServletResponse resp) { // 유효성 검사 실패 시 if (errors.hasErrors()) { throw new Exception400(errors.getAllErrors().get(0).getDefaultMessage()); } // HttpSession session = req.getSession(); User sessionUser = userService.로그인(reqDTO.getUsername(), reqDTO.getPassword()); session.setAttribute("sessionUser", sessionUser); // http Response header에 Set-Cookie: sessionKey 저장되서 응답됨. Cookie cookie = new Cookie("username", sessionUser.getUsername()); cookie.setHttpOnly(false); resp.addCookie(cookie); return "redirect:/"; } @PostMapping("/join") public String join(@Valid UserRequest.JoinDTO reqDTO, Errors errors){ // 유효성 검사 실패 시 if (errors.hasErrors()) { throw new Exception400(errors.getAllErrors().get(0).getDefaultMessage()); } userService.회원가입(reqDTO.getUsername(), reqDTO.getPassword(), reqDTO.getEmail()); return "redirect:/login-form"; } @GetMapping("/login-form") public String loginForm(){ return "user/login-form"; } @GetMapping("/join-form") public String joinForm(){ return "user/join-form"; } }

7) ReplyController.java

package com.example.boardv1.reply; import org.springframework.stereotype.Controller; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import com.example.boardv1._core.errors.ex.Exception400; import com.example.boardv1.user.User; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @Controller public class ReplyController { private final ReplyService replyService; private final HttpSession session; @PostMapping("/replies/save") public String save(@Valid ReplyRequest.SaveDTO reqDTO, Errors errors){ // 유효성 검사 실패 시 if (errors.hasErrors()) { throw new Exception400(errors.getAllErrors().get(0).getDefaultMessage()); } // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증되지 않았습니다."); int sessionUserId = sessionUser.getId(); replyService.댓글등록(sessionUserId, reqDTO.getBoardId(), reqDTO.getComment()); return "redirect:/boards/" + reqDTO.getBoardId(); } // /replies/5/delete?boardId=2 @PostMapping("/replies/{id}/delete") public String delete(@PathVariable("id") int id, @RequestParam("boardId") int boardId){ // 인증 User sessionUser = (User) session.getAttribute("sessionUser"); if (sessionUser == null) throw new RuntimeException("인증되지 않았습니다."); replyService.댓글삭제(id, sessionUser.getId()); return "redirect:/boards/" + boardId; } }
 
Share article