db에서 삭제 실패 해도 파일 시스템에서는 삭제 성공?!
이 코드의 문제는 뭘까? Optional을 findById에서 반환받아서 uploadFile 객체를 반환받았을 경우에만 fileStore에서 파일을 삭제하려는 시도는 좋았다.
하지만, 모종의 이유로 mapper.remove(UploadFileId) 메서드가 트랜젝션에서 롤백될 때 문제가 발생한다. fileStore에서 파일을 삭제하는 코드는 롤백이 되지 않기 때문이다.
@Override
public void remove(Long uploadFileId) {
mapper.findById(uploadFileId).ifPresent(uploadFile -> fileStore.deleteFile(uploadFile.getStoreFileName()));
mapper.remove(uploadFileId);
}
시도1: remove의 반환 결과에 따라 수정해보자
Mybatis는 update 코드에 대해 영향받은 row 수를 반환하도록 할 수 있으니, 이렇게 개선하면 되지 않을까? 하는 생각이 들었고, 코드를 수정해보았다.
결과는, 실패했다. 이유는, Mybatis의 remove는 영향을 준 로우 수를 반환하지만, 트랜잭션 rollback은 이 이후 시점이다. 따라서, 이 코드는 의미가 없다.
@Override
public void remove(Long uploadFileId) {
mapper.findById(uploadFileId).ifPresent(uploadFile -> {
if (mapper.remove(uploadFileId) > 0) {
fileStore.deleteFile(uploadFile.getStoreFileName());
}
});
}
시도2: affterCommit
최종적으로 성공한 방법이다. 그렇다면, fileStore.deleteFile을 커밋 이후에 하도록 하면 될 일이다.
TransactionSynchronizationManager.registerSynchronization으로 트랜잭션 동기화 콜백을 등록해준다. 현재 진행 중인 트랜잭션에 특정 작업(예: afterCommit, afterRollback 등)을 수행할 콜백을 등록하는 것을 의미한다.
@Override
public void remove(Long uploadFileId) {
mapper.findById(uploadFileId).ifPresent(uploadFile -> {
mapper.remove(uploadFileId);
deleteFile(uploadFile);
}
);
}
private void deleteFile(UploadFile uploadFile) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
fileStore.deleteFile(uploadFile.getStoreFileName());
}
});
}
개선: fileStore.deleteFile 메서드 안에서 트랜잭션 동기화 콜백 등록하기
위 방법은 커밋 후에 삭제를 시도하기 때문에 적절하기는 하지만, uploadFileRepository가 코드가 늘어나는 단점이 있다. 따라서 fileStore.deleteFile에서 추가해주는 방법으로 개선해주었다.
@Override
public void deleteFile(String storeFileName) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
File file = new File(getFullPath(storeFileName));
file.delete();
}
});
}
@Override
public void remove(Long uploadFileId) {
mapper.findById(uploadFileId).ifPresent(uploadFile -> {
mapper.remove(uploadFileId);
fileStore.deleteFile(uploadFile.getStoreFileName());
}
);
}