[Spring Boot] 11. Spring Boot Project (Blog v2 - jpa)_10.Exception (Custom)
Jul 22, 2025
401 → 인증오류
GlobalExceptionHandler
❗ LoveController는 Ajax 통신이므로 javascript가 아닌 json 형식으로 return 필요
package shop.mtcoding.blog._core.error;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.blog._core.error.ex.*;
import shop.mtcoding.blog._core.util.Resp;
@RestControllerAdvice // data return
public class GlobalExceptionHandler {
//401 -> 인증 안됨
@ExceptionHandler(Exception401.class)
public String ex401(Exception401 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
location.href="/login-form";
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi401.class)
public Resp<?> exApi401(ExceptionApi401 e) {
// catch 자리
return Resp.fail(401, e.getMessage());
}
}
Exception401
package shop.mtcoding.blog._core.error.ex; public class Exception401 extends RuntimeException { public Exception401(String message) { super(message); } }
ExceptionApi401
package shop.mtcoding.blog._core.error.ex; public class ExceptionApi401 extends RuntimeException { public ExceptionApi401(String message) { super(message); } }
UserController
package shop.mtcoding.blog.user;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
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 shop.mtcoding.blog._core.error.ex.Exception401;
import shop.mtcoding.blog._core.util.Resp;
import java.util.Map;
@RequiredArgsConstructor
@Controller
public class UserController {
private final UserService userService;
private final HttpSession session;
@GetMapping("/join-form")
public String joinForm() {
return "user/join-form";
}
@PostMapping("/join")
public String join(UserRequest.JoinDTO joinDTO) {
userService.회원가입(joinDTO);
return "redirect:/login-form";
}
@GetMapping("/check-username-available/{username}")
public @ResponseBody Resp<?> checkUsernameAvailable(@PathVariable("username") String username) {
Map<String, Object> dto = userService.유저네임중복체크(username);
return Resp.ok(dto);
}
@GetMapping("/login-form")
public String loginForm() {
return "user/login-form";
}
@PostMapping("/login")
public String login(UserRequest.LoginDTO loginDTO, HttpServletResponse response) {
User sessionUser = userService.로그인(loginDTO);
session.setAttribute("sessionUser", sessionUser);
if (loginDTO.getRememberMe() == null) {
Cookie cookie = new Cookie("username", null);
cookie.setMaxAge(0); // 즉시 삭제
response.addCookie(cookie);
} else {
Cookie cookie = new Cookie("username", loginDTO.getUsername());
cookie.setMaxAge(60 * 60 * 24 * 7); // cookie 하루동안 유지
response.addCookie(cookie);
}
return "redirect:/";
}
@GetMapping("/logout")
public String logout() {
session.invalidate();
return "redirect:/login-form";
}
@GetMapping("/user/update-form")
public String updateForm() {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다.");
// ViewResolver -> prefix = /templates/ suffix = .mustache
return "user/update-form";
}
@PostMapping("/user/update")
public String update(UserRequest.UpdateDTO updateDTO) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다.");
User user = userService.회원정보수정(updateDTO, sessionUser.getId());
// session 동기화 -> 동기화 안해주면 바꾸기 전 정보를 보게 된다
session.setAttribute("sessionUser", user);
return "redirect:/";
}
}
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.error.ex.Exception400;
import shop.mtcoding.blog._core.error.ex.Exception401;
import shop.mtcoding.blog._core.error.ex.Exception404;
import java.util.HashMap;
import java.util.Map;
// 비즈니스 로직, 트랜잭션 처리, DTO 완료
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
// 401 > 인증 403 > 권한 404 > 자원 못찾음
@Transactional
public void 회원가입(UserRequest.JoinDTO joinDTO) {
// 동일회원 있는지 검사
try {
userRepository.save(joinDTO.toEntity());
} catch (Exception e) {
//Exceptiom 400 (Bad request -> 잘못된 요청입니다)
throw new Exception400("동일한 username 이 존재합니다.");
}
// 회원가입
userRepository.save(joinDTO.toEntity());
}
public User 로그인(UserRequest.LoginDTO loginDTO) {
// username,password 검사
User user = userRepository.findByUsername(loginDTO.getUsername());
if (user == null) throw new Exception401("username 혹은 password 가 일치하지 않습니다");
if (!user.getPassword().equals(loginDTO.getPassword())) {
throw new Exception401("username 혹은 password 가 일치하지 않습니다");
}
// 로그인
return user;
}
public Map<String, Object> 유저네임중복체크(String username) {
User user = userRepository.findByUsername(username);
Map<String, Object> dto = new HashMap<>();
if (user == null) {
dto.put("available", true);
} else {
dto.put("available", false);
}
return dto;
}
@Transactional
public User 회원정보수정(UserRequest.UpdateDTO updateDTO, Integer userId) {
User user = userRepository.findById(userId);
//Exception404
if (user == null) throw new Exception404("회원을 찾을 수 없습니다");
user.update(updateDTO.getPassword(), updateDTO.getEmail()); // 영속화된 객체의 상태변경
return user;
} // 함수 종료될 때 더티 체킹(Dirty Checking) -> 상태가 변경되면 변경된 data를 가지고 update을 한다
}
BoardController
package shop.mtcoding.blog.board;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
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 shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
private final HttpSession session;
@GetMapping("/")
public String list(HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) {
request.setAttribute("models", boardService.글목록보기(null));
} else {
request.setAttribute("models", boardService.글목록보기(sessionUser.getId()));
}
return "board/list";
}
@PostMapping("/board/save")
public String save(BoardRequest.SaveDTO saveDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardService.글쓰기(saveDTO, sessionUser);
return "redirect:/";
}
@GetMapping("/board/save-form")
public String saveForm() {
return "board/save-form";
}
@GetMapping("/board/{id}")
public String detail(@PathVariable("id") Integer id, HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
// 비로그인 시 상세보기
Integer sessionUserId = (sessionUser == null ? null : sessionUser.getId());
BoardResponse.DetailDTO detailDTO = boardService.글상세보기(id, sessionUserId);
request.setAttribute("model", detailDTO);
return "board/detail";
}
@GetMapping("/board/{id}/update-form")
public String updateForm(@PathVariable("id") Integer id, HttpServletRequest request) {
User sessionUser = (User) session.getAttribute("sessionUser");
Board board = boardService.업데이트글보기(id, sessionUser.getId());
request.setAttribute("model", board);
return "board/update-form";
}
@PostMapping("/board/{id}/update")
public String update(@PathVariable("id") Integer id, BoardRequest.UpdateDTO reqDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardService.게시글수정(reqDTO, id, sessionUser.getId());
return "redirect:/board/" + id;
}
@PostMapping("/board/{id}/delete")
public String delete(@PathVariable("id") Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
boardService.게시글삭제(id, sessionUser.getId());
return "redirect:/";
}
}
LoveController
package shop.mtcoding.blog.love;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import shop.mtcoding.blog._core.util.Resp;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@RestController // 파일이 아닌 데이터 리턴하는 컨트롤러 (Ajax)
public class LoveController {
private final LoveService loveService;
private final HttpSession session;
@PostMapping("/love")
public Resp<?> saveLove(@RequestBody LoveRequest.SaveDTO reqDTO) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new ExceptionApi401("인증이 필요합니다.");
LoveResponse.SaveDTO respDTO = loveService.좋아요(reqDTO, sessionUser.getId());
return Resp.ok(respDTO);
}
@DeleteMapping("/love/{id}")
public Resp<?> deleteLove(@PathVariable("id") Integer id) {
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new ExceptionApi401("인증이 필요합니다.");
LoveResponse.DeleteDTO respDTO = loveService.좋아요취소(id, sessionUser.getId()); // loveId
return Resp.ok(respDTO);
}
}
detail
{{> layout/header}}
<input type="hidden" id="boardId" value="{{model.id}}">
<div class="container p-5">
<!-- 수정삭제버튼 -->
{{#model.isOwner}}
<div class="d-flex justify-content-end">
<a href="/board/{{model.id}}/update-form" class="btn btn-warning me-1">수정</a>
<form action="/board/{{model.id}}/delete" method="post">
<button class="btn btn-danger">삭제</button>
</form>
</div>
{{/model.isOwner}}
<div class="d-flex justify-content-end">
<b>작성자</b> : {{model.username}}
</div>
<!-- 게시글내용 -->
<div>
<h2><b>{{model.title}}</b></h2>
<hr/>
<div class="m-4 p-2">
{{model.content}}
</div>
</div>
<!-- AJAX 좋아요 영역 -->
<div class="my-3 d-flex align-items-center">
{{#model.isLove}}
<i id="loveIcon" class="fa fa-heart" style="font-size:20px; color:red"
onclick="deleteLove({{model.loveId}})"></i>
{{/model.isLove}}
{{^model.isLove}}
<i id="loveIcon" class="fa fa-heart" style="font-size:20px; color:black"
onclick="saveLove()"></i>
{{/model.isLove}}
<span class="ms-1"><b id="loveCount">{{model.loveCount}}</b>명이 이 글을 좋아합니다</span>
</div>
<!-- 댓글 -->
<div class="card mt-3">
<!-- 댓글등록 -->
<div class="card-body">
<form action="/reply/save" method="post">
<input type="hidden" name="boardId" value="{{model.id}}">
<textarea class="form-control" rows="2" name="content"></textarea>
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
</div>
</form>
</div>
<!-- 댓글목록 -->
<div class="card-footer">
<b>댓글리스트</b>
</div>
<div class="list-group">
<!-- 댓글아이템 -->
{{#model.replies}}
<div class="list-group-item d-flex justify-content-between align-items-center">
<div class="d-flex">
<div class="px-1 me-1 bg-primary text-white rounded">{{username}}</div>
<div>{{content}}</div>
</div>
{{#isOwner}}
<form action="/reply/{{id}}/delete" method="post">
<button type="submit" class="btn">🗑</button>
</form>
{{/isOwner}}
</div>
{{/model.replies}}
</div>
</div>
</div>
<script>
// setInterval(()=>{
// location.reload();
// },1000)
let boardId = document.querySelector("#boardId").value;
async function saveLove() {
let requestBody = {boardId: boardId};
let response = await fetch(`/love`, {
method: "POST",
body: JSON.stringify(requestBody),
headers: {"Content-Type": "application/json"}
});
let responseBody = await response.json(); // { loveId, loveCount }
console.log(responseBody);
if (responseBody.status != 200) {
alert(responseBody.msg);
} else {
// DOM 업데이트
let loveIcon = document.querySelector('#loveIcon');
let loveCount = document.querySelector('#loveCount');
loveIcon.style.color = 'red';
loveIcon.setAttribute('onclick', `deleteLove(${responseBody.body.loveId})`);
loveCount.innerHTML = responseBody.body.loveCount;
}
}
async function deleteLove(loveId) {
let response = await fetch(`/love/${loveId}`, {
method: "DELETE"
});
let responseBody = await response.json(); // response.text(); (X)
console.log(responseBody);
// DOM 업데이트
let loveIcon = document.querySelector('#loveIcon');
let loveCount = document.querySelector('#loveCount');
loveIcon.style.color = 'black';
loveIcon.setAttribute('onclick', `saveLove()`);
loveCount.innerHTML = responseBody.body.loveCount;
}
</script>
{{> layout/footer}}
ReplyController
package shop.mtcoding.blog.reply;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import shop.mtcoding.blog._core.error.ex.Exception401;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@Controller
public class ReplyController {
private final ReplyService replyService;
private final HttpSession session;
@PostMapping("/reply/save")
public String save(ReplyRequest.SaveDTO reqDTO) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다.");
replyService.댓글쓰기(reqDTO, sessionUser);
return "redirect:/board/" + reqDTO.getBoardId();
}
@PostMapping("/reply/{id}/delete")
public String delete(@PathVariable("id") Integer id) {
// 인증로직
User sessionUser = (User) session.getAttribute("sessionUser");
if (sessionUser == null) throw new Exception401("인증이 필요합니다.");
Integer boardId = replyService.댓글삭제(id, sessionUser.getId());
replyService.댓글삭제(id, sessionUser.getId());
return "redirect:/board/" + boardId;
}
}
1. 바로 /board/save-form으로 가면

→ alert 창이 뜬다
2. 바로 /user/update-form으로 가면

→ alert 창이 뜬다
3. 로그인 안 한 상태로 좋아요 누르면

→ alert 창이 뜬다
403 → 권한오류
GlobalExceptionHandler
package shop.mtcoding.blog._core.error;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.blog._core.error.ex.*;
import shop.mtcoding.blog._core.util.Resp;
@RestControllerAdvice // data return
public class GlobalExceptionHandler {
//401 -> 인증 안됨
@ExceptionHandler(Exception401.class)
public String ex401(Exception401 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
location.href="/login-form";
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi401.class)
public Resp<?> exApi401(ExceptionApi401 e) {
// catch 자리
return Resp.fail(401, e.getMessage());
}
// 403 -> 권한 없음
@ExceptionHandler(Exception403.class)
public String ex403(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi403.class)
public Resp<?> exApi403(ExceptionApi403 e) {
// catch 자리
return Resp.fail(403, e.getMessage());
}
}
Exception403
package shop.mtcoding.blog._core.error.ex; public class Exception403 extends RuntimeException { public Exception403(String message) { super(message); } }
ExceptionApi403
package shop.mtcoding.blog._core.error.ex; public class ExceptionApi403 extends RuntimeException { public ExceptionApi403(String message) { super(message); } }
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.error.ex.Exception403;
import shop.mtcoding.blog._core.error.ex.Exception404;
import shop.mtcoding.blog.love.Love;
import shop.mtcoding.blog.love.LoveRepository;
import shop.mtcoding.blog.reply.ReplyRepository;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardRepository boardRepository;
private final LoveRepository loveRepository;
private final ReplyRepository replyRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUserAndReplies(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id);
Boolean isLove = love == null ? false : true;
Integer loveId = love == null ? null : love.getId();
Long loveCount = loveRepository.findByBoardId(board.getId());
BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(board, userId, isLove, loveCount.intValue(), loveId);
return detailDTO;
}
public Board 업데이트글보기(Integer id, Integer sessionUserId) {
Board boardPs = boardRepository.findById(id);
if (boardPs == null) throw new Exception404("게시글을 찾을 수 없습니다.");
if (!boardPs.getUser().getId().equals(sessionUserId)) throw new Exception403("권한이 없습니다.");
return boardPs;
}
@Transactional
public Board 게시글수정(BoardRequest.UpdateDTO reqDTO, Integer id, Integer sessionUserId) {
Board board = boardRepository.findById(id);
if (board == null) throw new Exception404("게시글을 찾을 수 없습니다");
if (!board.getUser().getId().equals(sessionUserId)) throw new Exception403("권한이 없습니다.");
board.update(reqDTO.getTitle(), reqDTO.getContent(), reqDTO.getIsPublic());
return board;
}
@Transactional
public void 게시글삭제(Integer id, Integer sessionUserId) {
// 게시글 존재 확인
Board boardPs = boardRepository.findById(id);
if (boardPs == null) throw new Exception404("게시글을 찾을 수 없습니다");
if (!boardPs.getUser().getId().equals(sessionUserId)) throw new Exception403("권한이 없습니다.");
// 좋아요 삭제
loveRepository.deleteByBoardId(boardPs.getId());
boardRepository.deleteById(id);
}
}
LoveService
package shop.mtcoding.blog.love;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.error.ex.ExceptionApi403;
import shop.mtcoding.blog._core.error.ex.ExceptionApi404;
import shop.mtcoding.blog.board.BoardRepository;
import shop.mtcoding.blog.user.UserRepository;
@RequiredArgsConstructor
@Service
public class LoveService {
private final LoveRepository loveRepository;
private final BoardRepository boardRepository;
private final UserRepository userRepository;
@Transactional
public LoveResponse.SaveDTO 좋아요(LoveRequest.SaveDTO reqDTO, Integer sessionUserId) {
Love lovePs = loveRepository.save(reqDTO.toEntity(sessionUserId));
Long loveCount = loveRepository.findByBoardId(reqDTO.getBoardId());
return new LoveResponse.SaveDTO(lovePs.getId(), loveCount.intValue());
}
@Transactional
public LoveResponse.DeleteDTO 좋아요취소(Integer id, Integer sessionUserId) {
Love lovePs = loveRepository.findById(id);
if (lovePs == null) throw new ExceptionApi404("취소할 좋아요가 없습니다."); // ExceptionApi404
// 권한체크 (lovePs.gerUser().getId() 비교 sessionUserId)
if (!lovePs.getUser().getId().equals(sessionUserId)) {
throw new ExceptionApi403("권한이 없습니다."); // ExceptionApi403
}
Integer boardId = lovePs.getBoard().getId();
loveRepository.deleteById(id);
Long loveCount = loveRepository.findByBoardId(boardId);
return new LoveResponse.DeleteDTO(loveCount.intValue());
}
}
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.error.ex.Exception403;
import shop.mtcoding.blog._core.error.ex.Exception404;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@Service
public class ReplyService {
private final ReplyRepository replyRepository;
@Transactional
public void 댓글쓰기(ReplyRequest.SaveDTO reqDTO, User sessionUser) {
replyRepository.save(reqDTO.toEntity(sessionUser));
}
@Transactional
public Integer 댓글삭제(Integer id, Integer sessionUserId) {
Reply replyPs = replyRepository.findById(id);
if (replyPs == null) throw new Exception404("삭제할 댓글을 찾을 수 없습니다."); // 404
if (!(replyPs.getUser().getId().equals(sessionUserId))) throw new Exception403("삭제할 권한이 없습니다.");
Integer boardId = replyPs.getBoard().getId();
replyRepository.deleteById(id);
return boardId;
}
}
404 → 자원을 찾을 수 없음
GlobalExceptionHandler
package shop.mtcoding.blog._core.error;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.blog._core.error.ex.*;
import shop.mtcoding.blog._core.util.Resp;
@RestControllerAdvice // data return
public class GlobalExceptionHandler {
//401 -> 인증 안됨
@ExceptionHandler(Exception401.class)
public String ex401(Exception401 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
location.href="/login-form";
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi401.class)
public Resp<?> exApi401(ExceptionApi401 e) {
// catch 자리
return Resp.fail(401, e.getMessage());
}
// 403 -> 권한 없음
@ExceptionHandler(Exception403.class)
public String ex403(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi403.class)
public Resp<?> exApi403(ExceptionApi403 e) {
// catch 자리
return Resp.fail(403, e.getMessage());
}
// 404 -> 자원을 찾을 수 없음
@ExceptionHandler(Exception404.class)
public String ex404(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi404.class)
public Resp<?> exApi404(ExceptionApi404 e) {
// catch 자리
return Resp.fail(404, e.getMessage());
}
}
Exception404
package shop.mtcoding.blog._core.error.ex; public class Exception404 extends RuntimeException { public Exception404(String message) { super(message); } }
ExceptionApi404
package shop.mtcoding.blog._core.error.ex; public class ExceptionApi404 extends RuntimeException { public ExceptionApi404(String message) { super(message); } }
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.error.ex.Exception400;
import shop.mtcoding.blog._core.error.ex.Exception401;
import shop.mtcoding.blog._core.error.ex.Exception404;
import java.util.HashMap;
import java.util.Map;
// 비즈니스 로직, 트랜잭션 처리, DTO 완료
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
// 401 > 인증 403 > 권한 404 > 자원 못찾음
@Transactional
public void 회원가입(UserRequest.JoinDTO joinDTO) {
// 동일회원 있는지 검사
try {
userRepository.save(joinDTO.toEntity());
} catch (Exception e) {
//Exceptiom 400 (Bad request -> 잘못된 요청입니다)
throw new Exception400("동일한 username 이 존재합니다.");
}
// 회원가입
userRepository.save(joinDTO.toEntity());
}
public User 로그인(UserRequest.LoginDTO loginDTO) {
// username,password 검사
User user = userRepository.findByUsername(loginDTO.getUsername());
if (user == null) throw new Exception401("username 혹은 password 가 일치하지 않습니다");
if (!user.getPassword().equals(loginDTO.getPassword())) {
throw new Exception401("username 혹은 password 가 일치하지 않습니다");
}
// 로그인
return user;
}
public Map<String, Object> 유저네임중복체크(String username) {
User user = userRepository.findByUsername(username);
Map<String, Object> dto = new HashMap<>();
if (user == null) {
dto.put("available", true);
} else {
dto.put("available", false);
}
return dto;
}
@Transactional
public User 회원정보수정(UserRequest.UpdateDTO updateDTO, Integer userId) {
User user = userRepository.findById(userId);
//Exception404
if (user == null) throw new Exception404("회원을 찾을 수 없습니다");
user.update(updateDTO.getPassword(), updateDTO.getEmail()); // 영속화된 객체의 상태변경
return user;
} // 함수 종료될 때 더티 체킹(Dirty Checking) -> 상태가 변경되면 변경된 data를 가지고 update을 한다
}
BoardService
package shop.mtcoding.blog.board;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.error.ex.Exception403;
import shop.mtcoding.blog._core.error.ex.Exception404;
import shop.mtcoding.blog.love.Love;
import shop.mtcoding.blog.love.LoveRepository;
import shop.mtcoding.blog.reply.ReplyRepository;
import shop.mtcoding.blog.user.User;
import java.util.List;
@RequiredArgsConstructor
@Service
public class BoardService {
private final BoardRepository boardRepository;
private final LoveRepository loveRepository;
private final ReplyRepository replyRepository;
@Transactional
public void 글쓰기(BoardRequest.SaveDTO saveDTO, User sessionUser) {
Board board = saveDTO.toEntity(sessionUser);
boardRepository.save(board);
}
public List<Board> 글목록보기(Integer userId) {
return boardRepository.findAll(userId);
}
public BoardResponse.DetailDTO 글상세보기(Integer id, Integer userId) {
Board board = boardRepository.findByIdJoinUserAndReplies(id);
Love love = loveRepository.findByUserIdAndBoardId(userId, id);
Boolean isLove = love == null ? false : true;
Integer loveId = love == null ? null : love.getId();
Long loveCount = loveRepository.findByBoardId(board.getId());
BoardResponse.DetailDTO detailDTO = new BoardResponse.DetailDTO(board, userId, isLove, loveCount.intValue(), loveId);
return detailDTO;
}
public Board 업데이트글보기(Integer id, Integer sessionUserId) {
Board boardPs = boardRepository.findById(id);
if (boardPs == null) throw new Exception404("게시글을 찾을 수 없습니다.");
if (!boardPs.getUser().getId().equals(sessionUserId)) throw new Exception403("권한이 없습니다.");
return boardPs;
}
@Transactional
public Board 게시글수정(BoardRequest.UpdateDTO reqDTO, Integer id, Integer sessionUserId) {
Board board = boardRepository.findById(id);
if (board == null) throw new Exception404("게시글을 찾을 수 없습니다");
if (!board.getUser().getId().equals(sessionUserId)) throw new Exception403("권한이 없습니다.");
board.update(reqDTO.getTitle(), reqDTO.getContent(), reqDTO.getIsPublic());
return board;
}
@Transactional
public void 게시글삭제(Integer id, Integer sessionUserId) {
// 게시글 존재 확인
Board boardPs = boardRepository.findById(id);
if (boardPs == null) throw new Exception404("게시글을 찾을 수 없습니다");
if (!boardPs.getUser().getId().equals(sessionUserId)) throw new Exception403("권한이 없습니다.");
// 좋아요 삭제
loveRepository.deleteByBoardId(boardPs.getId());
boardRepository.deleteById(id);
}
}
LoveService
package shop.mtcoding.blog.love;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import shop.mtcoding.blog._core.error.ex.ExceptionApi403;
import shop.mtcoding.blog._core.error.ex.ExceptionApi404;
import shop.mtcoding.blog.board.BoardRepository;
import shop.mtcoding.blog.user.UserRepository;
@RequiredArgsConstructor
@Service
public class LoveService {
private final LoveRepository loveRepository;
private final BoardRepository boardRepository;
private final UserRepository userRepository;
@Transactional
public LoveResponse.SaveDTO 좋아요(LoveRequest.SaveDTO reqDTO, Integer sessionUserId) {
Love lovePs = loveRepository.save(reqDTO.toEntity(sessionUserId));
Long loveCount = loveRepository.findByBoardId(reqDTO.getBoardId());
return new LoveResponse.SaveDTO(lovePs.getId(), loveCount.intValue());
}
@Transactional
public LoveResponse.DeleteDTO 좋아요취소(Integer id, Integer sessionUserId) {
Love lovePs = loveRepository.findById(id);
if (lovePs == null) throw new ExceptionApi404("취소할 좋아요가 없습니다."); // ExceptionApi404
// 권한체크 (lovePs.gerUser().getId() 비교 sessionUserId)
if (!lovePs.getUser().getId().equals(sessionUserId)) {
throw new ExceptionApi403("권한이 없습니다."); // ExceptionApi403
}
Integer boardId = lovePs.getBoard().getId();
loveRepository.deleteById(id);
Long loveCount = loveRepository.findByBoardId(boardId);
return new LoveResponse.DeleteDTO(loveCount.intValue());
}
}
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.error.ex.Exception403;
import shop.mtcoding.blog._core.error.ex.Exception404;
import shop.mtcoding.blog.user.User;
@RequiredArgsConstructor
@Service
public class ReplyService {
private final ReplyRepository replyRepository;
@Transactional
public void 댓글쓰기(ReplyRequest.SaveDTO reqDTO, User sessionUser) {
replyRepository.save(reqDTO.toEntity(sessionUser));
}
@Transactional
public Integer 댓글삭제(Integer id, Integer sessionUserId) {
Reply replyPs = replyRepository.findById(id);
if (replyPs == null) throw new Exception404("삭제할 댓글을 찾을 수 없습니다."); // 404
if (!(replyPs.getUser().getId().equals(sessionUserId))) throw new Exception403("삭제할 권한이 없습니다.");
Integer boardId = replyPs.getBoard().getId();
replyRepository.deleteById(id);
return boardId;
}
}
400 → Bad Request (잘못된 요청)
GlobalExceptionHandler
package shop.mtcoding.blog._core.error;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.blog._core.error.ex.*;
import shop.mtcoding.blog._core.util.Resp;
@RestControllerAdvice // data return
public class GlobalExceptionHandler {
//401 -> 인증 안됨
@ExceptionHandler(Exception401.class)
public String ex401(Exception401 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
location.href="/login-form";
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi401.class)
public Resp<?> exApi401(ExceptionApi401 e) {
// catch 자리
return Resp.fail(401, e.getMessage());
}
// 403 -> 권한 없음
@ExceptionHandler(Exception403.class)
public String ex403(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi403.class)
public Resp<?> exApi403(ExceptionApi403 e) {
// catch 자리
return Resp.fail(403, e.getMessage());
}
// 404 -> 자원을 찾을 수 없음
@ExceptionHandler(Exception404.class)
public String ex404(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi404.class)
public Resp<?> exApi404(ExceptionApi404 e) {
// catch 자리
return Resp.fail(404, e.getMessage());
}
// 400 -> 잘못된 요청
@ExceptionHandler(Exception400.class)
public String ex400(Exception400 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
}
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.error.ex.Exception400;
import shop.mtcoding.blog._core.error.ex.Exception401;
import shop.mtcoding.blog._core.error.ex.Exception404;
import java.util.HashMap;
import java.util.Map;
// 비즈니스 로직, 트랜잭션 처리, DTO 완료
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
// 401 > 인증 403 > 권한 404 > 자원 못찾음
@Transactional
public void 회원가입(UserRequest.JoinDTO joinDTO) {
// 동일회원 있는지 검사
try {
userRepository.save(joinDTO.toEntity());
} catch (Exception e) {
//Exceptiom 400 (Bad request -> 잘못된 요청입니다)
throw new Exception400("동일한 username 이 존재합니다.");
}
// 회원가입
userRepository.save(joinDTO.toEntity());
}
public User 로그인(UserRequest.LoginDTO loginDTO) {
// username,password 검사
User user = userRepository.findByUsername(loginDTO.getUsername());
if (user == null) throw new Exception401("username 혹은 password 가 일치하지 않습니다");
if (!user.getPassword().equals(loginDTO.getPassword())) {
throw new Exception401("username 혹은 password 가 일치하지 않습니다");
}
// 로그인
return user;
}
public Map<String, Object> 유저네임중복체크(String username) {
User user = userRepository.findByUsername(username);
Map<String, Object> dto = new HashMap<>();
if (user == null) {
dto.put("available", true);
} else {
dto.put("available", false);
}
return dto;
}
@Transactional
public User 회원정보수정(UserRequest.UpdateDTO updateDTO, Integer userId) {
User user = userRepository.findById(userId);
//Exception404
if (user == null) throw new Exception404("회원을 찾을 수 없습니다");
user.update(updateDTO.getPassword(), updateDTO.getEmail()); // 영속화된 객체의 상태변경
return user;
} // 함수 종료될 때 더티 체킹(Dirty Checking) -> 상태가 변경되면 변경된 data를 가지고 update을 한다
}
그 외 오류
GlobalExceptionHandler
package shop.mtcoding.blog._core.error;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import shop.mtcoding.blog._core.error.ex.*;
import shop.mtcoding.blog._core.util.Resp;
@RestControllerAdvice // data return
public class GlobalExceptionHandler {
//401 -> 인증 안됨
@ExceptionHandler(Exception401.class)
public String ex401(Exception401 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
location.href="/login-form";
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi401.class)
public Resp<?> exApi401(ExceptionApi401 e) {
// catch 자리
return Resp.fail(401, e.getMessage());
}
// 403 -> 권한 없음
@ExceptionHandler(Exception403.class)
public String ex403(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi403.class)
public Resp<?> exApi403(ExceptionApi403 e) {
// catch 자리
return Resp.fail(403, e.getMessage());
}
// 404 -> 자원을 찾을 수 없음
@ExceptionHandler(Exception404.class)
public String ex404(Exception403 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
@ExceptionHandler(ExceptionApi404.class)
public Resp<?> exApi404(ExceptionApi404 e) {
// catch 자리
return Resp.fail(404, e.getMessage());
}
// 400 -> 잘못된 요청
@ExceptionHandler(Exception400.class)
public String ex400(Exception400 e) {
// catch 자리
String html = """
<script>
alert('${msg}')
</script>
""".replace("${msg}", e.getMessage());
return html;
}
// 그 외 오류
@ExceptionHandler(Exception.class)
public String exUnKnown(Exception e) {
// catch 자리
String html = """
<script>
alert('${msg}');
history.back();
</script>
""".replace("${msg}", "관리자에게 문의해주세요.");
System.out.println("관리자님 보세요: " + e.getMessage());
return html;
}
}
Share article