ํ์น ์กฐ์ธ(fetch join)
ํ์น ์กฐ์ธ์ด๋ SQL ์กฐ์ธ์ ์ข ๋ฅ๊ฐ ์๋๋ฉฐ, JPQL์์ ์ฑ๋ฅ ์ต์ ํ๋ฅผ ์ํด์ ์ ๊ณตํด์ฃผ๋ ๊ธฐ๋ฅ์ ๋๋ค.
ํ์น ์กฐ์ธ์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ ์ํฐํฐ๋ ์ปฌ๋ ์ ์ ํ๋ฒ์ SQL๋ก ํจ๊ป ์กฐํํ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํจ์ผ๋ก์จ N+1๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๊ฒ ๋์์ค๋๋ค.
์ผ๋ฐ ์กฐ์ธ์ ์คํ ์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํจ๊ป ์กฐํํ์ง ์์ต๋๋ค.
๋จ์ง ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์์ ํ ์ด๋ธ๊ฐ์ ํ์์ ์ํด ์ฌ์ฉ๋๋ ๊ฒ์ ๋๋ค.
๊ธฐ์กด ์กฐ์ธ์ ๋ฌธ์
@Entity
class Member(
@Id @Column(name = "member_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var username: String,
var age: Int,
@Embedded
var address: Address,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
var team: Team? = null,
) {
override fun toString(): String {
return "Member(id=$id, username='$username', age=$age, address=$address, team=$team)"
}
}
@Entity
class Team(
@Id @Column(name = "team_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
var name: String,
@OneToMany(mappedBy = "team")
var members: MutableList<Member> = ArrayList()
) {}
์์ ๊ฐ์ด Team๊ณผ Member ํด๋์ค๊ฐ ์๋ค๊ณ ํ๊ฒ ์ต๋๋ค.
val teamA = Team(name = "TEAM A")
val teamB = Team(name = "TEAM B")
val teamC = Team(name = "TEAM C")
val member = Member(username = "user 1", age = 10, address = Address("1", "1", "1"), team = teamA)
val member1 = Member(username = "user 2", age = 10, address = Address("1", "1", "1"), team = teamA)
val member2 = Member(username = "user 3", age = 10, address = Address("1", "1", "1"), team = teamB)
val member3 = Member(username = "user 4", age = 10, address = Address("1", "1", "1"), team = teamC)
em.persist(teamA)
em.persist(teamB)
em.persist(teamC)
em.persist(member)
em.persist(member1)
em.persist(member2)
em.persist(member3)
em.flush()
em.clear()
val resultList = em.createQuery("select m from Member m", Member::class.java).resultList
resultList.forEach{ println("${it.username} + ${it.team?.name}" ) }
์ ์ฝ๋์ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
N + 1 ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
ํ์น ์กฐ์ธ ์ฌ์ฉ ์์
JPQL ๋ถ๋ถ์ ์ ์ธํ๊ณ ๋ ๋๊ฐ์ ์ฝ๋์ ๋๋ค.
val teamA = Team(name = "TEAM A")
val teamB = Team(name = "TEAM B")
val teamC = Team(name = "TEAM C")
val member = Member(username = "user 1", age = 10, address = Address("1", "1", "1"), team = teamA)
val member1 = Member(username = "user 2", age = 10, address = Address("1", "1", "1"), team = teamA)
val member2 = Member(username = "user 3", age = 10, address = Address("1", "1", "1"), team = teamB)
val member3 = Member(username = "user 4", age = 10, address = Address("1", "1", "1"), team = teamC)
em.persist(teamA)
em.persist(teamB)
em.persist(teamC)
em.persist(member)
em.persist(member1)
em.persist(member2)
em.persist(member3)
em.flush()
em.clear()
val resultList = em.createQuery("select m from Member m join fetch m.team", Member::class.java).resultList
resultList.forEach{ println("${it.username} + ${it.team?.name}" ) }
์คํ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ฌ์ฉ๋ฒ
[ LEFT [OUTER] | INNER ] JOIN FETCH ์กฐ์ธ๊ฒฝ๋ก
์ผ๋ฐ join ๋ค์ fetch๋ฅผ ๋ถ์ฌ์ฃผ๋ฉด ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์น ์กฐ์ธ์ ์ํฐํฐ๋ฅผ ๋์์ผ๋ก ํ ์ ์๊ณ , ์ปฌ๋ ์ ์ ๋์์ผ๋ก ํ ์ ์์ต๋๋ค.
์ํฐํฐ ํ์น ์กฐ์ธ
ํ์น ์กฐ์ธ์ ์ฌ์ฉํด์ ํ์ ์ํฐํฐ๋ฅผ ์กฐํํ๋ฉด์ ์ฐ๊ด๋ ํ ์ํฐํฐ๋ ํจ๊ป ์กฐํํด๋ณด๊ฒ ์ต๋๋ค.
SELECT m FROM Member m join fetch m.team
์คํ๋๋ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ปฌ๋ ์ ํ์น ์กฐ์ธ
์ด๋ฒ์๋ ํ์์ ํ์๋ค์ ๋ฆฌ์คํธ๋ฅผ ํจ๊ป ์กฐํํด๋ณด๊ฒ ์ต๋๋ค.
SELECT t FROMTeam t join fetch t.members where t.name = 'ํA'
์คํ๋๋ ์ฟผ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ฃผ์์
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ปฌ๋ ์
์ ์กฐ์ธํ๋ฉด ๋ฐ์ดํฐ๊ฐ ๋์ด๋ฉ๋๋ค.
(2023-1์ 7์ผ ์ถ๊ฐ - ํ์ด๋ฒ๋ค์ดํธ6 ๋ถํฐ๋ distinct ๋ช ๋ น์ด๋ฅผ ์ฌ์ฉํ์ง ์์๋ ์ํฐํฐ์ ์ค๋ณต์ ์ ๊ฑฐํ๋๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค.)
๋ฐ๋ผ์ ์๋ ๋ด์ฉ์ ์ด์ ๋ฒ์ ์ ํด๋น๋ฉ๋๋ค.
์ด์ ๋ฒ์ ์์ ๋ฐ์ํ๋ ์ค๋ฅ๊ฐ ๊ด์ฌ ์์ผ์๋ค๋ฉด ์ฃผ์์ ์ ๊ฑด๋๋ฐ์๋ฉด ๋ฉ๋๋ค. :)
์ฐธ๊ณ - https://github.com/hibernate/hibernate-orm/blob/6.0/migration-guide.adoc#distinct
์์ ์ฝ๋์ ํจ๊ป ์ดํด๋ณด๊ฒ ์ต๋๋ค.
val teamA = Team(name = "TEAM A")
val teamB = Team(name = "TEAM B")
val teamC = Team(name = "TEAM C")
val member = Member(username = "user 1", age = 10, address = Address("1", "1", "1"), team = teamA)
val member1 = Member(username = "user 2", age = 10, address = Address("1", "1", "1"), team = teamA)
val member2 = Member(username = "user 3", age = 10, address = Address("1", "1", "1"), team = teamB)
val member3 = Member(username = "user 4", age = 10, address = Address("1", "1", "1"), team = teamC)
em.persist(teamA)
em.persist(teamB)
em.persist(teamC)
em.persist(member)
em.persist(member1)
em.persist(member2)
em.persist(member3)
em.flush()
em.clear()
val resultList = em.createQuery("select t from Team t join fetch t.members", Team::class.java).resultList
resultList.forEach{ println("${it.name} + ${it.members.size}" ) }
์ ์ฝ๋์ ๊ฒฐ๊ณผ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์์ธ
ํ A์ ์์๋์ด ์๋ ํ์์ด 2๋ช ์ผ ๋, TEAM๊ณผ MEMBER๋ฅผ ์กฐ์ธํด์ ๊ฐ์ ธ์ค๋ฉด row๊ฐ 2์ค๋ก ๋์ด๋๊ฒ ๋ฉ๋๋ค.
JPA๋ ๊ทธ๋ผ 2์ค์ ๋ชจ๋ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์, ํA๋ฅผ 2๊ฐ ๊ฐ์ง๊ณ ์ค๊ฒ ๋ฉ๋๋ค.
๋ฐ๋ผ์ SELECT t FROM Team t join fetch t.members where t.name = 'ํA' ๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์ํํ ๊ฒฐ๊ณผ์ List์ size๋ 2๊ฐ๊ฐ ๋์ด๋ฒ๋ฆฌ๋ ๊ฒ์ ๋๋ค.
DISTINCT
JPQL์์๋ SQL์์ ์ ๊ณตํ๋ DISTINCT๋ผ๋ ๋ช ๋ น์ด๋ฅผ ์ ๊ณตํฉ๋๋ค.
SQL์ DISTINCT ๋ช ๋ น์ด๋ ์ค๋ณต๋ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ฑฐํ๋ ๋ช ๋ น์ด์ง๋ง, JPQL์ DISTINCT๋ ํ๊ฐ์ง ๊ธฐ๋ฅ์ด ๋ ์์ต๋๋ค.
- SQL์ DISTINCT๋ฅผ ์ถ๊ฐํฉ๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ํฐํฐ์ ์ค๋ณต์ ์ ๊ฑฐํฉ๋๋ค.
์ ๊ธฐ๋ฅ์ ํ๋ ๋ ์ถ๊ฐํ ๊ฒ์ผ๊น์?
SQL์ DISTINCT๋ ROW์ ๋ชจ๋ ์์ฑ์ด ๊ฐ์์ผ๋ง ์ ๊ฑฐ๊ฐ ๋ฉ๋๋ง, ํด๋น ๊ธฐ๋ฅ์ผ๋ก๋ ํด๊ฒฐ๋์ง ์๋ ๋ถ๋ถ์ด ์์ต๋๋ค.
ID์ NAME์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ SQL์ DISTINCT๋ก๋ ์ ๊ฑฐ๊ฐ ๋์ง ์์ต๋๋ค.
๋ฐ๋ผ์ JPQL์ DISTINCT๋ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ํฐํฐ์ ์ค๋ณต์ ์ ๊ฑฐํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐ๋ก ๋ฃ์ด์ค ๊ฒ์ ๋๋ค.
JOINํ Member์ ๋ฐ์ดํฐ๋ ์ ๊ฑฐ๋์ง ์๊ณ 1์ฐจ์บ์์ ๋ณด๊ด๋ฉ๋๋ค.
์ ๊ฑฐ๋๋ ๊ฒ์ ์ค๋ณต๋ ํA ๋ผ๋ ์ด๋ฆ์ Team ์ํฐํฐ ๋ฟ์ ๋๋ค.
ํ์น ์กฐ์ธ์ ํน์ง๊ณผ ํ๊ณ
ํ์น ์กฐ์ธ ๋์์๋ ๋ณ์นญ์ ์ค ์ ์์ต๋๋ค.
JPAํ์ค์์๋ ํ์น ์กฐ์ธ ๋์์ ๋ณ์นญ์ ์ฃผ๋ ๊ฒ์ ์ง์ํ์ง ์์ผ๋, ํ์ด๋ฒ๋ค์ดํธ๋ฅผ ํฌํจํ ๋ช๋ช ๊ตฌํ์ฒด๋ค์ ๋ณ์นญ์ ์ง์ํฉ๋๋ค.
๊ทธ๋ฌ๋ ๋ณ์นญ์ ์ฌ์ฉํ๋ฉด ์์นซ ์ฐ๊ด๋ ๋ฐ์ดํฐ ์๊ฐ ๋ฌ๋ผ์ ธ์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ์ด ๊นจ์ง ์ ์์ผ๋ฏ๋ก ์ฃผ์ํ์ฌ์ผ ํฉ๋๋ค.
ํนํ 2์ฐจ ์บ์์ ํจ๊ป ์ฌ์ฉํ ๋ ์กฐ์ฌํด์ผ ํฉ๋๋ค.
์ฐ๊ด๋ ๋ฐ์ดํฐ ์๊ฐ ๋ฌ๋ผ์ง ์ํ์์ 2์ฐจ ์บ์์ ์ ์ฅ๋๋ค๋ฉด ๋ค๋ฅธ ๊ณณ์์ ์กฐํํ ๋๋ ์ฐ๊ด๋ ๋ฐ์ดํฐ์ ์๊ฐ ๋ฌ๋ผ์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๋ ์ด์์ ์ปฌ๋ ์ ์ ํ์นํ ์ ์์ต๋๋ค
๊ตฌํ์ฒด์ ๋ฐ๋ผ ๋๊ธฐ๋ ํ์ง๋ง, ์ปฌ๋ ์ * ์ปฌ๋ ์ ์ ์นดํ ์์ ๊ณฑ์ด ๋ง๋ค์ด์ง๋ฏ๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
ํ์ด๋ฒ๋ค์ดํธ ์ฌ์ฉ ์์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ฌ์ฉํ์ง ์๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
์ปฌ๋ ์ ์ ํ์น ์กฐ์ธํ๋ฉด ํ์ด์ง API (setFirstResult, setMaxResult)๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ปฌ๋ ์ ์ด ์๋ ๋จ์ผ ๊ฐ ์ฐ๊ด ํ๋๋ฅผ ํ์น ์กฐ์ธํ๋ฉด ์ฌ์ฉํ ์ ์์ผ๋, ์ปฌ๋ ์ ์ ํ์น ์กฐ์ธํ ๊ฒฝ์ฐ์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์ด๋ฒ๋ค์ดํธ์ ๊ฒฝ์ฐ์๋ ํด์ฃผ๊ธด ํ์ง๋ง, ๊ฒฝ๊ณ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ๋ฉ๋ชจ๋ฆฌ์์ ํ์ด์ง ์ฒ๋ฆฌ๋ฅผ ํ๋๋ฐ, ์ด๋ ๋ฐ์ดํฐ๊ฐ ๋ง์ ๊ฒฝ์ฐ ์ฑ๋ฅ ์ด์์ ๋ฉ๋ชจ๋ฆฌ ์ด๊ณผ ์์ธ๊ฐ ๋ฐ์ํ ์ ์๊ธฐ์ ๋งค์ฐ ์ํํฉ๋๋ค.
ํ์น ์กฐ์ธ ์ ์ ๋ณ์นญ์ ์ค ์ ์๊ฒ ๋ง๋ ์ด์
ํ์น์กฐ์ธ์ ํ ์ํฐํฐ์ ์ฐ๊ด๋ ๋ค๋ฅธ ์ํฐํฐ๋ฅผ ๋ชจ๋ ์กฐํํ๊ฒ ๋ค๋ ์๋ฏธ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
๊ทธ๋ฌ๋ ํจ์น ์กฐ์ธ์ ๋ณ์นญ์ ์ฌ์ฉํ์ฌ ํน์ ์กฐ๊ฑด์ ๋ถํฉํ๋ ๊ฒฐ๊ณผ๋ง ๊ฐ์ ธ๊ณ ์ค ์ถ์ ์ ์์ต๋๋ค.
๊ทธ๋ฌ๋ ํ์น์กฐ์ธ์ ์ ๋ ๊ทธ๋ ๊ฒ ์ฌ์ฉํด์๋ ์๋ฉ๋๋ค.
ํํฐ๋ง์ด ํ์ํ๋ค๋ฉด, ํ์น ์กฐ์ธ์ด ์๋๋ผ ๋ฐ๋ก ๋ฐ๋ก ์ํฐํฐ๋ฅผ ์กฐํํด์ผ ํฉ๋๋ค.
๊ทธ ์ด์ ๋ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋๋ฌธ์ ๋๋ค.
์กฐ๊ธ ์ฝ๊ฒ ์ดํดํ๊ธฐ ์ํด ์์๋ฅผ ๋ค์ด ์ค๋ช ํ๊ฒ ์ต๋๋ค.
ํA๊ฐ ์๊ณ , ํA์๋ ํ์์ด 5๋ช ์์ต๋๋ค.
ํ์1์ ๋์ด๊ฐ 10์ด ์ ๋๋ค.
ํ์2์ ๋์ด๊ฐ 20์ด ์ ๋๋ค.
ํ์3์ ๋์ด๊ฐ 30์ด ์ ๋๋ค.
ํ์4์ ๋์ด๊ฐ 40์ด ์ ๋๋ค.
ํ์5์ ๋์ด๊ฐ 50์ด ์ ๋๋ค.
์ ๋ ํ์ ์กฐํํ๋ฉด์, ์ฐ๊ด๋ ํ์์ ๊ฐ์ด ์กฐํํ๊ณ ์ถ์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ฐ๊ด๋ ํ์๋ค ์ค ํ์์ ๋์ด๊ฐ 20์ด ์ดํ์ธ ํ์๋ง ์กฐํํ๊ณ ์ถ์ต๋๋ค.
๊ทธ๋์ ๋ค์๊ณผ ๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ์์ต๋๋ค.
"select t from Team t join fetch t.members m where m.age <= 20"
ํด๋น ๊ฒฐ๊ณผ์ teamA.getMembers()์ size๋ 2์ ๋๋ค.
์ด๋ teamA์ Members์ ํ์1๊ณผ ํ์2๋ง ๋ค์ด์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์๋ TeamA์ ์ฐ๊ด๋ ํ์์ด 5๋ช ์ ๋๋ค.
๊ทธ๋ฌ๋ ์์์ฑ ์ปจํ ์คํธ์๋ TeamA์ ์ฐ๊ด๋ ํ์์ด 2๋ช ๋ฐ์ ์๋ ๊ฒ์ ๋๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ์ฒด์ ๋ฐ์ดํฐ ๊ฐ์๊ฐ ๋ฌ๋ผ์ ธ์
๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ์ด ๊นจ์ ธ๋ฒ๋ฆฌ๋ ๊ฒฐ๊ณผ๋ฅผ ์ด๋ํ๊ธฐ์, ์ด๋ฅผ ๋ฐฉ์งํ๊ณ ์ ํ์น์กฐ์ธ์๋ ๋ณ์นญ์ ์ค ์ ์๊ฒ ๋ง๋ ๊ฒ์ ๋๋ค.
์ฐธ๊ณ ๋ก ๋ณ์นญ์ ์ค ๋์์ where ์ ์์ ์ฌ์ฉํ๋ฉด ์์ธ๋ฅผ ๋ฐ์์ํค์ง๋ ์์ผ๋, on ์ ์์ ์ฌ์ฉ ์ ์์ธ๋ฅผ ๋ฐ์์ํต๋๋ค.
๋ฐ๋ผ์ ๊ฐ๊ธ์ ํ์น ์กฐ์ธ์ ๋์์๋ ๋ณ์นญ์ ์ฌ์ฉํ์ง ์๋๋ก ํ๊ณ ,
๋ณ์นญ์ ์ค๋ค๊ณ ํ๋๋ผ๋ ์ ๋๋ก where, on ์ ์ ํํฐ๋ง ๋์์ผ๋ก๋ ์ฌ์ฉํ์ง ์์์ผ ํฉ๋๋ค.
๋ณ์นญ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ
"select t from Team t join fetch t.members m join fetch m.xxx"
์์ฒ๋ผ ํ์น ์กฐ์ธ์ ์ฌ๋ฌ ๋จ๊ณ๋ก ํ๋ ๊ฒฝ์ฐ์๋ ์ฌ์ฉํด๋ ๊ด์ฐฎ์ต๋๋ค.
์ ๋ฆฌ
- ๋ชจ๋ ์ต์ ํ ๋ฌธ์ ๋ฅผ ํ์น ์กฐ์ธ์ผ๋ก ํด๊ฒฐํ ์๋ ์์ต๋๋ค.
- ์ฌ๋ฌ ํ ์ด๋ธ์ ์กฐ์ธํด์ ์ํฐํฐ๊ฐ ๊ฐ์ง ๋ชจ์์ด ์๋ ์ ํ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ผ ํ๋ฉด, ํ์น์กฐ์ธ ๋ณด๋ค๋ ์ผ๋ฐ ์กฐ์ธ์ ์ฌ์ฉํ๊ณ , ํ์ํ ๋ฐ์ดํฐ๋ค๋ง ์กฐํํด์ DTO๋ก ๋ฐํํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ ๋๋ค.
Reference
[์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ - ๊น์ํ]
'๐๏ธ Spring > JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[JPA] ์ํฐํฐ, ํ ์ด๋ธ, ์ปฌ๋ผ๋ช ์์ฑ ์ ๋ต (0) | 2021.12.18 |
---|---|
[JPA] ์๊ธฐ ์์ ์ ๊ณ์ธตํ์ผ๋ก ๋งคํํ๊ธฐ (0) | 2021.12.18 |
[JPA] JPQL์ ๋ฒํฌ ์ฐ์ฐ (0) | 2021.12.17 |
[JPA] JPQL Named ์ฟผ๋ฆฌ (0) | 2021.12.17 |
[JPA] JPQL ์ฌ์ฉ๋ฒ (0) | 2021.12.17 |