JPA에서는 JPA Auditing이라는 기능을 제공하고 있습니다. JPA에서 시간을 자동으로 값을 넣어주는 기능입니다. Entity를 영속성 컨텍스트에 저장하거나 조회를 수행한 후에 update를 하는 경우 매번 시간 데이터를 입력하여 주어야 하는데, JPA Auditing을 이용하면 자동으로 시간을 매핑하여 데이터베이스의 테이블에 넣어주게 됩니다.
저번에 프로젝트를 하다가 JPA Auditing을 사용해서 createdTime
, modifiedTime
를 가진 BaseTimeEntity
를 만들었습니다.
이 BaseTimeEntity
잘 사용하고 있다가 문제점을 하나 발견하였습니다.
BaseTimeEntity
를 다른 Entity 객체에 상속하여 실제 DB에 반영을 한 결과 문제가 있었습니다.
바로 JPA를 안걸치고 DB에 직접 데이터를 넣으면 createdTime
, modifiedTime
가 null 값으로 생성이 된다는 문제!!!
물론 스프링부트로 만든 API를 통해서만 DB에 접근을 하여 데이터들을 생성하면 아무 문제가 없겠지만
아래와 같은 상황들로 인해 문제가 발생할 수 있습니다.
무엇보다 JPA와 DB의 무결성이 깨진 상황이기 때문에 해당 문제는 고쳐야될 필요가 있다고 생각하였습니다.
문제 해결은 간단했습니다.
JPA Auditing 기능은 영속성 컨텍스트에서 작동합니다. 더 정확히는 영속성 컨텍스트에서만 작동합니다. 그렇기 때문에 DB에 직접 데이터를 넣었을 때는 당연히 createdTime
, modifiedTime
가 null값으로 들어오게되는 것입니다.
그러면 영속성 컨텍스트가 아닌 DB에서 createdTime
, modifiedTime
가 자동으로 만들어지게 하면 해결되는 문제였습니다.
왜냐! JPA로 접근하게 되면 Entity를 생성한 다음 DB에 생성해주기 때문에 DB에서 createdTime
, modifiedTime
를 자동으로 값을 넣어주는게 발생합니다.
즉 JPA이든 어떤 ORM이든 목적지가 DB인 이상 저 컬럼 데이터가 넣어지는 것이 무조건 발생하겠죠?
크게 두가지 방법이 있습니다.
1. @Column
으로 DDL 옵션을 넣어주는 것
2. 직접 DDL을 작성하여 DB를 구축하고 JPA에서는 spring.jpa.hibernate.ddl-auto=validate
로 운영하는 것
2번 방식이 매우 좋아보이기는 하지만 이미 프로젝트가 JPA 위주로 DB가 구축이 되어서......
(흑흑 과거의 저는 죽어도 마땅합니다.)
그래서 1번 방법으로 해결해 보려 합니다. 그러면 한번 코드상으로는 어떻게 해결했는지 알아보겠습니다.
// SpringBoot Application
@EnableJpaAuditing // JPA Auditing 기능을 활성화
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// BaseTimeEntity
@Getter
// 엔티티의 공통 매핑 정보가 필요할 때 주로 사용합니다.
// 즉, 부모 클래스(엔티티)에 필드를 선언하고 단순히 속성만 받아서 사용하고싶을 때 사용하는 것
@MappedSuperclass
// Spring Data JPA가 Auditing 기능을 사용하게 만듭니다.
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseTimeEntity{
// Entity가 생성되어 저장될 때 시간이 자동 저장됩니다.
@CreatedDate
private LocalDateTime createdDate;
// 조회한 Entity 값을 변경할 때 시간이 자동 저장됩니다.
@LastModifiedDate
private LocalDateTime modifiedDate;
}
기존에는 Auditing 기능을 사용하여 PrePersist, PreUpdate 콜백 시 시간을 자동 생성하게끔 되어있습니다.
@MappedSuperclass
public abstract class BaseTimeEntity {
@Column(name = "created_time", nullable = false, updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
public LocalDateTime createdTime;
@Column(name = "modified_time", nullable = false, updatable = true, columnDefinition = "TIMESTAMP ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
public LocalDateTime modifiedTime;
}
@Column
어노테이션을 사용해서 columnDefinition
항목에 직접 DDL를 넣어줬습니다.
변경 이후 DB에 데이터를 직접 생성해도 createdTime
, modifiedTime
가 값이 채워지는 것일 확인하여 문제를 해결하였습니다.
서비스를 운영하다 보면 JPA를 통한 데이터 관리보다는 쿼리문과 데이터베이스 툴로 데이터를 관리할 때가 많습니다.
그렇기 때문에 무결성이 깨지는 이 JPA Auditing 기능을 별로 좋지 않게 보았고 이러한 문제를 해결했습니다.
DDL로 DB를 구축하고 Spring-jpa-ddl-auto
옵션을 validate로 하여 개발을 했어야했는데
프로젝트의 기획이 확실하게 전부 설계된 것이 아니라서 도메인 설계가 계속 수정해야되는 상황들이 발생하여 JPA로 DB를 구축하는 쪽으로 변경되었습니다. ㅠㅠㅠㅠ
이 과정에서 기획이 전부 완료되고 개발하느냐, 기획 도중에 개발하느냐의 차이를 실감하게 되어서 기획의 중요성을 느끼게 되었습니다.
너무 ORM에 의존하지 않고 최대한 DB위주로 생각하여 서비스를 설계해야 될 것같습니다.
편의성을 생각하면 ORM만한게 없지만은 이것저것 다 생각하면 쿼리문 만한게 없네요 ㅎㅎㅎ
QueryDSL로 쿼리문 사용에 익숙해지는 것도 한 방법인 것 같습니다.