본문 바로가기
Blog/TIL

2025-02-19 (수)

by 코젼 2025. 2. 19.
728x90
반응형

📌 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가 가지고 있고, ParentEntitymappedBy로 참조

✅ 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에 저장됨.
@OneToManymappedBy를 사용하여 단순히 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.idparent.id가 동일한 값을 가지게 됨.
✔ 즉, child.idparent.id를 참조하는 FK 역할을 수행하지만, 동시에 PK로도 사용됨.

🚀 오늘은 JPA의 관계 매핑, Lazy Loading, API 설계 원칙에 대해 많이 배웠다!

728x90
반응형

'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

댓글