springframework/Spring Data JPA

Spring Data JPA : Auditing, Life Cycle Event

Jungsoomin :) 2020. 11. 19. 20:46

Auditing(감사)

  • 엔티티 변경 시점언제 , 누가 변경 했는지 정보를 기술하는 기능이다.
  • 스프링 부트의 자동설정 지원을 받지 않는다.

누가, 언제

  • @CreatedDate , @CreatedBy
  • @LastModifiedDate , @LastModifiedBy
  • 적용할 Entity 클래스 @EntityListener 선언
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Comment {

    @Id @GeneratedValue
    private Long id;

    private String title;

    private String comment;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

    private int up;

    private int down;

    private boolean best;

    // 언제 만들었는가
    @CreatedDate
    private Date created;
    // 누가 만들었는가
    @CreatedBy
    @ManyToOne
    private Account createdBy;
    // 언제 수정했는가
    @LastModifiedDate
    private Date updated;
    // 누가 수정했는가
    @LastModifiedBy
    @ManyToOne
    private Account updatedBy;

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Account getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(Account createdBy) {
        this.createdBy = createdBy;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

    public Account getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(Account updatedBy) {
        this.updatedBy = updatedBy;
    }

    public int getUp() {
        return up;
    }

    public void setUp(int up) {
        this.up = up;
    }

    public int getDown() {
        return down;
    }

    public void setDown(int down) {
        this.down = down;
    }

    public boolean isBest() {
        return best;
    }

    public void setBest(boolean best) {
        this.best = best;
    }

    public Post getPost() {
        return post;
    }

    public void setPost(Post post) {
        this.post = post;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", comment='" + comment + '\'' +
                ", post=" + post +
                ", up=" + up +
                ", down=" + down +
                ", best=" + best +
                ", created=" + created +
                ", createdBy=" + createdBy +
                ", updated=" + updated +
                ", updatedBy=" + updatedBy +
                '}';
    }
 }

사용

  • @Configuration 클래스에 @EnableJpaAuditing 선언
  • AuditorAware<Domain> 구현클래스 Bean 의 이름auditorAwareRef 속성에 기재한다.
  • 원래는 SpringSecurity 의 SecurityContextHolder 를 사용해서 Authetnication 객체를 가져와 유저정보를 찾아내지만, 스프링 시큐리티 설정을 따로 안해놓아서 Entity 클래스를 사용했음.
@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef = "accountAuditAware")
public class SpringdatajpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringdatajpaApplication.class, args);
    }

}

//AuditorAware<Domain> 구현클래스
@Service
public class AccountAuditAware implements AuditorAware<Account> {

    @Autowired
    private AccountRepository accountRepository;
    @Override
    public Optional<Account> getCurrentAuditor() {
        Account account = new Account();
        account.setUsername("Soomin");
        account.setPassword("Jung");
        account.setAge(28);
        System.out.println("Looking for current user");
        return Optional.of(accountRepository.save(account));
    }
}

///
@RunWith(SpringRunner.class)
@SpringBootTest
public class AuditingTest {

    @Autowired
    private AccountRepository accountRepository;
    @Autowired
    private CommentRepository commentRepository;
    @Autowired
    private PostRepository postRepository;

    @Test
    @Transactional
    public void testAuditing(){
        Post post = new Post();
        post.setTitle("JPA Auditing");

        Post savedPost = postRepository.save(post);

        Comment comment = new Comment();
        comment.setTitle("is");
        comment.setComment("Hibernate?");
        comment.setUp(1);
        comment.setBest(true);
        comment.setPost(savedPost);

        Comment savedComment = commentRepository.save(comment);

        List<Comment> all = commentRepository.findAll();
        all.forEach(System.out::println);
    }
}

// 쿼리 진행 결과
Hibernate: 
    call next value for hibernate_sequence
Looking for current user
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        post
        (created, title, id) 
    values
        (?, ?, ?)
2020-11-19 20:24:19.007 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [TIMESTAMP] - [null]
2020-11-19 20:24:19.008 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [JPA Auditing]
2020-11-19 20:24:19.010 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [1]
Hibernate: 
    insert 
    into
        account
        (age, password, username, id) 
    values
        (?, ?, ?, ?)
2020-11-19 20:24:19.014 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [28]
2020-11-19 20:24:19.014 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Jung]
2020-11-19 20:24:19.015 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [Soomin]
2020-11-19 20:24:19.015 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [BIGINT] - [2]
Hibernate: 
    insert 
    into
        comment
        (best, comment, created, created_by_id, down, post_id, title, up, updated, updated_by_id, id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2020-11-19 20:24:19.017 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BOOLEAN] - [true]
2020-11-19 20:24:19.017 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Hibernate?]
2020-11-19 20:24:19.017 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [TIMESTAMP] - [Thu Nov 19 20:24:18 KST 2020]
2020-11-19 20:24:19.028 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [BIGINT] - [2]
2020-11-19 20:24:19.028 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [INTEGER] - [0]
2020-11-19 20:24:19.029 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [6] as [BIGINT] - [1]
2020-11-19 20:24:19.029 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [7] as [VARCHAR] - [is]
2020-11-19 20:24:19.029 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [8] as [INTEGER] - [1]
2020-11-19 20:24:19.029 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [9] as [TIMESTAMP] - [Thu Nov 19 20:24:18 KST 2020]
2020-11-19 20:24:19.030 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [10] as [BIGINT] - [2]
2020-11-19 20:24:19.030 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [11] as [BIGINT] - [3]
Hibernate: 
    select
        comment0_.id as id1_1_,
        comment0_.best as best2_1_,
        comment0_.comment as comment3_1_,
        comment0_.created as created4_1_,
        comment0_.created_by_id as created_9_1_,
        comment0_.down as down5_1_,
        comment0_.post_id as post_id10_1_,
        comment0_.title as title6_1_,
        comment0_.up as up7_1_,
        comment0_.updated as updated8_1_,
        comment0_.updated_by_id as updated11_1_ 
    from
        comment comment0_
2020-11-19 20:24:19.045 TRACE 220 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_1_] : [BIGINT]) - [3]
Comment{id=3, title='is', comment='Hibernate?', post=Post{id=1, title='JPA Auditing', created=null}, up=1, down=0, best=true, created=Thu Nov 19 20:24:18 KST 2020, createdBy=Account{id=2, username='Soomin', password='Jung', age=28}, updated=Thu Nov 19 20:24:18 KST 2020, updatedBy=Account{id=2, username='Soomin', password='Jung', age=28}}

JPA LifeCycle Event 를 사용하는 방법도 있다.

엔티티에 변화가 일어날 시 Callback 을 실행할 수 있는 이벤트를 발생시킨다.

이를 받는 리스너를 등록하는 어노테이션들은 다음과 같다. 직관적 구성을 가지고 있다.

  • @PrePersist 
  • @PreUpdate
  • @PreRemove
  • @PostPersist
  • @PostUpdate
  • @PostRemove
  • @PostLoad
@Entity
public class Comment {

    @Id @GeneratedValue
    private Long id;

    private String title;

    private String comment;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;

    private int up;

    private int down;

    private boolean best;

    // 언제 만들었는가
    @CreatedDate
    private Date created;
    // 누가 만들었는가
    @CreatedBy
    @ManyToOne
    private Account createdBy;
    // 언제 수정했는가
    @LastModifiedDate
    private Date updated;
    // 누가 수정했는가
    @LastModifiedBy
    @ManyToOne
    private Account updatedBy;

    public Date getCreated() {
        return created;
    }

    public void setCreated(Date created) {
        this.created = created;
    }

    public Account getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(Account createdBy) {
        this.createdBy = createdBy;
    }

    public Date getUpdated() {
        return updated;
    }

    public void setUpdated(Date updated) {
        this.updated = updated;
    }

    public Account getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(Account updatedBy) {
        this.updatedBy = updatedBy;
    }

    public int getUp() {
        return up;
    }

    public void setUp(int up) {
        this.up = up;
    }

    public int getDown() {
        return down;
    }

    public void setDown(int down) {
        this.down = down;
    }

    public boolean isBest() {
        return best;
    }

    public void setBest(boolean best) {
        this.best = best;
    }

    public Post getPost() {
        return post;
    }

    public void setPost(Post post) {
        this.post = post;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", comment='" + comment + '\'' +
                ", post=" + post +
                ", up=" + up +
                ", down=" + down +
                ", best=" + best +
                ", created=" + created +
                ", createdBy=" + createdBy +
                ", updated=" + updated +
                ", updatedBy=" + updatedBy +
                '}';
    }

    @PrePersist
    public void prePersist(){
        System.out.println("=======================");
        System.out.println("Pre Persist is called.");
        System.out.println("=======================");
    }
}

///
	@Test
    @Transactional
    public void testAuditing(){
        Post post = new Post();
        post.setTitle("JPA Auditing");

        Post savedPost = postRepository.save(post);

        Comment comment = new Comment();
        comment.setTitle("is");
        comment.setComment("Hibernate?");
        comment.setUp(1);
        comment.setBest(true);
        comment.setPost(savedPost);

        Comment savedComment = commentRepository.save(comment);

        List<Comment> all = commentRepository.findAll();
        all.forEach(System.out::println);
    }
    
/// 영속화 시키기전에 Callback 이 실행되는 모습
Hibernate: 
    call next value for hibernate_sequence
=======================
Pre Persist is called.
=======================
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        post
        (created, title, id) 
    values
        (?, ?, ?)
2020-11-19 20:34:20.931 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [TIMESTAMP] - [null]
2020-11-19 20:34:20.932 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [JPA Auditing]
2020-11-19 20:34:20.933 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [1]
Hibernate: 
    insert 
    into
        comment
        (best, comment, created, created_by_id, down, post_id, title, up, updated, updated_by_id, id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2020-11-19 20:34:20.938 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BOOLEAN] - [true]
2020-11-19 20:34:20.939 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Hibernate?]
2020-11-19 20:34:20.939 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [TIMESTAMP] - [null]
2020-11-19 20:34:20.939 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [BIGINT] - [null]
2020-11-19 20:34:20.941 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [5] as [INTEGER] - [0]
2020-11-19 20:34:20.942 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [6] as [BIGINT] - [1]
2020-11-19 20:34:20.942 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [7] as [VARCHAR] - [is]
2020-11-19 20:34:20.942 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [8] as [INTEGER] - [1]
2020-11-19 20:34:20.943 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [9] as [TIMESTAMP] - [null]
2020-11-19 20:34:20.943 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [10] as [BIGINT] - [null]
2020-11-19 20:34:20.943 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [11] as [BIGINT] - [2]
Hibernate: 
    select
        comment0_.id as id1_1_,
        comment0_.best as best2_1_,
        comment0_.comment as comment3_1_,
        comment0_.created as created4_1_,
        comment0_.created_by_id as created_9_1_,
        comment0_.down as down5_1_,
        comment0_.post_id as post_id10_1_,
        comment0_.title as title6_1_,
        comment0_.up as up7_1_,
        comment0_.updated as updated8_1_,
        comment0_.updated_by_id as updated11_1_ 
    from
        comment comment0_
2020-11-19 20:34:20.954 TRACE 3360 --- [           main] o.h.type.descriptor.sql.BasicExtractor   : extracted value ([id1_1_] : [BIGINT]) - [2]