์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” EntityGraph์˜ ์‚ฌ์šฉ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

Entity๊ตฌ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

@Entity
class Team(
    var title: String,
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // DB์—์„œ ์ž๋™์œผ๋กœ ID increment
    @Column(name = "team_id")
    var id: Long = 0L
    
    // Onwer Entity๋ฅผ Player๋กœ ์ง€์ •, Lazy type fetch, Persistence ์ „์ด ํƒ€์ž…
    @OneToMany(mappedBy="team", cascade=[CascaseType.ALL], fetch = FetchType.LAZY) 
    var players : MutableList<Player> = mutableListOf()
}

@Entity
class Player(
    var name : String,
    
    @ManyToOne
    @JoinColumn(name = "team_id") // ์™ธ๋ž˜ํ‚ค ๋งคํ•‘์„ ์œ„ํ•œ ์„ค์ •
    var game: Game
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // DB์—์„œ ์ž๋™์œผ๋กœ ID increment
    @Column(name = "player_id")
    var id: Long = 0L
    
    var count : Long
}

Game <--> Player ์—”ํ‹ฐํ‹ฐ๊ฐ€ 1:N์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค.

ํ˜„์žฌ Team์˜ players๋Š” LAZY type์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜์–ด์žˆ๋Š”๋ฐ,

ํ•˜๋‚˜์˜ team.players ์กฐํšŒ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด 2๊ฐœ์˜ query๊ฐ€ ์ƒ์„ฑ๋œ๋‹ค.

select 
team0_.team_id as team_id1_3_0_, 
team0_.name as name2_3_0_ 
from 
team team0_ 
where 
team0_.team_id=?

select 
players0_.team_id as team_id4_1_0_, 
players0_.player_id as player_i1_1_0_, 
players0_.player_id as player_i1_1_1_, 
players0_.count as count2_1_1_, 
players0_.name as name3_1_1_, 
players0_.team_id as team_id4_1_1_ 
from 
player players0_ 
where 
players0_.team_id=?

๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ๊ธ€๋“ค์„ ๋ณด๋‹ˆ, ์—ฌ๊ธฐ์—์„œ N + 1๊ฐœ์˜ Query๋ฌธ์ด ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ํ•˜๋˜๋ฐ,

Fetch type์„ EAGER, LAZY๋กœ ๋ณ€๊ฒฝํ•˜๊ณ  ๋‹ค๋ฅธ ์˜ต์…˜๋“ค์„ ๋งŒ์ ธ๋ณด์•„๋„

Query๋ฌธ์€ ๋™์ผํ•˜๊ฒŒ 2๊ฐœ๋งŒ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

(์•„์ง JPA ORM๊ณผ ์นœ์ˆ™ํ•˜์ง€ ์•Š์•„์„œ ๋ชป๋‹ค๋ฃจ๋Š” ๊ฒƒ ์ผ์ˆ˜๋„ ์žˆ๋‹ค..)

 

ํ•˜์ง€๋งŒ ํ•˜๋‚˜์˜ request์— ๋Œ€ํ•ด 2๊ฐœ์˜ ์ฟผ๋ฆฌ๋ฌธ์ด ์ƒ๊ธฐ๋Š” ๊ฒƒ๋„ ์ด์ƒ์ ์ด์ง€๋Š” ์•Š๊ธฐ์—,

ํ•˜๋‚˜์˜ left outer join Query ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์ž.

Team Repository์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด @EntityGraph๋ฅผ ์„ค์ •ํ•œ๋‹ค.

interface TeamRepository : CrudRepository<Team, Long> {
    @EntityGraph(attributePaths = ["players"])
    override fun findById(id: Long): Optional<Team>
}

๋ณ€๊ฒฝ ํ›„์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด Query๊ฐ€ ๋‚˜๊ฐ€๊ฒŒ ๋œ๋‹ค.

select 
team0_.team_id as team_id1_3_0_, 
team0_.name as name2_3_0_, 
players1_.team_id as team_id4_1_1_, 
players1_.player_id as player_i1_1_1_, 
players1_.player_id as player_i1_1_2_, 
players1_.count as count2_1_2_, 
players1_.name as name3_1_2_, 
players1_.team_id as team_id4_1_2_ 
from 
team team0_ 
left outer join 
player players1_ 
on 
team0_.team_id=players1_.team_id 
where 
team0_.team_id=?

 

์ด๋กœ ์ธํ•ด fetch type EAGER์™€๋„ ๋น„์Šทํ•œ ๊ฒฐ๊ณผ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

 

์˜ค๋Š˜์€ ์—ฌ๊ธฐ๊นŒ์ง€..

ํ‹€๋ฆฐ ๋‚ด์šฉ์— ๋Œ€ํ•œ ์ง€์ ์€ ์–ธ์ œ๋“ ์ง€ ํ™˜์˜์ž…๋‹ˆ๋‹ค

'Coding > SpringBoot' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[SpringBoot] Kotlin - Database Lock - 1  (0) 2021.04.21

์ตœ๊ทผ์— ์„œ๋ฒ„ ๊ฐœ๋ฐœ์„ ํ•˜๋Š” ์™€์ค‘์—.. ํฐ ์‹ค์ˆ˜๋ฅผ ์ €์งˆ๋Ÿฌ ๋ฒ„๋ ธ๋‹ค.

์„œ๋ฒ„์˜ ๊ตฌ์„ฑ ์š”๊ฑด์ด ์•„๋ž˜์™€ ๊ฐ™์•˜๋‹ค.


1. ์„œ๋ฒ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค์—์„œ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ๊ฐ€์ •

2. ๋™์‹œ๋‹ค๋ฐœ์ ์ธ ์š”์ฒญ์—๋„ ๋ฌธ์ œ๊ฐ€ ์—†๋„๋ก ์„ค๊ณ„


์—ฌ๊ธฐ์—์„œ 1๋ฒˆ์€ ์–ด๋Š์ •๋„ ๊ฐ์•ˆ์„ ํ–ˆ์ง€๋งŒ 2๋ฒˆ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ์ง€ ์ƒ๊ฐ์ง€๋„ ๋ชปํ–ˆ๋‹ค

์•„๋Š” ์‚ฌ๋žŒ์—๊ฒŒ๋Š” ๋ณด์ด๊ฒ ์ง€๋งŒ.. ๋‚ด๊ณต์ด ๋ถ€์กฑํ•˜์—ฌ ๋ชป๋ณด๊ณ  ์ง€๋‚˜๊ฐ”๋˜ ํƒ“์ธ๊ฒƒ ๊ฐ™๋‹ค..

์ด๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Database์˜ ๋™์‹œ์„ฑ์„ ํ•ด๊ฒฐํ•ด์•ผ ํ•˜๋Š”๋ฐ


1. Read/Write์—์„œ Lock์„ ๊ฑธ์–ด์ฃผ๊ฑฐ๋‚˜

2. insert / update query๋ฅผ ํ•  ๋•Œ database ๋ฅผ ๋ฐ”๋กœ ์ฐธ์กฐํ•˜๋„๋ก ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.


์ฆ‰, Controller๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” Service function์—์„œ

@Transactional์ด ๊ฑธ๋ ค์žˆ๋‹ค๊ณ  ํ•ด์„œ Database์˜ ๋™์‹œ์„ฑ์„ ๋งŒ์กฑํ•  ์ˆ˜ ์—†๊ณ ,

๋ณ„๋„์˜ ์„ค์ •์„ ํ•ด ์ฃผ๊ฑฐ๋‚˜ Query๋ฅผ ๋‚ ๋ ค์•ผ ํ•˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

Entity๊ตฌ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

@Entity
class Team(
    var title: String,
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // DB์—์„œ ์ž๋™์œผ๋กœ ID increment
    @Column(name = "team_id")
    var id: Long = 0L
    
    // Onwer Entity๋ฅผ Player๋กœ ์ง€์ •, Lazy type fetch, Persistence ์ „์ด ํƒ€์ž…
    @OneToMany(mappedBy="team", cascade=[CascaseType.ALL], fetch = FetchType.LAZY) 
    var players : MutableList<Player> = mutableListOf()
}

@Entity
class Player(
    var name : String,
    
    @ManyToOne
    @JoinColumn(name = "team_id") // ์™ธ๋ž˜ํ‚ค ๋งคํ•‘์„ ์œ„ํ•œ ์„ค์ •
    var game: Game
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // DB์—์„œ ์ž๋™์œผ๋กœ ID increment
    @Column(name = "player_id")
    var id: Long = 0L
    
    var count : Long
}

Game <--> Player ์—”ํ‹ฐํ‹ฐ๊ฐ€ 1:N์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋‹ค.

Game์ด MappedBy๋ฅผ Player์˜ "team" field๋กœ ๋ช…์‹œ ํ•ด ์คŒ์œผ๋กœ์จ Player๊ฐ€ Owner๋กœ ๋“ฑ๋ก.

Player table์— ๋Œ€ํ•ด์„œ๋Š” ์•„๋ž˜ Query๋ฌธ๊ณผ ๊ฐ™์ด Foreign Key๋ฅผ ์ƒ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.

alter table player add constraint FKdvd6ljes11r44igawmpm1mc5s foreign key (team_id) references team

์—ฌ๊ธฐ์—์„œ Player ์ƒ์„ฑ ์‹œ Team์˜ total_player_number๋ฅผ ์ƒ๊ฐํ•ด๋ณด์ž.

๋‹น์—ฐํžˆ ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ๋งˆ๋‹ค 1์”ฉ ์ฆ๊ฐ€๋˜์–ด์•ผ ํ•  ๊ฒƒ์ด๋‹ค.

ํ•˜์ง€๋งŒ, ์—ฌ๋Ÿฌ Client๊ฐ€ ๋™์‹œ์— Player๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Database๊ฐ€ Lock์ด ๋˜์ง€ ์•Š์„๋•Œ๋ฅผ ๊ฐ€์ •ํ•ด๋ณด์ž.

 

2๊ฐœ์˜ Client์—์„œ ๊ฐ๊ฐ Player๋ฅผ ์ƒ์„ฑํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

๋˜ํ•œ, ๊ฐ๊ฐ์˜ Client์—์„œ Team ์ •๋ณด๋ฅผ ์ฝ์„ ๋•Œ total_player_number๊ฐ€ 0์ด์—ˆ๋‹ค.

์ด ๋•Œ, ๋™์‹œ์— request๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฉด 2๊ฐœ์˜ Player๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์ง€๋งŒ

Team์˜ total_player_number๋Š” 1์ด ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์กด์žฌํ•œ๋‹ค.

 

์ฒซ๋ฒˆ์งธ Player๋กœ ์ธํ•ด total_player_number๊ฐ€ 1๋กœ ์ฆ๊ฐ€ํ•˜์ง€๋งŒ,

๋‘๋ฒˆ ์งธ Player ์ €์žฅ ๋‹น์‹œ ์ด๋ฏธ ์ฝ์–ด์˜จ team์˜ ํ•ด๋‹น ํ•„๋“œ ๊ฐ’์ด 0์ด๊ธฐ ๋•Œ๋ฌธ์—

1 -> 2๋กœ ๋ณ€ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ 1 -> 1๋กœ ๋ฎ์–ด์”Œ์›Œ ์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


๋™์‹œ์„ฑ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์•˜์„ ๋•Œ Query ๋ฌธ

select 
team0_.team_id as team_id1_3_0_, 
team0_.name as name2_3_0_, 
team0_.total_player_number as total_pl3_3_0_, 
players1_.team_id as team_id4_1_1_, 
players1_.player_id as player_i1_1_1_, 
players1_.player_id as player_i1_1_2_, 
players1_.count as count2_1_2_, 
players1_.name as name3_1_2_, 
players1_.team_id as team_id4_1_2_ 
from 
team team0_ 
left outer join 
player players1_ 
on 
team0_.team_id=players1_.team_id 
where team0_.team_id=?

insert into player 
(player_id, count, name, team_id) 
values 
(null, ?, ?, ?)

update team 
set 
name=?, 
total_player_number=? 
where 
team_id=?

 

Team repository๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์„œ Pessimistic Lock์„ ๊ฑธ์–ด์ฃผ์—ˆ๋‹ค.

interface TeamRepository : JpaRepository<Team, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)	// ๋น„๊ด€์  Lock (Read / Write ๋ชจ๋‘)
    @EntityGraph(attributePaths = ["players"])
    override fun findById(id: Long): Optional<Team>
}

๋™์‹œ์„ฑ์„ ๊ณ ๋ คํ–ˆ์„ ๋•Œ Query ๋ฌธ

select 
team0_.team_id as team_id1_3_0_, 
team0_.name as name2_3_0_, 
team0_.total_player_number as total_pl3_3_0_, 
players1_.team_id as team_id4_1_1_, 
players1_.player_id as player_i1_1_1_, 
players1_.player_id as player_i1_1_2_, 
players1_.count as count2_1_2_, 
players1_.name as name3_1_2_, 
players1_.team_id as team_id4_1_2_ 
from 
team team0_ 
left outer join 
player players1_ 
on 
team0_.team_id=players1_.team_id 
where 
team0_.team_id=? 
for update		/* Database lock์„ ์œ„ํ•ด for update ๋ผ๋Š” ๊ตฌ๋ฌธ์ด ์ถ”๊ฐ€ ๋จ */

insert into player 
(player_id, count, name, team_id) 
values 
(null, ?, ?, ?)

update team 
set 
name=?, 
total_player_number=? 
where 
team_id=?

๋‹ค๋ฅธ๊ฑด ๋™์ผํ•˜์ง€๋งŒ, select ๋ฌธ์—์„œ ๋’ค์— ~ for update ๊ฐ€ ๋ถ™์–ด์žˆ๋‹ค.

์ด๋Š” ๋™์‹œ์„ฑ ์ œ์–ด๋ฅผ ์œ„ํ•ด ํŠน์ • row์— READ / WRITE Lock์ด ๊ฑธ๋ ค์žˆ๋‹ค๋Š” ๋ง์ด๋‹ค (๋ฐฐํƒ€์  Lock).

๋งŒ์•ฝ, Read๋Š” ์ž์œ ๋กญ๊ฒŒ ํ•˜๋˜, Write์—์„œ๋งŒ Lock์„ ๊ฑธ๊ณ ์‹ถ๋‹ค๋ฉด PESSIMISTIC_READ ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.


๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ ์ง์ ‘ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค.

 

'Coding > SpringBoot' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[SpringBoot] Kotlin - Entity Graph  (1) 2021.04.22

+ Recent posts