2025년 3월 23일
JPA
조회 : 43|3분 읽기

JPA - Oracle Sequence

🧩 JPA에서 Oracle 시퀀스로 String 타입 ID 생성하기

JPA를 사용하다 보면 기본적으로 @GeneratedValue(strategy = GenerationType.SEQUENCE)를 이용해 Oracle의 시퀀스를 ID로 사용하는 경우가 많습니다.
그런데 ID 타입이 Long, Integer가 아닌 String인 경우 문제가 발생합니다.
이번 글에서는 왜 그런 문제가 발생하는지 그리고 어떻게 커스텀 Generator로 해결할 수 있는지 정리해보겠습니다.

😕 문제 상황

아래처럼 String 타입의 ID 필드에 @GeneratedValue를 사용해 Oracle 시퀀스를 연결하면 어떻게 될까요?
java
1@Entity
2public class MyEntity {
3
4    @Id
5    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_seq_gen")
6    @SequenceGenerator(name = "my_seq_gen", sequenceName = "MY_SEQ", allocationSize = 1)
7    private String id;
8
9    // ...
10}
이유는?
JPA가 시퀀스 값을 가져올 때 내부적으로 ResultSet.getLong() 같은 숫자 전용 메서드를 사용하기 때문에, String 타입 필드에는 매핑할 수 없기 때문입니다.

✅ 해결책: 커스텀 IdentifierGenerator 구현

Hibernate에서는 org.hibernate.id.IdentifierGenerator를 구현해 ID 생성 방식을 커스터마이징할 수 있습니다.
다음은 Oracle 시퀀스를 사용하면서 String 타입으로 반환하는 커스텀 Generator 예제입니다.
📦 IdGenerator.java
java
1public class IdGenerator implements IdentifierGenerator, Configurable {
2
3    public static final String METHOD = "method";
4    public static final String SEQUENCE_NAME = "sequence_name";
5
6    private String method;
7    private String sequenceName;
8
9    @Override
10    public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) {
11        method = ConfigurationHelper.getString(METHOD, params);
12        sequenceName = ConfigurationHelper.getString(SEQUENCE_NAME, params);
13    }
14
15    @Override
16    public Serializable generate(SharedSessionContractImplementor session, Object obj) {
17        String sql = null;
18
19        if ("SEQUENCE".equals(method)) {
20            sql = "SELECT " + sequenceName + ".NEXTVAL FROM DUAL";
21        }
22
23        try (Connection con = session.getJdbcConnectionAccess().obtainConnection();
24             CallableStatement stmt = con.prepareCall(sql)) {
25
26            ResultSet rs = stmt.executeQuery();
27            if (rs.next()) {
28                return rs.getString(1); // String 타입으로 반환
29            }
30        } catch (SQLException e) {
31            throw new HibernateException(e);
32        }
33
34        return null;
35    }
36}

🧠 코드 한 줄씩 설명

클래스 선언

public class IdGenerator implements IdentifierGenerator, Configurable {
  • IdentifierGenerator: Hibernate에서 ID를 생성할 때 사용하는 인터페이스
  • Configurable: 외부에서 설정값을 받아올 수 있도록 함

설정 키 상수 선언

java
1    public static final String METHOD = "method";
2    public static final String SEQUENCE_NAME = "sequence_name";
  • @GenericGenerator로 넘겨주는 파라미터 이름을 정의함

설정값 저장 필드

java
1    private String method;
2    private String sequenceName;
  • 설정된 값을 클래스 내부에 보관해두는 변수

설정값 초기화 메서드

java
1    @Override
2    public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) {
3        method = ConfigurationHelper.getString(METHOD, params);
4        sequenceName = ConfigurationHelper.getString(SEQUENCE_NAME, params);
5    }
  • Hibernate가 Generator를 초기화할 때 호출됨
  • 전달된 설정값을 꺼내서 필드에 저장

ID 생성 메서드

java
1    @Override
2    public Serializable generate(SharedSessionContractImplementor session, Object obj) {
3        String sql = null;
  • Hibernate가 ID를 생성해야 할 때 호출됨
  • 내부적으로 JDBC를 통해 SQL 실행

SQL 구성

java
1      if ("SEQUENCE".equals(method)) {
2           sql = "SELECT " + sequenceName + ".NEXTVAL FROM DUAL";
3       }
  • method가 SEQUENCE일 경우 시퀀스를 호출하는 SQL 구성
  • Oracle 문법: NEXTVAL FROM DUAL

커넥션 획득 & SQL 실행

java
1        try (Connection con = session.getJdbcConnectionAccess().obtainConnection();
2             CallableStatement stmt = con.prepareCall(sql)) {
3
4            ResultSet rs = stmt.executeQuery();
5            if (rs.next()) {
6                return rs.getString(1); // String 타입으로 반환
7            }
  • JDBC 커넥션을 열고 쿼리 실행
  • 결과값이 있다면 getString(1)로 받아서 ID로 반환

예외 처리 및 fallback

java
1        } catch (SQLException e) {
2            throw new HibernateException(e);
3        }
4        return null;
  • SQL 실행 중 문제가 생기면 Hibernate 예외로 wrapping
  • 아무 결과가 없을 경우 null 반환

🔧 사용하는 방법

Entity 클래스에서 @GenericGenerator와 함께 설정합니다.
java
1@Entity
2public class MyEntity {
3
4    @Id
5    @GeneratedValue(generator = "string-seq-generator")
6    @GenericGenerator(
7        name = "string-seq-generator",
8        strategy = "kr.co.com.job.utils.IdGenerator",
9        parameters = {
10            @Parameter(name = "method", value = "SEQUENCE"),
11            @Parameter(name = "sequence_name", value = "MY_SEQ")
12        }
13    )
14    private String id;
15
16    // ...
17}

📌 정리

구분내용
기본 시퀀스 매핑@GeneratedValue(strategy = GenerationType.SEQUENCE)Long, Integer 등 숫자 타입 필드에서만 정상 작동
String 타입 ID 사용 시커스텀 IdentifierGenerator 구현 필요 (직접 JDBC로 시퀀스 호출하여 String 변환)
핵심 포인트Hibernate의 기본 시퀀스 전략은 ResultSet.getLong()을 사용 → String 타입 필드에 매핑 불가
실무 확장성접두사, 날짜, 복합 규칙 등을 조합하여 커스터마이징 가능 (e.g. "ORD-20250323-0001")
테스트 주의사항시퀀스가 실제로 Oracle DB에 존재해야 하며, 테스트 환경에서도 같은 방식의 시퀀스 생성 필요

💡 추가로 알아두면 좋은 점

1. 📈 allocationSize와 성능

  • @SequenceGenerator를 사용할 때는 allocationSize 값을 조절하면 DB와의 통신 횟수를 줄여 성능 최적화 가능
  • 하지만 커스텀 Generator에서는 이를 직접 반영해야 하므로 주의 필요
  • 커스텀 로직에 allocationSize를 추가하려면 시퀀스 값을 한 번에 여러 개 가져오는 로직 구현이 필요함
예시:
sql
1SELECT MY_SEQ.NEXTVAL FROM DUAL CONNECT BY LEVEL <= 10

2. 🔒 동시성 문제 고려

  • 다수의 쓰레드가 동시에 시퀀스를 요청하는 경우에도 Oracle 시퀀스는 자동으로 순서를 보장
  • 다만 접두사 + 시퀀스로 조합할 경우 중복 방지 로직은 개발자가 따로 신경 써야 함

3. ✨ 실무 적용 예시: 접두사 + 날짜 + 시퀀스

java
1@Override
2public Serializable generate(SharedSessionContractImplementor session, Object obj) {
3    String sql = "SELECT MY_SEQ.NEXTVAL FROM DUAL";
4    String prefix = "ORD";
5    String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
6
7    try (Connection con = session.getJdbcConnectionAccess().obtainConnection();
8         CallableStatement stmt = con.prepareCall(sql)) {
9
10        ResultSet rs = stmt.executeQuery();
11        if (rs.next()) {
12            long nextVal = rs.getLong(1);
13            return String.format("%s-%s-%06d", prefix, date, nextVal);
14        }
15    } catch (SQLException e) {
16        throw new HibernateException(e);
17    }
18
19    return null;
20}
결과 예시: ORD-20250323-000001

4. 🧪 테스트 환경에서 시퀀스 만들기

개발용 DB에서 시퀀스가 없으면 커스텀 Generator에서 NullPointerException이 발생할 수 있습니다.
따라서 테스트 환경에도 시퀀스를 다음과 같이 만들어주어야 합니다.
sql
1CREATE SEQUENCE MY_SEQ START WITH 1 INCREMENT BY 1;

✅ 마무리하며

Oracle 시퀀스를 JPA에서 사용할 때, 기본적으로는 @SequenceGenerator로 간단하게 처리할 수 있습니다.
하지만 ID가 String일 경우, 직접 커스텀 IdentifierGenerator를 구현해야 하며, JDBC를 이용한 세밀한 컨트롤이 필요합니다.
이 방식을 잘 응용하면 다음과 같은 다양한 포맷도 만들 수 있습니다.
  • ORD-20250323-000001
  • USER_000123
  • PRD-AUTO-239472
👉 즉, ID 생성 전략을 커스터마이징해야 하는 요구가 있다면 IdentifierGenerator는 강력한 도구입니다.

📎 참고 문서