📌 TIL (Today I Learned) - JPA & Kotlin Best Practices
오늘은 Kotlin + JPA를 활용한 엔티티 매핑과 API 설계에 대해 깊이 있게 학습했다.
특히, @OneToOne, @OneToMany, FetchType.LAZY, @Column, @JoinColumn 등 JPA 관련 개념을 정리했다.
🏆 오늘의 핵심 정리
✔ @OneToOne
관계는 한 쪽에만 @JoinColumn
을 설정하고, 반대쪽에서는 mappedBy
를 사용해야 한다.
✔ @OneToMany
↔ @ManyToOne
관계는 반드시 쌍으로 매핑해야 FK가 정상적으로 생성된다.
✔ FetchType.LAZY
를 사용할 경우, 필드는 var
로 선언해야 프록시 객체를 할당할 수 있다.
✔ @Column
을 사용하면 컬럼명 명확화, 제약 조건(nullable
, length
) 추가가 가능하다.
✔ PUT API를 통해 Boolean
값을 토글하는 로직을 구현하고, findById().orElseThrow()
로 존재 여부를 체크해야 한다.
✔ @JoinColumn(name = "id")
에서 id
는 FK 컬럼을 의미하며, @MapsId
가 설정되면 PK와 동일한 값을 공유한다.
✅ 1. @OneToOne
관계에서 한쪽에만 설정하는 이유
@OneToOne
관계에서는 한쪽만@JoinColumn
을 사용하고, 반대쪽에서는mappedBy
를 설정하는 것이 일반적이다.- 이유는 FK(외래 키)는 한 테이블에만 존재하면 되기 때문.
@JoinColumn
을 양쪽에 설정하면 불필요한 FK가 생성되거나 충돌 가능성이 생길 수 있음.
📌 예제
@Entity
class ParentEntity(
@Id
val id: String,
@OneToOne(mappedBy = "parent")
var child: ChildEntity? = null
)
@Entity
class ChildEntity(
@Id
val id: String,
@OneToOne
@JoinColumn(name = "id") // ✅ FK 설정
var parent: ParentEntity
)
✔ FK는 ChildEntity
가 가지고 있고, ParentEntity
는 mappedBy
로 참조
✅ 2. @OneToMany
↔ @ManyToOne
관계 매핑
- 1:N 관계를 매핑할 때, 한쪽은
@OneToMany
, 반대쪽은@ManyToOne
을 사용해야 한다. - FK는
@ManyToOne
이 선언된 테이블에 저장되며,@OneToMany
는 단순한 참조 역할을 한다.
📌 예제
@Entity
class ParentEntity(
@Id
val id: String,
@OneToMany(mappedBy = "parent", cascade = [CascadeType.ALL])
var children: List<ChildEntity> = mutableListOf()
)
@Entity
class ChildEntity(
@Id
val id: String,
@ManyToOne
@JoinColumn(name = "parent_id") // ✅ FK 저장
var parent: ParentEntity
)
✔ FK는 ChildEntity.parent_id
에 저장됨.
✔ @OneToMany
는 mappedBy
를 사용하여 단순히 ParentEntity
를 참조
✅ 3. FetchType.LAZY
설정 시, 필드는 var
로 선언해야 하는 이유
- JPA에서
FetchType.LAZY
를 사용할 경우, 프록시 객체를 주입해야 하는데,val
이면 불변(immutable)이기 때문에 프록시 객체를 할당할 수 없다. - 즉,
Lazy Loading
이 정상적으로 동작하려면var
을 사용해야 한다.
📌 잘못된 예제 (val
사용)
@Entity
class SampleEntity(
@Id
val id: String,
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "related_id")
val relatedEntity: RelatedEntity // ❌ Lazy Loading 불가능
)
✔ 프록시 객체를 주입할 수 없기 때문에 Lazy Loading이 실패할 수 있음.
📌 올바른 예제 (var
사용)
@Entity
class SampleEntity(
@Id
val id: String,
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "related_id")
var relatedEntity: RelatedEntity // ✅ Lazy Loading 가능
)
✔ var
로 선언하면 프록시 객체를 주입할 수 있어 Lazy Loading 정상 동작
✅ 4. @Column
을 사용하는 이유
- Kotlin + JPA에서는
@Column
을 생략해도 자동으로 필드명을 DB 컬럼명으로 매핑한다. - 하지만, 명확한 컬럼명을 지정하거나,
nullable
,length
등의 설정이 필요할 경우@Column
을 명시적으로 작성하는 것이 좋다.
📌 예제
@Column(name = "custom_name", nullable = false, length = 128)
val sampleField: String
✔ DB에서 컬럼명이 자동으로 sample_field
가 아니라 custom_name
으로 지정됨.
✔ NULL 허용 여부 및 문자열 길이를 명확히 설정 가능.
✅ 5. PUT API를 통한 상태 토글 구현
- PUT 요청이 들어올 때마다 특정 상태(
Boolean
) 값을 반전시키는 API를 구현. - JPA에서
analyticsYn = !analyticsYn
을 활용하여 값 토글.
📌 서비스 함수 (Service
)
@Transactional
fun updateStatusToggle(entityId: String) {
val entity = repository.findById(entityId)
.orElseThrow { throw IllegalArgumentException("해당 엔티티를 찾을 수 없습니다. ID: $entityId") }
entity.status = !entity.status // ✅ Boolean 값 반전
repository.save(entity) // ✅ 변경 사항 저장
}
✔ 현재 값(true
또는 false
)을 반전 후 저장.
✔ 조회 시 findById(entityId).orElseThrow {}
로 존재 여부 체크.
📌 컨트롤러 함수 (Controller
)
@PutMapping("/{entityId}/status/toggle")
fun putStatusToggle(@PathVariable entityId: String): ResponseEntity<Unit> {
service.updateStatusToggle(entityId)
return ResponseEntity.ok().build()
}
✔ PUT API 호출 시 상태 값이 자동으로 변경됨!
✅ 6. @JoinColumn(name = "id")
에서 id
가 의미하는 것
- JPA에서
@JoinColumn(name = "id")
을 설정할 경우, 해당 컬럼이 "현재 테이블의 FK"임을 의미함. - 하지만,
@MapsId
가 설정된 경우 해당 FK는PK
와 동일한 값을 공유하게 됨.
📌 예제
@Entity
class ChildEntity(
@Id
@Column(name = "id")
val id: String,
@OneToOne
@MapsId // ✅ PK = FK 설정
@JoinColumn(name = "id") // ✅ FK를 현재 테이블의 ID와 동일하게 설정
var parent: ParentEntity
)
✔ 이 설정을 사용하면 child.id
와 parent.id
가 동일한 값을 가지게 됨.
✔ 즉, child.id
는 parent.id
를 참조하는 FK 역할을 수행하지만, 동시에 PK로도 사용됨.
🚀 오늘은 JPA의 관계 매핑, Lazy Loading, API 설계 원칙에 대해 많이 배웠다!
'Blog > TIL' 카테고리의 다른 글
2025-02-21 (금) (0) | 2025.02.21 |
---|---|
2025-02-20 (목) (0) | 2025.02.20 |
2025-02-18 (화) (0) | 2025.02.18 |
2025-02-17 (월) (0) | 2025.02.17 |
2025-02-14 (금) (0) | 2025.02.14 |
댓글