본문 바로가기
공부/프로그래밍

[jpa] No qualifying bean of type EntityManagerFactoryBuilder 에러

by demonic_ 2021. 3. 5.
반응형

정리)

1) 여러 DataSource를 사용하는 중이라면 그 중 @Primary를 등록하여 EntityManagerFactoryBuilder 가 생성되도록 한다.

2) 만약 @Primary를 사용할 수 없다면 아래 직접설정하는 방법을 사용한다

 

 

JPA 설정을 위해 세팅중 다음 에러가 발생했다. 주입받을 EntityManagerFactoryBuilder를 생성하지 못해 발생한 에러.

...
    @Bean
    public LocalContainerEntityManagerFactoryBean testEntityManagerFactory (EntityManagerFactoryBuilder builder) {

...

에러메세지

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

 

결론만 이야기하면 VendorAdapter(데이터베이스 플랫폼 설정)이 없어서 난 에러였다.

 

이전까지만 해도 application.properties 에 다음과 같이 설정을 했었고, DataSource가 자동 지정되어 생성되었다. 그런데 이번에는 DataSource가 여러개였고, 여러 DataSource중에 @Primary를 등록하지 않았었다.(@Primary를 등록하면 EntityManagerFactoryBuilder가 자동 생성된다)

spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.default_batch_fetch_size = 1000
spring.jpa.hibernate.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

서로 다른 DBMS에 접근해야 하기 떄문에 자동설정 사용이 불가했고 @Primary 역시 사용할 수 없었기 때문에 직접 등록하도록 했다.

@Configuration
@RequiredArgsConstructor
public class TestJpaConfig {
    public final static String persistenceUnitName = "testEntityManager";
    private final String packageName = "kr.demonic.entity.test";
    private final String ddlAuto = "none";
    // MySQL 설정
    private final Database database = Database.MYSQL;
    private final String databasePlatform = "org.hibernate.dialect.MySQL5InnoDBDialect";

    private final DataSource testDataSource;
...
    @Bean
    public LocalContainerEntityManagerFactoryBean testEntityManagerFactory () {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabasePlatform(databasePlatform);
        vendorAdapter.setDatabase(database);

        JpaProperties jpaProperties = new JpaProperties();
        jpaProperties.setGenerateDdl(false);
        HibernateProperties hibernateProperties = new HibernateProperties();
        hibernateProperties.setDdlAuto(ddlAuto);
        Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
                jpaProperties.getProperties()
                , new HibernateSettings());

        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(testDataSource);
        emf.setPackagesToScan(packageName);
        emf.setPersistenceUnitName(persistenceUnitName);
        emf.setJpaVendorAdapter(vendorAdapter);
        emf.setJpaPropertyMap(properties);

        return emf;
    }
....

 

 

끝.

 

 

 

추가:

JPA를 기본설정하는 Configuration은 다음순서로 진행되는듯 하다

: JpaProperties => DataSourceInitializedPublisher => JpaBaseConfiguration

JpaProperties는 application.properties 에서 spring.jpa 로 시작하는 값이 있는경우 정보를 담는듯 하다

@ConfigurationProperties(prefix = "spring.jpa")
public class JpaProperties {
...

그리고 생성된 JpaProperties 클래스가 있을때 JpaBaseConfiguration를 실행하는 듯 한데, 이때 DataSourceInitializedPublisher를 Import 한다.

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JpaProperties.class)
@Import(DataSourceInitializedPublisher.Registrar.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
...

DataSourceInitializedPublisher 파일을 보면 dataSource가 주입되지 않으면 ddl기본옵션이 create-drop이다.

class DataSourceInitializedPublisher implements BeanPostProcessor {
...
	private boolean isInitializingDatabase(DataSource dataSource) {
		if (this.jpaProperties == null || this.hibernateProperties == null) {
			return true; // better safe than sorry
		}
		Supplier<String> defaultDdlAuto = () -> (EmbeddedDatabaseConnection.isEmbedded(dataSource) ? "create-drop"
				: "none");
		Map<String, Object> hibernate = this.hibernateProperties.determineHibernateProperties(
				this.jpaProperties.getProperties(), new HibernateSettings().ddlAuto(defaultDdlAuto));
		return hibernate.containsKey("hibernate.hbm2ddl.auto");
	}
...

그리고 이 파일은 Bean이 생성될때 실행된다.

...
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof DataSource) {
			// Normally this will be the right DataSource
			this.dataSource = (DataSource) bean;
		}
		if (bean instanceof JpaProperties) {
			this.jpaProperties = (JpaProperties) bean;
		}
		if (bean instanceof HibernateProperties) {
			this.hibernateProperties = (HibernateProperties) bean;
		}
		if (bean instanceof LocalContainerEntityManagerFactoryBean && this.schemaCreatedPublisher == null) {
			LocalContainerEntityManagerFactoryBean factoryBean = (LocalContainerEntityManagerFactoryBean) bean;
			EntityManagerFactory entityManagerFactory = factoryBean.getNativeEntityManagerFactory();
			publishEventIfRequired(factoryBean, entityManagerFactory);
		}
		return bean;
	}
...
	private void publishEventIfRequired(LocalContainerEntityManagerFactoryBean factoryBean,
			EntityManagerFactory entityManagerFactory) {
		DataSource dataSource = findDataSource(factoryBean, entityManagerFactory);
        // 이 부분에서 위 메서드(isInitializingDatabase) 호출
		if (dataSource != null && isInitializingDatabase(dataSource)) {
			this.applicationContext.publishEvent(new DataSourceSchemaCreatedEvent(dataSource));
		}
	}

자세한것은 해당 파일들을 하나씩 뜯어보면 좋을듯 하다.

반응형

댓글