정리)
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));
}
}
자세한것은 해당 파일들을 하나씩 뜯어보면 좋을듯 하다.
'공부 > 프로그래밍' 카테고리의 다른 글
[react, next.js] SSR환경에서 access_token, refresh_tokne 관리하기(cookie이용) (2) | 2021.03.12 |
---|---|
[swagger] ResourceServer 설정(HttpSecurity)으로 인해 UI접근이 안될 때 (0) | 2021.03.08 |
[springboot] Authorization Server 에서 서비스별 로그인 체크 & 토큰 발행(인증요청 시 파라미터 변경 포함) (0) | 2021.03.03 |
[spring] log4j 에서 logback 으로 변경하기 (0) | 2021.03.02 |
[springboot] 이미지 s3에 저장하기(파일 업로드) (0) | 2021.02.22 |
댓글