brunch

You can make anything
by writing

C.S.Lewis

언제까지 자동으로 잡아 주길 원해.

LocalContainerEntityManagerFactoryBean

안녕하세요.


카카오헤어샵 백엔드 개발팀 카이입니다.


이번 포스팅에서는 빈으로 등록된 값 사용한 AttributeConverter를 개발하면서 겪은 문제를 공유하고 어떻게 풀어 갔는지 소개해보려고 합니다.


저의 첫 포스팅 끝까지 읽어주실 거조. :)  (문제 해결이 궁금하시다면 아래 요약으로 SKIP)


문제의 서막


엔티티의 속성 값 중 디비에 저장 및 조회  전/후로 별도 처리가 필요하여 AttributeConverter 구현한 Converter 개발 시작


이때까지만 해도 마음이 편함 그 자체.


하지만 몇 분 후 그전/후 처리 로직에 bean으로 등록된 값을 사용해야 하는 부분에서 문제 발생


예시 코드

위 코드 중 secretVaultKey 값이 bean으로 등록된  값
위 코드 중 secretVaultKey 값이 bean으로 등록된  값

문제 분석 및 원인


실패 원인의 로그를 살펴보니 아래와 같이 출력되었다.


org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [MasterDataSourceConfig.class]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Unable to determine basic type mapping via reflection [도메인.문제_필드] Caused by: org.hibernate.MappingException: Unable to determine basic type mapping via reflection [도메인.문제_필드]  at org.hibernate.cfg.annotations.SimpleValueBinder.fillSimpleValue(SimpleValueBinder.java:514)  at org.hibernate.cfg.SetSimpleValueTypeSecondPass.doSecondPass(SetSimpleValueTypeSecondPass.java:25)  at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1693)  at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1651)  at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:295)  at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1224)  at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1255)  at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58)  at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)  at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:391)  at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:378)  at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1858)  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1795)  ... 104 more Caused by: org.hibernate.InstantiationException: Could not instantiate managed bean directly : SampleAccountAttributeConverter  at org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer.produceBeanInstance(FallbackBeanInstanceProducer.java:45)  at org.hibernate.resource.beans.container.spi.FallbackContainedBean.<init>(FallbackContainedBean.java:23)  at org.hibernate.resource.beans.internal.ManagedBeanRegistryImpl.getBean(ManagedBeanRegistryImpl.java:58)  at org.hibernate.boot.model.convert.internal.ClassBasedConverterDescriptor.createManagedBean(ClassBasedConverterDescriptor.java:38)  at org.hibernate.boot.model.convert.internal.AbstractConverterDescriptor.createJpaAttributeConverter(AbstractConverterDescriptor.java:107)  at org.hibernate.mapping.SimpleValue.buildAttributeConverterTypeAdapter(SimpleValue.java:581)  at org.hibernate.mapping.SimpleValue.setTypeUsingReflection(SimpleValue.java:542)  at org.hibernate.cfg.annotations.SimpleValueBinder.fillSimpleValue(SimpleValueBinder.java:510)  ... 117 more Caused by: java.lang.NoSuchMethodException: 도메인.문제되는_필드.<init>()  at java.base/java.lang.Class.getConstructor0(Class.java:3349)  at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553)  at org.hibernate.resource.beans.internal.FallbackBeanInstanceProducer.produceBeanInstance(FallbackBeanInstanceProducer.java:40)  ... 124 more


주요 실패 원인   

NoSuchMethodException:  SampleAttributeConverter을 생성자 메서드 찾기 실패

InstantiationException: SampleAccountAttributeConverter 인스턴스 생성 실패

MappingException: 생성을 못해서 매핑도 실패

BeanCreationException: bean 생성도 실패


SampleAttributeConverter의 bean 값을 가져오지 못 한 날갯짓을 시작으로 애플리케이션 구동 실패라는 태풍을 만들었다.


그렇다면 왜 bean 값을 가져오지 못했을까? 


해당 프로젝트는 멀티 데이터 소스를 사용하는 환경이었는데 그 경우 bean으로 선언된 값을 가져오기 위해 별도 설정이 필요하다는 것을 알게 되었다.


해결책

LocalContainerEntityManagerFactoryBean를 통한 entityManager 생성 시 bean으로 등록된 값을 주입

위 코드 중 [추가]에 해 되는 부분을 작성하면 엔티티 매니저가  bean를 주입받을 수 있게 된다.


참고. ConfigurableListableBeanFactory


요약


한 줄 요약 : 멀티 데이터 소스를 사용하는 환경에서 엔티티 매니저가 관리하는 객체 속성 중 빈으로 등록된 값을 사용하기 위해서는 별도 설정이 필요하다.   


문제점: entityManagerFactory가  bean으로 등록된 값을 가져오지 못함

오류: 

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource

오류의 조건: 

    1. 멀티 데이터 소스 사용

     2.  AttributeConverter 상속받은 구현체에서 bean으로 등록된 값을 사용

해결책:   EntityManager 생성 시 LocalContainerEntityManagerFactoryBean을 통해 bean 주입

LocalContainerEntityManagerFactoryBean emfb = ... emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));


마치며

처음에 AttributeConverter를 사용해 Converter를 만드는 비교적 간단한 일이라고 생각했는데 bean 주입 관련까지 다시 한번 확인하는 기회였다.


spring boot가 정말 많은 걸 해주고 있다는 것에 감사함을 느끼면서도 어디까지나 대신해 주고 있다는 걸 새삼 깨달았다. 해주지 않는 범위 안에서 만들어가려면 어디까지는 소스의 주인은 나라는 생각으로 spring boot에게 일을 시키는 개발자가 되어야겠다는 생각을 하게 됩니다.


다음에는 더욱 좋은 내용으로 돌아오겠습니다.


참고. https://github.com/spring-projects/spring-framework/issues/23968


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari