[TIL] 33일차 _ 일정관리 앱 Dev #8

Seoyeon Lee·2025년 11월 19일

Today I Learned ...

오늘은 코드카타 SQL과 알고리즘 48-50번 문제를 풀고, 일정관리 앱 Dev 과제를 마무리했다!


🗒️ 코드카타 #26

오늘은 총 3개의 SQL 문제를 풀었는데, 그 중 2개의 문제가 카테고리별 최댓값을 갖는 데이터를 출력하는 것이었다.
분명 사전캠프 때 이런 문제 몇번 풀었었는데,, 그새 다 까먹어버렸다...
where에 서브쿼리를 넣어서 최댓값과 일치하는 것을 찾아야 한다는 것은 알고 있었는데,
where에 서브쿼리를 넣게 되면, 서브쿼리가 출력하는 행의 개수는 1개여야만 했다..
그래서 서브쿼리에 group by로 카테고리별 최댓값을 구하는 것이 아니라,

select *
from table t1
where max_column = (select max(max_column)
                    from table t2
                    where t1.column = t2.column)

위와 같이 where를 사용해 메인 쿼리의 카테고리와 서브쿼리의 카테고리가 일치하는 경우를 특정했다.
처음에는 조금 헤맸지만, 저렇게 접근을 한 뒤에는 큰 어려움 없이 문제들을 해결할 수 있었다.

오늘은 총 3개의 알고리즘 문제를 풀었다.
첫 번째 문제는 입력받은 배열을 원하는 구역으로 자르고, 오름차순 정렬을 한 뒤에 K번째 숫자를 반환하는 것이었다.
나는 for문을 활용해 구역 내의 배열을 새로운 배열로 만들고, 새로운 배열에서 K번째 숫자를 찾아냈다.
문제 자체는 별 문제 없이 해결할 수 있었지만, 다른 사람들의 풀이를 보니...
copyOfRange()라는 원하는 범위를 지정해주면 그만큼의 배열을 복사해주는 아주 아주 편리한 메서드가 있더라..
처음 보는 메서드들이 너무 너무 많다.. 문제 많이 풀어보면서 여러가지 메서드들을 익혀봐야겠다..

오늘 푼 두 번째 문제는 입력받은 배열 중 2개의 숫자를 뽑아 더한 결과들을 반환하는 것이었다.
배열에서 2개의 숫자을 뽑아 계산한 결과들을 구하는 것은 이제 식은 죽 먹기다.
문제의 조건 중에 중복된 값을 제거하라는 내용이 있었는데, 이것도 스트림의 distinct() 메서드를 사용하니 아주 쉽게 해결할 수 있었다.
이렇게 두 번째 문제도 큰 어려움 없이 해결할 수 있었다.

마지막으로 문제는 세 번째 문제였는데...
문자열을 입력받으면, 각각의 문자와 같은 가장 가까운 문자의 인덱스 차이를 반환하는 것이다.
예를 들어, 'banana'를 입력받으면, [-1, -1, -1, 2, 2, 2]와 같이 처음 등장하는 문자는 -1로, 다시 등장하는 문자는 그 전에 있는 가장 가까운 문자와의 인덱스 차이를 반환하는 것이다.
이 문제는... 접근부터가 어려웠다...
일단 인덱스 정보에 접근해야 하기에 컬렉션을 사용해야 하나 싶었지만, 찾아보니 String 클래스에도 인덱스 정보를 찾아주는 메서드가 있었다!!
특히 이번 문제에는 lastIndexOf라는 메서드가 유용하게 사용되었는데,
String.lastIndexOf("c");로 사용하면 'c'의 마지막 인덱스 번호를 알려주고,
String.lastIndexOf("c", 2);로 사용하면 2번째 인덱스에서부터 앞으로(1, 0 인덱스 방향으로) 탐색하며 'c'의 마지막 인덱스 번호를 알려준다.
두 메서드 모두 해당 값이 없으면 '-1'을 반환한다.
이렇게 하니 정말 너무 편하게 문제를 해결할 수 있었다.
역시... 똑똑한 사람들이 이미 다 만들어놨더라... 빨리 여러 메서드들 사용해보면서 익혀야겠다..

각각의 문제와 풀이는 깃허브를 통해 업로드해두었다.
GitHub 보러가기


🖥️ 일정관리 앱 Dev 프로젝트 #8

오늘 드디어 일정관리 앱 Dev를 마무리했다!!!

어제 끝내지 못한 트러블슈팅을 해결하고, API 명세서도 싹 다 수정하고.. 오늘도 할 게 참 많았다..

먼저 어제 해결하지 못한 문제 중 하나는 유저 삭제 API를 실행할 때 자꾸 NullPointerException이 발생한다는 것이었다!

@DeleteMapping("/users/{userId}")
public ResponseEntity<Void> deleteUser(
        @SessionAttribute(name = "loginUser", required = false) UserForHttpSession sessionUser,
		... ) {
    if (userId.equals(sessionUser.getId())) { session.invalidate(); }
    ...
}

위와 같이 코드를 작성했었는데, SessionAttribute에 required = false 조건을 걸어두니
로그인 되지 않은 상태로 위 API에 접근을 하면 스프링은 UserForHttpSession 객체에 null을 담아준다.
그런데, 내가 null인 상태에서 UserForHttpSession의 getId() 메서드를 실행하라고 해버리니 NullPointerException이 발생한 것이었다.
처음에는 로직의 순서에 문제가 있는 줄 알고 헤매고 있었는데, 문제는 예외의 이름 그대로였다...

if (sessionUser != null && userId.equals(sessionUser.getId())) { session.invalidate(); }

결국은 위와 같이 null이 아닐 때만 getId를 실행하는 것으로 잘 해결할 수 있었다.

두 번째 문제는 User의 email을 unique key로 설정을 해두었더니, 중복된 이메일로 유저를 생성하면 DataIntegrityViolationException 예외가 발생했었다.
예외가 발생하는 것 자체는 문제가 되지 않았지만, 이 예외는 유저의 키가 무엇이고, 어떤 SQL 쿼리를 생성하는지 등등의 정보들을 메시지로 반환해버린다.
지금은 별 중요한 내용은 아니지만, 뭔가 이렇게 두면 안 될 것 같다는 생각에 이 예외를 따로 ExceptionHandler를 통해 처리를 해주었다.
그래서 클라이언트에게는 '이미 존재하는 유저입니다.'라는 메시지를 반환하지만, 로그로는 무엇 때문에 발생한 것이었는지를 자세하게 기록하도록 해두었다.
이런 부분을 현업에서는 어떻게 하는지 몰라 일단 뇌피셜로 처리를 했지만.. 다음에 튜터님께 여쭤봐야겠다.

이렇게 트러블슈팅도 끝내고, 페이지네이션 구현을 추가하고 싶었던 부분도 추가하며 이번 프로젝트도 잘 마무리했다!
내일 한 번 더 리팩토링을 하며 정리하고 최종 제출을 하려고 한다.

매번 나는 되게 잘했다고 생각하고 만족하며 제출하지만, 해설 강의를 들으면 늘 새로운 충격을 받기에..
내일 해설 강의가 기대되기도 하고, 걱정되기도 한다...
하지만 내가 할 수 있는 최선..을 다했다고 생각하기에 여기까지 구현하고 마칠려고 한다.

내가 작성한 코드는 깃허브에 업로드해두었다.
GitHub 보러가기


🙃 오늘의 느낀점

처음 과제 발제를 들을 때는 내가 정말 이걸 구현할 수 있을까 싶어서 이번에는 필수 과제까지만 구현해봐야겠다고 생각했지만..!
역시 하다보니 점점 감이 잡히게 되었고,
오히려 도전 과제에서는 배우지 않은 새로운 것들을 구현하기를 요구하니 더 재미있었다.
결국은 도전 과제까지 다 마무리하게 되어 너무 뿌듯하다!

내일 과제 제출까지 시간이 조금 남아있으니 내일은 코드를 잘 작성하는 방법을 좀 찾아보고 내 코드를 리뷰해봐야겠다.

profile
백엔드 개발자 지망생

0개의 댓글