CODING/스파르타 내일배움캠프 TIL

32_문자열 내림차순으로 배치하기_일정관리 과제 Lv 3 구현완료_25.1.27(월)

codingTrip 2025. 1. 27. 21:14

코트카타

34) 문자열 내림차순으로 배치하기

import java.util.*;

class Solution {
    public String solution(String s) {
        StringBuilder answer = new StringBuilder();

        s.chars() 
            .boxed() 
            .sorted(Comparator.reverseOrder())
            .forEach(i -> answer.append((char)(int)i)); 

        return answer.toString();
    }
}
 

 

코드 설명

  1. s.chars():
    • 문자열의 각 문자를 IntStream으로 변환합니다.
    • 예: "abc" → IntStream[97, 98, 99] (a, b, c의 ASCII 값).
  2. .boxed():
    • IntStream을 Stream<Integer>로 변환하여, Comparator를 사용할 수 있도록 합니다.
  3. .sorted(Comparator.reverseOrder()):
    • Stream의 요소를 내림차순으로 정렬합니다.
    • 예: IntStream[97, 98, 99] → [99, 98, 97] (c, b, a 순서).
  4. .forEach():
    • 정렬된 값을 순회하면서 StringBuilder에 추가합니다.
    • (char)(int)i를 사용하여 ASCII 값을 문자로 변환한 후 추가합니다.
  5. answer.toString():
    • 최종 결과를 문자열로 반환합니다.

일정관리 과제

도전

Lv 3. 연관 관계 설정 - 트러블 슈팅

작성자와 일정의 연결

ERD

위와 같이 필수 단계에서는 1개의 테이블로 관리하던 것을

2개의 테이블로 나누었다.

 

그리고 작성자 관련 CRUD 로직을 새롭게 구현하기 시작했다.

 

작성자 생성 POST 요청 시 404 및 405에러

Postman으로 POST 실행 결과

schedule 테이블 외에 추가로 writer(작성자) 테이블을 생성했다.

해당 테이블에 값을 insert하기 위한 코드를 작성하고, 실행하는 도중 위와 같은 오류가 발생했다.

 

처음에는 내가 모르는 놓친 부분이 있을까 싶어서 튜터님께 문의를 드렸다.

혼자서 해결하고자 했으나,

PostMapping을 실행할 때에는 아래처럼 임의로 작성한 로그도 보이지 않았다.

따라서 Controller 메서드도 접속하지 못한 것으로 보인다.

@PostMapping
public ResponseEntity<WriterResponseDto> createWriter(@RequestBody WriterRequestDto dto){
    log.info("dto={}", dto);
    return new ResponseEntity<>(writerService.saveWriter(dto), HttpStatus.CREATED);
}

 

그러나 목록 조회 GetMapping은 빈 배열이지만(데이터가 없으므로) 잘 연결 되어서 

결국 튜터님께 질문 드리기로 했다.

 

Postman으로 POST 실행 결과

원인은... 맥북 사용 시 발생하는 버그? 같은 것이었다.

경로를 입력할 때 localhost:8080와 writers 중간에 이상한 문자가 눈에 보이지는 않지만 들어간 것이 원인이었다.

따라서 잘 작성한 것 같음에도 오류가 발생했을 때는 위의 원인일 수 있으니 잘 확인해보자.

정말... 말도 안되지만 이런 경우로 문제가 발생하기도 하는 것이 개발자의 길이다.

 

이 일을 트러블슈팅에 적어야 하나 고민이 있었으나

그래도 이번에 알게 되었으니

이 글을 읽는 모든 분들은 나처럼 헤매지 않길 바란다.

 

추가로 튜터님께서 알려주신 방법은

Gradle -> clean 실행하기 였다.

나의 경우에는 효과가 없었으나, 혹시 모르니 이 기능도 사용해보길 바란다.

 

 

작성자 수정 PATCH 요청 시 500 서버에러

Postman으로 PATCH 실행 결과

2025-01-27T17:15:26.839+09:00 ERROR 5993 --- [schedule] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [UPDATE writer SET name = ?, email = ?, updatedAt = NOW()WHERE id = ?;]] with root cause java.sql.SQLSyntaxErrorException: Unknown column 'id' in 'where clause'

다행히? 이번에는 오류 관련 로그가 나와서 잘 읽어볼 수 있었다.

원인은 sql문에 있었다.

where 조건에 id가 아닌 writer_id가 들어가야 한다.

 

Postman으로 PATCH 실행 결과

수정하고 다시 실행하니 위와 같이 잘 되는 것을 확인할 수 있다.

삭제하는 로직도 동일한 실수를 했음을 발견해서 미리 수정해서 오류를 방지했다.

 

Postman으로 DELETE 실행 결과

삭제는 잘 되는 것을 확인할 수 있다.

 

일정 생성 POST 요청 시 schedule 테이블에 writer_id null 값 입력

Postman으로 POST 실행 결과
schedule 테이블 조회 결과

 

Map<String, Object> parameters = new HashMap<>();
        parameters.put("todo", schedule.getTodo());
        parameters.put("writer_id", schedule.getWriter_id()); // "writer"로 설정해서 오류
        parameters.put("password", schedule.getPassword());
        parameters.put("createdAt", schedule.getCreatedAt());
        parameters.put("updatedAt", schedule.getUpdatedAt());

원인은 Map에서 key를 writer로 설정해서 발생한 오류였다.

문제는 컴파일 오류 일명 빨간 줄은 나지 않는 오류이기 때문에

런타임 에러가 발생할 확률이 높아진다는 것이다.

따라서 작은 실수도 잘 봐야 한다.

 

오류 수정 후

Postman으로 POST 실행 결과
schedule 테이블 조회 결과

 

 

일정 조회 GET 요청 시 schedule 테이블에 writer_id 찾지 못함

2025-01-27T17:36:49.047+09:00 ERROR 6189 --- [schedule] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.jdbc.UncategorizedSQLException: StatementCallback; uncategorized SQLException for SQL [SELECT s.schedule_id, s.todo, w.name, s.createdAt, s.updatedAt FROM schedule s JOIN writer w ON s.writer_id = w.writer_id ORDER BY s.updatedAt DESC;]; SQL state [S0022]; error code [0]; Column 'writer_id' not found.] with root cause

매핑은 Long 타입의 writer_id로 설정했는데, 쿼리에서는 String 타입의 name을 조회하려니 문제가 발생했다.

 

생각해보니... writer 테이블에 있는 작성자명 컬럼도 같이 보여주려면

Schedule 클래스와  작성자명에 해당하는 필드도 만들어줘야 한다.

Schedule 클래스와 ScheduleResponseDto 클래스에 각각 작성자명(name) 필드를 생성했다.

private RowMapper<ScheduleResponseDto> scheduleRowMapper() {
        return new RowMapper<ScheduleResponseDto>() {
            @Override
            public ScheduleResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                // 문자열을 LocalDateTime으로 변환
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

                return new ScheduleResponseDto(
                        rs.getLong("scheduleId"),
                        rs.getString("todo"),
                        rs.getLong("writerId"),
                        rs.getString("name"),
                        LocalDateTime.parse(rs.getString("createdAt"), formatter),
                        LocalDateTime.parse(rs.getString("updatedAt"), formatter)
                );
            }
        };
    }

그리고 위의 코드처럼 RowMapper할 때에도 작성자명 필드를 추가해주었다.

 

작성자 id로 조회 시 오류

2025-01-27T18:36:17.129+09:00 ERROR 6912 --- [schedule] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [select * from schedule where schedule_id = ?]; SQL state [S0022]; error code [0]; Column 'name' not found.] with root cause
@Override
public Schedule findScheduleByIdOrElseThrow(Long scheduleId) {
    String sql = "SELECT s.scheduleId, s.todo, w.writerId, w.name, s.createdAt, s.updatedAt\n" +
            "FROM schedule s\n" +
            "         JOIN writer w\n" +
            "              ON s.writerId = w.writerId\n" +
            "WHERE s.scheduleId = ?\n" +
            "ORDER BY s.updatedAt DESC";
    List<Schedule> result = jdbcTemplate.query(sql, scheduleRowMapperV2(), scheduleId);
    return result.stream().findAny().orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = " + scheduleId));
}

쿼리로 조회되는 컬럼과 매핑이 일치하지 않다보니 발생한 오류였다.

 

 

schedule 테이블에서 삭제 

2025-01-27T20:10:56.534+09:00 ERROR 7607 --- [schedule] [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [delete from writer where writerId=?]; Cannot delete or update a parent row: a foreign key constraint fails (`schedule`.`schedule`, CONSTRAINT `schedule_ibfk_1` FOREIGN KEY (`writerId`) REFERENCES `writer` (`writerId`))] with root cause

schedule 테이블에 작성자 id가 남아있는 채로

writer 테이블에서 삭제하려고 해서 발생한 오류이다.

따라서 외래키 설정 시, ON DELETE CASCADE 조건에 대해 고민했으나,

연동됨에 따라 득보다는 실이 많을 것으로 여겨져

이번에는 하지 않기로 했다.

 

연휴 전날이라 그런지 오늘따라 집중이 어려웠다.

오늘은 계속 오류 로그를 읽으면서 원인을 파악하는 연습을 했다고 생각한다.

그래도 레벨 3까지는 구현했으니 뿌듯한 마음으로 설 연휴를 맞이 하고자 한다.