Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 아키텍처
- JPA
- AWS
- 객체지향원칙
- Kafka
- trouble shooting
- 프로그래머스
- Github Actions
- 유효성 검사
- 테스트 코드
- JWT
- aop
- MSA
- Intellij
- querydls
- testcode
- Java
- docker
- 어노테이션
- swagger
- spring boot
- 멀티 모듈
- DevOps
- Redis
- CI/CD
- Til
- EC2
- springboot
- algorithm
- rabbitmq
Archives
- Today
- Total
개발노트
25.04.17 MySQL에서 UUID, Double 처리 문제 및 해결 방안 본문
문제
JPA 쿼리에서 UUID 타입이나 Double 타입의 컬럼을 가져올 때, 값이 자동으로 업캐스팅되어 제대로 처리되던 방식이 갑자기 작동하지 않는 문제가 생겼다.
nativeQuery를 사용하여 SQL 쿼리를 실행한 후, UUID나 Double로 변환되지 않는 문제다.
@Query(value =
"SELECT r.performance_id AS performanceId, AVG(r.rating) AS avgRating, COUNT(r.id) AS reviewCount " +
"FROM p_review r " +
"WHERE r.deleted_at IS NULL " +
"GROUP BY r.performance_id",
nativeQuery = true)
List<Map<String, Object>> fetchPerformanceRatingStatsBulk();
@Override
public void setAvgRatingBulk() {
// DB에서 각 공연 아이디 별 평점과 리뷰의 수를 가져온다
List<Map<String, Object>> performanceReviews = reviewRepository.fetchPerformanceRatingStatsBulk();
// Redis pipline 을 잘라서 처리할 단위 설정
int batchSize = 1000;
// 조회된 데이터의 수
int totalRecords = performanceReviews.size();
// batchSize 단위로 나눠서 처리
for (int i = 0; i < totalRecords; i += batchSize) {
// start ~ end 범위 데이터만 처리
int start = i;
int end = Math.min(start + batchSize, totalRecords);
// Redis Pipeline 시작
redisTemplate.executePipelined((RedisCallback<Object>)connect -> {
for (int j = start; j < end; j++) {
Map<String, Object> map = performanceReviews.get(j);
UUID performanceId = (UUID)map.get("performanceId");
double avgRating = (double)map.get("avgRating");
long reviewCount = (long)map.get("reviewCount");
String avgRatingKey = AVG_RATING_KEY + performanceId.toString();
Map<String, Object> ratingInfo = new HashMap<>();
ratingInfo.put("avgRating", avgRating);
ratingInfo.put("reviewCount", reviewCount);
redisTemplate.opsForHash().putAll(avgRatingKey, ratingInfo);
}
return null;
});
}
}
문제 원인
우리 팀은 티켓팅 서비스를 개발하기 위해 PostgreSQL을 사용할 계획이었으나, 대용량 조회 성능을 고려해 MySQL을 선택하여 개발을 진행하게 되었다.
그러나 MySQL에서는 UUID 타입과 Double 타입을 직접 지원하지 않기 때문에, JPA에서 예상한 타입으로 자동 업캐스팅되지 않는 문제가 발생했다.
UUID 문제
- MySQL은 UUID 타입을 기본적으로 지원하지 않는다.
- 대신, UUID 값을 CHAR(36) 또는 BINARY(16) 형식으로 저장해야 한다
- CHAR(36) → 조회 시 String 형태로 반환된다.
- BINARY(16) → 조회 시 byte[] 형태로 반환된다.
- 따라서, JPA에서 Map<String, Object> 형태로 결과를 받아올 경우, UUID 타입으로 자동 변환되지 않고 String 또는 byte[] 형태로 반환되어 내가 따로 변환 로직을 구현해줘야한다..
Double 관련
- MySQL에서 AVG() 같은 집계 함수는 기본적으로 BigDecimal 타입으로 결과를 반환한다고한다
- 그래서 JPA로 가져올 때 Double로 직접 캐스팅하면 ClassCastException이 발생할 수 있으며, 반드시 BigDecimal → doubleValue() 을 통해 변환하는 로직도 내가 직접 구현해줘야한다...
뭐든지 장단점이 있는 거 같다..
한줄 요약
MySQL은 UUID와 Double 타입을 직접 지원하지 않기 때문에, JPA에서 자동 변환되지 않아 명시적인 타입 변환 처리가 필요했다.
해결 방법
1. UUID를 CHAR(36)으로 저장한 경우
CHAR(36)으로 저장된 UUID는 String으로 반환할 수 있다.
UUID로 변환하려면 String 가져와 한번 더 UUID.fromString() 메서드를 통해 변환 해주면된다.
String str = (String) data.get("performanceId");
UUID performanceId = UUID.fromString(str);
2. UUID를 BINARY(16)으로 저장한 경우
BINARY(16)으로 저장된 UUID는 byte[] 형태로 반환할 수있다.
이런 경우는 byte[]를 UUID로 변환하는 로직을 따로 구현해줘야한다.
byte[] bytes = (byte[]) data.get("performanceId");
ByteBuffer bb = ByteBuffer.wrap(bytes);
UUID performanceId = new UUID(bb.getLong(), bb.getLong());
3. BigDecimal 를 Double로 변환 할 때
캐스팅해서 doubleValue() 메서드로 변환해주면된다!
private double bigDecimalToDouble(Object value) {
if (value instanceof BigDecimal) {
return ((BigDecimal)value).doubleValue();
}
throw new PaymentException(ResponseCode.ILLEGAL_ARGUMENT);
}
'TroubleShooting' 카테고리의 다른 글
25.04.15 결제 도메인에서 마일리지/쿠폰 차감 책임을 분리한 이유 (0) | 2025.04.15 |
---|---|
25.04.13 Spring Redis hashOps.keys()로 패턴 조회 안 되는 이유와 SCAN을 사용한 해결법 (0) | 2025.04.13 |
25.04.10 Jackson LocalDateTime 직렬화 오류 해결 (0) | 2025.04.09 |
2025.03.22 RabbitMQ concurrency 설정과 비관적 락을 활용한 재고 감소 동시성 제어 (0) | 2025.03.25 |
25.03.21 사가 패턴 무한 재시도 방지 (0) | 2025.03.21 |