Java/Spring2019. 3. 13. 23:02
반응형

 스프링(Spring)에 JPA구현체로 하이버네이트(Hibernate)를 사용할 경우 AttributeConverter에서 스프링의 DI를 사용할 수 없는 것이 오랫동안 아쉬웠습니다. JPA의 스펙상에는 2.2에서 CDI지원이 추가 되었으나 그동안은 하이버네이트에서 지원하지 않았기 때문에 약간의 꼼수를 통해 스프링 빈을 사용해왔습니다. 사실 CDI 지원은 JPA 2.1부터 추가 된것으로 보이나 2.1에서는 EntityListener에 대한 지원만이 정의 되었으며 2.2부터 AttributeConverter가 추가되었습니다.



CDI(Contexts and Dependency Injection) 는 JavaEE의 의존성 주입(DI)을 의미함(JSR 299)

JPA 2.2는 2017년8월4일에 릴리스(JSR338)


 

 하이버네이트 5.3.0(HHH-12135)에서 드디어 JPA 2.1 및 JPA2.2의 CDI를 지원하기 시작함으로서 아쉬운 상황이 해소되었습니다. CDI를 지원하기 위해 BeanContainer라는 인터페이스가 추가되었으며 JavaEE관련 해당 인터페이스 구현이 제공되고 있습니다. 스프링도 5.1(SPR-16305) 부터 SpringBeanContainer라는 해당 인터페이스의 구현을 제공하기 시작합니다. 따라서 해당버전을 사용할 경우 간단한 설정만으로 AttributeConverter에 DI를 적용할 수 있습니다. 스프링 부트의 경우 2.1.0부터 자동설정되는 것으로 보입니다.(정확한 버전은 확인하지 못했습니다)


 따라서 스프링 부트를 사용하는 경우는 지원하는 버전의 경우 별다른 설정 없이도 적용되므로 AttributeConverter를 스프링 빈으로 설정하면 컨버터에 DI가 적용되므로 편리하게 사용할 수 있을 것으로 보입니다.


 스프링 부트를 사용하지 않는 경우는 아래와 같이 별도의 설정이 필요합니다. 


    //...
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean emf 
            = new LocalContainerEntityManagerFactoryBean();
        //....
        emf.getJpaPropertyMap()
            .put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));
        //.....
        return emf;
    }
    //...

※xml 베이스의 설정도 가능하며 자세한 내용은 이 링크를 참고하시기 바랍니다.




  또한, 하이버네이트는 5.3.0 이상을 사용할 수 있지만 5.1이전의 스프링을 사용할 수 밖에 없는 상황이라고 하면 SpringBeanContainer의 구현을 참고하여 해당 기능을 직접 구현하여 사용할 수 있을 듯 합니다. 하지만 하이버네이트도 이전 버전만 사용가능한 상황의 경우는 약간의 꼼수를 동원해야할 듯 합니다. 다른 글에서도 많이 소개된 패턴이므로 간단하게 소개하도록 하겠습니다.


 

아래와 같이 ApplicationContext를 레퍼런스를 static 필드로 가질수 있는 Holder클래스를 정의.

public class ApplicationContextHolder {
    private static ApplicationContext applicationContext;
    
    public void setApplicationContext(ApplicationContext applicationContext) {
        ApplicationContextHolder.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
}

 

스프링 초기화시에 ApplicationContext의 레퍼런스를 설정.

public class SomeConfiguration {
    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        ApplicationContextHolder.setApplicationContext(this.applicationContext);
    }
}

 

ApplicationContextHolder를 이용하여 AttributeConverter내에서 static으로 접근하여 스프링 빈을 얻어 사용.

public class CustomConverter implements AttributeConverter {
    @Override
    public String convertToDatabaseColumn(String attribute) {
        if ((attribute != null) && !attribute.isEmpty()) {
            return ApplicationContextHolder.getBean(Encoder.class).encode(attribute);
        }
        return attribute;
    }

    @Override
    public String convertToEntityAttribute(String dbData) {
        if ((dbData != null) && !dbData.isEmpty()) {
            return ApplicationContextHolder.getBean(Encoder.class).decode(dbData);
        }
        return dbData;
    }
}

※위에서 소개한 방법은 그다지 스프링다운 방식은 아니므로 추천하지는 않습니다만, 구버전을 사용하여 어쩔수 없는 경우에 한하여 사용하도록 합시다.


 한동안 지원되지 않아 불편했던 AttributeConverter에서 스프링 DI가 지원됨으로서 좀더 스프링스러운 방법으로 하이버네이트를 통합(integration)하여 사용할 수 있을듯 합니다. 여러분들도 가급적 새로 지원되는 기능을 이용하는 것을 추천 드립니다.



Posted by Reiphiel