CDI 통합과 커스텀 Repository 구현

뾰족머리삼돌이·2024년 9월 15일

Spring Data JPA

목록 보기
8/9

CDI 통합

Spring Data에서 기본적으로 Repository 인터페이스는 컨테이너에 의해 인스턴스화 되어 관리된다.
Java Config 혹은 XML Config를 통해 JPA Repository를 활성화시키면 하위 패키지들의 Repository 인터페이스들을 Bean으로 생성하고 등록한다.

Spring Data JPA 1.1.0 버전에서는 CDI( Contexts and Dependency Injection ) 환경에서 Repository 추상화를 사용할 수 있는 CDI Extension 을 제공한다.

그렇다면 CDI란 무엇인가?

자카르타 문서에 의하면, CDI는 Spring이 아닌 Jakarta EE의 여러 기능 중 하나다.
이를 활성화 했을 때, 객체의 의존성을 자동으로 제공할 수 있으며 CDI에서 의존성의 수명주기를 관리해준다.

서블릿 환경에서 Spring의 @Component, @Autowired와 유사하게 @RequestScoped, @Inject를 활용한 의존성 관리가 가능하다.

@RequestScoped
public class MessageB implements Message { ... }

@WebServlet("/cdiservlet")
public class NewServlet extends HttpServlet {
    @Inject private Message message;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
                  throws IOException {
        response.getWriter().write(message.get());
    }
}

즉, CDI는 Spring이 아닌 자바 표준에서의 의존성 관리기능이다.
이번에 소개하는 CDI 통합은 IoC 컨테이너에서 Spring의 DI와 자바 표준의 CDI의 의존성관리를 모두 관리할 수 있는 확장을 제공한다는 내용이다.

Spring Data JPA에서의 CDI 통합을 활성화하기 위해서는 Classpath에 Spring Data JPA JAR 파일을 포함시켜야한다.
또한, CDI는 Jakarta EE의 기능이기 때문에 관련 설정이 필요하다.

Java(Jakarta) EE와 Spring의 관계에 대한 포스팅은 이 글을 참고하는걸 추천한다
7년 전 글이지만 java의 역사와 Spring의 탄생 등을 이해하는데 좋다

implementation 'jakarta.enterprise:jakarta.enterprise.cdi-api:4.0.1'

결과적으로 이런 의존성을 추가해줘야 CDI 환경과 관련된 코드를 Spring 에서 작성할 수 있다.

class EntityManagerFactoryProducer {

  @Produces
  @ApplicationScoped
  public EntityManagerFactory createEntityManagerFactory() {
    return Persistence.createEntityManagerFactory("my-persistence-unit");
  }

  public void close(@Disposes EntityManagerFactory entityManagerFactory) {
    entityManagerFactory.close();
  }

  @Produces
  @RequestScoped
  public EntityManager createEntityManager(EntityManagerFactory entityManagerFactory) {
    return entityManagerFactory.createEntityManager();
  }

  public void close(@Disposes EntityManager entityManager) {
    entityManager.close();
  }
}

위 코드는 CDI 환경에서 EntityManagerFactoryEntityManager의 라이프사이클을 관리 예시코드다.

좀 더 자세한 내용은 Spring 문서Jakatra CDI 문서를 참고하자

커스텀 Repository 구현

Spring Data JPA에서는 이미 구현된 쿼리메서드를 사용하여 손쉽게 DB와의 상호작용이 가능하다.
하지만, 이러한 쿼리메서드가 애플리케이션의 기능에 충분하지 않은경우 별도의 커스텀 Repository를 작성하는 것도 가능하다.

interface CustomizedUserRepository {
  void someCustomMethod(User user);
}

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}

위 예시와 같이 커스텀한 Repository 인터페이스를 작성하고 구현체를 통해 동작할 작업을 작성할 수 있다.
이 과정에서 Spring 표준 DI를 이용하여 다른 Bean들을 주입받아 사용할 수도 있다.

interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

이후에는 Spring Data JPA 프레임워크에서 제공되는 표준 인터페이스와 함께 상속하여 커스텀 Repository에 작성된 쿼리메서드를 사용할 수 있다.
이렇게 작성된 커스텀 쿼리메서드들은 동일한 시그니쳐를 가지고있다면 표준 인터페이스의 쿼리메서드보다 우선순위가 높다.

interface CustomizedSave<T> {
  <S extends T> S save(S entity);
}

class CustomizedSaveImpl<T> implements CustomizedSave<T> {

  public <S extends T> S save(S entity) {
    // Your custom implementation
  }
}

예를 들어, 이런식으로 표준 인터페이스의 save()를 재정의하는 것이 가능하다.

Spring Data JPA는 커스텀 인터페이스 하위의 클래스들을 탐색하여 커스텀 구현체를 식별해내며,
이러한 클래스들은 기본적으로 Impl 접미사를 추가하는 명명규칙을 따라야한다.

만약, 이 접미사를 수정하고 싶다면 @EnableJpaRepositoriesrepositoryImplementationPostfix 속성을 작성하면 된다.
작성하고 나면 Impl이 붙은 구현체를 먼저 탐색하고, 작성한 접미사가 붙은 구현체를 탐색한다.

서로 다른 패키지에서 동일한 이름의 repository와 구현체가 존재한다면,
이를 식별하기 위해 @Component("specialCustom")와 같이 별도의 Bean 이름을 작성해줘야 한다.

Base Repository 커스텀

class MyRepositoryImpl<T, ID>
  extends SimpleJpaRepository<T, ID> {

  private final EntityManager entityManager;

  MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);

    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }

  @Transactional
  public <S extends T> S save(S entity) {
    // implementation goes here
  }
}

앞선 방식처럼 부분적인 쿼리메서드의 재정의 말고도 한 클래스에서 공통적으로 쿼리메서드를 재정의하여 관리할 수도 있다.
기본 쿼리 메서드를 제공하는 SimpleJpaRepository를 상속하여 기존의 쿼리메서드를 재정의할 수 있다.

@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration {}

@EnableJpaRepositoriesrepositoryBaseClass 속성에 해당 클래스를 명시하면 재정의된 쿼리메서드가 사용된다.

EntityManager 주입과 사용

EntityManager는 트랜잭션 관리 등을 위해 단순한 @Autowired를 이용한 주입이 아닌 @PersistenceContext를 이용해야 한다.

여러 스레드에서 동일한 EntityManager를 공유해서는 안된다.

@PersistenceContext외에도 @Autowired@Qualifier를 함께 사용하여 선택적으로 주입받아 관리하는 것도 가능하며,
Spring Data JPA 1.9에 추가된 JpaContext를 이용하여 EntityManager를 관리할 수 있다.

// JpaContext 사용예시
class UserRepositoryImpl implements UserRepositoryCustom {

  private final EntityManager em;

  @Autowired
  public UserRepositoryImpl(JpaContext context) {
    this.em = context.getEntityManagerByManagedType(User.class);
  }}

0개의 댓글