LocalContainerEntityManagerFactoryBean
안녕하세요.
카카오헤어샵 백엔드 개발팀 카이입니다.
이번 포스팅에서는 빈으로 등록된 값 사용한 AttributeConverter를 개발하면서 겪은 문제를 공유하고 어떻게 풀어 갔는지 소개해보려고 합니다.
저의 첫 포스팅 끝까지 읽어주실 거조. :) (문제 해결이 궁금하시다면 아래 요약으로 SKIP)
엔티티의 속성 값 중 디비에 저장 및 조회 전/후로 별도 처리가 필요하여 AttributeConverter 구현한 Converter 개발 시작
이때까지만 해도 마음이 편함 그 자체.
하지만 몇 분 후 그전/후 처리 로직에 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으로 등록된 값을 주입
참고. 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