본문 바로가기

Spring Boot

Spring JPA Envers 를 이용해 데이터의 변경이력 관리하기

728x90

개요

프로젝트를 수행하다 보면 삭제된 데이터를 어떻게 처리할 지에 대한 방법의 차이가 존재합니다.

Envers는 데이터의 추가, 수정, 삭제에 대한 모든 이력을 Entity 기준으로 자동으로 관리해 주기 때문에 이러한 고민을 덜어주는 아주 훌륭한 라이브러리입니다.



적용하기에 앞서

가령 아래와 같은 EventCode라는 엔티티가 있다고 가정을 해 봅시다.
이 프로젝트에서는 삭제 버튼을 눌러 엔티티를 삭제하면 DELETE 구문이 아닌 UPDATE 구분을 이용해 removed라는 필드를 true로 변경합니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Getter
@Setter
@Entity
@Table(name = "event_code",
    uniqueConstraints = @UniqueConstraint(columnNames = {"code""name"}))
public class EventCode {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;
 
  @Size(min = 1, max = 10)
  @Column(nullable = false)
  private String code;
 
  @Size(min = 2, max = 50)
  @Column(nullable = false)
  private String name;
 
  @Column(columnDefinition = "tinyint default b'0'")
  private boolean removed;
}
cs


하지만 이 엔티티는 code와 name필드를 Unique key로 정의하고 있습니다.

존재하던 데이터를 삭제한 뒤 동일한 code와 name값을 가진 데이터를 새로 추가 할 수 없다는 제약조건이 발생하죠.



이런 시간 소모적인 개발 방법은 더 이상 없었으면 좋겠습니다. 특히 공공 프로젝트에서 말이죠.

Envers는 개발자에게 "변경된 데이터를 어떻게 관리할 것인가를 고민하지 마" 라고 말해주는 훌륭한 라이브러리니까요.


프로젝트에 Envers 추가하기

본 예제는 Maven 및 Spring Boot 1.5.19.RELEASE 버전을 기준으로 작성되었습니다.

1. Dependency 추가

spring-data-envers Dependency를 추가해줍니다.
릴리즈된 버전은 여기서 확인 가능합니다.
1
2
3
4
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-envers</artifactId>
</dependency>
cs

2. Jpa Auditing 활성화

Application.java 또는 Config 파일에 @EnableJpaAuditing 어노테이션을 추가해줍니다.

1
2
@EnableJpaAuditing
public class Application extends SpringBootServletInitializer {
cs



여기까지 모든 준비는 완료되었습니다. 정말 간단하죠?

이제 Auditing(이력관리)을 적용할 Entity에 코드 몇 줄만 추가해 주면 됩니다.


3. Auditing 대상 Entity 수정

이제 이력관리를 할 대상 Entity 클래스에 @Audited 어노테이션을 추가합니다.

1
2
3
@Entity
@Audited
public class EventCode extends Auditable<String> {
cs

여기까지 Envers의 적용은 모두 완료되었습니다.  이제 부트를 재시작하기만 하면 됩니다.

4. 어떻게 반영되었을까?

이제 대상 데이터베이스에 접속하여 구조를 확인해 보겠습니다.
EventCode의 테이블인 event_code외에 event_code_aud라는 테이블이 새로 추가된 것을 보실 수 있습니다.


이 테이블은 Envers가 추가, 수정, 삭제에 대한 변경 사항을 revision별로 자동으로 관리해 주는 테이블 입니다.


*_aud 테이블에는 rev와 revtype이라는 컬럼이 추가되어 있는데, 각각 revision추가,수정,삭제에 대한 type을 기록합니다.

revtype 

 0

 1

 2

 타입

 추가

수정

삭제 




예외 처리

1. * to a not audited entity * Such mapping is possible... 에러가 나타나는 경우

아래 예제 코드와 같이 자식 Entity가 @Audited 어노테이션을 포함하지 않은 경우 (이력관리 대상이 아닌 경우) 나타나는 에러입니다.

아래 코드는 @Audited 어노테이션을 포함한 Code Entity의 자식 속성인 CodeGroup Entity가 @Audited 어노테이션을 포함하고 있지 않은 경우를 예로 들었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Table(name = "code")
@Audited
public class Code extends Auditable<String> {
 
  @Id
  @Size(min = 1, max = 10)
  @Column(nullable = false, updatable = false)
  private String code;
 
  @ManyToOne
  @JoinColumn(name = "code_group_id", nullable = false)
  @JsonManagedReference
  private CodeGroup codeGroup;        //해당 Entity가 @Audited 어노테이션을 가지지 않은 경우
}
 
@Table(name = "code_group")
public class CodeGroup {
 
  @Id
  @Size(min = 1, max = 10)
  @Column(nullable = false, updatable = false)
  private String code;
 
  @Size(min = 1, max = 30)
  @Column(nullable = false)
  private String name;
}
cs


이러한 경우에는 2가지 처리 방법이 있습니다. 요구사항에 맞게 반영하시면 되겠습니다.

1. 자식 Entity에 @Audited 어노테이션을 추가하여 두 Entity가 모두 이력관리 되도록 한다.

1
2
3
4
5
6
7
8
9
10
11
@Table(name = "code")
@Audited
public class Code extends Auditable<String> {
          ...
}
 
@Table(name = "code_group")
@Audited
public class CodeGroup {
          ...
}
cs

2. 자식 Entity는 버전관리 대상에서 제외한다. @NotAudited 어노테이션 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Table(name = "code")
@Audited
public class Code extends Auditable<String> {
 
  @Id
  @Size(min = 1, max = 10)
  @Column(nullable = false, updatable = false)
  private String code;
 
  @NotAudited
  @ManyToOne
  @JoinColumn(name = "code_group_id", nullable = false)
  @JsonManagedReference
  private CodeGroup codeGroup;        //해당 Entity가 @Audited 어노테이션을 가지지 않은 경우
}
 
cs



What's Next?

다음에는 특정 Entity들에 대해 CreatedBy, CreationDate, LastModifiedBy, LastModifiedDate 를 자동으로 입력해주는 추상 클래스인 Auditable 클래스를 만들어 보도록 하겠습니다.

1
2
3
4
5
@Table(name = "code")
@Audited
public class Code extends Auditable<String> {
          ...
}
cs










강윤구 / 대표

Yungu Kang / CEO

yungu@userinsight.co.kr

728x90