티스토리 뷰
데이터를 수정하던 중에 이 모든 변경사항을 관리하는 방법은 없을까? 라는 의문이 들었다.
그래서 Aop, EventListener 등 생각을 해보고 관련 정보를 찾아보던 중 Hibernate의 envers라는 모듈을 알게 됐다.
정확히는 김영한님의 envers 모듈 세션을 듣게 됐고 해당 내용을 간략히 정리하려고 한다.
방법은 간단하다. 의존성을 추가하고 이력관리를 하고 싶은 Entity 위에 @Audited 어노테이션을 추가하면 된다.
예제를 통해서 좀 더 자세히 알아보자.
Member 정보는 아래와 같다
create table member
(
id serial primary key,
name varchar
);
@Entity
@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Member Entity class 위에 @Audited 어노테이션을 추가하면 member_aud 라는 테이블이 생기고 해당 테이블에 member에 대한 변경 이력이 저장된다
1. member 데이터 생성
@Test
void create() {
Member member = Member.builder()
.name("홍길동")
.build();
memberRepository.save(member);
}
위처럼 member 데이터를 생성하면 member_aud에 아래와 같은 정보가 생성된다
rev는 조금 후에 알아보기로 하고 revtype은 데이터에 대한 타입을 가리킨다.
- 0은 생성
- 1은 수정
- 2는 삭제 이다.
나머지는 변경 후의 member 테이블에 있는 컬럼의 정보이다.
우리가 member 데이터를 생성한 대로 각 컬럼의 값이 채워진 것을 볼 수 있고 생성했으므로 revtype의 값은 0이다
2. member 데이터 수정
@Test
@Transactional
@Rollback(value = false)
void modify() {
Member member = memberRepository.findByName("홍길동").orElseThrow(() -> new RuntimeException("not found member"));
member.setName("일길동");
}
수정을 한대로 각 컬럼의 값이 채워진 것을 볼 수 있고 revType의 값은 1이다
3. member 데이터 삭제
@Test
@Rollback(value = false)
void delete() {
Member member = memberRepository.findByName("일길동").orElseThrow(() -> new RuntimeException("not found member"));
memberRepository.delete(member);
}
삭제를 하면 해당 데이터가 삭제되므로 모든 컬럼의 값이 null이 된 것을 볼 수 있고 revtype의 값은 2이다.
(soft delete를 해도 모든 컬럼의 값은 null로 채워진다)
이제 rev의 값을 알기 위해서 member에 team 연관관계를 추가해 보겠다.
Team 정보는 아래와 같다
create table team
(
id serial primary key,
name varchar
);
@Getter
@Entity
@Audited
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
private String name;
}
Member entity와 table도 아래와 같이 수정해 주자(ddl-auto=update 이므로 entity class만 수정해 주겠다)
@Getter
@Entity
@Builder
@Audited
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private Team team;
public void setName(String name) {
this.name = name;
}
}
이 상태로 하나의 트랜잭션에서 member와 team을 모두 데이터를 변경해 보자.
@Test
@Transactional
@Rollback(value = false)
void transaction() {
Team team = Team.builder()
.name("1팀")
.build();
Member member = Member.builder()
.name("홍길동")
.team(team)
.build();
memberRepository.save(member);
}
member_aud와 team_aud를 보면 rev 값이 같은 것을 볼 수 있다.
envers 의존성을 추가하면서 테이블이 하나 새로 추가됐을 텐데 revinfo라는 테이블이다.
revinfo 테이블은 아래와 같이 생겼다.
member_aud와 team_aud에 있는 rev값이 바로 여기 revinfo 테이블에 있는 rev값이다.
이 값은 트랜잭션을 나타내는데 값이 같으면 같은 트랜잭션 내에서 작업이 실행됐다는 것을 보여준다.
즉 하나의 트랜잭션에서 어떤 테이블들이 수정됐는지를 알 수 있게 해주는 값이다.
고급 기능
@Audited 어노테이션에 withModifiedFlag = true 옵션을 주면은 각 _aud 테이블에 컬럼의 수정여부를 나타내는 컬럼명_mod 컬럼들이 추가로 생성되고 각 컬럼에 boolean으로 수정 여부를 나타낸다.
@Getter
@Entity
@Builder
@Audited(withModifiedFlag = true) //withModifiedFlag = true 추가
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PUBLIC)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private Team team;
public void setName(String name) {
this.name = name;
}
}
member entity를 위처럼 변경 후 이름을 변경하는 작업을 실행하면 aud 테이블에 아래와 같은 데이터가 추가된다
name_mod와 team_mod 컬럼이 추가됐는데 우리가 작업한 것은 name만 변경했으므로 name_mod만 true로 돼 있는 것을 볼 수 있다.
이러한 변경이력을 조회할 수 있는 기능도 제공하니까 관심이 있으면 알아보도록 하자.
추가로 spring에서 변경이력에 대한 조회를 편하게 할 수 있는 spring-data-envers도 있다
'Spring' 카테고리의 다른 글
[Security] hasIpAddress 사용하기 (0) | 2024.03.09 |
---|---|
윈도우 함수 사용하기 - (1/3) (0) | 2023.12.22 |
트랜잭션의 전파속성 중 Nested 사용해보기 (0) | 2023.12.02 |
DispatcherServlet (1) | 2023.11.18 |
- Total
- Today
- Yesterday