본문 바로가기
TIL

Spring - 일정 관리 앱 만들기 과제 트러블 슈팅

by suyeoneee 2024. 12. 10.

Lv 0. API와 ERD 작성

API와 ERD는 아래와 같이 작성하였다.

API
ERD

LV 1. 일정 생성 및 조회

📋 요구사항

  • 일정 생성 - 고유 식별자, 할일, 작성자명, 비밀번호, 작성일, 수정일 저장
  • 전체 일정 조회 - 수정일(YYYY-MM-DD)과 작성자명을 바탕으로 내림차순 조회
  • 선택 일정 조회 - 일정의 고유 식별자를 사용하여 조회

Lv 2. 일정 수정 및 삭제

📋 요구사항

  • 선택 일정 수정 - 비밀번호 일치 시 할일, 작성자명만 수정 가능
  • 선택 일정 삭제 - 비밀번호 일치 시 일정 삭제 가능

🌀 배경

Lv2 단계에서 Row가 없는 todoId를 요청 인자로 넣었을 때 500 Internal Server Error 발생했다.

🌀 발단

todoId에 해당하는 Row가 있는지 확인도 안하고 비밀번호 검증을 하려니까 에러가 발생하는 것 같다.

비밀번호 검증을 하기위해서 sql문의 where todoId = ? 을 통해 식별자를 전달하는데, 이 과정에서 문제가 된 것.

 

🌀 전개

비밀번호 검증 단계 전에 id에 해당하는 Row가 있는 지 확인을 먼저하고 비밀번호를 검증해야겠다고 생각했다.

(id에 해당하는 Row가 있는지 확인 -> 비밀번호 검증 -> 수정 or 삭제)

id에 해당하는 Row가 있는 지 확인하는 것은 "선택 일정 조회" API를 구현하는 메서드를 활용하면 될 것 같다. Repository로 요청을 받아 Row 결과가 있을 때 비밀번호 검증, 수정 및 삭제가 이루어지게 수정하면 되지 않을까?

// 선택 일정 조회 Service
    @Override
    public TodoResponseDto findTodoById(Long todoId) {
        Todo todo = todoRepository.findTodoByIdOrElseThrow(todoId);
        return new TodoResponseDto(todo);
    }

 

// 선택 일정 조회 Repository
@Override
    public Todo findTodoByIdOrElseThrow(Long todoId) {

        List<Todo> result = jdbcTemplate.query("select * from todo where todoId = ?", todoRowMapperV2(), todoId);
        return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + todoId));

    }

    // todoRowMapperV2() : RowMapper<Todo>
    private RowMapper<Todo> todoRowMapperV2() {
        return new RowMapper<Todo>() {
            @Override
            public Todo mapRow(ResultSet rs, int rowNum) throws SQLException {
                Timestamp createdDateTimestamp = rs.getTimestamp("createdDate");
                LocalDateTime createdDate = createdDateTimestamp.toLocalDateTime();

                Timestamp modifiedDateTimestamp = rs.getTimestamp("lastModifiedDate");
                LocalDateTime modifiedDate = modifiedDateTimestamp.toLocalDateTime();

                return new Todo(
                        rs.getLong("todoId"),
                        rs.getString("contents"),
                        rs.getString("userName"),
                        rs.getString("password"),
                        createdDate,
                        modifiedDate
                );
            }
        };
    }

 

🌀 위기

todoRepository.pwCheck(todoId, password) 전에 id에 해당하는 Row가 있는지 확인하는 조건문을 넣어야 할텐데.. 그 조건문을 어떻게 짜야할 지 막막했다. 

// 선택 일정 수정 Service
@Override
public TodoResponseDto updateTodo(Long todoId, String contents, String userName, String password) {
    int updatedRow = 0;

    LocalDateTime modifiedDateTime = LocalDateTime.now();
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String formatted = modifiedDateTime.format(formatter);
    modifiedDateTime = LocalDateTime.parse(formatted, formatter);

    // 비밀번호 True
    if(todoRepository.pwCheck(todoId, password)) {
        // 필수값 검증
        if(contents == null && userName == null) { //일정 내용, 작성자명 수정 값 없을 때
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The content or userName are required values.");
        } else if(contents != null && userName == null) { // 일정 내용만 수정
            updatedRow += todoRepository.updateContents(todoId, contents, modifiedDateTime);
        } else if(contents == null && userName != null) { // 작성자명만 수정
            updatedRow += todoRepository.updateUserName(todoId, userName, modifiedDateTime);
        } else { //일정 내용, 작성자명 수정
            updatedRow += todoRepository.updateContents(todoId, contents, modifiedDateTime);
            updatedRow += todoRepository.updateUserName(todoId, userName, modifiedDateTime);
        }

        // 수정된 row가 0개라면 -> id 값으로 조회된 todo가 없을 때
        if(updatedRow == 0) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No data has been modified.");
        }

        // 수정된 일정 조회
        Todo todo = todoRepository.findTodoByIdOrElseThrow(todoId);
        return new TodoResponseDto(todo);
    }
    // 비밀번호 False
    else {
        throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid password");
    }
}

// 선택 일정 삭제 Service
@Override
public void deleteTodo(Long todoId, String password) {
    // 비밀번호 True
    if(todoRepository.pwCheck(todoId, password)) {
        int deletedRow = todoRepository.deleteTodo(todoId);
        if(deletedRow == 0) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No data has been modified.");
        }
    }
    // 비밀번호 False
    else {
        throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid password");
    }
}

 

그리고 Repository의 findTodoByIdOrElseThrow 메서드에서

return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + todoId));

이 부분을 결과가 있을 경우 / 없을 경우 코드를 나누려고 했는데 실습 코드를 참고한 터라 이해도가 부족해서 나누기 어려웠다..;ㅠ

 

🌀 절정

 

result가 List이니 단순하게 isEmpty() 메서드로 결과 유무를 나눈다.

// 선택 일정 조회 Repository
@Override
    public Todo findTodoByIdOrElseThrow(Long todoId) {

        List<Todo> result = jdbcTemplate.query("select * from todo where todoId = ?", todoRowMapperV2(), todoId);
        if(result.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + todoId);
        }
        else
            return result.get(0); // 고유 식별자 id로 조회하는 것이니 결과가 있다면 첫 번째 항목 하나만 있을 것
//        return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + todoId));

    }

 

반환되는 Todo가 있는지 없는지 확인하고자 아래와 같이 Service 코드를 짜서 포스트맨으로 테스트해보았다.

// 선택 일정 Service pwCheck 메서드 사용 전
if (todoRepository.findTodoByIdOrElseThrow(todoId) != null) {
    System.out.println("111");
} else {
    System.out.println("222");
}

 

 

콘솔에 "222"가 찍히지 않았다. 하지만 404 Not Found라고 상태 코드가 바뀐걸 보아하니 throw ResponseStatusException 으로 알아서 Repository에서 상태 코드를 전달하여 처리해주는 것을 알았다.

(row가 있는 id를 넣으면 콘솔에 "111" 정상 출력됨) 

 

따라서, 비밀번호 검증하기 전에  >> todoRepository.findTodoByIdOrElseThrow(todoId) << 이 실행되도록 코드를 고쳐주어야 한다.

그러나 사실 Service updateTodo 에서 이미 해당 코드 내용이 있었다. 

// 수정된 일정 조회
Todo todo = todoRepository.findTodoByIdOrElseThrow(todoId);

 

🌀 결말

비밀번호 전에 todoRepository.findTodoByIdOrElseThrow(todoId); 를 통해 데이터 유무를 확인하고, 

비밀번호 검증 후에 내용 수정까지 마치면, Service의 선택 메모 조회에 해당하는 findTodoById(todoId) 메서드 결과를 return 하여 업데이트된 데이터 내용으로 응답 처리한다. ** 선택 메모 삭제 코드에도 적용!

// 선택 일정 수정
    @Override
    public TodoResponseDto updateTodo(Long todoId, String contents, String userName, String password) {

        int updatedRow = 0;

        LocalDateTime modifiedDateTime = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        String formatted = modifiedDateTime.format(formatter);
        modifiedDateTime = LocalDateTime.parse(formatted, formatter);

        Todo todo = todoRepository.findTodoByIdOrElseThrow(todoId);

        // 비밀번호 True
        if(todoRepository.pwCheck(todoId, password)){
            // 필수값 검증
            if(contents == null && userName == null) { //일정 내용, 작성자명 수정 값 없을 때
                throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "The content or userName are required values.");
            }
            else if(contents != null && userName == null) { // 일정 내용만 수정
                updatedRow += todoRepository.updateContents(todoId, contents, modifiedDateTime);
            }
            else if(contents == null && userName != null) { // 작성자명만 수정
                updatedRow += todoRepository.updateUserName(todoId, userName, modifiedDateTime);
            }
            else{ //일정 내용, 작성자명 수정
                updatedRow += todoRepository.updateContents(todoId, contents, modifiedDateTime);
                updatedRow += todoRepository.updateUserName(todoId, userName, modifiedDateTime);
            }
            // 수정된 row가 0개라면 -> id 값으로 조회된 todo가 없을 때
            if(updatedRow == 0) {
                throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No data has been modified.");
            }
            // 수정된 일정 조회
            return findTodoById(todoId);
        }
        // 비밀번호 False
        else
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid password");
    }

404 Not Found (todoId에 해당하는 Row 없음)
401 Unauthorized (비밀번호 불일치)

 

200 Ok (수정 완료)

댓글