ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 다중 Datasource 에 연결하는 Spring Batch 를 개발할 때 이슈
    [IT] 공부하는 개발자/JAVA 2022. 12. 27. 23:45

     

     

    토이 프로젝트로 다중의 데이터 저장소에 연결하는 스프링 배치를 개발하다가, TransactionManager 관련해 트러블 슈팅 후 블로그에 기록으로 남긴다.

     

    다중 DataSoruce에 연결하기 위해서, 코드에 @EnableJpaRepositories를 포함하는 Config 파일을 각각 작성했을 것이다.

    각각의 DataSource 를 정의하는 Config 파일이다.

     

    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef = "db1EntityManagerFactory",
            transactionManagerRef = "db1TransactionManager",
            basePackages = {"com.sen.blog.repository.db1"}
    )
    @EnableAutoConfiguration
    public class Db1Config {
    
        @Bean
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource.db1")
        public DataSourceProperties db1DataSourceProperties() {
            return new DataSourceProperties();
        }
    
        @Bean(name = "db1DataSource")
        @Primary
        public DataSource db1DataSource() {
            return db1DataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
        }
    
        @Primary
        @Bean(name = "db1EntityManagerFactory")
        public LocalContainerEntityManagerFactoryBean db1EntityManagerFactory(EntityManagerFactoryBuilder builder,
                                                                              @Qualifier("db1DataSource") DataSource db1DataSource) {
            return builder
                    .dataSource(db1DataSource)
                    .packages("com.mdb.aging.domain.entity")
                    .build();
        }
    
        @Primary
        @Bean(name = "db1TransactionManager")
        public PlatformTransactionManager db1TransactionManager(
                @Qualifier("db1EntityManagerFactory") EntityManagerFactory db1EntityManagerFactory) {
            return new JpaTransactionManager(db1EntityManagerFactory);
        }
    }
    @Configuration
    @EnableTransactionManagement
    @EnableJpaRepositories(
            entityManagerFactoryRef = "db2EntityManagerFactory",
            transactionManagerRef = "db2TransactionManager",
            basePackages = {"com.sen.blog.repository.db2"}
    )
    @EnableAutoConfiguration
    public class Db2Config {
        @Bean
        @ConfigurationProperties(prefix = "spring.datasource.db2")
        public DataSourceProperties db2DataSourceProperties() {
            return new DataSourceProperties();
        }
    
        @Bean(name = "db2DataSource")
        public DataSource db2DataSource() {
            return db2DataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
        }
    
        @Bean(name = "db2EntityManagerFactory")
        public LocalContainerEntityManagerFactoryBean db2EntityManagerFactory(EntityManagerFactoryBuilder builder,
                                                                              @Qualifier("db2DataSource") DataSource db2DataSource) {
            return builder
                    .dataSource(db2DataSource)
                    .packages("com.mdb.aging.domain.entity")
                    .build();
        }
    
        @Bean(name = "db2TransactionManager")
        public PlatformTransactionManager db2TransactionManager(
                @Qualifier("db2EntityManagerFactory") EntityManagerFactory db2EntityManagerFactory) {
            return new JpaTransactionManager(db2EntityManagerFactory);
        }
    }

     

    다중 데이터 소스를 사용하지 않을 때는 JDBC 가 만들어주는 defaultTransactionManager 하나면 충분했었지만,

    우리는 여러 DB 에 연결하기 위해 각각의 Connection 에서 사용할 트랜잭션 관리자, 엔티티 관리자를 커스텀했다. (상식적으로 다른 DB 끼리 트랜잭션을 공유할 수는 없지 않은가? ㅎㅎ)

    그래서 이 Config 파일에 각 DB의 EntityManagerFactory, TransactionManager를 지정하는 코드가 포함된 것이다.

     

     

     

    그러면, Spring Batch 에 Step을 정의할 때에 이 Step 은 어떤 TransactionManager를 사용할지 지정해 주어야 한다.

    어떤 DB에 연결할지 지정해주는 작업이다.

    이걸 지정해줘야 배치가 정의한 Step 이 어떤 DB를 찾아가야 하는지 이해할 수 있다.

    여러 개의 DB에 동시 연결할 거라면, 여러 Step을 만들고 각각 다른 transactionManager를 걸어주면 된다.

     

    stepBuilderFactory.get(stepName)
                    .<Posting, Posting>chunk(CHUNK_SIZE)
                    .reader(jobReader())
                    .processor(jobProcessor())
                    .writer(jobWriter())
                    // 여기에요~~
                    .transactionManager(BeanUtils.getTransactionManagerBean(dbName))
                    .build();

     

    나는 데이터 소스가 여러 개여서, 각각의 TransactionManager bean을 쉽게 찾기 위해 별도의 ApplicationContextProvider, BeanUtils 유틸을 만들었다. ApplicationContext 코드는 멋진.. 다른 개발자분들의 public github를 참고했다아..

     

     

    @Component
    public class ApplicationContextProvider implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        @Override
        public void setApplicationContext(ApplicationContext ctx) throws BeansException {
            applicationContext = ctx;
        }
    
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    
    }
    @UtilityClass
    public class BeanUtils {
        public static <T> T getBean(String beanName, Class<T> classType) {
            ApplicationContext applicationContext = ApplicationContextProvider.getApplicationContext();
            return applicationContext.getBean(beanName, classType);
        }
    
        public static PlatformTransactionManager getTransactionManagerBean(int key) {
            return getBean(String.format("db%dTransactionManager", key), PlatformTransactionManager.class);
        }
    }

     

     

     

    또, JPA와 Chunk base 배치를 결합해 개발하고 있다면? Reader, Writer에 각각의 EntityManager 역시 지정해 주어야 한다.

     

    이렇게 각각의 관리자 빈을 추가해주자 계속 발생하던 Transaction 관련 에러가 사라졌다.

     

     

    댓글

Copyright in 2020 (And Beyond)