Backend/Spring

Spring JPA Custom Id Generator 만들기

keepbang 2024. 11. 6. 14:14

▤ 목차

     

    개요

    JPA를 사용할 때 보통 id는 시퀀스 id를 자주 사용합니다.

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

     

    만약 요구사항으로 "앞에 특정 문자를 포함한 아이디 체계"를 사용해야 한다고 하면 어떻게 해야할까 고민해보다가 내용을 찾고 정리해봤습니다.

     

     

    IdGeneratorType Annotation

     

    검색을 해보면 GenericGenerator 애너테이션을 사용해서 Generator 구현체와 함께 사용하는 예제들을 많지만 해당 애너테이션은 deprecated 되어있었습니다.

     

     

     

    다른 방식으로는 IdGeneratorType으로 애너테이션을 만들어서 사용하는 방법이 있습니다.

     

     

     

    내부에는 Generator를 상속 받는 클래스를 받고 있습니다.

     

    해당 애너테이션으로 prefix가 붙는 형식의 id를 만들어보겠습니다.

     

    @IdGeneratorType(PrefixIdGenerator::class)
    @Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION)
    @Retention(AnnotationRetention.RUNTIME)
    annotation class PrefixId(
        val name: String,
        val prefix: String = ""
    )

     

    방법은 간단합니다. 

    애너테이션을 만들고 IdGeneratorTypeGenerator 구현체를 넣으면 됩니다.

    저는 추가적으로 Generator 구현체에서 사용할 파라미터도 같이 추가했습니다.

     

    Generator 구현

     

    이제 Generator를 구현해 보겠습니다.

    GeneratorIdentifierGenerator 를 상속 받아 generate 함수를 override 해서 사용하면 됩니다.

    애노테이션에서 받은 파라미터들은 구현체 클래스의 프로퍼티로 애너테이션을 받아서 값을 가져올 수 있습니다.

     

    class PrefixIdGenerator(config: PrefixId) : IdentifierGenerator {
    
        // 1)
        private val prefix: String = config.prefix
    
        override fun generate(session: SharedSessionContractImplementor, `obj`: Any): Any {
            
            // 2)
            val identifierPropertyName = session.getEntityPersister(obj.javaClass.name, obj).identifierPropertyName
    
            // 3)
            val query = java.lang.String.format(
                "select %s from %s order by %s desc limit 1",
                identifierPropertyName,
                obj.javaClass.getSimpleName(),
                identifierPropertyName
            )
    
            val ids = session.createQuery(query, String::class.java)
                .stream()
    
    	// 4)
            val max: Long = ids.findFirst()
                .map { o -> o.toString().replace(prefix, "").toLong() }
                .getOrElse {0L}
    
    	// 5)
            return prefix + (max + 1)
        }
    }

     

    1. 프로퍼티로 받은 값을 가져와서 사용 할 수 있습니다.
    2. 해당 generator를 사용하는 entity의 아이디 이름을 가져옵니다.
    3. 마지막 id값을 가져오기 위한 쿼리문을 생성하고 실행시킵니다.
    4. 가져온 아이디값의 prefix제거하고 뒤에 숫자만 가져옵니다.
    5. prefix를 추가해서 마지막 id에 +1을 한 값으로 반환해서 아이디가 적용됩니다.

     

    위에 예제는 auto_increment로 되어있는 id 앞에 prefix를 넣는 방식으로 구현해 봤습니다.

     

    DB를 조회해서 아이디를 만드는 방식이 아닌 유일 아이디 생성기같은걸 만들어서 사용해도 될 것 같습니다.