Skip to content
isdnetworks
Go back

들여왔다고 켜진 게 아니다

(수정됨)

운영 데이터를 지워야 하는데 관리 화면에 삭제 기능이 없어서 직접 지우는 수밖에 없었다. 지우기 전에 복구가 가능한지 확인하려고 Member 엔티티 파일을 열었다.

Table of contents

Open Table of contents

증상 — 들여오기와 적용의 차이

파일 상단에 import org.hibernate.annotations.SQLDelete; 가 있어서 지워도 deleted_at 표시만 되는 줄 알았다. 그런데 클래스 위에 @SQLDelete(sql = "UPDATE members SET deleted_at = NOW() WHERE id = ?") 선언이 없었다.

파일 상단의 import 는 이름을 들여오는 것이다. 클래스 위의 애너테이션은 그것을 적용하는 것이다. Hibernate 5.4 공식 문서대로 @SQLDelete 는 붙인 엔티티에 대해서만 DELETE 문을 바꾼다. 같은 이름이 위치에 따라 다른 일을 하므로 들여왔다는 사실만으로는 켜졌다고 볼 수 없다.

검증 — 검색을 두 갈래로 나눈 이유

이것을 확인하려면 언급이 있는지와 클래스 선언이 있는지를 따로 봐야 했다. grep -rn SQLDelete 로 이름만 검색하면 상단의 import 가 걸린다. 그러면 소프트 삭제 엔티티로 잘못 판정한다.

grep -rn '^@SQLDelete' 로 줄 첫머리 애너테이션 형태로 다시 검색하니 그 줄이 없었다. 이 엔티티의 삭제는 표시가 아니라 실제 삭제였다.

원인 — 이력은 남고 복구는 안 되는 조합

같은 엔티티에 Envers 의 @Audited 는 제대로 선언돼 있었다. 변경과 삭제를 members_AUD 이력 테이블에 자동으로 남기는 것이었다.

그래서 이 테이블은 자동 이력은 있는데 복구는 불가능한 조합이 된다. 무엇이 지워졌는지는 이력으로 알 수 있지만 데이터 자체는 되돌릴 수 없다.

주의 — 직접 질의는 이력도 건너뛴다

여기에 하나가 더 있었는데 그 이력 기록이 Hibernate 이벤트로 동작한다는 점이었다. JdbcTemplate 직접 질의로 지우면 엔티티를 거치지 않으므로 이력도 남지 않는다.

내가 하려던 것이 바로 그 방식이었다. 엔티티를 거친 삭제는 흔적이라도 남지만 직접 질의는 아무 흔적 없이 사라지게 만든다.

조치 — 우회한 만큼 앞에서 채우는 순서

두 가지 방법이 있었다. 하나는 EntityManager.remove() 를 부르는 일회성 코드를 짜서 배포하는 것이다. 다른 하나는 대상 행을 mysqldump --where 로 통째로 백업하고 Envers 가 남겼을 형식대로 members_AUD 에 이력을 손으로 넣은 뒤에 지우는 것이다. 뒤쪽을 골랐다. 이유는 지울 대상이 몇 건이라 배포를 태우는 비용이 더 커서다. 대신 _AUD 행의 REVTYPE 과 REV 를 손으로 맞춰야 하는 것을 감수했다. 순서를 이렇게 잡은 이유는 지운 뒤에는 이력에 넣을 내용을 다시 알아내야 해서다.

일반화하면 운영 데이터를 직접 조작할 때 자동 훅의 목록을 먼저 만들어야 한다. 이력과 캐시 갱신과 이벤트 발행 중 필요한 것을 앞에서 채운다. 그러고 나서 조작한다.

정리


Share this post on:

Previous Post
컬럼 하나로 답이 안 나온다
Next Post
한쪽만 막으면 다른 쪽으로 샌다