-
다중 Datasource 에 연결하는 Spring Batch 를 개발할 때 이슈개발지식 아카이브/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 관련 에러가 사라졌다.
'개발지식 아카이브 > JAVA' 카테고리의 다른 글
[Java Stream] GroupingBy 를 이용한 그룹화 예시 소개 (3) 2021.02.15 [Json] gson, Jackson 컨버터의 null 필드 처리 방법 (0) 2020.10.31 [Spring Boot2] 스프링 부트2로 애플리케이션 개발: 환경설정 (0) 2020.07.11 [JAVA Collections API] 자료구조 요약: 구조/성능/용도 (3) 2019.10.28 [디자인패턴] 자바 싱글톤 Vs 스프링 싱글톤 패턴의 차이점 (1) 2019.09.28