[Spring] Entity의 Listener 활용

WOOK JONG KIM·2022년 11월 19일
0

패캠_java&Spring

목록 보기
54/103
post-thumbnail

Listener : 어떠한 이벤트가 발생하면 호출되어 특정 동작을 수행

Entity Listener Annotation

<method 실행 전>

@PrePersist: insert method가 호출되기 전
@PreUpdate: merge method가 호출되기 전
@PreRemove: delete method가 호출되기 전

<method 실행 이후>
@PostPersist: insert method가 호출된 이후
@PostUpdate: merge method가 호출된 이후
@PostRemove: delete method가 호출된 이후
@PostLoad: select 조회가 된 직후

메서드 생성(User.java)

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
@Table(name="user")
public class User {

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

test 코드

	@Test
    void listenerTest(){
        User user = new User();
        user.setEmail("martin@naver.com");
        user.setName("martin");

        userRepository.save(user);

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

        userRepository.save(user2);

        userRepository.deleteById(4L);
    }
>>> perPersist
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        user
        (created_at, email, gender, name, updated_at, id) 
    values
        (?, ?, ?, ?, ?, ?)
>>> PostPersist
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.created_at as created_2_1_0_,
        user0_.email as email3_1_0_,
        user0_.gender as gender4_1_0_,
        user0_.name as name5_1_0_,
        user0_.updated_at as updated_6_1_0_ 
    from
        user user0_ 
    where
        user0_.id=?
>>> PostLoad
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.created_at as created_2_1_0_,
        user0_.email as email3_1_0_,
        user0_.gender as gender4_1_0_,
        user0_.name as name5_1_0_,
        user0_.updated_at as updated_6_1_0_ 
    from
        user user0_ 
    where
        user0_.id=?
>>> PostLoad
>>> PreUpdate
Hibernate: 
    update
        user 
    set
        email=?,
        gender=?,
        name=?,
        updated_at=? 
    where
        id=?
>>> PostUpdate
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.created_at as created_2_1_0_,
        user0_.email as email3_1_0_,
        user0_.gender as gender4_1_0_,
        user0_.name as name5_1_0_,
        user0_.updated_at as updated_6_1_0_ 
    from
        user user0_ 
    where
        user0_.id=?
>>> PostLoad
>>> PreRemove
Hibernate: 
    delete 
    from
        user 
    where
        id=?
>>> PostRemove

사용 이유 예시(Jpa Auditing)

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

위 경우 insert,update 쿼리 실행 전 각 변수에 값이 들어감


여러 객체에서 공통적으로 Entity Listener 사용

Entity 객체에 @EntityListeners를 이용하여 공통화 가능

  • 다형성 처리가 가능한 interface 생성 (Auditable.java)
  • Listener 공통화 할 EntityListener 생성 (MyEntityListener.java)

Auditable.java


public interface Auditable {
    LocalDateTime getCreatedAt();
    LocalDateTime getUpdatedAt();

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

MyEntityListener

public class MyEntityListener {
    @PrePersist
    public void prePersist(Object o){
        if(o instanceof Auditable){
            ((Auditable) o).setCreatedAt(LocalDateTime.now());
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }

    @PreUpdate
    public void preUpdate(Object o){
        if(o instanceof Auditable){
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }
}

User

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

	@Column(updatable = false)
    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

Book

@Entity
@NoArgsConstructor
@Data
@EntityListeners(value ={ MyEntityListener.class })
public class Book implements Auditable{
		...
		
	private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

MyEntityListener 안에 메소드들은 어떤 Entity를 받는지 알기 위해 Object 파라미터를 받음
→ Listener에선 Object가 Auditable을 상속받은 class인지 체크 후 실행


History 용도로 Listener 사용

보통 update로 인한 수정이 일어나기 전 데이터를 저장하기 위해 사용

UserHistory

@Entity
@NoArgsConstructor
@Data
@EntityListeners(value = MyEntityListener.class)
public class UserHistory implements Auditable{
    @Id
    @GeneratedValue
    private Long id;

    private Long userId;

    private String name;

    private String email;

    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}
public interface UserHistoryRepository extends JpaRepository<UserHistory, Long> {
}
public class UserEntityListener {

    @PrePersist
    @PreUpdate
    public void prePersistAndUpdate(Object o){
        UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);

        User user = (User) o;
        UserHistory userHistory = new UserHistory();
        userHistory.setUserId(user.getId());
        userHistory.setName(user.getName());
        userHistory.setEmail(user.getEmail());

        userHistoryRepository.save(userHistory);
    }
}

jpa에 Listener는 @Component를 사용 못함, 즉 @Autowired를 할 수 없음
→ 그래서 BeanUtils를 통해 bean을 주입받아 사용

Bean Utils

@Component
public class BeanUtils implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanUtils.applicationContext = applicationContext;
    }

    // 클래스에 맞는 빈 주입
    public static <T> T getBean(Class<T> clazz){
        return applicationContext.getBean(clazz);
    }
}

test

	@Test
    void userHistoryTest(){
        User user = new User();
        user.setEmail("martin-new@naver.com");
        user.setName("martin-new");

        userRepository.save(user);

        user.setName("martin-new-new");

        userRepository.save(user);

        userHistoryRepository.findAll().forEach(System.out::println);

    }
UserHistory(id=6, userId=null, name=martin-new, email=martin-new@naver.com, createdAt=2022-11-19T18:19:43.775190, updatedAt=2022-11-19T18:19:43.775197)
UserHistory(id=8, userId=7, name=martin-new-new, email=martin-new@naver.com, createdAt=2022-11-19T18:19:43.796948, updatedAt=2022-11-19T18:19:43.796952)

AuditingEntityListener.class

MyEntityListener처럼 등록날짜, 수정날짜, 등록자, 수정자는 많은 곳에서 사용 필요

JPA, Spring Boot에선 @EnableJpaAuditing, AuditingEntityListener.class, annotation을 통해 사용 가능

@CreatedDate: 등록날짜, @LastModifiedDate: 수정날짜
@CreatedBy: 등록자, @LastModifiedBy: 수정자

AuditingEntityListener의 실행 결과는 MyEntityListener의 결과 동일

@SpringBootApplication
@EnableJpaAuditing
public class BookmanagerApplication {

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

User

@EntityListeners(value = {AuditingEntityListener.class, UserEntityListener.class})
public class User implements Auditable {
		...
		@Column(updatable = false)
    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

앞선 코드 Refactoring

createdAt, updatedAt은 여러 Entity에 공통으로 포함되어 있음
→ BaseEntity를 만들어 그 곳에서 관리하는 것으로 수정

@MappedSuperclass: 상속받는 Entity에 컬럼으로 포함할 때 사용
@ToString(callSuper = true) : 부모 Entity도 toString에 포함할 때 사용
@EqualsAndHashCode(callSuper = true) : 부모 Entity도 동등 비교에 포함할 때 사용

BaseEntity

@Data
@MappedSuperclass
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity {
    @Column(updatable = false)
    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;
}

User,Book,UserHistory에는 @ToString(callSuper = true) 와 @EqualsAndHashCode(callSuper=true) 지정 및 BaseEntity 상속 받음

//@ToString, @EqualsAndHashCode 선언 전
UserHistory(id=6, userId=null, name=Hong, email=test-new@gmail.com)
UserHistory(id=8, userId=7, name=Hong-new, email=test-new@gmail.com)

//@ToString, @EqualsAndHashCode 선언 후
UserHistory(super=BaseEntity(createdAt=2021-07-24T09:09:04.973194, updatedAt=2021-07-24T09:09:04.973194), id=6, userId=null, name=Hong, email=test-new@gmail.com)
UserHistory(super=BaseEntity(createdAt=2021-07-24T09:09:05.123189, updatedAt=2021-07-24T09:09:05.123189), id=8, userId=7, name=Hong-new, email=test-new@gmail.com)
profile
Journey for Backend Developer

0개의 댓글