Java 코드와 DB 쿼리 간 불일치 문제 CSV 필터링 개선하기

궁금하면 500원·2025년 4월 7일
0

미생의 개발 이야기

목록 보기
37/59

Java CSV 서비스에서 데이터 필터링 이슈 해결하기

문제 배경

기업 정보 크롤링 시스템 개발 중 CSV 파일로부터 데이터를 읽어들이는 과정에서 필터링 이슈가 발생했습니다.
시스템은 CSV 파일에서 법인 정보를 읽어와 필터링한 후 처리하는 기능을 제공하고 있었습니다.
특히 '법인번호'가 유효하지 않은 데이터(N/A 포함, 0000000000000 값)를 제외하는 로직을 구현했으나,
실제 데이터베이스 쿼리 결과와 Java 코드의 필터링 결과가 일치하지 않는 문제가 발생했습니다.

데이터베이스에서 직접 쿼리를 실행했을 때

  • SELECT * FROM CORP_MAST WHERE CORP_reg_NO = '0000000000000' → 1건 조회
  • SELECT * FROM CORP_MAST WHERE CORP_reg_NO LIKE '%N/A%' → 8건 조회

이러한 레코드들은 Java 코드에서 필터링되어야 했지만, 실제로는 정상적으로 필터링되지 않는 문제가 있었습니다.

변경 전 코드

private boolean isValidData(String[] tokens) {
    String corpRegNo = tokens.length > 3 ? tokens[3].trim() : "";

    // Null, '%N/A%' 및 0000000000000 값 제외
    return corpRegNo != null && !corpRegNo.isEmpty()
            && !corpRegNo.equals("0000000000000")
            && !corpRegNo.toUpperCase().contains("N/A"); // 'N/A' 포함된 값도 제외
}

기존 코드의 문제점

  • 디버깅을 위한 로깅이 부족했습니다.
  • 필터링 조건이 충분하지 않았습니다(예: 모든 0으로 이루어진 문자열 체크 누락).
  • 에러 발생 시 구체적인 원인 파악이 어려웠습니다.

해결 방법

1. 변경 후 코드

private boolean isValidData(String[] tokens) {
    if (tokens.length <= 3) {
        log.debug("유효하지 않은 데이터: 토큰 길이 부족 (length={})", tokens.length);
        return false;
    }
    
    String corpRegNo = tokens[3].trim();
    
    // 빈 값 체크
    if (corpRegNo == null || corpRegNo.isEmpty()) {
        log.debug("유효하지 않은 데이터: 빈 값");
        return false;
    }
    
    // '0000000000000' 체크 - 정확히 13자리 0인지
    if ("0000000000000".equals(corpRegNo)) {
        log.debug("유효하지 않은 데이터: '0000000000000' 값 (corpRegNo={})", corpRegNo);
        return false;
    }
    
    // 'N/A' 문자열 체크 - 정확히 일치하는지 (대소문자 무시)
    if (corpRegNo.toUpperCase().contains("N/A")) {
        log.debug("유효하지 않은 데이터: 'N/A' 포함 (corpRegNo={})", corpRegNo);
        return false;
    }

    // 추가 검증 - 모든 문자가 0인지 확인
    if (corpRegNo.matches("^0+$")) {
        log.debug("유효하지 않은 데이터: 모든 문자가 0 (corpRegNo={})", corpRegNo);
        return false;
    }
    
    return true;
}

2. 주요 개선 사항

a) 로깅 강화

각 필터링 단계마다 상세한 로그를 추가하여 어떤 데이터가 왜 필터링되는지 명확히 파악할 수 있도록 했습니다.

  • 변경 전: 로그 없음
  • 변경 후: 각 필터링 조건마다 디버그 로그 추가

b) 필터링 조건 강화

모든 0으로 이루어진 문자열, N/A 포함 문자열 등 다양한 케이스를 처리할 수 있도록 필터링 로직을 개선했습니다.

  • 변경 전: 기본적인 필터링만 구현
  • 변경 후: 정규표현식을 활용한 강화된 필터링 적용

c) 에러 처리 개선

전체 서비스 클래스에서 예외 처리와 로깅을 개선하여 문제 발생 시 빠르게 대응할 수 있도록 했습니다.

  • 변경 전
try (BufferedReader br = getReaderFromDefault(resource)) {
    return br.lines()
            .skip(1)
            .map(line -> line.split(",", -1))
            .filter(this::isBiz)
            .filter(this::isValidData)
            .map(this::parseCsvData)
            .collect(Collectors.toList());

} catch (IOException e) {
    throw new CsvParsingException(HttpStatus.INTERNAL_SERVER_ERROR, "CSV파일 읽어오기에 실패했습니다.");
}
  • 변경후
try (BufferedReader br = getReaderFromDefault(resource)) {
    List<BizCsvInfoDto> results = br.lines()
            .skip(1)
            .map(line -> line.split(",", -1))
            .filter(this::isBiz)
            .filter(this::isValidData)
            .map(this::parseCsvData)
            .collect(Collectors.toList());
    
    log.info("데이터 필터링 완료: 총 {}개의 유효한 레코드 처리됨", results.size());
    return results;

} catch (IOException e) {
    log.error("CSV 파일 읽기 실패: {}", e.getMessage(), e);
    throw new CsvParsingException(HttpStatus.INTERNAL_SERVER_ERROR, "CSV파일 읽어오기에 실패했습니다.");
}

3. SQL 쿼리 최적화

Java 코드의 필터링 조건과 일치하는 SQL 쿼리를 작성하여 데이터베이스 조회 결과와 Java 처리 결과의 일관성을 확보했습니다.

-- 유효한 데이터만 조회
SELECT * FROM CORP_MAST 
WHERE CORP_reg_NO NOT LIKE '%N/A%'
AND CORP_reg_NO != '0000000000000'
AND CORP_reg_NO NOT REGEXP '^0+$'  -- 모든 문자가 0인 경우 제외
AND CORP_reg_NO IS NOT NULL
AND TRIM(CORP_reg_NO) != '';

배우게 된 점

1. 데이터 검증의 중요성

시스템에서 처리하는 데이터의 무결성을 보장하기 위해서는 엄격한 검증 로직이 필요합니다.
특히 외부 소스(CSV 파일)에서 데이터를 가져올 때는 더욱 철저한 검증이 필요합니다.

2. 로깅의 가치

디버깅과 문제 해결을 위해 적절한 위치에 로그를 추가하는 것이 중요합니다.
로그를 통해 필터링 과정과 데이터 흐름을 명확히 파악할 수 있었고, 이로 인해 문제 해결 시간을 크게 단축할 수 있었습니다.

3. 일관성 있는 데이터 처리

애플리케이션 코드와 데이터베이스 쿼리 간의 일관성은 매우 중요합니다.
두 시스템에서 동일한 필터링 로직을 적용함으로써 데이터 불일치 문제를 예방할 수 있습니다.

4. 정규표현식 활용

복잡한 문자열 패턴 검증에는 정규표현식이 효과적입니다.
matches("^0+$")와 같은 정규표현식을 활용하여 모든 문자가 0으로 이루어진 경우를 효과적으로 필터링할 수 있었습니다.

결론

이번 프로젝트를 통해 데이터 필터링과 검증의 중요성을 다시 한번 깨달았습니다.
특히 외부 데이터 소스로부터 정보를 가져와 처리하는 시스템에서는 데이터 무결성을 보장하기 위한 철저한 검증 로직이 필수적입니다.
또한, 문제 발생 시 신속하게 원인을 파악하고 해결할 수 있도록 적절한 로깅 전략을 수립하는 것이 매우 중요하다는 점을 배웠습니다.
이러한 경험을 바탕으로 앞으로의 프로젝트에서도 더욱 견고하고 유지보수하기 쉬운 코드를 작성할 수 있을 것입니다.

  • 정상 요청 (data: 저장성공 )

  • 비정상 요청

  • 중복 요청

profile
꾸준히, 의미있는 사이드 프로젝트 경험과 문제해결 과정을 기록하기 위한 공간입니다.

0개의 댓글