본문 바로가기

삽질

Hibernate Envers 삭제 시 not null 에러

Hibernate Envers란?

hibernate에서 만든 envers는 데이터 변경 이력을 로깅하기 위한 라이브러리이다.
과거엔 로깅을 위한 엔티티와 history 테이블을 따로 만들어주고 CRUD 작업이 발생하면 이력을 쌓는 작업을 해줘야 했다. 번거롭게 엔티티가 추가될 때마다 반복 작업을 해야했다.
envers를 쓰면 이러한 번거로운 작업을 한번에 줄일 수 있다. 코드 몇 줄 추가로 이력 관리 테이블을 만들어주는 신비로운 기능이다.
기본적으로 jpa로 구현되어 있으며 후에 spring에도 spring data envers 프로젝트로 추가되었다.

Spring Boot + Envers로 엔티티 이력 관리하기 글을 보면 예제와 친절한 설명이 있다. envers를 사용하고 싶다면 참고하자!


삭제 이력

envers로 만들어진 이력 관리 테이블에서 삭제 이력 같은 경우엔 아래와 같이 REVTYPE은 2로, ID와 REV를 제외한 나머지 값은 null로 저장된다.

이때 이력 관리 테이블에 REVTYPE, ID, REV를 제외한 나머지 column에 not null 제약이 걸려있다면 어떻게 될까?

com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'succeed' cannot be null 

바로 요런 에러가 나게 된다.

회사 업무 중 이 부분을 해결해야 했는데, 혹시 내가 모르는 좋은 해결법이 있을까 열심히 찾아보았다. 결론부터 말하면 해결 방법은 두 가지 밖에 없다.


1. not null 제약 제거

당연한 해결 방법이지만 가장 바람직한 해결 방법이라고 생각한다.
이력 관리 테이블이 아닌 원본 테이블에 not null 제약이 걸려있으면 해당 컬럼은 자동으로 이력 관리 테이블에 값이 남을 것이다.
이러한 이유로 굳이 이력 관리 테이블에 not null 제약을 걸 필요가 없다는 생각을 했다.

물론 어떤 이유로 audit 테이블에도 not null 제약을 반드시 걸어야하는 경우가 있을 수 있다. 그럴 땐 두 번째 해결법을 사용해야 한다.


2. org.hibernate.envers.store_data_at_delete=true

org.hibernate.envers.store_data_at_delete=ture 설정을 해주면 엔티티 삭제 시 이력을 남길 때 null로 남기는 것이 아니라 삭제 직전의 엔티티 상태를 캡처하여 이력으로 남긴다.
not null 제약이 걸려있어도 삭제 시 컬럼 값이 null이 아니면 이력 관리 테이블 not null 제약으로 인한 실패는 발생하지 않는 것이다.
원본 테이블은 null 허용이고 이력 관리 테이블은 not null이면 실패할 수 있겠지만, 그럴 경우가 있나 싶다.

application.yml 파일에 아래와 같이 설정해 주면 된다.

org:
  hibernate:
    envers:
      store_data_at_delete: true 

EntityManager 설정을 따로 해주는 코드가 있으면 properties에 아래와 같이 설정해 줄 수 있다.

@Bean     
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) { 
    return builder.dataSource(new HikariDataSource())
            .properties(Map.of("org.hibernate.envers.store_data_at_delete", true))
            .packages("com.example")
            .persistenceUnit("example")
            .build();
}

결론

  • envers로 이력 관리를 편하게 하자!
  • audit table에는 특수한 상황이 아니라면 not null 제약을 걸지 말자!(id, rev revtype 제외)
  • not null을 걸어야 한다면 org.hibernate.envers.store_data_at_delete=ture 설정을 적용하자!

참고 자료