Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

Entity Listener - 1 본문

JPA

Entity Listener - 1

Seung__ 2021. 6. 15. 23:00

Listener : 이벤트를 관찰하고 있다가 이벤트가 발생하면 특정 동작을 진행하는 것을 말한다.

Entity Listener는 Entity가 동작하는 몇 가지 방법에 대한 이벤트를 관찰하고 있음.

 

1. Listener 관련 annotaions


@PrePersist Persist, insert method 실행 전(해당 엔티티 저장 전)
@PreUpdate Merge method 실행 전(해당 엔티티 업데이트 전)
@PreRemove Delete method  실행 전(해당 엔티티 삭제 전)
@PostPersist Persist method 실행 후(해당 엔티티 저장 후)
@PostUpdate Merge method 실행 후(헤당 엔티티 업데이트 후)
@PostRemove Delete method  실행 후(해당 엔티티 제거 후)
@PostLoad Select조회 직후(해당 엔티티 조회 후)

먼저 각 항목이 언제 작동되는지 살펴보자.

만들어둔 User class에 다음과 같이 추가한다.

@PrePersist
    public void prePersist(){
        System.out.println(">>>>pre Persist");
    }

    @PostPersist
    public void postPersist(){
        System.out.println(">>>>post Persist");

    }

    @PreUpdate
    public void preUpdate(){
        System.out.println(">>>>>>pre Update");
    }

    @PostUpdate
    public void postUpdate(){
        System.out.println(">>>>>>>post Update");
    }

    @PreRemove
    public void preRemove(){
        System.out.println(">>>>pre Remove");
    }

    @PostRemove
    public void postRemove(){
        System.out.println(">>>>post remove");
    }

    @PostLoad
    private void postLoad(){
        System.out.println(">>>>post load");
    }

insert, update, delete, select모두 동작하도록 테스트 코드를 적어준당

@Test
    void listenerTest(){

        User user = new User();
        user.setEmail("hyun10@navber.com");
        user.setName("hyun10");

        userRepository.save(user);


        User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user2.setName("hyyyyyyyyyyyyyyun");

        userRepository.save(user2);


        userRepository.deleteById(4L);


    }

실행결과 각 항목이 작동하는 위치? 를 알 수 있다. 닉값 그대로 한다.

>>>>pre Persist
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        user
        (created_at, email, gender, name, updated_at, id) 
    values
        (?, ?, ?, ?, ?, ?)
>>>>post Persist
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.gender as gender4_0_0_,
        user0_.name as name5_0_0_,
        user0_.updated_at as updated_6_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
>>>>post load
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.gender as gender4_0_0_,
        user0_.name as name5_0_0_,
        user0_.updated_at as updated_6_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
>>>>post load
>>>>>>pre Update
Hibernate: 
    update
        user 
    set
        created_at=?,
        email=?,
        gender=?,
        name=?,
        updated_at=? 
    where
        id=?
>>>>>>>post Update
Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.created_at as created_2_0_0_,
        user0_.email as email3_0_0_,
        user0_.gender as gender4_0_0_,
        user0_.name as name5_0_0_,
        user0_.updated_at as updated_6_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
>>>>post load
>>>>pre Remove
Hibernate: 
    delete 
    from
        user 
    where
        id=?
>>>>post remove

 

실제로 사용하는 예를 들어보자,  data를 저장할 때 updated time, created time을 다음과 같이 set을 해준다고 하자.

 @Test
    void prePersistTest(){
        User user = new User();
        user.setEmail("hyun10@navver.com");
        user.setName("hyun10");
        user.setCreatedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());

        userRepository.save(user);

        System.out.println(userRepository.findByEmail("hyun10@navver.com"));

    }

그런데 이러한 경우는 실수로 해당 코드를 지워버려 데이터 정합성에 문제가 생길 수 있다.ㅜ

 

그래서 아예~ Entity에 @PrePersist설정을 하면 실수없이 data 저장 전에 원하는 기능을 수행할 수 있다.

 

그러면 앞에서 작성한 prePersist method를 살짝 수정해보자.

@PrePersist를 지정하여 insert전에 생성시간, 업데이트 시간을 입력하도록 구성했다.

 @PrePersist
    public void prePersist(){
        this.createdAt = LocalDateTime.now(); //자기의 값을 변경시키기 때문에.
        this.updatedAt = LocalDateTime.now();
    }
    

실행 결과를 보면 생성/업데이트 시간이 입력된 것을 볼 수있당.

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.created_at as created_2_0_,
        user0_.email as email3_0_,
        user0_.gender as gender4_0_,
        user0_.name as name5_0_,
        user0_.updated_at as updated_6_0_ 
    from
        user user0_ 
    where
        user0_.email=?
User(name=hyun10, email=hyun10@navver.com, 
    createdAt=2021-06-15T22:02:52.202511, 
    updatedAt=2021-06-15T22:02:52.202511, id=6, testData=null, gender=null)

 

 

업데이트도 유사하게 작성해주면 된다.

@PreUpdate
    public void preUpdate(){
        this.updatedAt = LocalDateTime.now();
    }

 

  @Test
    void preUpdateTest(){
        User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);

        System.out.println("as - is : " + user);

        user.setName("lalalalalalalala");

        userRepository.save(user);

        System.out.println(" to be : " + userRepository.findAll().get(0));//이후 영속성 공부시...
    }

업데이트 시간이 변경되었당

as - is : User(name=hyun, email=hyun@naver.com, 
          createdAt=2021-06-15T22:12:47.858239, 
          updatedAt=2021-06-15T22:12:47.858239, id=1, testData=null, gender=null)
 to be : User(name=lalalalalalalala, email=hyun@naver.com, 
          createdAt=2021-06-15T22:12:47.858239, 
          updatedAt=2021-06-15T22:12:49.365017, id=1, testData=null, gender=null)

 

2. Book manager 만들기


Listener를 적용하기 전에 User는 그만 사용하고 Book을 만들자.

- Book class

package com.jpa.bookmanager.domain;

import ch.qos.logback.core.joran.spi.NoAutoStart;


import javax.persistence.*;

import lombok.*;

import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String author;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    @PrePersist
    public void prePersist(){
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdated(){
        this.updatedAt = LocalDateTime.now();
    }

}

- BookRepository

package com.jpa.bookmanager.repository;

import com.jpa.bookmanager.domain.Book;
import org.springframework.data.jpa.repository.JpaRepository;

public interface BookRepository extends JpaRepository<Book, Long> {//enum타입, id타입



}

 

- BookRepositoryTest

package com.jpa.bookmanager.repository;

import com.jpa.bookmanager.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class BookRepositoryTest {

    @Autowired //Bookrepository와 연결?
    private BookRepository bookRepository;

    @Test
    void bookTest(){
        Book book  = new Book();
        book.setName("에반게리온");
        book.setAuthor("안노");

        bookRepository.save(book);
        System.out.println(bookRepository.findAll());

    }
}

 

여기서 의문점. 어느 entity나 생성시간, 업데이트 시간은 필요할텐데.. 매 번 이렇게 해야하나..??

이때! Listener를 사용한다. 즉 Entity Listener를 만들어서 여러 Entity에 사용을 하자!

1) MyentityListener Class

ackage com.jpa.bookmanager.domain;

public class MyEntityListener {
}

2) Book class에 @EntityListeners 지정, value로는 Listener.class를 가져온다.

이를 통해 entity와 Listener가 연결된다?

(기존 @PrePersist, @PreUpdate는 주석처리)

package com.jpa.bookmanager.domain;

import ch.qos.logback.core.joran.spi.NoAutoStart;


import javax.persistence.*;

import lombok.*;

import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = MyEntityListener.class)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String author;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    /*@PrePersist
    public void prePersist(){
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdated(){
        this.updatedAt = LocalDateTime.now();
    }*/

}

 

3) Auditable interface 선언

우리가 사용할 리스너의 역할은 insert 전, update 전 생성/업데이트 시간을 입력하는 역할을 한다.

그러기 위해서는 리스너가 createdAt, updatedAt이라는 변수를 알고 있어야 한다.

이를 위해 interface를 하나 선언해주자. 리스너를 사용할 class에서 구현할 예정이다.

 

lombok을 사용하고 있기 때문에, 직접 보이지는 않지만

getCreatedAt(), getUpdatedAt()등 getter는 이미 존재한다.

package com.jpa.bookmanager.domain;

import java.time.LocalDateTime;

public interface Auditable {

    LocalDateTime getCreatedAt();
    LocalDateTime getUpdatedAt();

    void setCreatedAt(LocalDateTime createdAt);
    void setUpdatedAt(LocalDateTime updatedAt);
}

4) 그다음 Book class에서 implement를 해보자. 이미 다 구현이 되어있어서 에러안뜬다!(@Data)

package com.jpa.bookmanager.domain;

import ch.qos.logback.core.joran.spi.NoAutoStart;


import javax.persistence.*;

import lombok.*;

import java.time.LocalDateTime;

@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = MyEntityListener.class)
public class Book implements Auditable {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private String author;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    /*@PrePersist
    public void prePersist(){
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    @PreUpdate
    public void preUpdated(){
        this.updatedAt = LocalDateTime.now();
    }*/

}

 

5) MyEntityListener 작성

이제 진짜로 리스너를 만들어 보자.

* Listener는 해당 Entity를 받아서 처리를 해줘야 한다. 어떤 타입인지 모르니 Object로 받는다.

   이전에 Book class에서 단순히 PrePersist()함수로 구현했을 때는 this로 entity를 받을 수 있던 것과 차이점이 있따.

package com.jpa.bookmanager.domain;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;

public class MyEntityListener {

    @PrePersist //리스너는 해당 entity를 받아서! 처리를 해줘야 한다.
    public void prePersist(Object o){
        if(o instanceof Auditable){
            //Auditable 객체다? 이 리스너가 사용될 엔티티다?
            ((Auditable) o).setCreatedAt(LocalDateTime.now());
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }

    @PreUpdate //리스너는 해당 entity를 받아서! 처리를 해줘야 한다.
    public void preUpdate(Object o){
        if(o instanceof Auditable){
            //Auditable 객체다? 이 리스너가 사용될 엔티티다?
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }
}

 

6) Book class는 Autitable 인터페이스를 구현하고, Book에 entity Listener를 등록을 해줬다. 

   따라서 entity에  insert / update method 가 실행되기 전 리스너가 이를 감지하고 해당 entity를 받아와서

   entity의 createdAt, updatedAt을 입력할 수 있게 된다! 

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        book
        (author, created_at, name, updated_at, id) 
    values
        (?, ?, ?, ?, ?)
Hibernate: 
    select
        book0_.id as id1_0_,
        book0_.author as author2_0_,
        book0_.created_at as created_3_0_,
        book0_.name as name4_0_,
        book0_.updated_at as updated_5_0_ 
    from
        book book0_
[Book(id=6, name=에반게리온, author=안노, createdAt=2021-06-15T22:51:29.542252, updatedAt=2021-06-15T22:51:29.542252)]

 

결론! 이런 방식으로 리스너를 만들면 여러 entity에서 사용이 가능하다!

'JPA' 카테고리의 다른 글

Entity Listener : 3(@AuditingEntityListener)  (0) 2021.06.16
Entity Listener : 2  (0) 2021.06.16
@Entity 속성 2  (0) 2021.06.15
@Entity 기본 속성 - 1  (0) 2021.06.15
Query Method : paging  (0) 2021.05.27
Comments