CI 수집 로그 기능 개발하면서 배운 것들

syeony·2026년 3월 26일

backend

목록 보기
3/3
post-thumbnail

저축은행 신입 개발자 — Spring/Java/MyBatis 실무 삽질 기록 (2026년 3월 4주차)


목차

  1. CI 수집 로그 테이블 설계 — HTTP 헤더부터 타임스탬프까지
  2. Referer 헤더의 한계 — 팝업 인증이 망가뜨린 URL 추적
  3. NslApiManager → LogService 값 전달 삽질기 (static의 덫)
  4. SYS_CODE 공통코드로 하드코딩 없애기
  5. dbManager에서 MyBatis Mapper로 갈아타기

1. CI 수집 로그 테이블 설계 — HTTP 헤더부터 타임스탬프까지

이번 주 주요 업무는 방통위·KISA 가이드라인에 맞는 CI_TRANS_LOG 테이블 설계였다. 규제상 3년 보관 의무가 있고, 법적 증거 능력을 위해 타임스탬프 정밀도가 중요하다는 걸 처음 알았다.

처음엔 DATETIME으로 잡으려 했는데, 팀장님이 TIMESTAMP(6)으로 하라고 하셨다. 같은 초에 여러 요청이 들어올 때 순서를 보장하려면 마이크로초 단위 정밀도가 필요하기 때문이다. MySQL에서는 NOW(6)을 쓰면 마이크로초까지 채워진다.

이번에 배운 것: HTTP 헤더는 대소문자를 구분하지 않는다 (RFC 7230). getHeader("Referer")getHeader("referer")나 동일하게 동작한다.


2. Referer 헤더의 한계 — 팝업 인증이 망가뜨린 URL 추적

request.getHeader("referer")로 수집목적(service_type)을 구분하려 했는데 큰 벽에 막혔다.

  • 모바일웹: /lon/inq/qckLonLimt2.frm 같이 직전 페이지 URL이 잘 찍힘 ✅
  • 홈페이지(PC웹): PASS 인증이 팝업으로 열리다 보니 https://safe.ok-name.co.kr/CommonSvl (KCB 외부 도메인)이 찍혀버림 ❌

이때 동기가 "팝업은 URL이 없는데?" 라고 했는데, 정확히는 주소창에 안 보인다는 뜻이었고, 실제로는 팝업 자체가 저 URL로 서빙되고 있었다.

Referer 헤더는 "브라우저 주소창 URL"이 아니라 "HTTP 요청을 트리거한 페이지의 URL"이다.
팝업 안에서 요청이 발생하면 팝업 URL(또는 외부 도메인)이 찍힌다.

서버사이드(Java)에서는 브라우저 주소창 URL을 가져올 방법이 없다. JS에서 window.opener.location.href로 부모창 URL을 읽어올 수 있었다.


3. NslApiManager → LogService 값 전달 삽질기 (static의 덫)

NslApiManager(static 유틸 클래스)에서 LogService(Spring @Service)로 값을 넘기려다 삽질을 많이 했다.

req_url, ser_type 필드를 static으로 선언하고 값을 세팅한 다음 insertCITransLog()를 호출하려 했는데 계속 null이 들어갔다. 원인은 두 가지였다.

원인 1. insertCITransLog()가 애초에 호출되고 있지 않았다.

원인 2. Spring @Service는 싱글톤이다. 싱글톤 클래스에 static 필드를 쓰면 여러 요청이 동시에 들어올 때 서로 값을 덮어쓴다. 동시성 버그의 전형적인 원인.

해결책은 간단했다. 값을 필드에 저장하지 말고 메서드 파라미터로 직접 넘기는 것. 기존에 insertCITransLog()를 다른 곳에서도 쓰고 있어서 메서드 오버로딩으로 해결했다.

// 기존 메서드는 건드리지 않고
public void insertCITransLog(Map<String, Object> params) { ... }

// 파라미터 추가 버전을 오버로딩으로 추가
public void insertCITransLog(Map<String, Object> params, String reqUrl, String serType) { ... }

이번에 배운 것: static 필드로 상태를 공유하는 건 멀티스레드 환경에서 위험하다. 값은 항상 파라미터로 명시적으로 전달하자. 오버로딩은 기존 코드를 깨지 않고 새 시그니처를 추가하는 좋은 수단이다.


4. SYS_CODE 공통코드로 하드코딩 없애기

getServiceType(String url) 메서드를 처음엔 if-else 하드코딩으로 짜려 했다. 그런데 사수님이 어드민 페이지에서 관리하는 SYS_CODE 공통코드 테이블을 활용하라고 하셨다.

SYS_CODEUPPER_CODE로 그룹을 묶고, 하위 코드들이 실제 값(VALUE)과 이름(NAME)을 갖는 구조다. URL을 VALUE에, 서비스명을 NAME에 등록하면 코드 변경 없이 어드민에서 관리할 수 있다.

public String getServiceType(String url) {
    Map<String, Object> temp = new HashMap<>();
    List<Map<String, Object>> codeList = logMapper.s003(temp);

    for (Map<String, Object> code : codeList) {
        String value = (String) code.get("VALUE");
        if (url.equals(value)) {
            return (String) code.get("NAME");
        }
        // prefix 매칭 (예: /toss/ 로 시작하는 모든 URL)
        if (value.endsWith("/") && url.startsWith(value)) {
            return (String) code.get("NAME");
        }
    }
    return null;
}

startsWith() 쓸 때 팁: DB에 등록하는 값에 trailing slash(/)를 반드시 포함해야 의도치 않은 매칭을 막을 수 있다. /toss로 등록하면 /tosstest 같은 URL도 매칭돼버린다.

이번에 배운 것: 하드코딩은 언제나 유지보수 부채다. 값이 바뀔 가능성이 있는 건 DB나 설정 파일로 빼는 습관을 들이자.


5. dbManager에서 MyBatis Mapper로 갈아타기

모바일웹 코드에서는 dbManager.getRecordSet(ctx, "SYS_CODE.s003", temp) 패턴을 썼는데, 홈페이지 코드에서는 사수님이 dbManagerctx 대신 Mapper를 쓰라고 하셨다.

// 모바일웹 방식 (ctx 필요, 커스텀 프레임워크)
List<DataSet> list = dbManager.getRecordSet(ctx, "SYS_CODE.s003", temp);

// 홈페이지 방식 (MyBatis Mapper 직접 호출)
List<Map<String, Object>> list = logMapper.s003(temp);

여기서 실수를 했다. SqlSession sqlSession;@Autowired 없이 선언해서 NullPointerException이 날 뻔했다.

// 잘못된 방법 — @Autowired 없으면 null
private SqlSession sqlSession;

// 올바른 방법 — 이미 주입된 Mapper를 바로 쓰자
@Autowired
protected LogMapper logMapper;

Mapper 인터페이스에 메서드만 선언되어 있다면 sqlSession을 따로 쓸 이유가 없다.

이번에 배운 것: Spring 빈에서 필드 주입이 없으면 항상 null이다. 새 필드 선언할 때 @Autowired 빠뜨리지 말자.


회고

기능 하나 만드는 데 이렇게 많은 판단 포인트가 있을 줄 몰랐다. 규제 요구사항 → 테이블 설계 → 값 추적 문제 → 아키텍처 결정 → 코드 패턴 선택까지.

그래도 클로드와 함께 혼자 일주일동안 끙끙대면서 코드 하나하나 분석하면서 해결한게 뿌듯했다.
비록 프론트로 뽑혀 들어왔지만 첫업무부터 백엔드를 해서 약간은 힘들고 당황했지만 그래도 실시간으로 실력이 오르는 느낌이라서 좋다. 좋은 사수분을 만나서 질 좋은 교육을 돈받고 들을 수 있다고 긍정적으로 생각하고있다.
앞으로도 클로드 결제는 계속 할 것...


#Spring #Java #MyBatis #MySQL #신입개발자 #개발일지 #CI수집로그 #KCB인증

profile
cross platform과 aOS, iOS에 관심이 많은 모바일 개발자 지망생 오승연입니다

0개의 댓글