JPA 리스너

PGD·3일 전
0

JPA에서 Listener를 통해 엔티티 이벤트를 감지하고 이벤트를 핸들링할 수 있다.

Entity 이벤트 종류

  1. PostLoad: 엔티티가 영속성 컨텍스트에 조회된 직후 또는 refresh를 호출한 후 (2차 캐시에 저장되어 있어도 호출됨)
  2. PrePersist: persist() 메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다.
  3. PreUpdate: 엔티티를 데이터베이스에 수정하기 직전에 호출. - flush 혹은 commit
  4. PreRemove: remove() 메소드 호출을 통해 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 혹은 삭제 명령어로 영속성 전이가 일어날 때도 호출된다. orphanRemoval에 대해서는 flushcommit 시에 호출된다.
  5. PostPersist: flushcommit을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다.
  6. PostUpdate: flushcommit을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출된다.
  7. PostRemove: flushcommit을 호출해서 엔티티를 데이터베이스에서 삭제한 직후에 호출된다.

이벤트 적용 위치

  • 엔티티에 직접 적용
  • 별도의 리스너 등록
  • 기본 리스너 사용

엔티티에 직접 적용

  • 코드
@Entity
@Table(name = "members")
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String memberName;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    @PrePersist
    public void prePersist() {
        System.out.println("Member id=" + this.id);
    }

    @PostPersist
    public void postPersist() {
        System.out.println("Member id=" + this.id);
    }

    @PostLoad
    public void postLoad() {
        System.out.println("PostLoad");
    }

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

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

각각에 메소드에 어노테이션을 달아 줌으로써 리스너로 등록할 수 있다.

별도의 리스너 등록

  • 코드
@Entity
@Table(name = "members")
@EntityListeners(Member.MemberListener.class)
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String memberName;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    public static class MemberListener {

        @PrePersist
        public void prePersist(Object obj) {
            System.out.println("PrePersist, obj=" + obj);
        }

        @PostPersist
        public void postPersist(Object obj) {
            System.out.println("PostPersist, obj=" + obj);
        }

        @PostLoad
        public void postLoad(Object obj) {
            System.out.println("PostLoad, obj=" + obj);
        }

        @PreRemove
        public void preRemove(Object obj) {
            System.out.println("PreRemove, obj=" + obj);
        }

        @PostRemove
        public void postRemove(Object obj) {
            System.out.println("PostRemove, obj=" + obj);
        }
    }
}

상속받 Entity의 Event

@MappedSuperclass를 통해 상속받은 Entity의 Event도 핸들링할 수 있다.

  • BaseEntity
@MappedSuperclass
@Data
public abstract class BaseEntity {
    private LocalDateTime creationTime;
    private LocalDateTime lastModified;

    @PrePersist
    public void prePersist() {
        System.out.println("PrePersist of BaseEntity");
        this.creationTime = LocalDateTime.now();
        this.lastModified = null;
    }
}
  • Member
@Entity
@Table(name = "members")
@EntityListeners(Member.MemberListener.class)
@Data
public class Member extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String memberName;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    public static class MemberListener {

        @PrePersist
        public void prePersist(Object obj) {
            System.out.println("PrePersist, obj=" + obj);
            System.out.println("creationTime=" + ((Member)obj).getCreationTime());
            System.out.println("lastModified=" + ((Member)obj).getLastModified());
        }

        @PostPersist
        public void postPersist(Object obj) {
            System.out.println("PostPersist, obj=" + obj);
        }

        @PostLoad
        public void postLoad(Object obj) {
            System.out.println("PostLoad, obj=" + obj);
        }

        @PreRemove
        public void preRemove(Object obj) {
            System.out.println("PreRemove, obj=" + obj);
        }

        @PostRemove
        public void postRemove(Object obj) {
            System.out.println("PostRemove, obj=" + obj);
        }
    }
}

다음 테스트 코드를 실행하면,

@Test
void test() {
    EntityManager em1 = this.emf.createEntityManager();
    em1.getTransaction().begin();
    Member member = new Member();
    member.setMemberName("HELLO");
    em1.persist(member);
    em1.getTransaction().commit();
    em1.close();

    System.out.println("---------------");

    EntityManager em2 = this.emf.createEntityManager();
    em2.getTransaction().begin();
    Member findMember = em2.find(Member.class, member.getId());
    System.out.println("creationTime=" + findMember.getCreationTime());
    System.out.println("lastModified=" + findMember.getLastModified());
    em2.getTransaction().commit();
    em2.close();

    this.emf.close();
}

다음과 같이 출력된다.

PrePersist, obj=Member(id=null, memberName=HELLO, team=null)
creationTime=null
lastModified=null
PrePersist of BaseEntity
Hibernate: insert into members (creation_time,last_modified,member_name,team_id) values (?,?,?,?)
PostPersist, obj=Member(id=1, memberName=HELLO, team=null)
---------------
Hibernate: select m1_0.id,m1_0.creation_time,m1_0.last_modified,m1_0.member_name,t1_0.id,t1_0.team_name from members m1_0 left join team t1_0 on t1_0.id=m1_0.team_id where m1_0.id=?
PostLoad, obj=Member(id=1, memberName=HELLO, team=null)
creationTime=2024-11-20T16:57:22.332274
lastModified=null

보이는 바와 같이 자식 엔티티의 리스너가 먼저 호출된 후 부모 엔티티의 리스너가 호출된다.

더 세밀한 설정

  • jakarta.persistence.ExcludeDefaultListners: 기본 리스너 무시
  • jakarta.persistence.ExcludeSuperclassListeners: 상위 클래스 이벤트 리스너 무시

JPA Auditing

데이터가 언제 생성되었는지, 언제 수정되었는지 기록하는 것은 중요하다. 그래서 많은 경우에 엔티티의 생성일자, 수정일자를 기록한다. JPA Auditing은 이와 같은 공통적인 엔티티 생성시간, 마지막 수정 시간을 기록하는 기능을 제공해 준다.

  1. JPA Auditing을 활성화한다.
@SpringBootApplication
@EnableJpaAuditing
public class Applicaiton {

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

}
  1. BaseEntity를 다음과 같이 수정
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {

    @CreatedDate
    private LocalDateTime creationTime;

    @LastModifiedDate
    private LocalDateTime lastModified;
}

AuditingEntityListener를 리스너로 등록하고, 생성 시간과 마지막 수정 시간을 기록하는 필드에 각각 어노테이션을 달았다.

테스트

테스트 코드는 다음과 같다.

@SpringBootTest
class SampleTest {

    @Autowired
    private EntityManagerFactory emf;

    @Test
    void test() throws Exception {
        EntityManager em1 = this.emf.createEntityManager();
        em1.getTransaction().begin();
        Member member = new Member();
        member.setMemberName("HELLO");
        em1.persist(member);
        em1.getTransaction().commit();
        em1.close();

        System.out.println("---------------");

        EntityManager em2 = this.emf.createEntityManager();
        em2.getTransaction().begin();
        Member findMember = em2.find(Member.class, member.getId());
        System.out.println("creationTime=" + findMember.getCreationTime());
        System.out.println("lastModified=" + findMember.getLastModified());
        em2.getTransaction().commit();
        em2.close();

        Thread.sleep(1000);

        System.out.println("-----------------");

        EntityManager em3 = this.emf.createEntityManager();
        em3.getTransaction().begin();
        Member memberToUpdate = em3.find(Member.class, member.getId());
        memberToUpdate.setMemberName("MODIFIED");
        em3.getTransaction().commit();
        em3.close();

        System.out.println("-----------------");

        EntityManager em4 = this.emf.createEntityManager();
        em4.getTransaction().begin();
        Member updatedMember = em4.find(Member.class, member.getId());
        System.out.println("creationTime=" + updatedMember.getCreationTime());
        System.out.println("lastModified=" + updatedMember.getLastModified());
        em4.getTransaction().commit();

        this.emf.close();
    }
}

출력 결과는 다음과 같다.

creationTime=2024-11-20T17:09:57.627068900
lastModified=2024-11-20T17:09:57.627068900
---------------
PostLoad, obj=Member(id=1, memberName=HELLO, team=null)
creationTime=2024-11-20T17:09:57.627069
lastModified=2024-11-20T17:09:57.627069
-----------------
PostLoad, obj=Member(id=1, memberName=HELLO, team=null)
-----------------
PostLoad, obj=Member(id=1, memberName=MODIFIED, team=null)
creationTime=2024-11-20T17:09:57.627069
lastModified=2024-11-20T17:09:58.801323

처음 엔티티를 영속화할 때 creationTimelastModified가 기록되고, 이후 엔티티를 수정할 때 lastModified 값이 업데이트되는 것을 확인할 수 있다.

profile
student

0개의 댓글