목차

  1. 일반적인 JPA의 id 컬럼 설정 방법
  2. 만약 이런 경우라면..?
  3. 해결 방법
  4. 마치며

일반적인 JPA의 id 컬럼 설정 방법

JPA에서 mysql과 같이 auto increment PK를 사용하는 경우 아래처럼 작성합니다.

1
2
3
4
5
6
7
8
9
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// .. 기타등등 필드
}

이러면 entity 생성시 위 id에 뭘 안넣어도 JPA가 auto increment 다음 값을 가져와서 entity에 값을 잘 설정해줍니다.

만약 이런 경우라면..?

우리가 어떤 두 시스템을 통합한다고 가정합시다.
라이브 중인 서비스는 이미 이 통합작업을 고려하여 auto increment 값이 10000000번 부터 시작된 프로젝트입니다.
마이그레이션 할 데이터들의 PK는 1번부터 시작하며 100만건 남짓 입니다.
따라서 라이브중인 시스템과 마이그레이션 할 대상의 시스템의 PK는 겹치지 않습니다.
이 두 시스템의 데이터를 합쳐야 합니다.



요약하면, 마이그레이션 데이터의 PK를 유지한채 현 라이브중인 시스템으로 가져와야 된다는 의미입니다.


아래처럼 하면 쉽게 될것 같지만.. 과연 그럴까요?

1
2
3
4
5
6
@Transactional
public void migrate(Long originalPK) {
Product p = new Product();
p.setId(originalPK); // 가져올 데이터베이스의 PK값을 강제로 id필드에 할당
productRepository.save(p);
}

결론은, 안됩니다. auto increment 컬럼과 매핑된 @GeneratedValue(strategy = GenerationType.IDENTITY) 이 속성의 @Id 필드에는요.
자동으로 auto increment 다음 값을 가져와서 넣어버립니다.

해결 방법

많은 삽질을 해본 결과 아래와 같은 답을 찾았습니다. IdGenerator를 커스터마이징 하는 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
@Entity
@Table(name = "products")
public class Product {
@Id
@GenericGenerator(name = "productId", strategy = "com.mingpd.jpa.ProductIdGenerator")
@GeneratedValue(generator = "productId") // 위 name과 같은 이름을 써줍시다.
// @GeneratedValue(strategy = GenerationType.IDENTITY) // identity 전략은 사용하지 않습니다.
private Long id;

// .. 기타등등 필드
}

이제 ProductIdGenerator를 아래처럼 만들어봅시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.io.Serializable;

import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.IdentityGenerator;

import com.minpd.domain.Product;

public class ProductIdGenerator extends IdentityGenerator {
@Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
if (object instanceof Product) {
Product p = (Product) object;
return p.getId() == null ? super.generate(session, object) : p.getId();
} else {
throw new RuntimeException("Product entity가 아니에요.");
}

/*
// 이것도 됨
Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
*/
}
}

IdentityGenerator 클래스를 상속받아 만들었는데요. 이놈이 뭐하는애냐면, PK 생성이 IDENTITY 전략일 때 불리는 애 입니다.
쉽게 말해, 현재 Product 엔티티의 id가 null일때만 auto increment를 이용하겠다는 의미입니다.

마치며

사실 이 방법이 올바른건진 잘 모르겠으나, 일단 되긴 됩니다. 그럼 됐죠 뭐..