[JPA] @EnableJpaAuditing

김대니·2023년 1월 16일
0

JPA

목록 보기
1/1
post-thumbnail

들어가며

데이터를 생성하거나 수정할 때, 누가, 언제했는지를 알기 위해 보통 아래와 같은 필드를 각 테이블에 같이 포함 시켜 테이블을 생성합니다.

  • createdAt (생성일자)
  • modifiedAt (수정일자)
  • createdBy (생성자)
  • modifiedBy (수정자)

가장 간단한 방법으로는 생성자 또는 setter 메소드를 활용하여 직접 값을 설정할 수 있습니다.

@Table(name = members)
@Entity
public class Member {
	...
    
	@Column
	private Datetime createdAt;
    
    @Column
    private Datetime modifiedAt;
    
    @Column
    private String createdBy;
    
    @Column
    private String modifiedBy;
    
	public Member(..., String createdBy, String modifiedBy) {
		...
		this.createdAt = Datetime.now();
        this.modifiedAt = Datetime.now();
        this.createdBy = createdBy;
        this.modifiedBy = modifiedBy;
    }
    
    public modify(..., String modifiedBy) {
    	...
        this.modifiedAt = Datetime.now();
        this.modifiedBy = modifiedBy;
    }
}

하지만 매번 이렇게 코드를 작성하는 것은 귀찮고 번거롭습니다.

EnableJpaAuditing

엔티티 객체가 생성이 되거나 변경이 되었을 때 @EnableJpaAuditing 어노테이션을 활용하여 자동으로 값을 등록할 수 있습니다.

1. 어노테이션 추가

@EnableJpaAuditing 어노테이션은 Spring Data 에서 제공하므로 아래처럼 디펜던시를 추가해주어야 합니다..

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

그리고 Application 클래스에 @EnableJpaAuditing 어노테이션을 추가합니다.

@EnableJpaAuditing // 추가
@SpringBootApplication
public class MyApplication {
    ...
}

2. 생성일자, 생성자를 위한 공통 엔티티 생성

생성일자생성자 를 관리하는 클래스로 BaseEntity 를 생성합니다.

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
public abstract class BaseEntity {
	@CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

	@CreatedBy
	@Column(updatable = false)
	private String createdBy;
}

어노테이션

  • @MappedSuperclass : 공통 맵핑 정보가 필요할 때 사용하며 부모 클래스에서 선언하고 속성만 상속 받아서 사용하고 싶을 때 사용합니다. BaseEntity 를 상속받는 클래스는 모두 createdAt, createdBy 필드가 있어야 합니다.
  • @EntityListeners(AuditingEntityListener::class) : JPA Entity 에 이벤트가 발생할 관련 코드를 실행합니다.
  • @CreatedDate : 생성 일자를 관리하는 필드에 현재 날짜를 주입하는 작업을 수행합니다.
  • @Column(updatable = false) : 생성일자, 생성자에 대한 필드이기 때문에 수정 불가하도록 설정합니다.

3. 수정일자, 수정자를 위한 공통 엔티티 생성

수정일자수정자 를 관리하는 클래스로 MutableBaseEntity 를 생성합니다.

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
public abstract class MutableBaseEntity extends BaseEntity {
	@LastModifiedDate
    @Column(updatable = true)
    private LocalDateTime modifiedAt;

	@LastModifiedBy
	@Column(updatable = true)
	private String modifiedBy;
}

4. JPA Config. 설정

JPA Config. 설정을 따로 해주어야 합니다.

클래스에 @Configuration 어노테이션을 부여함으로써 설정 클래스로 변경하고, @EnableJpaAuditing 어노테이션을 추가해줍니다. 이 이노테이션으로 createdBy, modifiedBy 에 값을 주입할 때는 auditorAwareRef 속성으로 설정하고, createdAt, modifiedAt 에 시간값을 정의할 때에는 dateTimeProviderRef 속성으로 설정할 수 있습니다.

@EnableJpaAuditing(
	auditorAwareRef = "principalAuditorAware",
    dateTimeProviderRef = "principalAuditorAware"
)
@Configuration
public class JpaAuditConfig {
	...
}

PrincipalAuditorAware

이 클래스는 다음과 같이 설정할 수 있습니다.

@Component
public class PrincipalAuditorAware implements AuditorAware<String>, DateTimeProvider {
    
    private final ApplicationContext applicationContext;
    
    public PrincipalAuditorAware(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    
    @Override
    public Optional<TemporalAccessor> getNow() {
        return Optional.of(ZonedDateTime.now());
    }

    @Override
    public Optional<String> getCurrentAuditor() {
        return Optional.of(applicationContext.getBean(PrincipalProvider.class))
            .flatMap(PrincipalProvider::getPrincipal);
    }
}

PrincipalProvider

public interface PrincipalProvider {
	Optional<String> getPrincipal();
}

AuthenticationHolder

public interface AuthenticationHolder {
	Optional<Authentication> getAuthentication();
	void setAuthentication(Authentication authentication);
}

AuthenticationHolderImpl

@Component
public class AuthenticationHolderImpl implements AuthenticationHolder, PrincipalProvider {
	private final Authentication authentication;
    
    public AuthenticationHolderImpl(Authentication authentication) {
    	this.authentication = authentication;
    }
    
    @Override
    public Optional<Authentication> getAuthentication() {
    	return Optional.ofNullable(authentication);
    }
    
    @Override
    public void setAuthentication(Authentication authentication) {
    	this.authentication = authentication;
    }
    
    @Override
    public Optional<String> getPrincipal() {
    	return getAuthentication().map(each -> each.getPrincipal());
    }
}

5. JPA Entity 적용

BaseEntityMutableBaseEntity 클래스를 생성한 뒤, 다시 Member 엔티티로 돌아가봅시다.

MutableBaseEntity 를 상속받았기 때문에 createdAt, createdBy, modifiedAt, modifiedBy 과 관련된 코드는 삭제해도 됩니다.

@Table(name = members)
@Entity
public class Member extends MutableBaseEntity {
	...
}

Issues

@EnableJpaAuditing 어노테이션을 적용하면서 해결해나갔던 이슈들을 정리합니다.

Issue 1. dateTime format error

datetime 포맷이 맞지 않다면서 엔티티 저장이 제대로 되지 않았습니다.

아래 글을 참고하여 해결을 했습니다.

https://github.com/spring-projects/spring-boot/issues/10743

@EnableJpaAuditingdateTimeProviderRef 도 있고 auditorAwareRef 라는 속성도 있습니다. 날짜와 관련된 속성은 dateTimeProviderRef 로 설정할 수 있으니 필요하면 꼭 넣어야 합니다.

Issue 2. ZonedDateTime 숫자값으로 표현

아래 그림처럼 modifiedAt 이 숫자값으로 표현이 됩니다.

날짜로 표현되어야 하는데 말이죠..

해결하는 방법은 @JsonFormat 을 사용하면 됩니다.

Reference

profile
?=!, 물음표를 느낌표로

1개의 댓글

comment-user-thumbnail
2023년 11월 15일

안녕하세요! 글 잘봤습니다! 혹시 마이바티스 사용할때도 createAt, createdBy, modifiedAt, modifiedBy를 디비 테이블 설계시 만들어줘도 괜찮을까요??

답글 달기