예외처리를 활용한 리팩토링
변경사항
환경 설정 : Springboot-starter-aop와 spring-boot-starter-validation 추가
plugins {
id 'java'
id 'org.springframework.boot' version '3.3.5'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '3.3.5'
// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '3.3.5'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-mustache'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
Board 패키지
DTO - Request에서 validation의 @NotBlank를 사용
public class BoardRequest {
@Data //getter,setter, toString
public static class SaveDTO{
@NotBlank(message = "title을 입력해주세요")
private String title;
@NotBlank(message = "content를 입력해주세요.")
private String content;
public Board toEntity(){
Board board = new Board(null,title, content, null);
return board;
}
}
@Data //getter,setter, toString
public static class updateDTO{
@NotBlank
private String title;
@NotBlank
private String content;
}
}
public class BoardResponse {
@Data
public static class updateFormDTO{
private int id;
private String title;
private String content;
private String createdAt;
public updateFormDTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.createdAt = board.getCreatedAt().toString();// TODO: 2024.11.18 형태로 변경
}
}
@Data
public static class DetailDTO{
private int id;
private String title;
private String content;
private String createdAt;
public DetailDTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.createdAt = board.getCreatedAt().toString();// TODO: 2024.11.18 형태로 변경
}
}
@Data
public static class DTO{
private int id;
private String title;
public DTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
}
}
}
Controller - validation의 @Valid 사용
import com.example.blog._core.error.ex.Exception400;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import java.util.List;
// Controller의 책임 : 외부 클라이언트의 요청을 받고 그 요청에 대한 응답을 함
@Controller
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
//was scope : application, session, request, page
// session
// request
@GetMapping("/")
public String list(Model model) { // DS(request 객체를 model이라는 객체로 랩핑해서 전달.)
List<BoardResponse.DTO> boardList = boardService.게시글목록보기();
model.addAttribute("models", boardList);
return "list";
}
@GetMapping("/save-form")
public String saveForm() {
return "save-form";
}
@GetMapping("/board/{id}/update")
public String updateForm(@PathVariable Integer id, Model model) {
model.addAttribute("model",boardService.게시글수정화면보기(id));
return "update-form";
}
@GetMapping("/board/{id}")
public String detail(@PathVariable("id") Integer id, Model model) {
model.addAttribute("model",boardService.게시글상세보기(id));
return "detail";
}
@PostMapping("/board/save")
public String saveV2(@Valid BoardRequest.SaveDTO saveDTO, Errors errors) {
boardService.게시글쓰기(saveDTO);
return "redirect:/";
}
@PostMapping("/board/{id}/delete")
public String delete(@PathVariable("id") int id) {
boardService.게시글삭제(id);
return "redirect:/";
}
@PostMapping("/board/{id}/update")
public String update(@PathVariable("id") int id, @Valid BoardRequest.updateDTO updateDTO, Errors errors) {
boardService.게시글수정(id,updateDTO);
return "redirect:/";
}
}
Service
import com.example.blog._core.error.ex.Exception404;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoartRepository boardRepository;
public List<BoardResponse.DTO> 게시글목록보기(){
return boardRepository.findAll().stream()
.map(BoardResponse.DTO::new)
.toList();
};
public BoardResponse.updateFormDTO 게시글수정화면보기(int id) {
return new BoardResponse.updateFormDTO(boardRepository.findById(id).orElseThrow(() ->new Exception404("해당 ID의 게시글이 없습니다. : " + id)));
}
public BoardResponse.DetailDTO 게시글상세보기(int id) {
return new BoardResponse.DetailDTO(boardRepository.findById(id).orElseThrow(() ->new Exception404("해당 ID의 게시글이 없습니다. : " + id)));
}
@Transactional
public void 게시글쓰기(BoardRequest.SaveDTO saveDTO) {
boardRepository.save(saveDTO.toEntity());
}
@Transactional
public void 게시글삭제(int id) {
boardRepository.delete(id);
}
@Transactional
public void 게시글수정(int id, BoardRequest.updateDTO updateDTO) {
Board board =boardRepository.findById(id).orElseThrow(() ->new Exception404("해당 ID의 게시글이 없습니다." + id));
board.update(updateDTO.getTitle(), updateDTO.getContent());
}
}
Repository
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
// Repository 책임 : DB 상호작용
@Repository
@RequiredArgsConstructor
public class BoartRepository {
//JPA는 EntityManager로 DB에 접근한다.(JAVA에서 DBConnection)
private final EntityManager em;
public List<Board> findAll() {
return em.createQuery("select b from Board b order by b.id desc",Board.class).getResultList();
}
public Optional<Board> findById(int id) {
return Optional.ofNullable(em.find(Board.class, id));
}
public void save(Board board) {
em.persist(board);
}
public void delete(int id){
em.createQuery("delete from Board b where b.id=:id").setParameter("id", id).executeUpdate();
}
}
Util 패키지
Aop - Controller의 @Vaild를 Errors에 담아 에외처리
import com.example.blog._core.error.ex.Exception400;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
@Aspect
@Component
public class MyValidationAspect {
@Before("@annotation(org.springframework.web.bind.annotation.PostMapping)")
// Around는 JoinPoint가 아니라 ProceedingJoinPoint 사용
public void validationCheck(JoinPoint jp) throws Throwable {
Object[] args =jp.getArgs();
for(Object arg : args) {
if(arg instanceof Errors) {
Errors errors = (Errors) arg;
if (errors.hasErrors()) {
String errMsg = errors.getFieldErrors().get(0).getField() +" : "+errors.getFieldErrors().get(0).getDefaultMessage();
throw new Exception400(errMsg);
}
}
}
}
}
MyControllerAdvice
public class Exception400 extends RuntimeException{
public Exception400(String msg) {
super(msg);
}
}
public class Exception404 extends RuntimeException{
public Exception404(String message) {
super(message);
}
}
import com.example.blog._core.error.ex.Exception400;
import com.example.blog._core.error.ex.Exception404;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
// 에러 처리
@ControllerAdvice
public class MyControllerAdvice {
@ResponseBody
@ExceptionHandler(Exception400.class)
public String err400(Exception400 e) {
System.out.println("err400");
String body = """
<script>
alert('${msg}');
history.back();
</script>
""".replace("${msg}", e.getMessage());
return body;
}
@ResponseBody
@ExceptionHandler(Exception404.class)
public String err404(Exception404 e) {
System.out.println("err404");
String body = """
<script>
alert('${msg}');
history.back();
</script>
""".replace("${msg}", e.getMessage());
return body;
}
}
Share article