2023. 1. 12. 15:56ㆍJPA

이렇게 커스텀 시퀀스로 pk를 만들어야 할 일이 있습니다. 쿼리문으로도 할 수 있겠지만, jpa로 구현하려면 어떻게 해야할까요?
org.hibernate.id.enhanced.SequenceStyleGenerator; 라는 클래스를 상속하면 커스텀 시퀀스를 만들 수 있습니다.
config 패키지에 DatePrefixedSequenceIdGenerator 라는 클래스를 만들어주겠습니다.
@Slf4j
public class DatePrefixedSequenceIdGenerator extends SequenceStyleGenerator {
public static final String NUMBER_FORMAT_PARAMETER = "numberFormat";
public static final String NUMBER_FORMAT_DEFAULT = "%07d";
private String format;
@Override
public Serializable generate(SharedSessionContractImplementor session,
Object object) throws HibernateException {
// 20230112%d7 => 이거를 12345~~ 시퀀스에 합쳐서 시퀀스를 만듭니다.
return String.format(format, super.generate(session, object));
}
/*
* A common scenario for this kind of id generator requires you to reset the sequence number at the end of each day or month or year.
* This can’t be handled by the id generator. You need to configure a job that resets your database sequence in the required intervals and
* the DatePrefixedSequenceIdGenerator will automatically get the new values when it requests the next value from the sequence.
* 이 id generator에서는 매일매일 시퀀스를 초기화 해줄 수 없다고 합니다. db설정을 따로 하던지 아니면 다른 수를 써야합니다.
*/
@Override
public void configure(Type type, Properties params,
ServiceRegistry serviceRegistry) throws MappingException {
// params는 시퀀스 번호
super.configure(LongType.INSTANCE, params, serviceRegistry);
// 지금 날짜 를 yyyyMMdd string 형태로
String dateFormat = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
// "%07d"의 형태로 바꿔준다.
String numberFormat = ConfigurationHelper.getString(NUMBER_FORMAT_PARAMETER, params, NUMBER_FORMAT_DEFAULT);
// format 지정
this.format = dateFormat+numberFormat;
}
}
그리고 이제 이 CustomSequence를 사용할 entity로 가서 이 시퀀스를 사용하여 봅시다.
sequence가 정수이긴 하지만 커스텀시퀀스를 사용하니 String으로 해줍시다.
// 이 컬럼이 pk임을 명시 하는 @Id 어노테이션을 사용합니다.
@Id
@Column(name = "goods_no", length = 15, updatable = false, nullable = false)
// 시퀀스를 하나 만들어 줍니다.
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_goods")
// 그리고 위에서 만든 시퀀스를 사용하려 custom sequence를 만들 것입니다.
@GenericGenerator(
name = "seq_goods",
//시퀀스 전략을 아까 만들어준 CustomSequence의 클래스 경로로 설정해줍니다.
strategy = "net.e4net.demo.config.DatePrefixedSequenceIdGenerator",
// parameters를 통해서 SequenceStyleGenerator의 여러 상수들을 미리 설정할 수 있습니다.
// INCREMENT_PARAM 뿐 아니라 많은 속성들이 있는데 Docs 링크 첨부해드립니다.
// https://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/id/enhanced/SequenceStyleGenerator.html
parameters = {
// INCREMENT_PARAM은 sql에서 increment by 입니다. 한번에 얼마나 증가할 것인가!
@Parameter(name = DatePrefixedSequenceIdGenerator.INCREMENT_PARAM, value = "1")
})
private String goodsNo; // 상품번호 pk(yyyymmdd(8) + sequence(7)), nn
그리고 매일 날짜가 바뀔 때마다 숫자가 1붙 시작하도록 시퀀스를 초기화 해줘야 우리가 생각한 시나리오대로 되는 것인데요, 첫번째 코드 블럭 영어로 된 주석에 설명했던 것처럼 그건 SequenceStyleGenerator에서는 불가능 합니다. 그래서 우리가 따로 설정을 해줘야 합니다.
저는 EntityManager가 아닌 JpaRepository상속하는 RepositoryInterface에서
@Modyfying 어노테이션과 @Query 어노테이션을 사용하여 시쿼스를 초기화하는 DDL(데이터 정의어)를 사용하여 매일매일 시퀀스가 초기화 되도록 하였습니다.
원래는 CRUD만 가능하지만, @Modyfying 어노테이션을 사용하면 CRUD인 DML 뿐 아니라 CREATE, ALTER 등 정의어도 사용이 가능합니다. 물론 @Modyfying 어노테이션에는 DML도 포함이 되어있구요.

아무튼, RepositoryInterface에 하나의 추상메서드를 하나 추가해줍니다.
alter sequence seq_goods restart with 1 => 이 sql문이 실행되면 시퀀스가 1부터 다시 시작 되겠죠.
@Modifying
@Query(value = "alter sequence seq_goods restart with 1", nativeQuery = true)
void restartSeqGoods();
마지막으로 이 커스텀 시퀀스를 사용하여 data를 insert하는 service(혹은 Test)로 가봅니다.
// 최신 pk 얻어오기
String recentPk = goodsRepositoryImple.findFirstByOrderByGoodsNoDesc();
log.debug("recentPk => {}",recentPk);
// 날짜 비교용 - 최신 pk의 데이터
recentPk = recentPk.substring(0,8);
// 날짜 비교용 - 오늘 날짜
String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
// 날짜가 달라지거나 7자리 넘을라고 그러면 시퀀스 초기화
if (!recentPk.equals(today) || recentPk == "9999999") {
goodsRepository.restartSeqGoods();
}
String.subString으로 앞의 8글자를 잘라내어 오늘의 yyyyMMdd를 비교하여 다르다면 날이 달라진 것이니 시퀀스를 초기화 하는 방법을 사용하였습니다. 그랬더니 이렇게 잘 되네요:)

이상입니다.
궁금하신 점 있으시면 댓글 남겨주시구요, 도움이 되셨다면 좋아요 부탁드립니다:)
참고:
-https://thorben-janssen.com/custom-sequence-based-idgenerator/