티스토리 뷰
728x90
반응형
패턴 1 — Surrogate Key 방식 (가장 일반적)
- equals / hashCode 아예 구현하지 않음
- 객체 동일성 = JVM 참조 동일성
- HashSet에 넣어도 안전
- id가 바뀌어도 hashCode 불변
- Hibernate 프록시도 안전
@Entity
class Member(
var name: String
) {
@Id
@GeneratedValue
var id: Long? = null
}
영속성 컨텍스트(1차 캐시)
- JPA는 이미 객체 동일성 컨텍스트를 갖고 있다
- 같은 ID → 같은 객체 인스턴스 하나만 사용
val m1 = em.find(Member::class.java, 1L)
val m2 = em.find(Member::class.java, 1L)
m1 === m2 // true
////////////////////////////////////////////////
val set = HashSet<Member>()
val m = Member("Alice")
set.add(m)
m.name = "Bob"
set.contains(m) // true (참조 동일성)
패턴 2 — Natural Key 방식 (도메인 키)
- 비즈니스적으로 절대 변하지 않는 값이 있을 때만
- persist 전/후 동일
- 컬렉션 안전
- 캐시/프록시도 안전
@Entity
class Member(
val email: String, // natural key, 유니크
var name: String
) {
@Id
@GeneratedValue
var id: Long? = null
override fun equals(other: Any?) =
other is Member && email == other.email
override fun hashCode() = email.hashCode()
}
kassava 라이브러리
- equals / hashCode / toString을 data class처럼 만들어주는 라이브러리
- 참고 : https://techblog.woowahan.com/2675/
- kassave 라이브러리는 객체(Value Object)를 위해서 사용
JPA 엔티티에 kassava 쓰면 생기는 버그
@Entity
class User(
var name: String
) {
@Id @GeneratedValue
var id: Long? = null
override fun equals(other: Any?) = kassava.equals(this, other)
override fun hashCode() = kassava.hashCode(this)
}
- User("A") 생성 → id=null
- HashSet에 넣음
- persist → id=10
- name 변경 → hashCode 변경 ❌
- 컬렉션 붕괴
필드 로딩 트리거
@Entity
class Order(
@ManyToOne(fetch = FetchType.LAZY)
val member: Member
) {
@Id @GeneratedValue
var id: Long? = null
override fun equals(other: Any?) = kassava.equals(this, other)
override fun hashCode() = kassava.hashCode(this)
}
- 실제 DB row는 아직 Member 안 읽음
- equals 호출 시 proxy.member 호출 (모든 프로퍼티 getter를 호출해서 비교)
- LAZY → EAGER로 강제 전환
val o1 = em.find(Order::class.java, 1L)
val o2 = em.find(Order::class.java, 1L)
o1 == o2 // kassava.equals 호출
LazyInitializationException
- equals() 호출이 DB 접근을 시도 → 트랜잭션 없으면 LazyInitializationException
val order = em.find(Order::class.java, 1L)
em.close()
order == someOtherOrder // equals 발생 시 order.member 조회
성능 폭탄 시나리오
- 모든 연관관계 접근 → N+1 폭탄
val orders = orderRepository.findAll()
orders.distinct() // 내부에서 equals 수천 번 호출
각 메서드에 사용할 프로퍼티를 명시적으로 지정 해서 사용해야함
ID가 아직 없는 상태(transient)에서 비교하면 의도치 않은 결과 발생할 수 있음
→ hashCode가 바뀌어 컬렉션에서 객체를 못 찾는 문제
@Entity
class Employee(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var name: String,
@ManyToOne(fetch = FetchType.LAZY)
var company: Company?
) {
override fun equals(other: Any?) = kotlinEquals(other = other, properties = equalsAndHashCodeProperties)
override fun hashCode() = kotlinHashCode(properties = equalsAndHashCodeProperties)
companion object {
private val equalsAndHashCodeProperties = arrayOf(Employee::id)
}
}
728x90
반응형
'Java' 카테고리의 다른 글
| [Kotlin] JPA Entity를 data class 로 사용할 경우 문제 (0) | 2026.01.11 |
|---|---|
| [Kotlin] JPA Entity @Id 필드 선언 방식 (0) | 2026.01.11 |
| [Kotlin] nested class vs. inner class (0) | 2026.01.11 |
| [Java] openjdk:11-jdk-slim 이미지 사용 시 POI 엑셀 기능 불가 (0) | 2026.01.10 |
| [Java] Jackson @JsonAnySetter, @JsonAnyGetter (2) | 2025.07.08 |
반응형
300x250