포스트

Common

JSP 기반 시스템의 구조적 문제를 해결한 아키텍처 전환기: MyBatis와 JPA 공존 환경의 데이터 정합성 전략

레거시(MyBatis)와 JPA 시스템의 공존 시 데이터 정합성 전략

새로운 시스템을 도입하면서 기존 레거시 시스템을 한 번에 폐기하기는 현실적으로 어렵습니다. 그래서 두 시스템을 동시에 운영하거나 점진적으로 전환하게 되는데, 두 시스템이 같은 테이블을 읽고 쓰는 동안에는 데이터 정합성 문제가 생기기 쉽습니다.

이 글은 MyBatis 기반 레거시 시스템과 JPA 기반 신규 시스템을 병행 운영하면서 마주친 데이터 정합성 문제와 해결 전략을 다룹니다.


시스템 구성

구분레거시 시스템신규 시스템
기술 스택Spring MVC + MyBatisSpring Boot + JPA/Hibernate
DB 스키마동일한 테이블을 공유동일
주요 차이SQL 직접 작성ORM 매핑 기반
운영 방식점진적 전환 (Dual Run)신규 기능부터 점진 이관

문제 상황들

1-1. Enum 매핑 불일치 문제

  • 문제: MyBatis에서는 String으로 저장된 열거형 값이 있었으나, JPA에서는 @Enumerated(EnumType.STRING)을 사용함.
  • 결과: enum에 정의되지 않은 값이 DB에 존재하면 JPA에서 IllegalArgumentException이 발생하고 조회가 실패함.

EnumType.STRING은 Jakarta Persistence Javadoc의 설명대로 enum 값을 문자열로 저장하는 방식입니다(EnumType). 문자열을 enum 상수로 되돌리는 Enum.valueOf는 해당 이름의 상수가 없으면 IllegalArgumentException을 던집니다(Enum.valueOf Javadoc). 그래서 레거시가 enum 정의 밖의 문자열을 하나라도 남겨 두면 신규 시스템의 조회가 깨집니다.

예시

1
2
-- DB에는 "DEPRECATED" 값이 존재
-- 하지만 JPA enum에는 ACTIVE, INACTIVE만 정의되어 있음

1-2. 날짜/타임스탬프 정렬 및 기본값 차이

  • MyBatis에서는 SQL에서 SYSDATE, NOW() 등을 직접 사용하던 필드들이 JPA에서는 @CreationTimestamp 등으로 처리됨.
  • 이로 인해 초 단위 차이, 정렬 순서 역전, 또는 NULL 처리 방식의 불일치가 발생.

1-3. Null / Default 값 처리 불일치

  • MyBatis는 null이 DB에 저장되더라도 application 레벨에서 이를 방어하는 로직이 있는 반면,
  • JPA는 @NotNull, nullable = false 등 제약 조건이 강하게 설정됨.
  • 이 경우 DB에는 null이 존재하지만 JPA 매핑은 이를 허용하지 않으므로, Entity 로딩 시 오류가 발생할 수 있음.

1-4. 중복된 비즈니스 로직의 불일치

  • 레거시 시스템에서 이미 보정된 데이터가 신규 시스템에서는 다른 기준으로 처리되는 경우.
  • 예: 특정 상태값 계산, 포맷팅, 수동 조작 이력 반영 로직 등이 MyBatis에는 SQL로 박혀 있고, JPA에는 누락됨.

2. 해결 전략 및 실무 적용 예시

전략 1. Enum 매핑 안전화

방법

  • @Enumerated 대신 @Convert(converter = SafeEnumConverter.class) 사용
  • enum에 UNKNOWN, DEPRECATED 등 안전한 기본값 추가

@Convert에 지정하는 변환기는 AttributeConverter를 구현합니다. 이 인터페이스에서 DB 컬럼 값을 엔티티 속성 값으로 바꾸는 메서드는 convertToEntityAttribute입니다(AttributeConverter Javadoc). 이 읽기 경로에서 아래 Status.from처럼 예외 대신 UNKNOWN을 돌려주면, 정의 밖의 값이 있어도 조회는 실패하지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
public enum Status {
    ACTIVE, INACTIVE, DEPRECATED, UNKNOWN;

    public static Status from(String value) {
        try {
            return Status.valueOf(value.toUpperCase());
        } catch (Exception e) {
            return UNKNOWN;
        }
    }
}

전략 2. 데이터 보정 스크립트 + 릴리즈 체크리스트화

상황

  • 신규 시스템 배포 직전, 데이터 정합성 문제가 있는 상태를 자동으로 탐지하거나 사전에 보정

접근

  • SQL 스크립트 작성: enum 범위, null 값, 중복 키, 날짜 역전 현상 등 사전 진단
  • 배포 전후 SQL 예시:
1
2
3
4
5
-- 상태값 중 enum에 없는 값 목록 추출
SELECT DISTINCT status FROM user WHERE status NOT IN ('ACTIVE', 'INACTIVE');

-- 잘못된 날짜 정렬 수정
UPDATE content SET created_at = updated_at WHERE created_at IS NULL;
  • 운영 중에도 배치 프로세스 또는 알림을 통해 자동 감지 및 롤백 시나리오 준비

전략 3. 공통 DTO 및 Adapter Layer 도입

  • 데이터 변환을 위한 어댑터 혹은 DTO를 두 시스템에서 동일하게 사용하도록 정의
  • 특히 enum ↔ String, LocalDate ↔ Timestamp, NULL ↔ Default값 매핑 규칙을 일관되게 유지
1
2
3
4
5
6
7
// DTO 변환 예시
public UserDto toDto(User entity) {
    return new UserDto(
        entity.getId(),
        Optional.ofNullable(entity.getStatus()).orElse(Status.UNKNOWN).name()
    );
}

전략 4. 읽기/쓰기 분리 운영

  • 초기에는 읽기는 JPA, 쓰기는 MyBatis처럼 역할을 분리
  • 점진적으로 JPA 쓰기 로직을 도입하면서 보정 및 검증을 진행

이중 쓰기 방지 및 동시성 이슈에 대한 관리도 병행 필요 (ex: 분산락, last_updated 기준 우선순위). 동시 수정 제어는 Spring과 JPA에서 Race Condition을 다루는 방법에서 따로 다룹니다.


전략 5. 운영 중 정합성 모니터링 지표화

  • 잘못된 enum 값 수, null 비율, 마이그레이션 비율 등을 지표화
  • ex: Grafana + Custom Query, Prometheus Exporter, Kibana에서 쿼리 저장 등
1
SELECT status, COUNT(*) FROM user GROUP BY status;

결론 및 권장 프로세스

단계작업 내용
1단계기존 데이터 정합성 점검 (SQL 기반 진단 스크립트)
2단계신규 enum/DTO 등 매핑 구조 정의 및 어댑터 작성
3단계신규 시스템 배포 전, 데이터 보정 및 검증 작업 수행
4단계신규 시스템과 레거시 시스템의 공존 전략 수립 (읽기/쓰기 분리 등)
5단계운영 중 모니터링 및 이상 징후 자동 알림 설정
6단계완전한 전환 전까지는 매 릴리즈마다 보정 체크리스트 수행

마무리하며

레거시 시스템과 신규 시스템을 병행 운영할 때 가장 위험한 것은 아무도 알아채지 못한 채 쌓이는 정합성 오류입니다. 레거시는 SQL과 애플리케이션 코드로 이런 값을 조용히 넘겨 왔지만, JPA는 같은 값을 만나면 조회 단계에서 예외를 냅니다.

이 엄격함을 이점으로 바꾸려면 배포 전에 정합성을 진단하고, 정의 밖의 값을 우회할 구조를 먼저 설계해야 합니다. 이 글에서 택한 순서는 정합성 점검, 우회 처리, 점진적 전환입니다.

참고

결제·정산 정합성 Spring과 JVM 백엔드 아키텍처와 마이그레이션
이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

변경이력

2번 수정

  1. docs(notes): cite sources and ease reading in legacy-jsp-system-refactoring-3
  2. docs(posts): normalize common note metadata and headings

댓글

아직 댓글이 없습니다