๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ’ป ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ/JPA - Hibernate

03. Soft Delete ๋” ํŽธํ•˜๊ฒŒ ํ•˜๊ธฐ

by YangsDev 2021. 7. 15.

๋“ค์–ด๊ฐ€๋ฉฐ

DB๋ฅผ ๋งŒ๋“ค๋ฉด์„œ ์šฐ๋ฆฌ๋Š” ์‚ญ์ œ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

๋ฐฉ์‹ ์„ค๋ช…
Soft Delete   `UPDATE table SET delete = 1 WHERE id = 2` ํ˜•ํƒœ๋กœ
ROW๊ฐ€ ์‚ญ์ œ ๋˜์ง€ ์•Š๊ณ  flag๋ฅผ ํ†ตํ•œ ์ œ์–ด ํ•˜๋Š” ๋ฐฉ์‹
Hard Delete DELETE FROM table WHERE id = 2
ํ˜•ํƒœ๋กœ ROW๊ฐ€ ์‹ค์ œ๋กœ ์‚ญ์ œ ๋˜๋Š” ๋ฐฉ์‹

Soft Delete๋กœ ๋ฒ„๊ทธ๊ฐ€ ์ƒ๊ธด๋‹ค๋ฉด, ๋Œ€๋žต ์ •์‹ ์ด ๋ฉํ•ด์ง„๋‹ค.

`Hard Delete`๋ฅผ ํ•˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์ง€๋งŒ, `Soft Delete`๋ฅผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋” ๋งŽ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ์— JPA Entity Graph ์˜ ๊ฒฝ์šฐ๋Š” `Soft Delete`์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์ด ๋‹ด๊ฒจ์„œ ์˜จ๋‹ค. 

`Application level์—์„œ ํ•„ํ„ฐ ํ•˜๋ฉด ๋˜๋Š”๊ฑฐ ์•„๋‹ˆ๋ƒ? ` ๋ผ๊ณ  ์ƒ๊ฐ ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ธ๊ฐ„์ด๊ธฐ ๋•Œ๋ฌธ์— ๋กœ์ง์— ๊ตฌ๋ฉ์ด ๋งŽ์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.

์˜ค๋Š˜์€ JPA์—์„œ Soft Delete๋ฅผ ๋”์šฑ ํŽธํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐ ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

 

 

@Where Annotation

https://docs.jboss.org/hibernate/orm/5.4/javadocs/org/hibernate/annotations/Where.html

 

Where (Hibernate JavaDocs)

 

docs.jboss.org

" Where clause to add to the element Entity or target entity of a collection. The clause is written in SQL. A common use case here is for soft-deletes." 

 

@Where ์–ด๋…ธํ…Œ์ด์…˜์€ Entity์˜ Default ์กฐ๊ฑด์„ ์ง€์ • ํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ์ด๋‹ค.

์žฅ์ ์€ 'Soft Delete'๋กœ ์ธํ•˜์—ฌ, ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„์—์„œ ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ œ๋ฅผ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

๋˜ํ•œ,@OneToMany, @ManyToOne ๋“ฑ์˜ ์กฐ์ธ์ด ๊ฑธ๋ฆฐ ๊ฒฝ์šฐ์—๋„ ๋”ฐ๋กœ ์ฟผ๋ฆฌ ์กฐ๊ฑด์„ ์ง€์ • ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํŽธํ•ด์ง„๋‹ค.  

 

๋‹จ์ ์€, ๋ฌด์กฐ๊ฑด ์กฐ๊ฑด์ด ์ ์šฉ ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์‚ญ์ œ๋œ ๊ฑธ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋Š” ์ƒํ™ฉ์—๋Š” ๋˜ ์• ๋งคํ•ด์ง„๋‹ค. 

(์ด ๊ฒฝ์šฐ๋Š” @FilterDef์„ ํ™œ์šฉํ•˜๋ฉด ๋œ๋‹ค๊ณ  ํ•˜๋Š”๋ฐ, ์กฐ๋งŒ๊ฐ„ ๋”ฐ๋กœ ๋‹ค๋ค„๋ณผ๋ ค๊ณ  ํ•œ๋‹ค) 

(๊ทธ๋ฆฌ๊ณ  ๊ฐœ์ธ์ ์œผ๋ก  ์‚ญ์ œ์— ๋Œ€ํ•œ๊ฑธ ๋‹ค์‹œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ๋„ ๋ชจ์ˆœ ์•„๋‹๊นŒ.. ์‹ถ์€๋ฐ..) 

 

 

์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜์™€ ๊ฐ™์€ Entity๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฐ€์ •์„ ํ•ด๋ณด์ž.

@Entity
@Where("del=0")
class Product(
    var name: String,
    val del: Boolean = false
) {

    @Id
    @GeneratedValue
    var id: Long? = null
        protected set
    
    @ManyToOne
    val image: Image? = null
    
    fun expire() {
    	this.del = true
    }
}

์ด ๋ฐ์ดํ„ฐ๊ฐ€ id=1 ์ด๊ณ , del=true ์ƒํ™ฉ์ด๋ผ๋ฉด, productRepository.findById(1) ๋กœ ์กฐํšŒ์‹œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค๊ณ  ๋ฐ˜ํ™˜ ๋˜๊ฒŒ ๋œ๋‹ค.

 

@SQLDelete Annotation

์กฐํšŒ๋ฅผ ํ• ๋•Œ๋Š” ๊ธฐ๋ณธ์ ์ธ ์กฐ๊ฑด์ด ๋ถ™์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฌด๋ฆฌ ์—†์ด ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ ์ธ๊ฐ„์˜ ์š•์‹ฌ์€ ๋์ด ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์‚ญ์ œ๊ฐ€ ๋ ๋•Œ๋„ flag๋ฅผ ์ง์ ‘ ๋ณ€๊ฒฝ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ORM Level์—์„œ ์ง€์›์„ ํ•ด์คฌ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค ๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค.  ๊ทธ๊ฒƒ์„ ํ•ด๊ฒฐ ํ•˜๊ธฐ ์œ„ํ•ด ๋‚˜์˜จ๊ฒƒ์ด @SQLDelete ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

 

"Custom SQL statement for delete of an entity/collection."

 

SQLDelete (Hibernate JavaDocs)

check For persistence operation what style of determining results (success/failure) is to be used.

docs.jboss.org

 

์•„๋ž˜์˜ ์˜ˆ์‹œ๋ฅผ ๋ณด์ž 

@Entity
@Where("del=0")
@SQLDelete("UPDATE product SET del=1 WHERE id = ?")
class Product(
    var name: String,
    val del: Boolean = false
) {

    @Id
    @GeneratedValue
    var id: Long? = null
        protected set
    
    @ManyToOne
    val image: Image? = null
}

 

@SQLDelete ์–ด๋…ธํ…Œ์ด์…˜์„ ํ†ตํ•ด, ์‹ค์ œ ์‚ญ์ œ๊ฐ€ ์ด๋ฃจ์–ด์งˆ๋•Œ ์ˆ˜ํ–‰ ํ•  ์ฟผ๋ฆฌ๋ฅผ ์ ์œผ๋ฉด ๋œ๋‹ค.

๊ทธ๋Ÿผ productRepository.deleteById(1L) ํ˜•ํƒœ๋กœ ์ง„ํ–‰์ด ๋˜๋ฉด 'DELETE FROM product WHERE id = 1' ํ˜•ํƒœ๋กœ ์ฟผ๋ฆฌ๊ฐ€ ๋™์ž‘ ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ  'Update product SET del 1 WHERE id = 1' ํ˜•ํƒœ๋กœ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒ๋˜์–ด ๋™์ž‘ํ•˜๊ฒŒ ๋œ๋‹ค.

๋Œ“๊ธ€