inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기 (Java 프로젝트 리팩토링)

코틀린 JPA 질문이 있습니다

482

화이팅

작성한 질문수 37

2

안녕하세요.

Sto라는 부모 엔티티가 있습니다.

자식으로는 Realestate 라는 엔티티가 있습니다.

해당 엔티티의 관계는 Sto + Realestate 이렇게 1개의 쌍을 이루어 생성이 됩니다.

저는 @OneToOne 양뱡향 관계를 사용하고 있습니다.

cascade 속성을 이용하여, Sto를 save 했을 때 하위 엔티티까지 함께 저장하려고 하고 있습니다.

이때, 모든 엔티티를 val를 사용하려고 했는데 Sto 생성자 호출 시 하위 엔티티에서 Sto 엔티티의 값을 알 수가 없어서 생성이 불가한 상태입니다.
물리적으로 Sto + Realestate는 한쌍인데 방법이 없을까 싶어 문의드립니다.

var + null을 사용하면, 어떻게든 할 수 있을 것 같은데 양방향 설정을 위해 이렇게 풀어서 사용해야 하나 궁금하네요..

@Entity
@Table(name = "sto")
class Sto(
    @Id
    @Column(name = "sto_id", nullable = false)
    private val id: String,

    @OneToOne(
        mappedBy = "sto",
        cascade = [CascadeType.PERSIST]
    )
    val realestate: Realestate,

    @OneToOne(
        mappedBy = "sto",
        cascade = [CascadeType.PERSIST]
    )
    val youtube: Youtube,
)

@Entity
class Realestate(
    @Id
    @Column(name = "realestate_id", nullable = false)
    private val id: String,

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "sto_id", nullable = false)
    val sto: Sto,
)

fun main() {
    Sto(
        id = "sto_id",
        realestate = Realestate(
            id = "realestate_id",
            sto = 여기가 문제네요..
        )
    )
}

 

 

java spring kotlin spring-boot 리팩토링

답변 1

1

최태현

안녕하세요, 민영님! 좋은 질문 감사드립니다! 🙏 결론부터 말씀드리면 방법은 있습니다... 예시로 코드를 조금 간소화 해서 A / B 클래스로 보여드리겠습니다.

@Entity
class A(
  @Id
  val id: String,

  bId: String,
) {
  @OneToOne(
    mappedBy = "a",
    cascade = [CascadeType.PERSIST] // [주의 2]
  )
  val b: B = B(bId, this) // [주의 1]
}

@Entity
class B(
  @Id
  private val id: String,

  @OneToOne(fetch = FetchType.LAZY)
  val a: A,
)

 

코드로 느낌이 오시겠지만, 방법을 말씀드리자면,

  • 실제 프로퍼티 선언은 class body에 하고

    val b: B = B(bId, this)
  • B 클래스에서 필요한 필드를 생성자에 넘겨, B 인스턴스 자체를 A class body 안에서 만드는

방법입니다!

 

만약 1 : N 관계라면, class body에 MutableList를 만들고 부모 클래스를 인스턴스화 한 다음 자식 클래스들을 addAll 함으로써 nullable하지 않게 코드를 작성할 수 있습니다.

 

다만 제가 주의 1, 2를 적어두었는데요!

 

[주의 1]

클래스 A는 Entity 이고, Entity 클래스일 경우 보통 allOpen 설정을 해두니 final 클래스가 아닙니다.

final이 아닌 코틀린 클래스를 인스턴스화 하는 과정에서 this 를 사용하면 위험할 수 있는데요!

(관련 강의 : <자바 개발자를 위한 코틀린 입문 : 10강>)

image

(관련 문서 : https://kotlinlang.org/docs/inheritance.html#derived-class-initialization-order)

  1. 현재 코드는 A 클래스의 하위 클래를 인스턴스화 하고 있지도 않고

  2. JPA가 데이터를 가져와 Entity를 만들 때는 기본적으로 필드에 직접 값을 넣어주기에

문제될 부분이 없지만, IDE에서는 경고를 주고 있어 찜찜하실 수 있습니다. Hibernate 버전이 올라가면서 2번의 동작이 변경될 수도 있고요!

image

 

[주의 2]

이 부분은 Kotlin과 관련된 부분은 아니고 Entity에서 @Id를 not null String으로 갖고 있는 경우 발생할 수 있는데요!

PERSIST 만 casecade 옵션에 주신 것은

  • 최초 save 하는 과정에서 새로운 Entity 이면 PERSIST 를 사용하기 때문

으로 생각됩니다.

image

다만, 여기에 최대 함정이 있는데요! isNew(entity) 에서 not null String은 false 가 나온다는 것입니다!!! 즉, SimpleJpaRepository.save 메소드는 isNew 를 하는 과정에서 PK를 확인하는데, PK 값이

  • null 이면 새로운 Entity

  • 숫자타입이고 0이면 새로운 Entity

  • 위의 두 경우 모두 아니다~ 그럼 새롭지 않은 Entity

로 판단해버려요!

 

관련 로직은 AbstractEntityInformation 에서 확인해보실 수 있습니다.

image

따라서, 사실상 DB에 영속화 되지 않은 새로운 Entity이더라도, 보내주신 것처럼 PK가 String으로 미리 설정되어 있다면, MERGE 까지 cascade 옵션을 켜주셔야 의도하시는 동작이 가능할 겁니다!

 

아이고~ 글이 너무 길었네요! 요약 드려보겠습니다!

  1. JPA + 코틀린으로 원하시는 바 가능하긴 합니다! 다만 사람에 따라 살짝 찜찜할지도...

  2. Cascade 옵션에 PERSIST 말고 MERGE를 함께 주셔야할 수 있어요!

 

궁금한 점이 해소되셨으면 좋겠습니다~ 감사합니다!! 🙇🙇

1

화이팅

궁금한 점이 해소되는 답글이였습니다!
가끔 질문할 때마다 좋은 답변을 제시해주셔서 감사합니다~!

추후 강의 계획이 있다면 궁금하네요!
코틀린 심화[코루틴 등등]!

2

최태현

아이고~~ 여쭤봐주셔서 감사해요!!! ☺️☺️

넵넵! 현재

  • 코틀린 고급편

    • 제네릭 / 위임, 지연 / 복잡한 함수형 프로그래밍

    • DSL / 어노테이션 및 리플렉션 / 기타 꿀팁

  • 2시간 안에 끝내는 코루틴

2가지 강의를 준비중에 있습니다! ☺️

두 주제를 하나로 합쳐 오픈 할까 하다가, 강의 가격도 더 낮추고, 원하시는 파트만 집중해서 보실 수 있게끔 분리하는 선택을 해보았습니다.

두 강의 모두 늦어도 8월 말까지는 선보일 수 있도록, 열심히 준비해보겠습니다.

감사합니다!! 🙇

안녕하세요 혹시 프론트 코드 제공받을 수 있을까요?

0

69

2

실행이 안되네요

0

67

2

프론트 영역 보는법

0

52

2

companion object

0

79

2

Custom 레프직토리 형식

0

60

2

Querydsl 도입

0

68

2

fetch join DISTINCT 중복제거

1

84

2

표준 예외와 커스텀 예외 사용 전략 질문

0

92

3

이 질문이 왜 없는지 이해가 안 되지만 문제 인식 및 해결 방법 남깁니다.

1

177

2

테스트를 위한 코드

1

105

2

프로젝트 실행 에러

0

153

2

PDF 문서에 오타가 있어서 알려드립니다.

1

102

1

enum질문

1

86

1

테스트 후 AfterEach 함수에서 나오는 쿼리

0

136

2

테스트 fixture

1

211

2

./gradlew test 실행시 인식할수 없다고 뜹니다.

0

141

1

test 코드 실행시 경고가 발생합니다.

0

141

1

13강 User Kotlin 변환중

2

170

3

'추가 - 코프링과 플러그인' 강의 7:46 allopen 관련 질문

2

174

1

-

0

141

2

4:28 build.gradle 수정 시 kotlin-reflect관련 implementation 추가 해야할까요?

0

314

3

junit import 불가

0

276

3

테스트 코드와 관련하여 질문이 있습니다.

1

219

1

hibernate가 select를 두번 하는 이유

0

240

1