JPA의 EntityListener는 엔티티의 라이프사이클 이벤트(pre-persist, post-update 등)에 대해 특정 작업을 수행할 수 있도록 도와줍니다.
그런데, 일반적으로 JPA EntityListener는 Spring 컨테이너의 관리 대상이 아니므로, Spring Bean을 직접 주입받기 힘든 구조입니다.
때문에 다음과 같은 기법들을 통해 간접적으로 Bean을 주입하는 방법을 생각할 수 있습니다.
방법1: @Configurable 사용하기
@Configurable 어노테이션은 AspectJ와 같은 AOP와 함께 사용되어, JPA가 직접 생성한 객체에도 Spring의 의존성 주입을 적용할 수 있도록 해줍니다.
1. Bean을 주입할 클래스에 @Configurable 적용
@Configurable
public class MyEntityListener {
@Autowired
private FooService fooService;
@PrePersist
public void prePersist(MyEntity myEntity) {
fooService.doSomething();
}
@PostPersist
public void postPersist(MyEntity myEntity) {
fooService.doOtherThing();
}
}
2. AspectJ LoadTimeWeaving 사용 설정
AspectJ 의존성을 추가하고, Main 클래스나 설정 클래스에서 LTW(LoadTimeWeaving)을 활성화합니다.
@Configuration
@EnableLoadTimeWeaving
public class AppConfig {
}
- 장점: 리스너에 필요한 Bean을 자동으로 주입할 수 있음
- 단점:
- AspectJ Weaving 설정이 필요함
- aspectjweaver.jar를 javaagent로 추가해야 하며, META-INF/aop.xml 설정도 필요
- @EnableLoadTimeWeaving을 활성화해야 하며, 일반적인 Spring Bean과 비교해 설정이 복잡함
- Java 17부터 보안 정책 강화 (JEP 396 등)으로 LTW가 기본적으로 동작하지 않음
- Java 16에서 적용된 JEP 396로 인해, LTW가 JDK 내부 API 접근 시 충돌 발생
- Java 17+에서는 JEP 403 (Strong Encapsulation) 등 추가 보안 제한이 적용됨
- AspectJ Weaving 설정이 필요함
※ 참고: AspectJ 1.9.7 Release Notes에 weaving 관련 내용
aspectj/docs/release/README-1.9.7.adoc at master · eclipse-aspectj/aspectj
Contribute to eclipse-aspectj/aspectj development by creating an account on GitHub.
github.com
방법2: ApplicationContext 인터페이스 활용
두 번째 방법은 ApplicationContextAware 인터페이스를 구현하여,
Spring의 ApplicationContext로부터 필요한 Bean을 직접 조회하는 방법입니다.
아래는 특정 엔티티에 대응하는 JpaRepository를 주입받기 위한 추상 클래스 예시입니다.
public abstract class AbstractJpaRepositoryAware<T extends JpaRepository<?, ?>> implements ApplicationContextAware {
protected T repository;
/**
* 사용할 {@link JpaRepository} 타입을 반환한다.
*
* @return 주입받을 {@link JpaRepository} 타입
* @implNote 구현체에서는 해당 메서드를 구현하여 사용할 {@link JpaRepository} 타입을 반환해야 한다.
*/
protected abstract Class<T> repositoryType();
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.repository = applicationContext.getBean(this.repositoryType());
}
}
// 엔티티 변경 이력에 대한 로그를 저장하는 구현체 예시
// JpaRepository<MyEntity, Long>를 상속하는 인터페이스(e.g., MyEntityRepository extends JpaRepository<MyEntity, Long>) 가 있다면,
public class MyEntityAuditingListener extends AbstractJpaRepositoryAware<MyEntityRepository> {
@Override
protected Class<MyEntityRepository> repositoryType() {
return MyEntityRepository.class;
}
@PostPersist
public void postPersist(MyEntity myEntity) {
repository.save(LogType.POST_PERSIST, myEntity);
}
@PostUpdate
public void postUpdate(MyEntity myEntity) {
repository.save(LogType.POST_UPDATE, myEntity);
}
}
이 클래스를 상속한 구체 클래스는 repositoryType() 메서드를 구현해 특정 Repository를 주입받아 사용할 수 있습니다.
- 장점: 명시적으로 어떤 Repository를 사용할지 정의할 수 있음
- 단점: 매 엔티티(또는 리스너)마다 별도의 추상 클래스 상속 및 구현이 필요
'Framework > Spring' 카테고리의 다른 글
[Spring] Java Bean 규약과 JSON 직렬화 문제 (0) | 2024.09.03 |
---|---|
Gradle의 의존성 구성 (0) | 2024.07.21 |
[Spring] 애플리케이션과 NCP Object Storage 연결하기 (0) | 2024.03.25 |
[Spring] 스프링의 캐시 추상화 (0) | 2024.03.17 |
[Spring] SpEL으로 더 강력하게 표현식 작성하기 (0) | 2024.03.13 |