[JPA] SequenceStyleGenerator을 상속하여 yyyyMMdd+시퀀스 만들기

2023. 1. 12. 15:56JPA

이렇게 커스텀 시퀀스로 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/