Spring Data JPA Multi Database Configure - 이것이 Spring에서 추천하는 방식!!

YouMakeMeSmile·2025년 1월 4일
0
post-thumbnail

이글은 Spring Boot AutoConfigure에 관해 글을 작성하던 도중에 예제를 작성하다 이제는 정말 미루고 미루고 미루다. 이제는 정말 작성해야 된다고 생각하여 작성한다.


본 내용은 Spring Data JPA를 활용하여 2개 이상의 데이터베이스를 사용해야 하는 경우 설정을 Spring Boot 공식문서를 기반으로 코드를 작성하였다.


먼저 application.yml에 정의는 다음과 같이 하였다. spring.datasource1, spring.datasource2와 같은 접두사는 상황에 따라 변경하여도 문제는 없다.

여기서 공식문서에 없고 내가 추가한 부분은 datasource.jpa.hibernate이다.
각 데이터베이스의 특성에 따라 명명규칙을 다르게 설정해야 하는 경우가 있을 수 있기 때문에 해당 속성을 추가하였다. 이에 따라 공식 문서에는 없는 설정들도 추가가 된다.

spring:
  application:
    name: sample
  datasource1:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: root
    hikari:
      maximum-pool-size: 5
    jpa:
      hibernate:
        ddl-auto: create-drop
        naming:
#          implicit-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
          physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
      show-sql: true
  datasource2:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/board
    username: root
    password: root
    hikari:
      maximum-pool-size: 3
    jpa:
      hibernate:
        ddl-auto: create-drop
      show-sql: true

각각의 DataSource를 생성한다.

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource1")
    public DataSourceProperties primaryDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource1.hikari")
    public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

각각의 HibernateProperties, JpaProperties를 통해서 EntityManagerFactory를 생성한다.
createJpaVendorAdapterSpring Boot AutoConfigure 로직를 그대로 활용하였다.
createEntityManagerFactoryBuilderSrping Boot 공식 문서를 활용하여 datasource.jpa.hibernate를 추가하기 위해서 Spring Boot AutoConfigure 로직을 참고하여 약간 변경하였다.

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource1.jpa.hibernate")
    public HibernateProperties primaryHibernateProperties() {
        return new HibernateProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource1.jpa")
    @Primary
    public JpaProperties primaryJpaProperties() {
        return new JpaProperties();
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(@Qualifier("primaryDataSource") DataSource dataSource,
                                                                              @Qualifier("primaryJpaProperties") JpaProperties jpaProperties,
                                                                              @Qualifier("primaryHibernateProperties") HibernateProperties hibernateProperties) {
        EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties, hibernateProperties);
        return builder.dataSource(dataSource).packages("io.velog.youmakemesmile.primary").build();
    }

    private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
        JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
        Map<String, Object> jpaProperties1 = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(hibernateProperties::getDdlAuto).hibernatePropertiesCustomizers(new ArrayList<>()));
        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties1, null);
    }

    private JpaVendorAdapter createJpaVendorAdapter(JpaProperties properties) {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(properties.isShowSql());
        if (properties.getDatabase() != null) {
            adapter.setDatabase(properties.getDatabase());
        }
        if (properties.getDatabasePlatform() != null) {
            adapter.setDatabasePlatform(properties.getDatabasePlatform());
        }
        adapter.setGenerateDdl(properties.isGenerateDdl());
        return adapter;
    }

각각의 TransactionManager 생성이 필요하다. 그리고 로직이 두개의 데이터베이스가 분리되어 동작한다고 한다면
@Transactional(transactionManager = "primaryTransactionManager") 이와 같이 로직 마다 각각의 TrasactionManager를 지정해야한다. 만약 두개의 데이터베이스가 모두 사용되는 로직인 경우 JTA를 적용해야하지만 현재는 Deprecated 상태인 ChainedTransactionManager를 사용하면 반절의 회피?는 가능하다. ChainedTransactionManager도 회피일 뿐이지 해결 방법은 아니다!!!

    @Bean
    public JpaTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory) {
        return new JpaTransactionManager(primaryEntityManagerFactory);
    }

    @Bean
    @Primary
    public ChainedTransactionManager transactionManager(@Qualifier("primaryTransactionManager") JpaTransactionManager primaryTransactionManager, @Qualifier("secondTransactionManager") JpaTransactionManager secondTransactionManager) {
        return new ChainedTransactionManager(secondTransactionManager, primaryTransactionManager);
    }

최종 설정 코드는 다음과 같다.

@Configuration
@EnableJpaRepositories(
        basePackages = {"io.velog.youmakemesmile.primary"},
        entityManagerFactoryRef = "primaryEntityManagerFactory",
        transactionManagerRef = "primaryTransactionManager"
)
@EnableTransactionManagement
public class PrimaryConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource1")
    public DataSourceProperties primaryDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource1.hikari")
    public DataSource primaryDataSource(@Qualifier("primaryDataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource1.jpa.hibernate")
    public HibernateProperties primaryHibernateProperties() {
        return new HibernateProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource1.jpa")
    @Primary
    public JpaProperties primaryJpaProperties() {
        return new JpaProperties();
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(@Qualifier("primaryDataSource") DataSource dataSource,
                                                                              @Qualifier("primaryJpaProperties") JpaProperties jpaProperties,
                                                                              @Qualifier("primaryHibernateProperties") HibernateProperties hibernateProperties) {
        EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties, hibernateProperties);
        return builder.dataSource(dataSource).packages("io.velog.youmakemesmile.primary").build();
    }

    private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
        JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
        Map<String, Object> jpaProperties1 = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(hibernateProperties::getDdlAuto).hibernatePropertiesCustomizers(new ArrayList<>()));
        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties1, null);
    }

    private JpaVendorAdapter createJpaVendorAdapter(JpaProperties properties) {
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(properties.isShowSql());
        if (properties.getDatabase() != null) {
            adapter.setDatabase(properties.getDatabase());
        }
        if (properties.getDatabasePlatform() != null) {
            adapter.setDatabasePlatform(properties.getDatabasePlatform());
        }
        adapter.setGenerateDdl(properties.isGenerateDdl());
        return adapter;
    }

    @Bean
//    @Primary
    public JpaTransactionManager primaryTransactionManager(@Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory) {
        return new JpaTransactionManager(primaryEntityManagerFactory);
    }

    @Bean
    @Primary
    public ChainedTransactionManager transactionManager(@Qualifier("primaryTransactionManager") JpaTransactionManager primaryTransactionManager, @Qualifier("secondTransactionManager") JpaTransactionManager secondTransactionManager) {
        return new ChainedTransactionManager(secondTransactionManager, primaryTransactionManager);
    }
}
@AutoConfiguration
@EnableJpaRepositories(
        basePackages = {"io.velog.youmakemesmile.second"},
        entityManagerFactoryRef = "secondEntityManagerFactory",
        transactionManagerRef = "secondTransactionManager"
)
public class SecondConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource2")
    public DataSourceProperties secondDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource2.hikari")
    public DataSource secondDataSource(@Qualifier("secondDataSourceProperties") DataSourceProperties dataSourceProperties) {
        return dataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();

    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource2.jpa.hibernate")
    public HibernateProperties secondHibernateProperties() {
        return new HibernateProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource2.jpa")
    @Primary
    public JpaProperties secondJpaProperties() {
        return new JpaProperties();
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(@Qualifier("secondDataSource") DataSource dataSource,
                                                                             @Qualifier("secondJpaProperties") JpaProperties jpaProperties,
                                                                             @Qualifier("secondHibernateProperties") HibernateProperties hibernateProperties) {
        EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties, hibernateProperties);
        return builder.dataSource(dataSource).persistenceUnit("second").packages("io.velog.youmakemesmile.second").build();
    }


    private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
        JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
        Map<String, Object> jpaProperties1 = hibernateProperties.determineHibernateProperties(jpaProperties.getProperties(), new HibernateSettings().ddlAuto(hibernateProperties::getDdlAuto).hibernatePropertiesCustomizers(new ArrayList<>()));
        return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaProperties1, null);
    }

    private JpaVendorAdapter createJpaVendorAdapter(JpaProperties properties) {
        // ... map JPA properties as needed
        HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
        adapter.setShowSql(properties.isShowSql());
        if (properties.getDatabase() != null) {
            adapter.setDatabase(properties.getDatabase());
        }
        if (properties.getDatabasePlatform() != null) {
            adapter.setDatabasePlatform(properties.getDatabasePlatform());
        }
        adapter.setGenerateDdl(properties.isGenerateDdl());
        return adapter;
    }

    @Bean
    public JpaTransactionManager secondTransactionManager(@Qualifier("secondEntityManagerFactory") EntityManagerFactory secondEntityManagerFactory) {
        return new JpaTransactionManager(secondEntityManagerFactory);
    }
}

해당 설정은 JPA를 활용하여 2개의 DataSource를 연결한 경우이고 이외에 MyBatis, Jdbc등 여러 조합을 통해서 두개 이상의 데이터베이스를 설정하는 경우도 추후... 미루고 미루고 미루다 작성해보겠다.

profile
어느새 7년차 중니어 백엔드 개발자 입니다.

0개의 댓글

관련 채용 정보