Contents
1. 예외(Exception)처리1) GlobalExceptionHandler.java2) GlobalExceptionHandler.java - 수정(알림창 띄우기)3) Exception400.java4) Exception401.java5) Exception403.java6) Exception404.java7) Exception500.java8) GlobalExceptionHandler.java - 수정(Exception 종류별로 메서드 만들기)2. 기존 코드 수정 (예외 처리 반영)1) UserService.java - 예외 처리 수정2) BoardController.java - 예외 처리 수정3) BoardService.jave - 예외 처리 수정4) 테스트3. 댓글이 등록된 게시물 삭제문제 상황1) BoardController.java - Exception500으로 처리2) 오류 조치1. 예외(Exception)처리

1) GlobalExceptionHandler.java
① 코드
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\_core\errors\GlobalExceptionHandler.java
package com.example.boardv1._core.errors;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(exception = RuntimeException.class)
public String ex(RuntimeException e) {
return e.getMessage();
}
}② 테스트

2) GlobalExceptionHandler.java - 수정(알림창 띄우기)
① 코드
package com.example.boardv1._core.errors;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(exception = RuntimeException.class) // 어떤 예외인지 지정하기
public String ex(RuntimeException e) {
String html = String.format("""
<script>
alert('%s');
history.back();
</script>
""", e.getMessage());
return html;
}
}
② 테스트

3) Exception400.java

C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\_core\errors\ex\Exception400.java
package com.example.boardv1._core.errors.ex;
// 유효성검사 실패시 / 중복
public class Exception400 extends RuntimeException {
public Exception400(String message) {
super(message);
}
}4) Exception401.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\_core\errors\ex\Exception401.java
package com.example.boardv1._core.errors.ex;
// 인증 실패시
public class Exception401 extends RuntimeException {
public Exception401(String message) {
super(message);
}
}5) Exception403.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\_core\errors\ex\Exception403.java
package com.example.boardv1._core.errors.ex;
// 권한 실패시
public class Exception403 extends RuntimeException {
public Exception403(String message) {
super(message);
}
}6) Exception404.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\_core\errors\ex\Exception404.java
package com.example.boardv1._core.errors.ex;
// 자원을 찾을 수 없을 때
public class Exception404 extends RuntimeException {
public Exception404(String message) {
super(message);
}
}7) Exception500.java
C:\workspace\spring_lab\boardv1\src\main\java\com\example\boardv1\_core\errors\ex\Exception500.java
package com.example.boardv1._core.errors.ex;
// 서버측 에러(미리 설계할 수 없음) -> 만들어놨다가 나중에 예외가 터지면 잡으면 됨
public class Exception500 extends RuntimeException {
public Exception500(String message) {
super(message);
}
}8) GlobalExceptionHandler.java - 수정(Exception 종류별로 메서드 만들기)
package com.example.boardv1._core.errors;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import com.example.boardv1._core.errors.ex.Exception400;
import com.example.boardv1._core.errors.ex.Exception401;
import com.example.boardv1._core.errors.ex.Exception403;
import com.example.boardv1._core.errors.ex.Exception404;
import com.example.boardv1._core.errors.ex.Exception500;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(exception = Exception400.class) // 어떤 예외인지 지정하기
public String ex400(Exception400 e) {
String html = String.format("""
<script>
alert('%s');
history.back();
</script>
""", e.getMessage());
return html;
}
@ExceptionHandler(exception = Exception401.class) // 어떤 예외인지 지정하기
public String ex401(Exception401 e) {
String html = String.format("""
<script>
alert('%s');
location.href = '/login-form';
</script>
""", e.getMessage());
return html;
}
@ExceptionHandler(exception = Exception403.class) // 어떤 예외인지 지정하기
public String ex403(Exception403 e) {
String html = String.format("""
<script>
alert('%s');
history.back();
</script>
""", e.getMessage());
// 나중에는 log도 남겨야 함
return html;
}
@ExceptionHandler(exception = Exception404.class) // 어떤 예외인지 지정하기
public String ex404(Exception404 e) {
String html = String.format("""
<script>
alert('%s');
history.back();
</script>
""", e.getMessage());
return html;
}
@ExceptionHandler(exception = Exception500.class) // 어떤 예외인지 지정하기
public String ex500(Exception500 e) {
String html = String.format("""
<script>
alert('%s');
history.back();
</script>
""", e.getMessage());
return html;
}
@ExceptionHandler(exception = Exception.class) // 어떤 예외인지 지정하기
public String exUnknown(Exception e) {
String html = String.format("""
<script>
alert('%s');
history.back();
</script>
""", "관리자에게 문의하세요");
System.out.println("error : " + e.getMessage());
// 1. 로그
// 2. SMS 알림 -> e.getMessage()
return html;
}
}
2. 기존 코드 수정 (예외 처리 반영)
1) UserService.java - 예외 처리 수정
package com.example.boardv1.user;
import java.util.Optional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.boardv1._core.errors.ex.Exception400;
import com.example.boardv1._core.errors.ex.Exception401;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
@Transactional
public void 회원가입(String username, String password, String email) {
// 1. username 중복 체크
Optional<User> optUser = userRepository.findByUsername(username);
if (optUser.isPresent())
throw new Exception400("유저네임이 중복되었습니다"); // 정확히는 409! 일단 400으로 처리
// 2. 비영속 객체
User user = new User();
user.setUsername(username);
user.setPassword(password);
user.setEmail(email);
// 3. save() 호출
userRepository.save(user);
}
public User 로그인(String username, String password) {
User findUser = userRepository.findByUsername(username)
.orElseThrow(() -> new Exception401("username을 찾을 수 없어요"));
if (!findUser.getPassword().equals(password))
throw new Exception401("패스워드가 일치하지 않아요");
return findUser;
}
}2) BoardController.java - 예외 처리 수정
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Controller;
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.Exception401;
import com.example.boardv1.reply.ReplyRequest;
import com.example.boardv1.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
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(BoardRequest.SaveOrUpdateDTO reqDTO){ // new해서 넣어줌! 필드가 많을 때 상태만 추가하면 되기 때문에 매우 편함! 재사용 가능!(필드명 잘 적어야 함)
// 인증(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, BoardRequest.SaveOrUpdateDTO reqDTO){
// 인증(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("인증되지 않았습니다.");
boardService.게시글삭제(id, sessionUser.getId());
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;
}
}
3) BoardService.jave - 예외 처리 수정
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.boardv1._core.errors.ex.Exception403;
import com.example.boardv1._core.errors.ex.Exception404;
import com.example.boardv1.reply.Reply;
import com.example.boardv1.user.User;
import com.example.boardv1.user.UserRepository;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
// 책임 : 트랜잭션 관리 + DTO 만들기 + 권한 체크(DB정보가 필요하기 때문)
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardRepository boardRepository;
private final EntityManager em;
public List<Board> 게시글목록() {
return boardRepository.findAll();
}
public Board 수정폼게시글정보(int id, int sessionUserId) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없어요"));
// 권한
if (sessionUserId != board.getUser().getId())
throw new Exception403("수정할 권한이 없습니다");
return board;
}
public BoardResponse.DetailDTO 상세보기(int id, Integer sessionUserId) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없어요"));
return new BoardResponse.DetailDTO(board, sessionUserId);
}
@Transactional // update, delete, insert 할때 붙이세요!!
public void 게시글수정(int id, String title, String content, int sessionUserId) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("수정할 게시글을 찾을 수 없어요"));
// 권한
if (sessionUserId != board.getUser().getId()) {
throw new Exception403("수정할 권한이 없습니다");
}
board.setTitle(title);
board.setContent(content);
}
// 원자성(모든게 다되면 commit, 하나라도 실패하면 rollback)
// 트랜잭션 종료시 flush 됨.
@Transactional
public void 게시글쓰기(String title, String content, int sessionUserId) {
User user = em.getReference(User.class, sessionUserId);
// 1. 비영속 객체
Board board = new Board();
board.setTitle(title);
board.setContent(content);
board.setUser(user);
System.out.println("before persist " + board.getId());
// 2. persist
boardRepository.save(board);
System.out.println("after persist " + board.getId());
}
@Transactional
public void 게시글삭제(int id, int sessionUserId) {
// 영속화
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("삭제할 게시글을 찾을 수 없어요"));
// 권한
if (sessionUserId != board.getUser().getId())
throw new Exception403("삭제할 권한이 없습니다");
boardRepository.delete(board);
} // 자동 flush
}4) 테스트
① 중복된 username으로 회원 가입

② 로그인 시 username 잘못 입력

③ 로그인 시 password 잘못 입력

④ 다른 유저가 등록한 게시글 수정 요청

⑥ 등록되지 않은 게시글 요청

3. 댓글이 등록된 게시물 삭제
문제 상황
댓글이 등록된 게시물은 삭제 불가 상태!


1) BoardController.java - Exception500으로 처리
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Controller;
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.Exception401;
import com.example.boardv1._core.errors.ex.Exception500;
import com.example.boardv1.reply.ReplyRequest;
import com.example.boardv1.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
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(BoardRequest.SaveOrUpdateDTO reqDTO){ // new해서 넣어줌! 필드가 많을 때 상태만 추가하면 되기 때문에 매우 편함! 재사용 가능!(필드명 잘 적어야 함)
// 인증(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, BoardRequest.SaveOrUpdateDTO reqDTO){
// 인증(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;
}
}

2) 오류 조치
① 조치방법

reply는 board에 의존하고 있기 때문에
6번 board에 의존 중인 3개의 reply의 board_id를 null로 update하면 삭제 가능
② BoardService.java - 수정(게시글 삭제할 때 댓글 업데이트)
package com.example.boardv1.board;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.boardv1._core.errors.ex.Exception403;
import com.example.boardv1._core.errors.ex.Exception404;
import com.example.boardv1._core.errors.ex.Exception500;
import com.example.boardv1.reply.Reply;
import com.example.boardv1.user.User;
import com.example.boardv1.user.UserRepository;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
// 책임 : 트랜잭션 관리 + DTO 만들기 + 권한 체크(DB정보가 필요하기 때문)
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardRepository boardRepository;
private final EntityManager em;
public List<Board> 게시글목록() {
return boardRepository.findAll();
}
public Board 수정폼게시글정보(int id, int sessionUserId) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없어요"));
// 권한
if (sessionUserId != board.getUser().getId())
throw new Exception403("수정할 권한이 없습니다");
return board;
}
public BoardResponse.DetailDTO 상세보기(int id, Integer sessionUserId) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없어요"));
return new BoardResponse.DetailDTO(board, sessionUserId);
}
@Transactional // update, delete, insert 할때 붙이세요!!
public void 게시글수정(int id, String title, String content, int sessionUserId) {
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("수정할 게시글을 찾을 수 없어요"));
// 권한
if (sessionUserId != board.getUser().getId()) {
throw new Exception403("수정할 권한이 없습니다");
}
board.setTitle(title);
board.setContent(content);
}
// 원자성(모든게 다되면 commit, 하나라도 실패하면 rollback)
// 트랜잭션 종료시 flush 됨.
@Transactional
public void 게시글쓰기(String title, String content, int sessionUserId) {
User user = em.getReference(User.class, sessionUserId);
// 1. 비영속 객체
Board board = new Board();
board.setTitle(title);
board.setContent(content);
board.setUser(user);
System.out.println("before persist " + board.getId());
// 2. persist
boardRepository.save(board);
System.out.println("after persist " + board.getId());
}
@Transactional
public void 게시글삭제(int id, int sessionUserId) {
// 영속화 (EAGER전략 -> join)
Board board = boardRepository.findById(id)
.orElseThrow(() -> new Exception404("삭제할 게시글을 찾을 수 없어요"));
// 권한
if (sessionUserId != board.getUser().getId())
throw new Exception403("삭제할 권한이 없습니다");
board.getReplies().forEach(r -> {
r.setBoard(null);
});
boardRepository.delete(board);
} // 자동 flush
}③ 테스트


Share article