kotlin jpa entity id(uuid, ulid)

배현서·2024년 9월 6일

kotlin

목록 보기
2/2

모든 엔티티에는 primary key(주키)가 있다.
나는 지금까지 java로 개발할때 primary key를 @GeneratedValue(strategy = GenerationType.IDENTITY)을 활용해 Long을 사용했다. int보다 많은 양의 수를 처리하고 자동으로 값을 늘려주는 장점이 있기에? 편리해서? 그냥 딴 사람이 다 이렇게 써서? 이런 이유였다.

kotlin을 공부하던 중 Long 형을 한계에 대해 보았다.
1. 다른 entity끼리의 키값으로 중복이 될 수 있음(유일성)
2. 그래도 Long 형이 가진 범위가 적을 수 있다??-> 가능성은 적겠지만

이런 이 부분을 uuid 타입은 해결해준다고 한다.

UUID란?

UUID(Universally Unique Identifier)는 정보 시스템에서 객체나 엔티티를 고유하게 식별하기 위해 사용되는 128비트 길이의 식별자이다.

Long 타입의 2배이니 2번 조건을 해결해주고
실제로 테스트해보니 0191bbaa-74cd-75d9-aba7-dada993c5d5b 이런 형태의 값이 나온다. 겹칠일도 없으니 1번도 해결이다.
하지만 단점이 있다.

Long타입의 경우 순서를 가질 수 있기 때문에 정렬 시 성능적인 이점이 있었지만, uuid는 순서가 없다.

그래서 또 발전된? 게 뭐냐 하면 ulid이다.

데이터 크기도 UUID에 비해 작기 때문에 저장용량 및 생성 비용을 적게 가져갈 수 있다는 장점이 있다고 한다.

또한 시간순으로 정렬이 가능하다는 점에서 정렬적인 단점도 해결이 된다.

그런데 라이브러리를 가져와서 사용해야하기에 FasterXML에서 제공하는 java-uuid-generator (JUG) 라이브러리를 사용하여 kotlin entity를 만들어봤다.

UUID 타입을 이용해 만든 ENTITY

UserEntity를 정의하고 주키를 String으로 준 후 DomainId를 dataClass로 정의하고 User 객체를 만들때 uuid 타입의 주키를 생성하고 String으로 변환 이런 형식이다.

이렇게 한 이유는 다음과 같다.

  1. 성능적으로 더 안 좋겠지만, 그럴거면 ORM 도입 고민을 먼저 해야하지 않을까?
  2. 처리하기 쉽다.
  3. 호환성이 좋다.

지금 나는 간단한 프로젝트를 하고 있기에 이런 방식을 채탁했다!

나중엔 ULID로 디벨롭 해볼 예정이다.

DomainId

import com.fasterxml.uuid.Generators

data class DomainId(
    val id: String
) {
    companion object{
        fun generate(): DomainId {
            return DomainId(Generators.timeBasedEpochGenerator().generate().toString())
        }
    }
}

User

package com.ddaom.back.domain.user.model

import com.ddaom.back.domain.common.DomainId

data class User (

    val id: String = DomainId.generate().id,
    val nickname: String,
    var gender: String,
    val age: Int,
    val thumbnail: String,
    val refreshToken: String = ""
){

}

UserEntity

import com.ddaom.back.domain.novel.model.Novel
import com.ddaom.back.domain.user.enums.Gender
import com.ddaom.back.domain.user.model.User
import com.ddaom.back.infra.persistence.novel.entity.NovelEntity
import jakarta.persistence.*

@Entity
@Table(name = "user")
class UserEntity (
    @Id
    @Column(name = "id", unique = true)
    val domainId: String,

    @Column(name = "nickname")
    val nickname: String,

    @Column(name = "gender")
    @Enumerated(EnumType.STRING)
    val gender: Gender,

    @Column(name = "age")
    val age: Int,

    @Column(name = "thumbnail")
    val thumbnail: String,

    @Column(name = "refreshToken", length = 1000)
    val refreshToken: String

    ){
    companion object {
        fun of(user: User) =
            UserEntity(
                domainId = user.id,
                nickname = user.nickname,
                gender = Gender.getEnumGender(user.gender),
                age = user.age,
                thumbnail = user.thumbnail,
                refreshToken = user.refreshToken
            )
        fun fromId(userId: String) =
            UserEntity(
                domainId = userId,
                nickname = "",
                gender = Gender.OTHER,
                age = 0,
                thumbnail = "",
                refreshToken = ""
            )
    }

    fun updateRefreshToken(newRefreshToken: String): UserEntity {
        return UserEntity(
            domainId = this.domainId,
            nickname = this.nickname,
            gender = this.gender,
            age = this.age,
            thumbnail = this.thumbnail,
            refreshToken = newRefreshToken
        )
    }
}

fun UserEntity.toDomain() = User(
    id = this.domainId,
    nickname = this.nickname,
    gender = this.gender.stringGender,
    age = this.age,
    thumbnail = this.thumbnail,
    refreshToken= this.refreshToken

)

0개의 댓글