데이터 정합성을 보장하기 위해서 surrogate key 를 사용하곤 한다.
주로 UUID나 autoincrement integer id 를 사용한다.
surrogate key 는 db의 정합성을 보장하지만 별 정보가 없다는 단점이 있다.
정보에 의미가 없다는 것은 나중에 정보를 찾기도 쉽지가 않고, 디버깅 하기도 어렵다.
그래서 pk는 그걸로 두되, natrual key를 따로 사용할 수 있다.
natural key는 사용자의 아이디나 이메일같이 유니크하게 식별 가능한 키이다.
테이블의 조인 성능, 데이터 추가 시 정합성을 위해 랜덤한(혹은 sequential한) 값을 사용하면서 막상 조회할 때는 사람이 알아볼 수 있는 Natural Key를 사용할 수 있다.
다만 JPA는 1차 캐싱 시 ID만 지원한다. Natural Key에 해당하는 칼럼에 대해 DB 측에서 인덱싱을 할 지라도 일단 쿼리 한 번은 나가고 본다.
따라서 JPA에서 Natural Key와 실제 Id를 매핑하는 작업이 필요하다.
JPA에서 natural id 를 지원한다.
@Entity
class User {
@Id
var userId: Int = 0
@NaturalId
var username: String = ""
}
그리고 Jpa는 이를 자동으로 지원하지 않기 때문에 하이버네이트를 이용해서 매핑해준다.
@NoRepositoryBean
interface BaseRepository<T, ID, NID> : JpaRepository<T, ID> {
fun findByNaturalKey(nid: NID): Optional<T & Any>
}
class BaseRepositoryImpl<T, ID :Serializable, NID : Serializable>(
entityInformation: JpaEntityInformation<T, *>,
private val entityManager: EntityManager,
) : SimpleJpaRepository<T, ID>(entityInformation, entityManager), BaseRepository<T, ID, NID> {
override fun nnnnn(nid: NID): Optional<T & Any> {
val entity = entityManager.unwrap(Session::class.java).bySimpleNaturalId(this.domainClass).load(nid)
return if (entity != null)
Optional.of(entity)
else {
Optional.empty()
}
}
}
JPA Config 클래스에서 다른 JpaRepository 구현체가 BaseRepositoryImpl을 상속받을 수 있도록 한다.
@Configuration
@EnableJpaRepositories(
basePackages = ["com.example"],
repositoryBaseClass = BaseRepositoryImpl::class
)
class PersistenceConfig {
}
이후 BaseRepository를 상속받은 레포지토리는 findByNaturalKey를 사용하여 natrual id 에 해당하는 칼럼으로 쿼리를 캐싱할 수 있다.