DB에서는 분명 정렬했는데, Java로 오면 뒤죽박죽?
실무에서 자바(Java)로 웹 개발을 하다 보면, 데이터베이스(DB)에서 쿼리문(ORDER BY)을 통해 예쁘게 정렬해서 가져온 데이터가 Java 단으로 넘어와서 List<Map<String, Object>> 형태로 담기는 순간 순서가 뒤죽박죽 섞여버리는 황당한 경험을 하곤 합니다.
이런 현상이 발생하는 이유는 우리가 흔히 사용하는 HashMap 객체 자체가 내부적으로 데이터의 '입력 순서를 보장하지 않는' 특성을 가지고 있기 때문입니다. (TreeMap을 사용하면 Key 값을 기준으로 자동 정렬이 되긴 하지만, 우리가 원하는 것은 특정 Value 값을 기준으로 전체 리스트를 정렬하는 것입니다.)
결국 DB에서 가져온 데이터를 다시 화면에 원하는 순서대로 뿌려주기 위해서는 Java 단에서 Collections.sort를 이용하여 Value 값을 기준으로 직접 재정렬을 해주어야 합니다. 오늘은 이 과정에서 흔히 겪게 되는 '숫자형 문자열 정렬 오류'와 그 해결 방법에 대해 생생한 트러블슈팅 경험담을 공유해 보겠습니다.
1. 기본 접근: Collections.sort 와 Comparator 사용하기
List 안에 담긴 Map 객체들을 특정 Value 기준으로 정렬하려면 Collections.sort() 메서드와 Comparator 인터페이스를 구현하여 사용해야 합니다. 예를 들어, Map 안에 들어있는 "num"이라는 Key의 Value 값을 기준으로 정렬을 시도해 보겠습니다.
| // 쿼리에서 가져온 articleList 값을 다시 정렬하기 위한 초기 시도 Collections.sort(articleList, new Comparator<Map<String, Object>>() { private final Collator collator = Collator.getInstance(); @Override public int compare(Map<String, Object> values1, Map<String, Object> values2) { // "num" 키를 가진 값을 String으로 형변환하여 비교 return collator.compare(String.valueOf(values1.get("num")), String.valueOf(values2.get("num"))); } }); |
처음에는 위와 같이 코드를 작성했습니다. 문법상으로는 전혀 문제가 없고 로직도 잘 돌아갑니다. 하지만 막상 결과를 출력해 보니 전혀 예상치 못한 치명적인 문제가 발생했습니다.
2. 문제 발생: 문자열 숫자 정렬의 함정 (1, 10, 2, 3...)
제가 정렬하고 싶었던 "num"의 값은 1부터 15까지 순차적으로 증가하는 숫자였습니다. 당연히 1, 2, 3, 4 ... 14, 15 순서로 정렬될 줄 알았습니다.
하지만 결과는 1, 10, 11, 12, 13, 14, 15, 2, 3, 4, 5... 순으로 나오게 되었습니다. 왜 이런 일이 벌어졌을까요?
이유는 바로 제가 비교할 때 String.valueOf()를 사용하여 데이터를 숫자(int)가 아닌 문자열(String)로 취급하여 비교했기 때문입니다. 컴퓨터가 문자열을 비교할 때는 사전식(사전 맨 앞자리부터 차례대로 비교)으로 비교합니다. 따라서 맨 앞글자가 '1'인 "10"이나 "15"가 맨 앞글자가 '2'인 "2"보다 먼저 오게 되는 것입니다.
3. 해결 방법: String.format을 이용해 '0' 붙여 자릿수 맞추기
이 문제를 해결하기 위해 깊은 고민에 빠졌습니다. "문자열 상태를 유지하면서 사전식 정렬을 했을 때 올바른 숫자 순서대로 나오게 하려면 어떻게 해야 할까?"
정답은 바로 앞에 '0'을 붙여서 모든 숫자의 자릿수를 동일하게 맞춰주는 것이었습니다. 01, 02, 03... 10, 11... 이렇게 자릿수를 맞춰주면 문자열 비교를 하더라도 0으로 시작하는 숫자들이 먼저 오기 때문에 완벽하게 정렬이 됩니다.
자바의 String.format() 메서드를 사용하여 소스를 아래와 같이 수정했습니다.
| // 수정된 정렬 로직 (자릿수 포맷팅 적용) Collections.sort(articleList, new Comparator<Map<String, Object>>() { private final Collator collator = Collator.getInstance(); @Override public int compare(Map<String, Object> values1, Map<String, Object> values2) { // String 값을 int로 파싱한 뒤, 앞자리에 0을 붙여 3자리 문자열로 포맷팅 ("%03d") String str1 = String.format("%03d", Integer.parseInt(String.valueOf(values1.get("num")))); String str2 = String.format("%03d", Integer.parseInt(String.valueOf(values2.get("num")))); return collator.compare(str1, str2); } }); |
포맷팅에서 %3d는 단순 3자리, %03d는 남는 빈자리를 0으로 채우는 3자리라는 뜻입니다. 저는 1~15까지의 숫자였으므로 넉넉하게 3자리(%03d)로 변경하여 서로 비교해 주었습니다.
결과는 대성공! 의도했던 대로 1부터 15까지 예쁘게 정렬되어 나오는 것을 확인할 수 있었습니다.
보너스 팁: Integer 타입으로 바로 비교하기
물론 위처럼 문자열 포맷팅을 사용하는 방법도 훌륭한 해결책이지만, 자바에서 제공하는 원시 타입 비교 메서드를 사용하면 코드를 조금 더 간결하게 만들 수도 있습니다. 애초에 문자열(String)이 아니라 정수(int) 형태로 변환하여 숫자 자체의 크기를 비교해 버리는 것입니다.
| // 대안: Integer.compare() 를 이용한 숫자 직접 비교 Collections.sort(articleList, new Comparator<Map<String, Object>>() { @Override public int compare(Map<String, Object> values1, Map<String, Object> values2) { int num1 = Integer.parseInt(String.valueOf(values1.get("num"))); int num2 = Integer.parseInt(String.valueOf(values2.get("num"))); // 오름차순 정렬 (내림차순일 경우 num2, num1 위치 변경) return Integer.compare(num1, num2); } }); |
이렇게 하면 자릿수를 맞출 필요 없이 숫자 크기 자체로 비교가 됩니다. 상황에 따라 포맷팅 방식과 직접 비교 방식 중 본인의 프로젝트에 더 알맞은 방법을 선택하여 사용하시길 바랍니다.
오늘은 List<Map> 구조에서 특정 값을 기준으로 정렬할 때 마주칠 수 있는 함정과 해결법에 대해 알아보았습니다. 코드를 짜다 보면 로직은 맞는데 데이터 타입의 특성 때문에 엉뚱한 결과가 나오는 경우가 참 많은 것 같습니다.
이번 트러블슈팅 경험을 통해 String 비교와 int 비교의 차이점을 확실하게 짚고 넘어갈 수 있었습니다. 저와 비슷한 문제로 골머리를 앓고 계신 다른 개발자분들께도 이 글이 시원한 해결책이 되었기를 바랍니다!
'프로그래밍&DB' 카테고리의 다른 글
| 자바 class파일 버젼 확인 (0) | 2026.04.08 |
|---|---|
| [자바/Java] 형변환 완벽 정리: String을 int로, int를 String으로 변환하는 방법 (0) | 2026.04.08 |
| TNS-12560: TNS:프로토콜 어댑터 오류 (0) | 2019.10.10 |
| 테이블스페이스에서 확장할 수 없습니다. (0) | 2018.10.04 |
| 리눅스에서 오라클접속(리스너) (0) | 2018.09.19 |