[Mybatis] 로컬 캐시 문제: 프로시저 호출 시 발생하는 캐시 이슈
·
Spring
서론진행 중인 프로젝트에서 `sequence` 테이블을 별도로 두고 프로시저를 통해 PK를 채번하는 시스템에서 예상치 못한 캐시 문제를 마주했던 내용을 정리했다. Mybatis 로컬 캐시는 기본적으로 활성화되어 있는데, 이게 별도 트랜잭션에서 실행되는 프로시저와 만나면서 예상치 못한 동작을 하는 바람에 원인을 찾기까지 꽤 많은 삽질을 했다. 특히 이 프로젝트는 Mybatis 캐시 정책에 대한 제대로 된 이해 없이 "JPA와 비슷하겠지"라는 안일한 생각으로 임했던 것이 화근이었다. 하지만 막상 파보니 JPA의 영속성 컨텍스트와는 완전히 다른 캐시 정책을 가지고 있어서 더욱 혼란스러웠고, 결국 근본적인 차이점부터 다시 공부해야 했다. 실제 발생한 문제 상황프로시저를 통해 채번한 `sequence` 값이 캐시..
[Mybatis] Generic 기반 TypeHandler를 자동 등록하기
·
Spring
서론Java Enum 타입과 RDB의 enum 데이터 타입여러 프로젝트에서 `Y`, `N` 같이 특정 값들을 가지면서 해당 그룹이 잘 변하지 않으면 Enum 타입으로 설계하곤 한다. RDB에 이런 Enum 성격을 갖는 값들을 저장할 땐 데이터 타입을 `char`, `varchar`, `enum`을 사용한다. (MySQL, MariaDB, PostgreSQL 등)Mybatis EnumTypeHandlerMyBatis에선 Java의 Enum 타입을 멤버 변수 이름 그대로 매핑해 주는 `EnumTypeHandler`를 기본 TypeHandler로 사용하는데, 모종의 이유(레거시 프로젝트 등)로 DB에는 소문자나, 다른 값으로 저장해야 할 때가 있다. 이때 Java Enum 타입의 멤버 변수는 상수이기 때문에 ..
MySQL NULL 정렬 (ORDER BY)
·
MySQL
SQL에서 `NULL`은 '값이 없음'을 의미하지만, 이는 단순히 비어 있다는 의미가 아니라 '값이 아직 정해지지 않았거나 알 수 없는 상태'를 뜻합니다. 이처럼 `NULL` 값은 비교나 정렬에서 특별한 처리를 필요로 하며, DBMS마다 `NULL` 정렬 처리 방식이 다릅니다.DBMS마다 다른 NULL 정렬 처리 방식DBMS 오름차순 (ASC) 정렬 시 NULL 위치 내림차순 (DESC) 정렬 시 NULL 위치 MySQL맨 위 (가장 작은 값 취급)맨 아래 (가장 큰 값 취급)PostgreSQL맨 아래맨 위Oracle맨 아래맨 위SQL Server맨 위 (기본 설정 시)맨 아래 (기본 설정 시) 표준 ANSI 에 따르면 `NULL` 값을 어떻게 취급할지에 대해 정의하지 않아 데이터베이스마다 다르게 취급할..
쿠버네티스에서 MySQL dump 하기 (uuid 필드를 사용할 때)
·
Kubernetes
이번 포스팅은 네이버 클라우드(NKS)에서 구글 클라우드(GCP)로 마이그레이션을 하며 MySQL dump를 수행했을 때 일어났던 문제를 다룹니다.문제 상황MySQL 덤프한 파일로 import 수행 시 아래와 같이 `ERROR 1062 (23000)` 에러가 발생했는데요,ERROR 1062 (23000) at line 347: Duplicate entry '\xEF\xBF\xBB\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD\xEF\xBF\xBD\xEF' for key 'letter.PRIMARY'command terminated with exit code 1PK에 중복된 값이 있어서 실패했음을 알 수 있습니다.원인PK 필드를 UUID로 사용했는데, 타입을 BINARY(16)로 사용했습..
프로젝트 리팩토링 (5) - HTTP 요청 비동기 처리
·
Project
이번 포스팅에서는 여러 번 반복되는 HTTP 요청을 비동기로 처리해 응답 속도를 단축시키는 과정을 담았습니다.문제이전 포스팅 마지막에서 다뤘던 문제는 반복문 안에서 HTTP 요청을 보내는 것이었습니다. 기존 코드public void preCreateDailyReport(UUID userId, LocalDate startDate, LocalDate endDate) { // 편지 서비스에게 `분석 가능한 편지들` 찾기 위임 List analyzableLetters = letterService.findAnalyzableLettersInRange(userId, startDate, endDate); for (DailyLetters dailyLetters : analyzableLetters) { ..
프로젝트 리팩토링 (4) - 책임 재할당과 트랜잭션에서 외부 API 분리하기
·
Project
이전 포스팅에서는 객체들의 책임이 올바르게 할당되지 않았던 문제를 다뤘습니다.그러다 보니 거대한 몬스터 메서드들이 생겨났고, 이로 인해 유지보수와 확장이 어려웠었죠. 이 문제를 해소하기 위해 책임의 재할당을 다뤘었는데요, 이번 포스팅에서도 객체 지향 관점에서 책임의 재할당을 다룹니다.책임의 재할당 과정에서 트랜잭션에서 외부 API 호출 로직을 분리하고, 데이터 주도 협력 관계에서 책임 주도 협력 관계로 나아가는 과정을 담았습니다.의미 있는 타입으로 묶기이전 포스팅에서 책임 재할당의 결과왼쪽은 `편지 서비스`가 해야 할 책임이 `데일리 리포트 서비스`에 있었던 부분입니다. (코드 20줄)오른쪽은 `편지 서비스`에게 책임을 재할당하고, 해당 서비스에게 요청을 보내는 부분으로 개선된 코드입니다. (코드 1줄)..
프로젝트 리팩토링 (3) - 책임 재할당과 실행 계획 분석으로 검증하기
·
Project
문제 상황간략한 위클리 리포트 서비스 소개위클리 리포트 서비스는 이름대로 한 주 동안 유저가 작성한 편지(또는 일기)에 대한 분석을 제공합니다.서비스 정책 안에서 왕성히 활동한 유저의 7일 치 글을 모두 합치면 최대 16만 글자가 넘을 수 있습니다. 현실적으로 16만 글자를 생성형 AI에게 전달하는 것은 비용 문제와 서비스 품질 문제로 이어질 수 있기 때문에 `데일리 리포트`를 이용하는데요, `데일리 리포트`에 하루치가 요약되어 있는 내용을 이용해 `위클리 리포트`를 생성합니다.서비스 정책 간략 소개`위클리 리포트`는 절대적으로 `데일리 리포트`에 의존하기 때문에 반드시 `데일리 리포트`가 존재해야 합니다.위의 의존성 문제 때문에 우리 서비스의 정책은 유저가 한 주간 편지를 작성만 하고 `데일리 리포트`..
프로젝트 리팩토링 (2) - 도메인 모델 리팩토링
·
Project
이번 포스팅은 올려 올려 라디오 프로젝트의 도메인 모델 개선에 대해 다룹니다.현재 도메인 모델의 문제점을 짚어보고, 더 나은 도메인 모델을 제시하면서 그 근거를 정리하겠습니다. 시작하기 앞서, 프로젝트 리팩토링 과정을 잘 이해하기 위해 우리 프로젝트가 어떻게 동작하는지 간단하게 짚고 넘어가겠습니다.프로젝트 동작 구조답장 서비스 동작 구조위 그림은 우리 프로젝트의 첫 번째 MVP인 `답장 서비스`가 동작하는 구조입니다.유저가 `편지`를 써서 전달하면 생성형 AI가 `답장`을 생성해 응답합니다.우리 프로젝트는 유저가 작성한 `편지`와 `답장`을 각각 별개의 엔티티로 관리합니다.두 엔티티 간 관계는 1:1 관계를 갖고 있습니다. 데일리 리포트 서비스 동작 구조우리 프로젝트의 두 번째 MVP인 `데일리 리포트..