실제 시스템을 운영하다보면 프로세스상에는 필요하지 않지만 운영의 편의성을 위해서 중요데이터의 변경사항을 history 테이블에 기록해 놓는 경우가 있다. 운영면에서는 분명 필요한 일이고, 많은 편의성을 제공해 주는 기능이기는 하지만 실제 개발하는 입장에서는 history를 관리하는 테이블의 변경이 있을때마다 함께 작업해 줘야 하기때문에 귀찮은 일인 경우가 많다.
이 때 spring-data-envers 를 이용하면 이 귀찮은 작업을 편하게 간단한 설정만 추가해서 처리할 수 있다.
hibernate-envers 를 스프링에서 사용하기 편하도록 랩핑한 프로젝트이다. 이 기능을 이용하면 중요 테이블의 history 기록을 간단하게 처리할 수 있다.
build.gradle 에 아래와 같이 spring-data-envers를 추가한다.
implementation 'org.springframework.data:spring-data-envers'
@SpringBootApplication이 있거나 @Configuration이 있는 설정 클래스에 아래의 어노테이션을 추가해 준다.
@EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
history를 관리할 대상이 되는 Entity에 아래와 같이 @Audited 어노테이션을 추가해준다.
참고로 @Audited는 클래스뿐 아니라 필드에도 설정 가능하다.
@Audited
@Getter @Setter
@ToString
@Entity
@Table(name = "category")
public class Category extends AuditEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long categoryNo;
private long parentCategoryNo;
private String name;
}
그럼 이제 아래와 같이 2개의 테이블이 생성된다. 참고로 아래의 테이블 생성은 hibernate의 ddl-auto 가 creata 또는 update로 설정되어 있을때 자동으로 생성해 준 테이블이다.
CREATE TABLE `revinfo` (
`rev` int NOT NULL AUTO_INCREMENT,
`revtstmp` bigint DEFAULT NULL,
PRIMARY KEY (`rev`)
);
CREATE TABLE `category_aud` (
`category_no` bigint NOT NULL,
`rev` int NOT NULL,
`revtype` tinyint DEFAULT NULL,
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`parent_category_no` bigint DEFAULT NULL,
PRIMARY KEY (`rev`,`category_no`),
CONSTRAINT `FKc9m640crhsib2ws80um6xuk1w` FOREIGN KEY (`rev`) REFERENCES `revinfo` (`rev`)
);
먼저 revinfo 는 envers가 동작했을때의 기록이다. rev 는 시퀀스번호이고, revtstmp는 기능이 동작했을때의 timestamp 값이다.
history 테이블은 위와같이 _aud가 테이블명 뒤에 붙는다.
rev 는 revinfo 테이블과의 연관관계를 가지는 키값이고, revtype 은 기록에 대한 타입니다.
revtype의 값은 아래와 같다.
0 : 입력
1 : 수정
2 : 삭제
revinfo 테이블의 이름 및 컬럼명은 수정이 가능하다. 필요하면 컬럼을 더 추가할 수도 있다.
아래와 같이 @RevisionEntity 를 이용해서 revinfo 테이블 재정의 가능하다.
@Entity
@RevisionEntity
public class Revision {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@RevisionNumber
private long revisionNo;
@RevisionTimestamp
private long createdAt;
}
@Audited
@Getter @Setter
@ToString
@Entity
@Table(name = "category")
public class Category extends AuditEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long categoryNo;
private long parentCategoryNo;
private String name;
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
@ManyToOne
private Center center;
}
org.hibernate.envers.configuration.EnversSettings 파일을 열어보면 사용할 수 있는 옵션들이 정의되어 있는것을 확인 할 수 있다.
그 중 자주 사용될 만한 옵션 몇가지만 소개한다.