프로젝트가 보낸 상장 정보 시트를 다시 받아 coin_master 를 맞춰야 했고 가장 짧은 방법이 눈에 보였다. 그 코인의 기존 행을 전부 지우고 받은 대로 새로 만드는 방식이었다.
Table of contents
Open Table of contents
배경 — 두 줄로 끝나는 방식
deleteAll() 뒤에 saveAll() 을 두는 이 방식은 개수 차이와 순서 변경과 값 변경을 전부 한꺼번에 처리한다. 제자리 갱신은 기존 것과 짝을 맞추고 값을 고치고 개수 차이를 따로 다뤄야 해서 훨씬 길어진다.
코드 길이만 보면 비교가 안 될 만큼 짧은 쪽이 나아 보였다. 넣기 직전에 기존 행을 지우고 새로 만들면 안 된다는 제지를 받았다.
원인 — 코인을 가리키던 하위 관계
왜 안 되는지 보려고 CoinMaster 엔티티를 열었더니 하위 관계가 여럿이었다. 마켓 markets 와 지갑 설정 wallet_configs 와 수수료 정책 fee_policies 가 각각 coin_id 를 외래키로 갖고 있었다.
코인 행을 지우고 새로 만들면 coin_id 가 바뀌므로 셋 다 옛 식별자를 가리키게 된다. 코인은 새로 생기지만 그 아래 매달려 있던 것들이 전부 갈 곳을 잃는다.
조용히 끊어지는 연결
이 구조의 위험은 오류가 안 난다는 점이었다. MariaDB 공식 문서대로면 외래키 제약이 걸려 있을 때 부모 행 삭제는 기본 동작 RESTRICT 로 1451 오류가 나는데, 이 스키마는 coin_id 를 제약 없이 그냥 컬럼으로만 두고 있어서 삭제가 그대로 성공했다.
마켓 목록에서 코인이 빠지고 출금 수수료가 기본값으로 돌아가는 형태로 나중에 드러난다. 행위가 성공했다는 것과 의도한 상태가 됐다는 것이 갈리는 전형적인 자리였다.
조치 — 제자리 갱신으로 바꾼다
두 가지 방법이 있었다. 하위 세 표에 외래키 제약을 걸어 삭제 자체가 실패하게 하는 것과, 기존 행을 유지하면서 값만 고치고 저쪽이 더 많으면 부족분만 추가하는 것이다. 뒤쪽을 골랐다. 이유는 제약을 거는 것은 실수를 오류로 바꿀 뿐이고 갱신 자체는 여전히 못 하기 때문이다. 대신 짝 맞추기 코드가 길어지는 것을 감수했다. coin_id 가 유지되므로 하위 관계가 그대로 남는다.
저쪽 개수가 더 적을 때도 남는 것을 지우지 않고 use_yn = 'N' 으로 두었다. 지우는 순간 같은 문제가 다시 생기기 때문이다.
가리키는 쪽을 찾는 방법
이 함정이 생기는 것은 작업 중인 테이블만 눈에 들어오기 때문이다. coin_master 를 다루고 있으면 그것을 가리키는 다른 테이블은 시야에 없다.
미리 알려면 grep -rn coin_id src/ 로 외래키 컬럼 이름으로 코드를 훑어 가리키는 쪽을 찾는다. 엔티티의 @OneToMany 만 보면 역방향이 선언 안 된 것을 놓치므로 컬럼 이름으로 찾는 편이 확실하다.
이 규칙은 이 함수 하나가 아니라 coin_master 를 수정하는 모든 경로에 적용된다. 한 곳만 고치면 다른 경로에서 같은 일이 그대로 난다.
정리
- 삭제 후 재생성은 하위 데이터의 연결을 전부 끊는다
coin_id가 바뀌면 그것을 가리키던 것들이 갈 곳을 잃는다- 외래키 제약이 없으면 오류가 안 나고 새 행은 잘 만들어진다
- 값만 제자리 갱신하고 부족분만 추가한다
- 개수가 줄어도 지우지 않고
use_yn으로 둔다 - 작업 중인 테이블만 보이므로 가리키는 쪽을 따로 찾는다
- 외래키 컬럼 이름으로 찾는 편이
@OneToMany선언보다 확실하다 - 짧은 코드가 더 위험할 수 있다