[๋ฒ์ญ] How to implement a soft delete with Hibernate(์ด๋ป๊ฒ Hibernate๋ฅผ ์ฌ์ฉํด ์ํํธ ์ญ์ ๋ฅผ ๊ตฌํํ๋๊ฐ)
๐ก ์๋ณธ๊ธ : https://thorben-janssen.com/implement-soft-delete-hibernate/
์ผ๋ถ ์์ฉ ํ๋ก๊ทธ๋จ์์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ ์ฝ๋๋ฅผ ์๊ตฌ์ ์ผ๋ก ์ญ์ ํ๊ณ ์ถ์ง ์๊ฑฐ๋ ์ญ์ ๊ฐ ํ์ฉ๋์ง ์์ ์ ์์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ ์ด์ ํ์ฑ ์ํ๊ฐ ์๋ ๋ ์ฝ๋๋ฅผ ์ญ์ ํ๊ฑฐ๋ ์จ๊ฒจ์ผ ํ ํ์๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ฌ์ ํ ์ฌ์ฉ ์ค์ธ ๋ค๋ฅธ ๋น์ฆ๋์ค ๊ฐ์ฒด์ ์ฐ๊ฒฐ๋ ์ฌ์ฉ์ ๊ณ์ ์ ์ ์งํ๋ ค๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
์์คํ ์์ ์ด ์ ๋ณด๋ฅผ ์ ์งํ๊ธฐ ์ํด ๋ ๊ฐ์ง ๊ธฐ๋ณธ ์ต์ ์ด ์์ต๋๋ค. ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ์ ๋ฌธ์ํํ๋ ๊ฐ์ฌ ๋ก๊ทธ(audit log)๋ฅผ ์ ์งํ๊ฑฐ๋ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ์จ๊ธฐ๋ ์ํํธ ์ญ์ ๋ฅผ ์ํํ ์ ์์ต๋๋ค. ๊ฐ์ฌ ๋ก๊ทธ(audit log) ์ต์ ์ ๋ํ ์ค๋ช ์ Hibernate Envers์ ๊ดํ ๋ด ๊ธฐ์ฌ์์ ๋ค๋ฃจ์์ต๋๋ค. ์ค๋์ Hibernate๋ฅผ ์ฌ์ฉํ์ฌ ์ํํธ ์ญ์ ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ๋๋ฆฌ๋ ค๊ณ ํฉ๋๋ค. ๊ทธ๋ฌ๊ธฐ ์ ์ ์ํํธ ์ญ์ ๊ฐ ๋ฌด์์ธ์ง ๊ฐ๋จํ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
์ํํธ ์ญ์ ๋ ๋ฌด์์ธ๊ฐ์?
์ํํธ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์์ ์ญ์ ํ๋ ๋์ ๋ ์ฝ๋๋ฅผ ์ญ์ ๋ก ํ์ํ๊ธฐ ์ํ ์ ๋ฐ์ดํธ๋ฅผ ์ํํฉ๋๋ค. ์ํํธ ์ญ์ ๋ฅผ ๋ชจ๋ธ๋งํ๋ ์ผ๋ฐ์ ์ธ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- ๋ ์ฝ๋๊ฐ ํ์ฑ ๋๋ ์ญ์ ๋ ์ํ๋ฅผ ๋ํ๋ด๋ boolean,
- ๋ ์ฝ๋ ์ํ๋ฅผ ๋ชจ๋ธ๋งํ๋ enum,
- ์ํํธ ์ญ์ ๊ฐ ์ํ๋ ๋ ์ง์ ์๊ฐ์ ์ ์ฅํ๋ timestamp.
์ด๋ฌํ ํ๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๋น์ฐํ ์ํํธ ์ญ์ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํ ์ฒซ ๋ฒ์งธ ๋จ๊ณ์ผ ๋ฟ์ ๋๋ค. ์๋ก์ด ๋ ์ฝ๋๋ฅผ ์ ์ฅํ ๋ ์ด๋ฅผ ์ค์ ํด์ผ ํ๋ฉฐ, ๋ ์ฝ๋๋ฅผ ์ญ์ ํ๋ ๋์ ์ํํธ ์ญ์ ํ์๋ฅผ ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค. ๋ํ ์ฌ์ฉ์์๊ฒ ์ํํธ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ์จ๊ธฐ๋ ค๋ฉด ์ํํธ ์ญ์ ํ์๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ ์ฝ๋๋ฅผ ์ ์ธํ๋ ๋ชจ๋ ์ฟผ๋ฆฌ๋ฅผ ์กฐ์ ํด์ผ ํฉ๋๋ค.
์ด๊ฒ์ ๋ง์ ์์ ์ฒ๋ผ ๋ค๋ฆด ์ ์์ง๋ง, Hibernate๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ ๊ทธ๋ ์ง ์์ต๋๋ค.
์ด๋ป๊ฒ Hibernate๋ฅผ ์ฌ์ฉํด ์ํํธ ์ญ์ ๋ฅผ ๊ตฌํํ๋๊ฐ.
Hibernate 6.4 ๋ฒ์ ์์ Hibernate ํ์ Hibernate ORM์ ์ํ ๊ณต์ ์ํํธ ์ญ์ ๊ธฐ๋ฅ์ ์๊ฐํ์ต๋๋ค. ์ด์ ์ํฐํฐ ํด๋์ค์์ ์ํํธ ์ญ์ ๋ฅผ ํ์ฑํํ๋ ค๋ฉด 1๊ฐ์ ์ด๋ ธํ ์ด์ ๋ง ํ์ํฉ๋๋ค. Hibernate์ ๊ทธ๋ฐ ๋ค์ ๋ ์ฝ๋๋ฅผ ์ํํธ ์ญ์ ํ๊ธฐ ์ํ ํ์ํ SQL UPDATE ๋ฌธ์ ์์ฑํ๊ณ ์ํํธ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ์ ์ธํ ๋ชจ๋ ์ฟผ๋ฆฌ ๋ฌธ์ ์กฐ์ ํฉ๋๋ค. ์ด ๊ธฐ์ฌ์ ๋ค์ ์น์ ์์ ์ํํธ ์ญ์ ๋ฅผ ํ์ฑํํ๊ณ ๋ค์ํ ๊ตฌ์ฑ ์ต์ ์ ๋ณด์ฌ ๋๋ฆฌ๊ฒ ์ต๋๋ค.
Hibernate ๋ฒ์ ์ด 6.3 ์ดํ์ธ ๊ฒฝ์ฐ, ์ํํธ ์ญ์ ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํด์ผ ํฉ๋๋ค. ์ด ์์ ์๋ ์ฝ๊ฐ์ ์ถ๊ฐ ์์ ์ด ํ์ํ์ง๋ง ๋ณต์กํ์ง ์์ผ๋ฉฐ ์ํฐํฐ ๋งคํ์์ ํ์ํ ๋ชจ๋ ๋ถ๋ถ์ ์ ์ธํ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋น์ฆ๋์ค ์ฝ๋์์ ์ฒ๋ฆฌํ ํ์๊ฐ ์์ต๋๋ค. ์ด ๊ธฐ์ฌ์ ๋์์ ๋งคํ์ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ๋ณด์ฌ ๋๋ฆฌ๊ฒ ์ต๋๋ค.
6.4 ์ด์ Hibernate ์ํํธ ์ญ์
Hibernate 6.4์์ ์ํํธ ์ญ์ ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค.์ํฐํฐ ํด๋์ค์ @SoftDelete ์ด๋ ธํ ์ด์ ์ ๋ถ์ด๋ฉด Hibernate๊ฐ ๋๋จธ์ง ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํฉ๋๋ค.
Hibernate์ ๊ธฐ๋ณธ ์ํํธ ์ญ์ ๊ตฌํ
๋ค์ ์ฝ๋ ์กฐ๊ฐ์ Hibernate์ ๊ธฐ๋ณธ ์ํํธ ์ญ์ ๊ตฌํ์ ์ฌ์ฉํฉ๋๋ค. ์ด ๊ตฌํ์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ํ ์ด๋ธ์์ boolean ์ ํ์ deleted ์ด์ ๊ธฐ๋ํฉ๋๋ค. @SoftDelete ์ด๋ ธํ ์ด์ ์ columnName ์์ฑ์ ์ฌ์ฉํ์ฌ ํด๋น ์ด์ ๋ค๋ฅธ ์ด๋ฆ์ ์ง์ ํ ์ ์์ต๋๋ค.
@Entity
@SoftDelete
public class Account { ... }
Hibernate๋ ์๋ก์ด Account ์ํฐํฐ๋ฅผ ์์ํํ ๋ ์๋์ผ๋ก deleted ํ๋๋ฅผ false๋ก ์ค์ ํฉ๋๋ค.
Account a = new Account();
a.setName("thjanssen");
em.persist(a);
10:30:49,099 DEBUG SQL:135 - insert into Account (name,deleted,id) values (?,false,?)
์ํฐํฐ๋ฅผ ์ญ์ ํ๋ฉด Hibernate๋ deleted ํ๋๋ฅผ true๋ก ์ค์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ Hibernate๋ ๋ชจ๋ JPQL ๋ฐ Criteria ์ฟผ๋ฆฌ ๋ฐ ์์ฑ๋ ๋ชจ๋ ๋ฌธ์ ๋ํด deleted=true์ธ ๋ชจ๋ ๋ ์ฝ๋๋ฅผ ์ ์ธํ๋ ์กฐ๊ฑด์ ์ถ๊ฐํฉ๋๋ค.
Account a = em.find(Account.class, a.getId());
em.remove(a);
TypedQuery<Account> q = em.createNamedQuery("Account.FindByName", Account.class);
q.setParameter("name", "%ans%");
a = (Account) q.getSingleResult();
10:30:49,199 DEBUG SQL:135 - select a1_0.id,a1_0.name from Account a1_0 where a1_0.deleted=false and a1_0.id=?
10:30:49,211 DEBUG SQL:135 - update Account set deleted=true where id=? and deleted=false
10:30:49,234 DEBUG SQL:135 - select a1_0.id,a1_0.name from Account a1_0 where a1_0.name like ? escape '' and a1_0.active=true
๋ค๋ฅธ SoftDeleteType ์ฌ์ฉํ๊ธฐ
Hibernate๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ boolean์ ์๋ฏธ๋ฅผ ๊ฒฐ์ ํ๋ ๋ ๊ฐ์ง ๋ค๋ฅธ SoftDeleteType์ ์ง์ํฉ๋๋ค.
- SoftDeleteType.ACTIVE: ๊ฐ์ด true๋ ๋ ์ฝ๋๋ฅผ ํ์ฑ์ผ๋ก ํ์ํ๋ฉฐ Hibernate์ ๊ธฐ๋ณธ์ ์ผ๋ก active๋ฅผ ์ด ์ด๋ฆ์ผ๋ก ์ฌ์ฉํฉ๋๋ค.
- SoftDeleteType.DELETED: ๊ฐ์ด true๋ ๋ ์ฝ๋๋ฅผ ์ญ์ ๋ก ํ์ํ๋ฉฐ Hibernate์ ๊ธฐ๋ณธ์ ์ผ๋ก deleted๋ฅผ ์ด ์ด๋ฆ์ผ๋ก ์ฌ์ฉํฉ๋๋ค. ์ด๊ฒ ๊ธฐ๋ณธ๊ฐ์ ๋๋ค.
์ฌ๊ธฐ์์ SoftDeleteType์ ACTIVE๋ก ์ค์ ํ๋ ๊ฐ๋จํ ๋งคํ์ ๋ณผ ์ ์์ต๋๋ค.
@Entity
@SoftDelete(strategy = SoftDeleteType.ACTIVE)
public class Account { ... }
์ด์ ์ด์ ๊ณผ ๋์ผํ ํ ์คํธ๋ฅผ ์คํํ๋ฉด Hibernate๊ฐ ์ํฐํฐ์ ํ์ฌ ์ํ๋ฅผ active ์ด์ ์ ์ฅํฉ๋๋ค. ์ํฐํฐ๋ฅผ ์ญ์ ํ๋ฉด Hibernate๊ฐ ํด๋น ํ๋๋ฅผ false๋ก ์ค์ ํ๊ณ ๋ชจ๋ ์ฟผ๋ฆฌ๋ active=true์ธ ๋ ์ฝ๋๋ง ๋ฐํํฉ๋๋ค.
// persist a new Account
Account a = new Account();
a.setName("thjanssen");
em.persist(a);
// find and remove an Account
a = em.find(Account.class, a.getId());
em.remove(a);
// query an Account
TypedQuery<Account> q = em.createNamedQuery("Account.FindByName", Account.class);
q.setParameter("name", "%ans%");
a = (Account) q.getSingleResult();
// persist a new Account
10:46:26,099 DEBUG SQL:135 - insert into Account (name,active,id) values (?,true,?)
// find and remove an Account
10:46:26,199 DEBUG SQL:135 - select a1_0.id,a1_0.name from Account a1_0 where a1_0.active=true and a1_0.id=?
10:46:26,211 DEBUG SQL:135 - update Account set active=false where id=? and active=true
// query an Account
10:46:26,234 DEBUG SQL:135 - select a1_0.id,a1_0.name from Account a1_0 where a1_0.name like ? escape '' and a1_0.active=true
๋ค๋ฅธ ์ปฌ๋ผ ํ์ ์ ์ฌ์ฉํ๊ธฐ
Hibernate์ ๊ธฐ๋ณธ ์ํํธ ์ญ์ ๊ตฌํ์ ๊ฐ ๋ ์ฝ๋์ ํ์ฌ ์ํ๋ฅผ ์ ์ฅํ๋ ๋ฐ boolean ์ ํ์ ์ด์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉ๋๋ Boolean์ ์ํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ํ์ ๋งคํํ๋ AttributeConverter๋ฅผ ์ ๊ณตํ์ฌ ์ด๋ฅผ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.
์ด๊ฒ์ ๋ ๊ฑฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์ ํ๋ ๊ฒฝ์ฐ ์ ์ฉํฉ๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ ๊ฐ ๋ ์ฝ๋์ ํ์ฌ ์ํ๋ฅผ ๋ํ๋ด๋ String ๋๋ ์ด๊ฑฐํ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
์ฌ๊ธฐ์์ ์ํฐํฐ ๋งคํ์ ๋ณผ ์ ์์ผ๋ฉฐ Hibernate์๊ฒ ํ์ฌ ๋ ์ฝ๋์ ์ํ๋ฅผ state ์ด์ ์ ์ฅํ๋๋ก ์ง์ํ๊ณ StateConverter AttributeConverter๋ฅผ ์ฌ์ฉํ์ฌ ์์ฑ์ ๋งคํํฉ๋๋ค.
@Entity
@SoftDelete(strategy = SoftDeleteType.ACTIVE, columnName = "state", converter = StateConverter.class)
public class Account { ... }
StateConverter ๊ตฌํ์ ๊ฐ๋จํฉ๋๋ค. AttributeConverter ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ฉฐ convertToDatabaseColumn ๋ฐ convertToEntityAttribute ๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค.
Hibernate์ ์ํํธ ์ญ์ ๊ตฌํ์ ๋ ์ฝ๋๊ฐ ํ์ฑ ์ํ์ธ์ง ์ญ์ ์ํ์ธ์ง๋ฅผ ๋ํ๋ด๋ Boolean์ ์์กดํฉ๋๋ค. ๋ฐ๋ผ์ AttributeConverter์ ์ฒซ ๋ฒ์งธ ์ ํ ๋งค๊ฐ๋ณ์๋ Boolean์ด์ด์ผ ํฉ๋๋ค. ๋ ๋ฒ์งธ ์ ํ ๋งค๊ฐ๋ณ์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ๋ ค๋ ์ ํ์ ์ง์ ํฉ๋๋ค. ์ด ์์ ์์๋ Boolean์ String๊ฐ์ธ "active" ๋๋ "inactive"์ ๋งคํํ๋ ค๊ณ ํฉ๋๋ค.
public class StateConverter implements AttributeConverter<Boolean, String>{
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return attribute ? "active" : "inactive";
}
@Override
public Boolean convertToEntityAttribute(String dbData) {
return dbData.equals("active");
}
}
When I now execute the same test as before, Hibernate stores the values “active” or “inactive” in the state column. When I remove the entity, Hibernate sets that field to “inactive”, and all queries only return records where state=active.
// persist a new Account
Account a = new Account();
a.setName("thjanssen");
em.persist(a);
// find and remove an Account
a = em.find(Account.class, a.getId());
em.remove(a);
// query an Account
TypedQuery<Account> q = em.createNamedQuery("Account.FindByName", Account.class);
q.setParameter("name", "%ans%");
a = (Account) q.getSingleResult();
// persist a new Account
10:46:26,099 DEBUG SQL:135 - insert into Account (name,state,id) values (?,'active',?)
// find and remove an Account
11:18:39,857 DEBUG SQL:135 - select a1_0.id,a1_0.name from Account a1_0 where a1_0.state='active' and a1_0.id=?
11:18:39,867 DEBUG SQL:135 - update Account set state='inactive' where id=? and state='active'
// query an Account
11:18:39,889 DEBUG SQL:135 - select a1_0.id,a1_0.name from Account a1_0 where a1_0.name like ? escape '' and a1_0.state='active'
6.4 ๋ฏธ๋ง Hibernate ์ํํธ ์ญ์ ๊ตฌํ
Hibernate 6.4 ๋ฏธ๋ง์์ ์ํํธ ์ญ์ ๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๊ทธ๋ฆฌ ์ด๋ ต์ง ์์ง๋ง ์ฝ๊ฐ์ ์ถ๊ฐ ์์ ์ด ํ์ํฉ๋๋ค. ๋ค์์ ์ํํด์ผ ํฉ๋๋ค.
- ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ์ ๊ฑฐํ ๋ ์ญ์ ์์ ๋์ SQL UPDATE๋ฅผ ์ํํ๋๋ก Hibernate์ ์ง์ํ๊ณ
- ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์์ ๋ชจ๋ ์ํํธ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ์ ์ธํฉ๋๋ค.
๋ค์ ์์ ์์ ์ด๋ฅผ ์ฝ๊ฒ ์ํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ ๋๋ฆฌ๊ฒ ์ต๋๋ค. ์ด ์์ ๋ ๋ชจ๋ Account ์ํฐํฐ๋ฅผ ์ฌ์ฉํ๋ฉฐ AccountState ์ํ ์์ฑ์ ์ฌ์ฉํ์ฌ ๊ณ์ ์ด INACTIVE, ACTIVE, ๋๋ DELETED ์ธ์ง๋ฅผ ๋ํ๋ ๋๋ค.
@Entity
@NamedQuery(name = "Account.FindByName", query = "SELECT a FROM Account a WHERE name like :name")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Column
private String name;
@Column
@Enumerated(EnumType.STRING)
private AccountState state;
…
}
๋ ์ฝ๋๋ฅผ ์ญ์ ํ๋ ๋์ ๋ ์ฝ๋๋ฅผ ์ ๋ฐ์ดํธํ๊ธฐ.
์ํํธ ์ญ์ ๋ฅผ ๊ตฌํํ๋ ค๋ฉด Hibernate์ ๊ธฐ๋ณธ์ ์ธ ์ ๊ฑฐ ์์ ์ ์ฌ์ ์ํด์ผ ํฉ๋๋ค. ์ด๋ฅผ @SQLDelete ์ด๋ ธํ ์ด์ ์ผ๋ก ์ํํ ์ ์์ต๋๋ค. ์ด ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ์ํฐํฐ๋ฅผ ์ญ์ ํ ๋ Hibernate๊ฐ ์คํํ ์ฌ์ฉ์ ์ ์ ๋ค์ดํฐ๋ธ SQL ์ฟผ๋ฆฌ๋ฅผ ์ ์ํ ์ ์์ต๋๋ค. ๋ค์ ์ฝ๋ ์กฐ๊ฐ์์ ์์ ๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
@Entity
@SQLDelete(sql = "UPDATE account SET state = ‘DELETED’ WHERE id = ?", check = ResultCheckStyle.COUNT)
public class Account { … }
์ด์ ์ฝ๋ ์กฐ๊ฐ์ @SQLDelete ์ด๋ ธํ ์ด์ ์ Hibernate์๊ฒ ๊ธฐ๋ณธ SQL DELETE ๋ฌธ ๋์ ์ง์ ๋ SQL UPDATE ๋ฌธ์ ์คํํ๋๋ก ์ง์ํฉ๋๋ค. ์ด๊ฒ์ ๊ณ์ ์ ์ํ๋ฅผ DELETED๋ก ๋ณ๊ฒฝํ๋ฉฐ ์ญ์ ๋ ๊ณ์ ์ ์ ์ธํ๋ ค๋ฉด ๋ชจ๋ ์ฟผ๋ฆฌ์์ state ์์ฑ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Account a = em.find(Account.class, a.getId());
em.remove(a);
16:07:59,511 DEBUG SQL:92 – select account0_.id as id1_0_0_, account0_.name as name2_0_0_, account0_.state as state3_0_0_ from Account account0_ where account0_.id=? and ( account0_.state <> 'DELETED')
16:07:59,534 DEBUG SQL:92 – UPDATE account SET state = 'DELETED' WHERE id = ?
๊ธฐ๋ณธ์ ์ธ ์ํํธ ์ญ์ ๊ตฌํ์ ์์ฑํ๋ ๋ฐ ํ์ํ ๊ฒ์ ์ด๊ฒ๋ฟ์ ๋๋ค. ๊ทธ๋ฌ๋ ์ฒ๋ฆฌํด์ผ ํ ๋ค๋ฅธ ๋ ๊ฐ์ง ์ฌํญ์ด ์์ต๋๋ค.
- Account ์ํฐํฐ๋ฅผ ์ญ์ ํ ๋ Hibernate๋ ํ์ฌ ์ธ์ ์์ state ์์ฑ์ ๊ฐ์ ์ ๋ฐ์ดํธํ์ง ์์ต๋๋ค.
- ์ญ์ ๋ ์ํฐํฐ๋ฅผ ์ ์ธํ๊ธฐ ์ํด ๋ชจ๋ ์ฟผ๋ฆฌ๋ฅผ ์กฐ์ ํด์ผ ํฉ๋๋ค.
ํ์ฌ ์ธ์ ์์ state ์์ฑ์ ์ ๋ฐ์ดํธํ๊ธฐ.
Hibernate๋ @SQLDelete ์ด๋ ธํ ์ด์ ์ ์ ๊ณตํ๋ ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ฅผ ๊ตฌ๋ฌธ ๋ถ์ํ์ง ์์ต๋๋ค. ๊ทธ์ ๋งค๊ฐ๋ณ์ ๊ฐ์ ์ค์ ํ๊ณ ์คํํฉ๋๋ค. ๋ฐ๋ผ์ @SQLDelete ์ด๋ ธํ ์ด์ ์ DELETE ๋ฌธ ๋์ SQL UPDATE ๋ฌธ์ ์ ๊ณตํ๋ค๋ ์ฌ์ค์ ์ธ์ํ์ง ์์ต๋๋ค. ๋ํ ์ญ์ ์์ ์ ์ํํ ํ state ์์ฑ์ ๊ฐ์ด ์์ ๊ฐ์ด๋ผ๋ ๊ฒ์ ์์ง ๋ชปํฉ๋๋ค.
๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ์ด๋ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค. Hibernate๊ฐ SQL ๋ฌธ์ ์คํํ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ์ฝ๋๊ฐ ์ ๋ฐ์ดํธ๋๊ณ ๋ชจ๋ ์ฟผ๋ฆฌ๊ฐ ์๋ก์ด state ๊ฐ์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฌ๋ EntityManager.remove(Object entity) ์์ ์ ์ ๊ณตํ Account ์ํฐํฐ๋ ์ด๋ป๊ฒ ๋ ๊น์?
๊ทธ ์ํฐํฐ์ state ์์ฑ์ ์์ ์ํ์ ๋๋ค. ์ญ์ ํ ํ ์ฆ์ ์ฐธ์กฐ๋ฅผ ํด์ ํ๋ ๊ฒฝ์ฐ ํฐ ๋ฌธ์ ๊ฐ ๋์ง ์์ต๋๋ค. ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ ์์ฑ์ ์ง์ ์ ๋ฐ์ดํธํด์ผ ํฉ๋๋ค.
๊ฐ์ฅ ์ฌ์ด ๋ฐฉ๋ฒ์ ๋ค์ ์ฝ๋ ์กฐ๊ฐ์์์ฒ๋ผ ๋ผ์ดํ์ฌ์ดํด ์ฝ๋ฐฑ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. @PreRemove ์ด๋ ธํ ์ด์ ์ deleteUser ๋ฉ์๋์ ์์ผ๋ฉฐ Hibernate์๊ฒ ์ ๊ฑฐ ์์ ์ ์ํํ๊ธฐ ์ ์์ด ๋ฉ์๋๋ฅผ ํธ์ถํ๋๋ก ์ง์ํฉ๋๋ค. ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ state ์์ฑ์ ๊ฐ์ DELETED๋ก ์ค์ ํฉ๋๋ค.
@Entity
@SQLDelete(sql = "UPDATE account SET state = 'DELETED' WHERE id = ?", check = ResultCheckStyle.COUNT)
public class Account {
...
@PreRemove
public void deleteUser() {
this.state = AccountState.DELETED;
}
}
์ฟผ๋ฆฌ์์ ์ํํธ ์ญ์ ๋ ์ํฐํฐ ์ ์ธ
์ญ์ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ์ฝ๋๋ฅผ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์์ ์ ์ธํ๋ ค๋ฉด ๋ชจ๋ ์ฟผ๋ฆฌ์์ state ์์ฑ์ ํ์ธํด์ผ ํฉ๋๋ค. ์ด ์์ ์ ์๋์ผ๋ก ์ํํ ๊ฒฝ์ฐ ์ค๋ฅ ๋ฐ์ ๊ฐ๋ฅ์ฑ์ด ์์ผ๋ฉฐ ๋ชจ๋ ์ฟผ๋ฆฌ๋ฅผ ์ง์ ์ ์ํด์ผ ํฉ๋๋ค. EntityManager.find(Class entityClass, Object primaryKey) ๋ฉ์๋์ Hibernate์ Session์ ํด๋นํ๋ ๋ฉ์๋๋ state ์์ฑ์ ์๋ฏธ๋ฅผ ์์ง ๋ชปํ๋ฉฐ ์ด๋ฅผ ๊ณ ๋ คํ์ง ์์ต๋๋ค.
Hibernate์ @Where ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ์ญ์ ๋ ์ํฐํฐ๋ฅผ ์ ์ธํ๋ ๋ ๋์ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. ์ด ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ฉด Hibernate์ด ๋ชจ๋ ์ฟผ๋ฆฌ์ WHERE ์ ์ ์ถ๊ฐํ SQL ์ค๋ํซ์ ์ ์ํ ์ ์์ต๋๋ค. ๋ค์ ์ฝ๋ ์กฐ๊ฐ์ state๊ฐ DELETED์ธ ๊ฒฝ์ฐ ๋ ์ฝ๋๋ฅผ ์ ์ธํ๋ @Where ์ด๋ ธํ ์ด์ ์ ๋ณด์ฌ์ค๋๋ค.
@Entity
@SQLDelete(sql = "UPDATE account SET state = 'DELETED' WHERE id = ?", check = ResultCheckStyle.COUNT)
@Where(clause = "state <> 'DELETED'")
@NamedQuery(name = "Account.FindByName", query = "SELECT a FROM Account a WHERE name like :name")
public class Account { ... }
๋ค์ ์ฝ๋ ์กฐ๊ฐ์์ ๋ณผ ์ ์๋ฏ์ด Hibernate์ JPQL ์ฟผ๋ฆฌ๋ฅผ ์ํํ๊ฑฐ๋ EntityManager.find(Class entityClass, Object primaryKey) ๋ฉ์๋๋ฅผ ํธ์ถํ ๋ ์ ์๋ WHERE ์ ์ ์ถ๊ฐํฉ๋๋ค.
TypedQuery<Account> q = em.createNamedQuery("Account.FindByName", Account.class);
q.setParameter("name", "%ans%");
Account a = q.getSingleResult();
16:07:59,511 DEBUG SQL:92 – select account0_.id as id1_0_, account0_.name as name2_0_, account0_.state as state3_0_ from Account account0_ where ( account0_.state <> 'DELETED') and (account0_.name like ?)
Account a = em.find(Account.class, a.getId());
16:07:59,540 DEBUG SQL:92 – select account0_.id as id1_0_0_, account0_.name as name2_0_0_, account0_.state as state3_0_0_ from Account account0_ where account0_.id=? and ( account0_.state <> 'DELETED')
๊ฒฐ๋ก
๋ณด์๋ฏ์ด Hibernate๋ฅผ ์ฌ์ฉํ์ฌ ์ํํธ ์ญ์ ๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค.
Hibernate ๋ฒ์ ์ด 6.4 ์ด์์ด๋ผ๋ฉด ์ํฐํฐ ํด๋์ค์ @SoftDelete ์ด๋ ธํ ์ด์ ๋ง ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค. Hibernate๋ ๊ฐ ๋ ์ฝ๋์ ํ์ฌ ์ํ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํ๊ณ ๋ชจ๋ ์ฟผ๋ฆฌ๋ฅผ ์ํํธ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ์ ์ธํ๋๋ก ์๋์ผ๋ก ์กฐ์ ํฉ๋๋ค.
Hibernate ๋ฒ์ ์ด 6.3 ์ดํ์ธ ๊ฒฝ์ฐ ์ํํธ ์ญ์ ๋ฅผ ์ง์ ๊ตฌํํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด ์ํฐํฐ ํด๋์ค์ @SQLDelete ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ๊ณ ๋ ์ฝ๋์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ SQL UPDATE ๋ฌธ์ ์ ๊ณตํด์ผ ํฉ๋๋ค. ๋ํ Hibernate์ @Where ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ฌ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ ์ํํธ ์ญ์ ๋ ๋ ์ฝ๋๋ฅผ ์ ์ธํ๋ ์กฐ๊ฑด์ ์ ์ํด์ผ ํฉ๋๋ค.