티스토리 뷰
본 필자는 회사에서 일하면서 고객의 정보를 변경하면 수정 이력을 나타내는 기능과 수정 했던 사람을 추적해야 하는 기능을 구현해야 했다.
기존에는 직접 업데이트가 될 때 마다 전 후 비교를 통해 시간을 업데이트 시키는 방식으로 복잡하게 비즈니스 로직이 수행되었는데 필자는 이걸 좀 더 수월하게 이용하기 위해서 JPA Auditing 기술을 이용하고자 했다.
JPA Auditing 이란?
데이터베이스에서 누가 수정하고 언제 수정되었냐는 기록은 매우 중요할 수 있는 데이터이다. JPA에서는 이러한 기능을 추적할 수 있는 기능을 제공하고 있는데 그것이 바로 JPA Auditing
기술이다.
이 기술을 사용하면 자동으로 JPA에서 자동으로 생성이나 수정 발생 시 시간을 매핑하여 데이터베이스에 넣어준다.
사용해보자!
보통 요즘은 BaseTimeEntity를 이용하여 상속을 통해 모든 Entity에 해당 속성들을 넣어준다. 그렇다면 코드를 보자
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
@CreatedDate
@Column(name = "create_at", updatable = false)
private LocalDateTime createAt;
@LastModifiedDate
@Column(name = "update_at")
private LocalDateTime updateAt;
@CreatedBy
@Column(name = "created_by", updatable = false)
private String createdBy;
@LastModifiedBy
@Column(name = "updated_by")
private String updatedBy;
public LocalDateTime createAt() { return createAt; }
public LocalDateTime updateAt() { return updateAt; }
public String createdBy() { return createdBy; }
public String updatedBy() { return updatedBy; }
}
- @EntityListeners : Entity를 DB에 적용하기 이전, 이후에 커스텀 콜백을 적용하기 위한 클래스
- class AuditingEntityListener : 영속화 및 업데이트 시 Auditing 정보를 캡처하는 클래스
- @CreatedDate : 데이터 생성 날짜를 자동으로 저장해주는 어노테이션
- @LastModifiedDate : 데이터 수정 날짜를 자동으로 저장해주는 어노테이션
- @CreatedBy : 데이터 생성 시 생성한 사용자 저장
- @LastModifiedBy : 데이터 업데이트 시 업데이트한 사용자 저장
물론 원래는 생성/업데이트 시간과 생성한 사용자에 대한 정보는 따로 분리해야 하지만 편의상 하나로 묶었다.
이제 상속을 해줄 BaseTimeEntity에 대한 내부 작성이 끝났다. 이제 사용하기 위해 활성화를 시켜보자
@EnableJpaAuditing // 활성화를 위한 어노테이션
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- JPA Auditing 기술을 사용하기 위해선 꼭 해당 어노테이션을 Application 클래스에 적용하여 활성화를 시켜줘야한다.
자 그럼 우린 개발자기 때문에 테스트를 통해 실제 결과를 확인해봐야 한다.
해당 엔티티는 게시판 엔티티다 현재 BaseEntity를 상속받아 사용한다. 또한 CreatedBy와 lastModifiedBy 정보를 저장하기위해 AuditorAware를 시큐리티에서 사용자 정보를 가져와 이벤트 발생 시 저장하는 클래스를 정의하였다.
@Configuration
public class SpringSecurityAuditorAware implements AuditorAware<Long> {
@Override
public Optional<Long> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.map(authentication -> {
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean isUser = authorities.contains(new SimpleGrantedAuthority("USER"));
CustomUserDetails principal = (CustomUserDetails) authentication.getPrincipal();
return isUser ? principal.getId() : null;
});
}
}
각 CreatedBy와 LastModifiedBy에는 사용자의 식별값(PK)이 들어갈 예정이다. 그럼 테스트 결과를 보자
@Test
@DisplayName("게시판 정보를 수정하고 수정 날짜와 수정자를 기록한다.")
@WithAuthUser
void update() {
// given
BoardUpdateRequest request =
BoardUpdateRequest.of(Title.from("수정된 게시판 제목입니다."), Content.from("안녕하세요 수정된 게시판 내용입니다."));
// when
boardService.update(request.toEntity(), 1L, 1L);
Board board = boardRepository.findById(1L).orElseThrow(
() -> new BoardNotFoundException(ErrorCode.BOARD_NOT_FOUND));
// then
assertThat(board.getTitle()).isEqualTo(Title.from("수정된 게시판 제목입니다."));
assertThat(board.getContent()).isEqualTo(Content.from("안녕하세요 수정된 게시판 내용입니다."));
assertThat(board.getLastModifiedBy()).isEqualTo(1L);
assertThat(board.getLastModifiedTime()).isAfter(board.getCreateTime());
}
다음 코드로 테스트를 진행할 예정이다.
테스트는 잘 성공한다!!!
쿼리도 잘 나가는 것을 알 수 있다. !!
DB도 결과적으로 잘 나오는 것을 알 수 있다!!!
'Spring Framwork & JPA > JPA' 카테고리의 다른 글
JPA 연관관계에 대한 생각 (4) | 2022.07.26 |
---|---|
프로젝트 JPA 성능 개선기(2) - 불필요한 Join 삭제 (0) | 2022.06.04 |
프로젝트 JPA 성능 개선기 (1) - 로그인 시 성능을 올려보자~! (1) | 2022.06.04 |
JPA란 무엇인가? (0) | 2021.10.28 |
JPA에 시작 (0) | 2021.10.10 |
- Total
- Today
- Yesterday
- 코드
- 자바
- Kotlin
- 개발
- Spring
- 인터뷰
- CS
- 게시판
- thread
- 취준
- MySQL
- 백엔드
- 취업
- DevOps
- docker
- IT
- 프로그래밍
- java
- 개발자
- 취업준비
- 코딩
- Redis
- DB
- 프로젝트
- 면접 준비
- swarm
- 면접준비
- 면접
- 동시성
- JPA
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 28 |
29 | 30 | 31 |