JPA에서 Listener를 통해 엔티티 이벤트를 감지하고 이벤트를 핸들링할 수 있다.
refresh
를 호출한 후 (2차 캐시에 저장되어 있어도 호출됨)persist()
메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재하지 않는다. 새로운 인스턴스를 merge
할 때도 수행된다.flush
혹은 commit
remove()
메소드 호출을 통해 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 혹은 삭제 명령어로 영속성 전이가 일어날 때도 호출된다. orphanRemoval
에 대해서는 flush
나 commit
시에 호출된다.flush
나 commit
을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다.flush
나 commit
을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출된다.flush
나 commit
을 호출해서 엔티티를 데이터베이스에서 삭제한 직후에 호출된다.@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);
}
}
}
@MappedSuperclass
를 통해 상속받은 Entity의 Event도 핸들링할 수 있다.
@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;
}
}
@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은 이와 같은 공통적인 엔티티 생성시간, 마지막 수정 시간을 기록하는 기능을 제공해 준다.
@SpringBootApplication
@EnableJpaAuditing
public class Applicaiton {
public static void main(String[] args) {
SpringApplication.run(Applicaiton.class, args);
}
}
@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
처음 엔티티를 영속화할 때 creationTime
과 lastModified
가 기록되고, 이후 엔티티를 수정할 때 lastModified
값이 업데이트되는 것을 확인할 수 있다.