JPA 테이블 별 공통 함수 및 인터페이스 개발 방향
개요
befw-lib-core 라이브러리 내 JPA Service 계층의 공통화 모듈 개발 방향을 정의한다.
반복적인 DB 접근 코드를 줄이고, 일관된 에러 처리 및 테넌트 격리를 자동화하는 것이 목표다.
관련 모듈
- 라이브러리:
befw-lib-core - 베이스 패키지:
com.tsh.starter.befw.lib.core.data.orm - 현재 구조:
BasicAudit→BaseModel→ 실제 엔티티BaseJpaRepository→ 엔티티별 RepoCrudService→AbstractCrudService→ 엔티티별 Access
개발 아이템
아이템 목록
| 번호 | 아이템 | 상태 |
|---|---|---|
| 1 | UK별 CRUD 자동 생성 | 설계확정 |
| 2 | DB 에러 처리 공통화 | 설계확정 |
| 3 | Tenant 격리 자동화 | 설계확정 |
개발 순서
아이템 3 (Tenant 격리 자동화)
↓
아이템 2 (에러 처리 공통화) ← Tenant 에러도 여기서 처리
↓
아이템 1 (UK별 CRUD 자동 생성) ← 에러 처리 + Tenant 격리 위에서 동작
순서 이유
상위 아이템이 하위 아이템의 기반이 되기 때문에 Tenant 격리 → 에러 처리 → UK CRUD 순서로 개발한다.
아이템 1. UK별 CRUD 자동 생성
목적
엔티티의 Unique Key(UK) 조합으로 조회 / 수정 / 삭제하는 경우, 매번 각 Access 클래스에 중복 메서드를 작성하지 않도록 공통화한다.
확정 설계
| 항목 | 확정 내용 |
|---|---|
| UK 선언 방식 | @Table(uniqueConstraints) 재활용 (별도 파일/어노테이션 없음) |
| 복합 UK 여러 개 | name 속성으로 구분하여 각각 CRUD 생성 |
| 조회 실패 시 | EntityNotFoundException throw |
| Update 방식 | Model 객체로 받되 null 필드는 무시, Audit 자동 유지 |
| DDL 전략 | dev: ddl-auto: update / prod: 추후 Flyway 도입 검토 |
UK 선언 방식
기존 JPA 표준 어노테이션인 @UniqueConstraint를 재활용한다.
별도 어노테이션이나 설정 파일 없이 UK 변경 시 @UniqueConstraint만 수정하면 CRUD도 자동으로 따라간다.
// 예시: GnMsgSrvConnModel
@Table(
name = GlobalTableName.GN_MSG_SRV_CONN,
uniqueConstraints = {
@UniqueConstraint(name = "uk_msg_srv", columnNames = {"env", "host", "port"})
}
)
public class GnMsgSrvConnModel extends BaseModel {
...
}DDL 자동화 전략
UK 추가/변경 시 코드 어노테이션만 수정하면 DB에 자동 반영된다.
# befw-config-repo 환경별 설정
# local / dev
spring:
jpa:
hibernate:
ddl-auto: update
# prod (추후 Flyway 도입 후 변경)
spring:
jpa:
hibernate:
ddl-auto: validate주의사항
ddl-auto: update는 UK 추가는 자동 반영되지만 UK 삭제/변경은 자동 반영되지 않는다. prod 환경 전환 시 반드시 Flyway 도입을 검토한다.
자동 생성되는 메서드 구조
AbstractCrudService가 런타임에 @UniqueConstraint를 읽어서 아래 메서드를 자동으로 처리한다.
findByUk(ukName, params) → UK로 단건 조회
updateByUk(ukName, params, model) → UK로 조회 후 부분 업데이트
deleteByUk(ukName, params) → UK로 조회 후 Soft Delete
Update 정책
- 호출부에서
Model객체를 넘긴다. null인 필드는 업데이트에서 제외한다.BasicAudit의modifiedAt,modifiedBy는@LastModifiedDate,@LastModifiedBy로 자동 유지된다.
아이템 2. DB 에러 처리 공통화
목적
JPA에서 발생하는 다양한 예외를 서비스 계층에서 일관된 방식으로 처리하고, 디버깅에 필요한 파라미터/쿼리 정보를 함께 로깅한다.
확정 설계
| 항목 | 확정 내용 |
|---|---|
| 에러 형태 | 공통 DataErrorResponse DTO 반환 |
| 로깅 수준 | 일단 동일 수준, 추후 심각도별 구분 검토 |
| Optimistic Lock 재시도 | 최대 2회, 총 100ms 초과 금지 |
| 파라미터 로깅 | 실행 시 넣은 파라미터 + 시도한 쿼리 정보 함께 출력 |
DataErrorResponse DTO 구조
DataErrorResponse {
String errorCode; // "UK_DUPLICATE", "NOT_FOUND", "LOCK_CONFLICT", "DB_UNAVAILABLE"
String message; // 사람이 읽을 수 있는 설명
String entity; // 어떤 엔티티에서 발생했는지
Object params; // 실행 시 넣은 파라미터
String query; // 시도한 쿼리 정보
LocalDateTime occurredAt; // 발생 시각
}에러 코드 정의
| errorCode | 원인 JPA 예외 | 설명 |
|---|---|---|
NOT_FOUND | EntityNotFoundException | PK/UK로 조회했는데 없음 |
UK_DUPLICATE | DataIntegrityViolationException | UK 중복 저장 시도 |
INVALID_REQUEST | ConstraintViolationException | 필수값 누락 등 |
LOCK_CONFLICT | OptimisticLockingFailureException | 동시 수정 충돌 |
DB_UNAVAILABLE | JpaSystemException | 연결 실패 / 타임아웃 |
Optimistic Lock 이란?
Optimistic Lock 개념
DB에 실제 락을 걸지 않고, 저장 시점에 버전 번호로 충돌을 감지하는 방식이다.
예시 시나리오
- Agent A가 설정값을 읽음 (version = 1)
- Agent B도 같은 설정값을 읽음 (version = 1)
- Agent A가 먼저 저장 → DB version = 2
- Agent B가 저장 시도 → DB는 version 2인데 B는 version 1 기준으로 수정
- 충돌!
OptimisticLockException발생이를 방지하려면
BasicAudit에@Version필드 추가가 필요하다.
Optimistic Lock 재시도 정책
최대 재시도 횟수: 2회
총 허용 시간: 100ms 초과 금지
1차 시도 실패
→ 대기 후 2차 시도
→ 2차 시도 실패
→ LOCK_CONFLICT 에러 반환
주의사항
총 재시도 시간이 100ms를 초과하면 즉시 실패 처리한다.
아이템 3. Tenant 격리 자동화
목적
개발자가 tenant 파라미터를 수동으로 넘기지 않아도 자동으로 주입되도록 하여, 실수로 tenant 조건을 빠뜨려 다른 테넌트 데이터가 노출되는 리스크를 방지한다.
확정 설계
| 항목 | 확정 내용 |
|---|---|
| 주입 방식 | ThreadLocal 기반 TenantResolver 인터페이스 |
| tenant 없는 요청 | 예외 처리 (배치 포함 무조건 tenant 필수) |
| 적용 대상 | 전체 엔티티 |
| 확장 전략 | 추후 JWT/Security 방식으로 교체 가능하도록 인터페이스로 추상화 |
TenantResolver 인터페이스 구조
각 시스템이 TenantResolver 인터페이스만 구현하면 자동으로 연결된다.
현재는 ThreadLocal 방식으로 구현하며, 추후 JWT/Security 방식으로 교체 가능하다.
TenantResolver (인터페이스)
↓
ThreadLocalTenantResolver (현재 구현체)
↓ (추후 교체 가능)
JwtTenantResolver / SecurityTenantResolver
Tenant 주입 흐름
요청 진입 (HTTP / Solace 메시지 / 배치)
↓
TenantResolver.set(tenant) ← 각 시스템이 진입점에서 세팅
↓
Hibernate Filter 자동 활성화
↓
모든 JPA 쿼리에 WHERE tenant = ? 자동 추가
↓
요청 완료 후 TenantResolver.clear() ← ThreadLocal 반드시 정리
Tenant 없는 요청 처리
TenantResolver.get() 호출
↓
tenant == null 또는 blank
↓
TenantMissingException throw
→ DataErrorResponse { errorCode: "TENANT_MISSING" } 반환
주의사항
ThreadLocal은 요청 처리 완료 후 반드시
clear()해야 한다. 특히 Thread Pool 환경에서 clear 누락 시 다른 요청에 이전 tenant가 묻어나올 수 있다.
확장 전략
현재는 백엔드 서비스 개발에 집중하고, 서비스 운영 단계에서 아래 방식으로 전환을 검토한다.
| 단계 | 방식 | 비고 |
|---|---|---|
| 현재 (개발) | ThreadLocal | 시스템 간 약속으로 tenant 세팅 |
| 추후 (운영) | JWT 클레임 기반 | HTTP 요청에 자연스러움 |
| 추후 (운영) | Spring Security Context | Security 의존성 추가 필요 |
현재 구조와의 연결
BasicAudit
└── BaseModel ← Hibernate Filter로 tenant 자동 주입 (아이템 3)
└── GnMsgSrvConnModel
└── @UniqueConstraint ← UK CRUD 자동 생성 (아이템 1)
AbstractCrudService
├── 에러 처리 공통화 (아이템 2)
├── Tenant 격리 (아이템 3)
└── UK별 CRUD (아이템 1)
관련 문서
변경 이력
| 날짜 | 내용 | 작성자 |
|---|---|---|
| 2026-04-13 | 최초 작성 - 3개 아이템 설계 확정 | David |