[JPA] 하나의 컬럼으로 2개의 필드 매핑하기

최대한·2023년 2월 5일
0
post-custom-banner

개요

Jpa 에서는 보통 다른 객체와의 연관관계를 맺어줄 때 직접참조(Direct Reference: @OneToMany, @ManyToOne 등) 를 하고, Lazy fetch 로 해당 객체에 접근하는 방식을 주로 사용한다. 이럴 경우 Lazy fetch 라면, Proxy 객체를 만들어 가지고 있다가 실제 객체에 접근할 때 쿼리를 날려 fetch 해온다. 이는 N+1 Problem 을 야기할 수 있는데, 이 때문에 종종 id 로만 조회해와야하는 경우들이 생긴다.

ManyToOne 의 경우 id 로 조회하려고 하면 결국 해당 연관된 객체에 한번 접근해야하기 때문에, Many 측에서 접근할 때마다 다시 쿼리를 날리게 되고 똑같은 N+1 문제가 발생하게 된다.

이때 아래와 같은 방법을 이용하면 쉽게 하나의 컬럼을 여러 field/property 로 매핑할 수 있다.

코드

1. Team.kt


@Entity
class Team (
   @Id
   val id: UUID = UUID.randomUUID(),

   val name: String = "",

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "team")
   val members: MutableList<Member> = mutableListOf()
)

⭐2. Member.kt

@Entity
class Member (
    @Id
    val id: UUID = UUID.randomUUID(),

    val name: String = "",

    team: Team? = null,

	// ⭐⭐⭐
    @Column(name = "team_id", insertable = false, updatable = false)
    val teamId: UUID? = null,
) {

    @ManyToOne(fetch = LAZY, cascade = [CascadeType.PERSIST])
    @JoinColumn(name = "team_id")
    var team: Team? = null
        set(value) {
            field = value
            team?.let { it.members += this }
        }

    init {
        this.team = team
    }
}

3. Test.kt

@SpringBootTest
internal class Test {

    @Autowired
    private lateinit var teamRepo: TeamRepo
    @Autowired
    private lateinit var memberRepo: MemberRepo

    @Test
    fun `fetch teamId`() {
        val team = Team(name = "ONiON").also(teamRepo::save)
        val memberId1 = Member(name = "Vita", team = team).also(memberRepo::save).id
        val memberId2 = Member(name = "Max", team = team).also(memberRepo::save).id
        val member1 = memberRepo.findByIdOrNull(memberId1)
        val member2 = memberRepo.findByIdOrNull(memberId2)
        assertNotNull(member1)
        assertNotNull(member2)
        member1!!.let {
            assertNotNull(it.team)
            assertEquals(team.id, it.teamId)
        }
        member2!!.let {
            assertNotNull(it.team)
            assertEquals(team.id, it.teamId)
        }
    }
}

interface TeamRepo: JpaRepository<Team, UUID>
interface MemberRepo: JpaRepository<Member, UUID>

결과


결론

2. Member.kt 의 teamId 프로퍼티에서 볼 수 있다시피, @Column 안에 insertable = false, updatable = false 옵션을 주면 쉽게 여러 프로퍼티에 매핑이 가능하다.

하나라도 누락될 경우

Caused by: org.hibernate.MappingException: Column 'team_id' is duplicated in mapping for entity '....Member' (use '@Column(insertable=false, updatable=false)' when mapping multiple properties to the same column)
	at app//org.hibernate.mapping.PersistentClass.checkColumnDuplication(PersistentClass.java:1009)

예외가 발생한다.

profile
Awesome Dev!
post-custom-banner

0개의 댓글