도전과제를 마무리했다. 이번에는 3개의 브랜치로 나눠서 작업을 했다. 근데 마지막에 challenge에서 main브랜치로 옮길 때 커밋내역을 안 지워서 커밋이 32개인 상태로 main에 올라가게 되었다.
필수과제만 올릴 땐 잘 올렸는데 실수했다 ..
https://github.com/roqkfchqh/Scheduler/tree/main
GitHub - roqkfchqh/Scheduler
Contribute to roqkfchqh/Scheduler development by creating an account on GitHub.
github.com
여튼 리드미도 완성하고 제출까지 완...ㄹ...
를 하려 하는데
난 왜 이런걸 맨 마지막에 발견하니 항상.. 한두번이 아니다 ㅠㅠ 글을 왜이렇게 못읽지
그리 어려운 작업은 아니라서 수정하고 자야겠다.. 맨날 나무를 봐야 하는데 숲만 봐서 문제다 ㅠ
일단 여태 적용했던 것들 저번에 쓰다 날려먹은 거 간단하게 정리해야겠다.
1. Custom exception
public abstract class BaseException extends RuntimeException{
private final String message;
private final HttpStatus httpStatus;
protected BaseException(String message, HttpStatus httpStatus) {
super(message);
this.message = message;
this.httpStatus = httpStatus;
}
public HttpStatus getStatus(){
return httpStatus;
}
@Override
public String getMessage(){
return message;
}
}
exception을 추상화시켜서, 일반적인 예외가 발생하는 CustomException과 repository 단에서 사용되는 CustomSQLException을 나눠 관리했다. enum을 이용해 에러코드도 열거형으로 관리했고, 각자 분리되어있다.
@RestControllerAdvice
public class GlobalExceptionHandler {
//validException
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException e){
Map<String, String> errorMessages = new HashMap<>();
for(FieldError error : e.getBindingResult().getFieldErrors()){
errorMessages.put(error.getField(), error.getDefaultMessage());
}
return buildErrorResponse(HttpStatus.BAD_REQUEST, "validation exception", errorMessages);
}
//baseException
@ExceptionHandler(BaseException.class)
public ResponseEntity<Map<String, Object>> handleBaseException(BaseException e){
return buildErrorResponse(e.getStatus(), e.getMessage(), null);
}
//공통 errorResponse
private ResponseEntity<Map<String, Object>> buildErrorResponse(HttpStatus status, String message, Object errors){
Map<String, Object> response = new HashMap<>();
response.put("status", status.value());
response.put("error", status.getReasonPhrase());
response.put("message", message);
if(errors != null){
response.put("errors", errors);
}
return ResponseEntity.status(status).body(response);
}
}
GlobalExceptionHandler에서, validException도 함께 다룬다.
컨트롤러 계층에서 @valid어노테이션을 사용하면 Requestbody를 검증해주는데, 이 때 내가 입력한 조건들에 부합하지 않으면 exceptionHandler를 통해 예외를 던진다.
API 설계
PUT, DELETE 사용을 그냥 배제해보았다. 프론트 단에서는 put과 delete의 requestbody를 처리하지 못한다. 그러면 비밀번호가 노출위험이 있다 생각해서 get외엔 모두 post로만 처리했다. token이 있다면 put과 delete도 자유로이 사용해볼 수 있을 것 같다. 다음 프로젝트엔 jwt나 oauth를 통해 사용해보자.
@Transactional
//create
public ScheduleResponseDto createSchedule(ScheduleRequestDto dto){
Schedule schedule = new Schedule(dto.getContent(), dto.getAuthor_id());
scheduleDao.createSchedule(schedule);
return readSchedule(schedule.getId());
}
schedule create, update 등에서 한 메서드가 2개의 dao를 요청하고 있다. 이 상황에서 무언가의 개입이 있다면 데이터가 잘못 처리될 가능성이 있겠다고 느꼈고, 어디에선가 봤던 transactional을 적용해보기로 했다.
트랜잭션이란 데이터의 일관성을 유지하고 오류가 발생할 경우 작업을 되돌릴 수 있도록 해주는 중요한 기능으로, 데이터의 무결성을 보장하는데에 유용하다.
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Service
@RequiredArgsConstructor
@Transactional
public class ScheduleCRUDService {
private final ScheduleDao scheduleDao;
private final ServiceValidationService serviceValidationService;
//read
@Transactional(readOnly = true)
public ScheduleResponseDto readSchedule(UUID scheduleId){
ScheduleResponseDto schedule = scheduleDao.findScheduleById(scheduleId);
if(schedule == null){
throw new CustomException(ErrorCode.NOT_FOUND);
}
return schedule;
}
//delete
public void deleteSchedule(UUID scheduleId, String authorPassword){
serviceValidationService.validateIdAndPassword(scheduleId, authorPassword);
scheduleDao.deleteSchedule(scheduleId);
}
}
transactionManager를 스프링 빈에 등록하고, 트랜잭션을 사용할 클래스에 @Transactional을 달아주면 된다.
read기능을 하는 곳엔 readOnly=true로 create나 update, delete작업을 하지 않도록 막을 수 있다.
service단에서 dao를 호출 중이고, 트랜잭션을 적용중이므로 dao단에선 사용하지 않아도 된다.
마지막으로 파일 리팩토링 : common(공통 처리되는 패키지), controller(model계층을 포함하는 패키지) 로 나누어서 정리했다.
db에 사용되는 connection 정보들도 캡슐화하고 세세하게 나눠줬다.
왜 나는 다 한게 아닌걸까..
'개인 공부용 프로젝트 > schedule.jdbc.api' 카테고리의 다른 글
(도전과제 시작) author 테이블 만들기, custom exception 추가 (0) | 2024.12.05 |
---|---|
트러블슈팅 : 필수과제 버그 수정 (0) | 2024.12.05 |
Lv1, Lv2 (0) | 2024.12.04 |