DevLog ๐ถ
[JPA] @OneToMany ๋จ๋ฐฉํฅ ๋งคํ ์ ๊ณ ๋ คํด์ผ ํ๋ ๊ฒ๋ค ๋ณธ๋ฌธ
[JPA] @OneToMany ๋จ๋ฐฉํฅ ๋งคํ ์ ๊ณ ๋ คํด์ผ ํ๋ ๊ฒ๋ค
dolmeng2 2023. 7. 16. 20:13๐ฑ ๋ค์ด๊ฐ๊ธฐ ์
JPA์์ @OneToMany ๋จ๋ฐฉํฅ ๋งคํ๋ณด๋ค๋ ๋ค๋์ผ ์๋ฐฉํฅ ๋งคํ์ ๊ถ์ฅํ๋ค๋ ๋ง์ ๋ณด์์ ๊ฒ์ด๋ค.
ํ์ง๋ง, ๊ฐ์ฒด์งํฅ์ผ๋ก ์ฝ๋๋ฅผ ์ค๊ณํ๋ค ๋ณด๋ฉด ๋ค๋์ผ์ ์ํฉ๋ณด๋ค๋ 1์ธ ์ํฐํฐ๋ฅผ ๊ธฐ์ค์ผ๋ก N์ธ ์ํฐํฐ๋ฅผ ์กฐํํ๋ ๊ฒฝ์ฐ๊ฐ ๋ ๋ง๊ณ , (์ ์ด๋ ๋ด ๊ฒฝํ์์๋ ๊ทธ๋ฌ๋ค) N์ธ ์ํฐํฐ๊ฐ ๊ตณ์ด 1์ธ ์ํฐํฐ์ ๋ํ ์ ๋ณด๋ฅผ ๋ชฐ๋ผ๋ ๋๋ ์ ์ด ๋ง์๋ค. ๊ทธ๋ฌ๋ค ๋ณด๋ ์์ฐ์ค๋ฝ๊ฒ ์ผ๋๋ค ๋จ๋ฐฉํฅ ๋งคํ์ ๋ง์ด ์ฌ์ฉํ์๋๋ฐ, ์ ๊ทธ๋ฌ๋ ๊ฒ์ธ์ง ๊ถ๊ธํด์ ๋๋ฆ๋๋ก ์คํ์ ํด๋ณด๊ณ ์ ํ๋ค.
(ex. ๊ฒ์๊ธ๊ณผ ๊ฒ์๊ธ ์ด๋ฏธ์ง์ ๊ด๊ณ์์, ๊ฒ์๊ธ ์ด๋ฏธ์ง๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฒ์๊ธ ์ ๋ณด๋ฅผ ์ฐพ์์ค๋ ๊ฒ๋ณด๋ค๋ ๊ฒ์๊ธ์ ๊ธฐ์ค์ผ๋ก ๊ฒ์๊ธ ์ด๋ฏธ์ง ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ ๋ ๋ง์์)
๐ฑ ๊ธฐ๋ณธ ์ํฐํฐ ์ค๊ณํ๊ธฐ
@Entity
class Board(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@Embedded
val title: BoardTitle,
@Embedded
val content: BoardContent,
@Embedded
var boardTags: BoardTags = BoardTags(Collections.emptyList())
) {
fun addTag(boardTag: BoardTag) {
boardTags.addTag(boardTag)
boardTags = BoardTags(boardTags.tags)
}
}
ํ๋์ ๊ฒ์๊ธ์ ์ ๋ชฉ๊ณผ ๋ด์ฉ ์ ๋ณด๊ฐ ์กด์ฌํ๋ฉฐ, ๊ฒ์๊ธ ํ๊ทธ์ ๋ํ ์ ๋ณด๊ฐ 1:N์ผ๋ก ๊ตฌ์ฑ๋ ์ํฐํฐ๋ค.
BoardTitle๊ณผ BoardContent๋ VO์ด๊ธฐ ๋๋ฌธ์ ๋ณ๋๋ก ์ฒจ๋ถํ์ง๋ ์๊ฒ ๋ค.
์ฌ๊ธฐ์ ๊ฒ์๊ธ ํ๊ทธ ์ํฐํฐ์ ๋ํด์ ์ด๋ค ์์ผ๋ก ์ค์ ํ๋์ง์ ๋ฐ๋ผ์ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๊ฒ ๋๋ค.
๐ฑ ์กฐ์ธ ํ ์ด๋ธ์ ์์ฑํ์ฌ ์ผ๋๋ค ๊ด๊ณ ๊ตฌ์ฑํ๊ธฐ
@Embeddable
class BoardTags(
tags: MutableList<BoardTag> = Collections.emptyList()
) {
@OneToMany(fetch = FetchType.LAZY)
val tags: MutableList<BoardTag> = tags.toMutableList()
fun addTag(boardTag: BoardTag) {
tags.add(boardTag)
}
}
๋จ์ํ @OneToMany ์ด๋ ธํ ์ด์ ๋ง ๋ฌ์์ ๋, ์์ฑ๋ ํ ์ด๋ธ ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด board_tags๋ผ๋ ๋ณ๋์ ์กฐ์ธ ํ ์ด๋ธ์ด ์์ฑ๋๋ค.
์ฌ๊ธฐ์ board_tags์๋ board_id์ tags_id๋ผ๋ ํ๋๊ฐ ์ถ๊ฐ๋์๋ค. ์ฌ๊ธฐ์ board_id์ ๋ค์ด๋ฐ์ BoardTags๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ํฐํฐ (board)์ ์ด๋ฆ + _id๋ฅผ ํฉ์น ๊ฒ์ด๊ณ , tags_id์ ๋ค์ด๋ฐ์ @OneToMany๋ก ์ ์ธํ ํ๋๋ช (tags) + _id๋ฅผ ๋ถ์ธ ํํ์ด๋ค.
โ๏ธ ์ ์ฅ ํ ์คํธ
@Test
@Rollback(false) // ํ
์คํธ ํ๊ฒฝ์์ ๋กค๋ฐฑ๋๋ ๊ฒ์ ๋ฐฉ์ง
fun save() {
val ํ๊ทธ1 = BoardTag(name = "ํ๊ทธ1")
val ํ๊ทธ2 = BoardTag(name = "ํ๊ทธ2")
boardTagRepository.save(ํ๊ทธ1)
boardTagRepository.save(ํ๊ทธ2)
val board = Board(
title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ")
)
board.addTag(ํ๊ทธ1)
board.addTag(ํ๊ทธ2)
boardRepository.save(board)
}
๊ฐ์ฅ ๋จผ์ , ๊ฒ์๊ธ ํ๊ทธ์ ๊ฒ์๊ธ์ ๋ํด์ insert ํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
์ด๋, ๊ฒ์๊ธ ํ๊ทธ ์ ์ฅ ์ board_id์ ๋ํ ์ปฌ๋ผ์ ์ฑ์์ง์ง ์๊ณ name, id์ ๋ํด์๋ง ์ฝ์
๋๋ค.
cf) ํค ์ ๋ต์ด IDENTITY์ด๊ธฐ ๋๋ฌธ์ save ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
์ดํ, flush ์์ ์์ ์กฐ์ธ ํ
์ด๋ธ์ ์์ ๊ฐ์ด insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
์ฆ, ์ฟผ๋ฆฌ๊ฐ ์ด 5๋ฒ์ด๋ ๋๊ฐ๊ฒ ๋๋ ๊ฒ์ด์ด์ ์๋นํ ๋นํจ์จ์ ์ด๋ค.
โ๏ธ ์ญ์ ํ ์คํธ
๊ฒ์๊ธ์ ๋ํ ํ๊ทธ๋ฅผ ์ญ์ ํ๋ ํ
์คํธ์ด๋ค.
์ด๋, ๊ฒ์๊ธ์ ์กด์ฌํ๋ ํ๊ทธ ์ ๋ณด๋ฅผ ์ ๊ฑฐํ๊ณ , ๊ทธ ๋ค์ ๊ฒ์๊ธ์ ํ๊ทธ ์ ๋ณด๋ฅผ ์ ๊ฑฐํ๋ค.
@Test
@Rollback(false)
fun delete() {
// ์ ์ฅ ๋ก์ง
val ํ๊ทธ1 = BoardTag(name = "ํ๊ทธ1")
val ํ๊ทธ2 = BoardTag(name = "ํ๊ทธ2")
boardTagRepository.save(ํ๊ทธ1)
boardTagRepository.save(ํ๊ทธ2)
val board = Board(title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ"))
board.addTag(ํ๊ทธ1)
board.addTag(ํ๊ทธ2)
boardRepository.save(board)
entityManager.flush()
/** ์ ๊ฑฐ ๋ก์ง */
val savedBoard = boardRepository.findById(1L).get()
savedBoard.boardTags.tags.removeAt(0) // ํ์!
boardTagRepository.deleteById(1L)
}
๊ธฐ๋ณธ์ ์ผ๋ก ํ๊ทธ ์ ๋ณด๋ฅผ ์ ๊ฑฐํ๋ ค๋ฉด deleteById๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ์๊ฐํ ์ ์๋ค. ํ์ง๋ง, ๋ฐ๋ก deleteById๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ์กฐ์ธ ํ
์ด๋ธ์ ๊ฑธ๋ ค ์๋ ์ธ๋ํค์ ์ํด์ board_tag ํ
์ด๋ธ์ ์๋ ํ๋๋ฅผ ๋ฐ๋ก ์ ๊ฑฐํ ์ ์๋ค.
๊ทธ๋์ board์ ์กด์ฌํ๋ tag ๋ฆฌ์คํธ์์ removeAt์ ํตํด 1์ฐจ์ ์ผ๋ก ์กฐ์ธ ํ
์ด๋ธ์ ๋ํด์ ์ ๊ฑฐ๋ฅผ ํด์ผ ํ๋ค. (savedBoard.boardTags.tags.removeAt(0))
์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ฉด, ์ฐ์ ์กฐ์ธ ํ
์ด๋ธ์ธ board_tags์ ๋ํด์ delete ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค. ์ด๋, board_id๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ๊ฑฐํ๊ธฐ ๋๋ฌธ์ board_tags์ ์ ์ฅ๋ 2๊ฐ์ ๋ ์ฝ๋ ๋ชจ๋๊ฐ ์ ๊ฑฐ๋๋ค. ์ดํ, ์ ๊ฑฐ๋์ง ์์ ๋จ์ ๋ ์ฝ๋์ ๋ํด (tags_id = 2์ธ ๋ ์ฝ๋) ๋ค์ ์ฝ์
ํ๋ ๊ณผ์ ์ด ๋ฐ์ํ๋ค. ๋ง์ฝ, board_id = 1์ธ ๋ ์ฝ๋๊ฐ 5๊ฐ๊ฐ ์์๋ค๋ฉด 5๊ฐ ๋ชจ๋์ ๋ํด ์ ๊ฑฐํ๊ณ , 4๊ฐ์ ๋ํด์ ์ถ๊ฐ์ ์ธ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ฌ ๋ ๋ถํ์ํ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์์ ๊ฒ์ด๋ค.
์ต์ข
์ ์ผ๋ก ์์ ๊ณผ์ ์ด ๋๋๊ณ board_tag์ ๋ํด์ ์ต์ข
์ ์ธ ํ๊ทธ ์ ๊ฑฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก insert ์ฟผ๋ฆฌ๋ก ์ธํด์ ์ํ์ง ์๋ ์ฟผ๋ฆฌ๊ฐ ์์ฒญ ๋๊ฐ ์๋ ์๋ค๋ ํน์ง์ ๊ฐ์ง๋ค.
๐ฑ ์กฐ์ธ ํ ์ด๋ธ์ ์์ ๋ณด๊ธฐ
์กฐ์ธ ํ ์ด๋ธ์ ์ํด์ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ผ๋ฉด, ์กฐ์ธ ํ ์ด๋ธ ์์ฒด๋ฅผ ์์ ๋ฉด ๋์ง ์์๊น?
@Embeddable
class BoardTags(
tags: MutableList<BoardTag> = Collections.emptyList()
) {
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id")
val tags: MutableList<BoardTag> = tags.toMutableList()
fun addTag(boardTag: BoardTag) {
tags.add(boardTag)
}
}
@JoinColumn์ ํตํด์ board_tag์ ๋ช
์์ ์ผ๋ก board์ ๋ํ ์์ด๋๋ฅผ ์ ์ฅํ ์ ์๋ ํ๋๋ฅผ ์ง์ ํด์ฃผ๋ฉด ๋๋ค.
1:N ๊ด๊ณ์์ ์ธ๋ํค๋ ํญ์ N ํ
์ด๋ธ์ ๊ฑธ๋ฆฌ๊ธฐ ๋๋ฌธ์ n์ชฝ์์ column ์ ๋ณด๋ฅผ ์ง์ ํด์ค๋ค.
์์ฑ๋ ํ ์ด๋ธ์ ๋ณด๋ฉด, ์ด๋ฒ์๋ ์กฐ์ธ ํ ์ด๋ธ ๋์ board_tag ํ ์ด๋ธ์ board ์ ๋ณด๋ฅผ ์ง์ ํ๊ธฐ ์ํ board_id ์ ๋ณด๊ฐ ์ถ๊ฐ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
โ๏ธ ์ ์ฅ ํ ์คํธ
@Test
@Rollback(false) // ํ
์คํธ ํ๊ฒฝ์์ ๋กค๋ฐฑ๋๋ ๊ฒ์ ๋ฐฉ์ง
fun save() {
val ํ๊ทธ1 = BoardTag(name = "ํ๊ทธ1")
val ํ๊ทธ2 = BoardTag(name = "ํ๊ทธ2")
boardTagRepository.save(ํ๊ทธ1)
boardTagRepository.save(ํ๊ทธ2)
val board = Board(
title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ")
)
board.addTag(ํ๊ทธ1)
board.addTag(ํ๊ทธ2)
boardRepository.save(board)
}
๋จผ์ , ํ๊ทธ 2๊ฐ๊ฐ ์ ์ฅ๋ ๊ฒฐ๊ณผ์ด๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก name, id๋ง ๊ฐ์ด ์ฑ์์ ธ์ board_id์ ๋ํ ์ปฌ๋ผ์ null ๊ฐ์ ๊ฐ์ง๊ฒ ๋๋ค.
๊ทธ๋ฆฌ๊ณ , ๊ฒ์๊ธ์ ๋ํ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
ํ์ง๋ง, ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. board_tag์ ๋ํด ์ถ๊ฐ์ ์ผ๋ก ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
์ด๋ ์ฒ์์ board_tag๋ฅผ ์ ์ฅํ ๋ board_id์ ๋ํ ์ ๋ณด๊ฐ ์๋ ์ํ์๊ธฐ ๋๋ฌธ์ flush ์์ ์ board.addTag()๋ก ์ธํด ์ถ๊ฐ๋ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ ๋ฐ์ดํธ๋ฅผ ํ๊ฒ ๋ ๊ฒ์ด๋ค.
์์ ๊ฐ์ด @OneToMany ๋จ๋ฐฉํฅ์์๋ ๋ฐ์ดํฐ ์ ์ฅ ์ ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์๋ค๋ ๋จ์ ์ด ์๋ค.
โ๏ธ ์ญ์ ํ ์คํธ
@Test
@Rollback(false)
fun delete() {
// ์ ์ฅ ๋ก์ง
...
// ๊ฐ์ flush -> update ์ฟผ๋ฆฌ ๋ฐ์
entityManager.flush()
// ์กฐํ -> ์์์ฑ ์ปจํ
์คํธ์์ ๋ฐ์
val savedBoard = boardRepository.findById(1L).get()
// ๋ณด๋์ ์กด์ฌํ๋ ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
savedBoard.boardTags.tags.removeAt(0)
// ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
boardTagRepository.deleteById(1L)
}
entityManager.flush ์ดํ์ ๋ก์ง์์ ๋ฐ์ํ๋ ์ฟผ๋ฆฌ์ด๋ค.
์ ์ฅ ํ ์์์ฑ ์ปจํ
์คํธ์์ ๊ฒ์๊ธ์ ์กฐํํ ๋ค, ํด๋น ๊ฒ์๊ธ์ ์ฐ๊ด๋ ํ๊ทธ๋ฅผ ์ ๊ฑฐํ์๋ค. removeAt(0)์ ์งํํ๊ฒ ๋๋ฉด ๊ฒ์๊ธ ๊ธฐ์ค์ผ๋ก ‘๊ฒ์๊ธ์ ์ฒซ ๋ฒ์งธ ํ๊ทธ ์ ๋ณด๊ฐ ์ ๊ฑฐ’ ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ id = 1์ธ board_tag์ ์กด์ฌํ๋ board_id๋ฅผ null๋ก ์ธํ
ํ๋ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ ์ฌ์ค Hibernate์ flush ์์์ ์ข ๋ ๊ฐ๊น์ด ๋ฌธ์ ์ด๋ค. Hibernae์์ ์์ ์ํฐํฐ๋ฅผ FK ์์ด ์ ์ฅํ๊ณ ์ปฌ๋ ์ ์ ๋ํด ์ฒ๋ฆฌ๋ฅผ ํ๊ฒ ๋๋ฉด FK์ ๋ํ update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ด๋ค. ํ์ฌ ํ ์ด๋ธ ๊ตฌ์กฐ ์ FK๊ฐ ๊ฑธ๋ฆฌ์ง ์์์ FK๊ฐ ์๋ ์ํ๋๊น update ์ฐ์ฐ์ด ๋จผ์ ๋ฐ์ํ๊ณ ์ํฐํฐ์ ๋ํ delete๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
cf) ์ฐธ๊ณ ๋ก, update - delete ๋ชจ๋ flush ์์ ๋ฐ์ํ๋ฉฐ ์ค์ ๋ก board_tag๋ฅผ ์ ๊ฑฐํ์ง ์๋๋ค๋ฉด (deleteById) ๋ ์ฟผ๋ฆฌ ๋ชจ๋ ๋ฐ์ํ์ง ์๋๋ค. delete ์์ ์ถ๊ฐ๋ก update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๋๋์ด๋ค.
๐ก Hibernate์ flush ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
1. OrphanRemovalAction
2. AbstractEntityInsertAction
3. EntityUpdateAction
4. QueuedOperationCollectionAction
5. CollectionRemoveAction
6. CollectionUpdateAction
7. CollectionRecreateAction
8. EntityDeleteAction
+ ๋ฌด์กฐ๊ฑด '์ ๊ฑฐ ํ์'๋ฅผ ํ๋ค๊ณ ํด์ update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ์๋๋ค.
@Test
@Rollback(false)
fun delete() {
// ์ ์ฅ ๋ก์ง
...
// ๊ฐ์ flush -> update ์ฟผ๋ฆฌ ๋ฐ์
entityManager.flush()
// ์กฐํ -> ์์์ฑ ์ปจํ
์คํธ์์ ๋ฐ์
val savedBoard = boardRepository.findById(1L).get()
// ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
boardTagRepository.deleteById(1L)
}
ํ์ง๋ง, removeAt()์ ํตํด์ ์ ๊ฑฐํ์ง ์๋๋ค๋ฉด ๋จ์ํ delete ์ฟผ๋ฆฌ๋ง ๋ฐ์ํ๋ค.
๋จ์ํ boardTag์ id ๊ฐ๋ง์ ์ฐธ๊ณ ํ์ฌ delete๋ฅผ ํ๊ธฐ ๋๋ฌธ์, board_id์ ๋ํ ์ ๋ณด๊ฐ ๋ฑํ ์๊ธฐ ๋๋ฌธ์ด๋ค. (์ด์ ์๋ ๊ฒ์๊ธ์ ๊ธฐ์ค์ผ๋ก ํ๊ทธ ์ ๋ณด๋ฅผ ์ ๊ฑฐํ์๊ธฐ ๋๋ฌธ)
๊ทธ๋์ ์ญ์ ์์ update ์ฟผ๋ฆฌ๊ฐ ๋ฌด์กฐ๊ฑด์ ์ผ๋ก ๋ฐ์ํ๋ค๋ ๋ง์ ํ๋ฆฐ ๋ง์ด๋ค.
๐ฑ ๊ทธ๋ ๋ค๋ฉด, ์ผ๋๋ค ๋จ๋ฐฉํฅ์ ์์ ์ฐ๋ฉด ์ ๋๋ ๊ฑธ๊น? - updatable = false ์ง์ ํ๊ธฐ
๊ทธ๋ ๋ค๋ฉด ์ฐ๋ฆฌ๋ ํญ์ ์ผ๋๋ค ๋จ๋ฐฉํฅ์ ์ฌ์ฉํ๋ฉด ์ด๋ ๊ฒ update ์ฟผ๋ฆฌ๋ฅผ ๋ด์ผ ํ๋ ๊ฒ์ผ๊น?
update ์ฟผ๋ฆฌ๋ฅผ ์์ ๊ณ ์ถ์ ๊ฒ์ด๋ค. ๊ทธ๋ ๋ค๋ฉด, ์ถ๊ฐ์ ์ผ๋ก board_id์ ๋ํ ์ ๋ฐ์ดํธ๊ฐ ์งํ๋์ง ์๋๋ก ์ฟผ๋ฆฌ ์์ฒด์ updatable์ ๋ํ ์กฐ๊ฑด์ false๋ก ์ง์ ํด๋ณด์.
@Embeddable
class BoardTags(
tags: MutableList<BoardTag> = Collections.emptyList()
) {
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", updatable = false)
val tags: MutableList<BoardTag> = tags.toMutableList()
...
}
์์ ๊ฐ์ด updatable=false๋ฅผ ์ง์ ํ๊ฒ ๋๋ฉด, board_tag ํ ์ด๋ธ์ ์กด์ฌํ๋ board_id ํ๋๋ ์์ ๋ถ๊ฐ๋ฅ ์ํ๊ฐ ๋๋ค.
์ฆ, ํ ๋ฒ ์ง์ ๋ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋ค์ ์ธํ ํ๋ ๊ฒ์ด ๋ถ๊ฐ๋ฅํ๊ฒ ๋ ๊ฒ์ด๋ค.
โ๏ธ ์ ์ฅ ํ ์คํธ
@Test
@Rollback(false)
fun save() {
val ํ๊ทธ1 = BoardTag(name = "ํ๊ทธ1")
val ํ๊ทธ2 = BoardTag(name = "ํ๊ทธ2")
boardTagRepository.save(ํ๊ทธ1)
boardTagRepository.save(ํ๊ทธ2)
val board = Board(
title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ")
)
board.addTag(ํ๊ทธ1)
board.addTag(ํ๊ทธ2)
boardRepository.save(board)
}
update ์ฟผ๋ฆฌ๋ ๋ฐ์ํ์ง ์์ง๋ง, ์ฒ์ board_tag๋ฅผ ์ฝ์ ํ ๋ board_id์ ๋ํ ์ ๋ณด๊ฐ ์ง์ ๋์ง ์๋ ์ํ๋ก insert๊ฐ ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ถํ board๊ฐ ์ฝ์ ๋๋๋ผ๋ ๊ณ์ board_tag์ board_id๋ ๊ณ์ null๋ก ๋จ๊ฒ ๋๋ ๊ฒ์ด๋ค.
โ๏ธ ์ญ์ ํ ์คํธ
@Test
@Rollback(false)
fun delete() {
// ์ ์ฅ ๋ก์ง
...
// ๊ฐ์ flush -> update ์ฟผ๋ฆฌ ๋ฐ์
entityManager.flush()
// ์กฐํ -> ์์์ฑ ์ปจํ
์คํธ์์ ๋ฐ์
val savedBoard = boardRepository.findById(1L).get()
// ๋ณด๋์ ์กด์ฌํ๋ ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
savedBoard.boardTags.tags.removeAt(0)
// ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
boardTagRepository.deleteById(1L)
}
ํ์ง๋ง, ์ญ์ ํ ์คํธ์์๋ ๋ณด๋์ ์กด์ฌํ๋ ํ๊ทธ ์ ๋ณด๋ฅผ removeAt์ผ๋ก ์ ๊ฑฐํ๋๋ผ๋ update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์๋๋ค!
insert ์ ์ด๋ฏธ board_id๊ฐ null์ด๊ธฐ ๋๋ฌธ์ ๋ณ๋์ update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ํ์๊ฐ ์์ด์ ๊ทธ๋ฅ delete ์ฟผ๋ฆฌ๋ง ๋๊ฐ ๊ฒ์ด๋ค.
๐ฑ board_id๋ ๊ฐ์ด ์ฑ์์ก์ผ๋ฉด ์ข๊ฒ ์ด! - nullable = false ์ง์ ํด์ฃผ๊ธฐ
ํ์ง๋ง, ์์ ๋ฐฉ๋ฒ์ ์ฐ๊ด๊ด๊ณ๊ฐ ์ธํ ๋์ง ์์ผ๋๊น ๋งค์ฐ ์ฐ์ฐํ๋ค.
board_id๊ฐ null์ธ ๊ฒ์ด ๋ง์์ ๋ค์ง ์๋ ๊ฒ์ด๋๊น, ์ ์ด์ null ๊ฐ์ด ๋ค์ด์ค์ง ์๋๋ก ๋ง๋ค์ด๋ณด์.
@Embeddable
class BoardTags(
tags: MutableList<BoardTag> = Collections.emptyList()
) {
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "board_id", updatable = false, nullable = false)
val tags: MutableList<BoardTag> = tags.toMutableList()
...
}
โ๏ธ ์ ์ฅ ํ ์คํธ
@Test
@Rollback(false)
fun save() {
val ํ๊ทธ1 = BoardTag(name = "ํ๊ทธ1")
val ํ๊ทธ2 = BoardTag(name = "ํ๊ทธ2")
boardTagRepository.save(ํ๊ทธ1)
boardTagRepository.save(ํ๊ทธ2)
val board = Board(
title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ")
)
board.addTag(ํ๊ทธ1)
board.addTag(ํ๊ทธ2)
boardRepository.save(board)
}
null ๊ฐ์ด ์ ์ฅ๋์ง ์์ ์ํ๋ ๊ฒฐ๊ณผ๊ฐ ๋์ฌ ๊ฒ ๊ฐ๋ค๊ณ ์์ํ์ง๋ง, ์์ ๊ฐ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ด๋, board_tag์ ํ๊ทธ ์ ๋ณด๊ฐ ์ ์ฅํ๋ ์์ ์ board_id์ ๋ํ ์ ๋ณด๊ฐ ๋ค์ด๊ฐ์ผ ํ๋๋ฐ ํ์ฌ๋ board์ ๋ํ ์ ๋ณด๊ฐ ํ๋๋ ์์ผ๋ null ๊ฐ์ด ๋ค์ด๊ฐ๋ ค๋ค๊ฐ ์ ์ฝ ์กฐ๊ฑด ์๋ฐ์ผ๋ก ์ธํด ์์ธ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
๐ฑ ํ๊ทธ ์ ๋ณด๊ฐ ์ ์ฅ๋ ๋ ๊ฒ์๊ธ ์ ๋ณด๋ ํจ๊ป ์ ์ฅํ ๋! - CascadeType ์ง์ ํ๊ธฐ
๊ทธ๋ ๋ค๋ฉด, board_tag๊ฐ ์ ์ฅํ๋ ์์ ์ board์ ๋ํ ์ ๋ณด๋ฅผ ์๋ฉด ๋๋ ๊ฒ์ด ์๋๊น?
ํ์ง๋ง, ์ฐ๋ฆฌ๋ board → board_tag๋ก์ ๋จ๋ฐฉํฅ ๊ด๊ณ๋ง ๊ฐ์ง๋๋ก ๋ง๋ค๊ณ ์ถ๊ธฐ ๋๋ฌธ์ board_tag๊ฐ board์ ๋ํ ์ ๋ณด๋ฅผ ์์ง ์๊ธฐ๋ฅผ ๋ฐ๋๋ค. ์ด๋ฅผ ์ํด์๋, board_tag๋ฅผ ์๊ณ ์๋ board๊ฐ ์ ์ฅ๋๋ ์์ ์ ํ๊ทธ์ ๋ํ ์ ๋ณด๋ ํจ๊ป ์ ์ฅ๋๋๋ก ๋ง๋ค ์ ์๋ค.
@Embeddable
class BoardTags(
tags: MutableList<BoardTag> = Collections.emptyList()
) {
@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST])
@JoinColumn(name = "board_id", updatable = false, nullable = false)
val tags: MutableList<BoardTag> = tags.toMutableList()
...
}
์์ ๊ฐ์ด cascade ์ต์
์ ํ์ฉํ์ฌ PERSIST๋ก ์ง์ ํด์ฃผ์.
์ด๋ฌ๋ฉด board๊ฐ ์ ์ฅ๋๋ ์์ ์ board_tag ์ ๋ณด๋ ํจ๊ป ์๊ฒ ๋๋ค.
โ๏ธ ์ ์ฅ ํ ์คํธ
๊ธฐ์กด์ ์ ์ฅ ๋ก์ง์ ๋ณ๊ฒฝํ์ฌ, board๊ฐ ์ ์ฅ๋ ๋ board_tag๋ ํจ๊ป ์ง์ ํ์ฌ ์ ์ฅํด๋ณด์.
@Test
@Rollback(false)
fun save_casacde() {
val boardTags = mutableListOf(BoardTag(name = "ํ๊ทธ1"), BoardTag(name = "ํ๊ทธ2"))
val board = Board(
title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ"),
boardTags = BoardTags(boardTags)
)
boardRepository.save(board)
}
๋จผ์ , board์ ๋ํ ์ ๋ณด๊ฐ ์ ์ฅ๋๋ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ , board_id๊ฐ 1๋ก ์ฑ์์ง board_tag์ ๋ํ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฆ, update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด์ ์ฐ๋ฆฌ๊ฐ ์ํ๋๋๋ก ์ ๋์ํ๋ ๊ฒ์ด๋ค.
๐ฑ @OneToMany ์๋ฐฉํฅ
์ฒ์๋ถํฐ ์๋ฐฉํฅ์ผ๋ก ์ค์ ํ๊ฒ ๋๋ค๋ฉด ๋ณ๋์ ์ค์ ํ์ ์์ด๋ ์ ๋์ํ๋ค.
๊ทธ๋ฌ๋, ์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ค์ ํ๊ธฐ ์ํ ์ฐ๊ด๊ด๊ณ ํธ์ ๋ฉ์๋๊ฐ ์กด์ฌํด์ผ ํ๋ค.
@Entity
class Board(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@Embedded
val title: BoardTitle,
@Embedded
val content: BoardContent,
@Embedded
var boardTags: BoardTags = BoardTags(Collections.emptyList())
) {
fun addTag(boardTag: BoardTag) {
boardTag.board = this // here!
boardTags.addTag(boardTag)
boardTags = BoardTags(boardTags.tags)
}
}
@Embeddable
class BoardTags(
tags: MutableList<BoardTag> = Collections.emptyList()
) {
@OneToMany(fetch = FetchType.LAZY, mappedBy = "board")
val tags: MutableList<BoardTag> = tags.toMutableList()
...
}
@Entity
class BoardTag(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
val name: String,
@ManyToOne(fetch = FetchType.LAZY)
var board: Board? = null
) {
...
}
addTag() ๋ฉ์๋์์ ์ฐ๊ด๊ด๊ณ๋ฅผ ์ธํ ํด์ฃผ๊ธฐ ์ํด boardTag.board = this๋ผ๋ ๋ฉ์๋๋ฅผ ์ถ๊ฐํด์ฃผ์๋ค.
โ๏ธ ์ ์ฅ ํ ์คํธ
@Test
@Rollback(false)
fun save() {
val ํ๊ทธ1 = BoardTag(name = "ํ๊ทธ1")
val ํ๊ทธ2 = BoardTag(name = "ํ๊ทธ2")
val board = Board(
title = BoardTitle("์ ๋ชฉ"), content = BoardContent("๋ด์ฉ")
)
board.addTag(ํ๊ทธ1)
board.addTag(ํ๊ทธ2)
boardRepository.save(board)
boardTagRepository.save(ํ๊ทธ1)
boardTagRepository.save(ํ๊ทธ2)
}
์๋ฐฉํฅ ์ฐ๊ด๊ด๊ณ๊ฐ ์ค์ ๋์๊ธฐ ๋๋ฌธ์ ํ๊ทธ๋ฅผ ๋จผ์ ์ ์ฅํ๋ ๋์ ์, ๊ฒ์๊ธ์ ๋ํด์ ๋จผ์ ์ ์ฅํด์ค๋ค.
์ด๋ board.addTag() ์ board์ ๋ํ ์ฐ๊ด๊ด๊ณ๋ ์ค์ ๋๊ธฐ ๋๋ฌธ์ ์์ฐ์ค๋ฝ๊ฒ boardTag๊ฐ ์ ์ฅ๋๋ ์์ ์ board์ ๋ํ ์ ๋ณด๋ฅผ ์๊ฒ ๋์ด insert ์ ์
๋ฐ์ดํธ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์๋๋ค.
์ค์ ๋ก ์ฟผ๋ฆฌ ์ ๋ณด๋ฅผ ๋ณด๋ฉด, ๋จผ์ ๊ฒ์๊ธ์ ๋ํ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ค.
๊ทธ๋ฆฌ๊ณ , board_tag์ ๋ํด์ insert ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ๋๋ board_id์ ๋ํ ์ ๋ณด๊ฐ ์ ์ฑ์์ ธ์ ์ฝ์ ๋๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
โ๏ธ ์ญ์ ํ ์คํธ
@Test
@Rollback(false)
fun delete_bidirection() {
// ์ ์ฅ ๋ก์ง
...
// ์กฐํ -> ์์์ฑ ์ปจํ
์คํธ์์ ๋ฐ์
val savedBoard = boardRepository.findById(1L).get()
// ๋ณด๋์ ์กด์ฌํ๋ ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
savedBoard.boardTags.tags.removeAt(0)
// ๋ณด๋ ํ๊ทธ ์ ๊ฑฐ
boardTagRepository.deleteById(1L)
}
์ด๋๋ ๋จ์ํ board_tag์ ๋ํ delete ์ฟผ๋ฆฌ ํ ๋ฒ๋ง ๋ฐ์ํ๊ฒ ๋๋ค.
tags.removeAt(0)์ ์งํํ๋๋ผ๋ ์ธ๋ํค ์ ์ฝ์กฐ๊ฑด์ผ๋ก ์ธํด์ update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ง ์๊ณ ๋ฐ๋ก ์ํฐํฐ์ ๋ํ delete๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.
๐ก ๊ฒฐ๋ก
- @OneToMany ๋จ๋ฐฉํฅ ๊ด๊ณ์์๋ ๋ถํ์ํ update ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.
- ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ nullable = false, updatable = false๋ฅผ ์ง์ ํด์ฃผ๊ณ ๋ถ๋ชจ ์ํฐํฐ๊ฐ ์์ํ๋ ๋ ์์ ์ํฐํฐ๋ ํจ๊ป ์์ํ๋ ์ ์๋๋ก PERSIST ์ต์ ์ ์ง์ ํ์.
- ๊ทธ๋ฐ ํ์ ๊ด๊ณ๊ฐ ์๋๋ผ๋ฉด ์๋ฐฉํฅ ๊ด๊ณ๋ฅผ ๊ณ ๋ คํ์.
์ผ๋๋ค ๋จ๋ฐฉํฅ์ ๋ถ๋ชจ ์ํฐํฐ์ ์์ ์ํฐํฐ์ ์๋ช ์ฃผ๊ธฐ๊ฐ ๊ฐ์ ๊ฒฝ์ฐ๋ง ์ฌ์ฉํ๋๋ก ํ์! (์์ ํ ํ์ ๊ด๊ณ์ผ ๋)
๊ทธ๊ฒ ์๋๋ผ๋ฉด ์๋ฐฉํฅ์ผ๋ก ์ค์ ํ๊ฑฐ๋, ์๋๋ฉด ๊ทธ๋ฅ ์ํฐํฐ๋ผ๋ฆฌ ์ฐ๊ด๊ด๊ณ๋ฅผ ๊ฐ์ง๋ ๊ฒ ์๋๋ผ ๋จ์ํ id ๊ฐ์ ํ๋๋ก ๊ฐ์ง๋๋ก ๋ง๋ค ๊ฒ ๊ฐ๋ค.