개발/Spring

[Spring] Spring boot multi source transaction

hojak99 2018. 10. 6. 17:20

Spring Boot JPA MultiSource Transaction

Spring boot : 2.0.5 버전을 사용 중이다.

Spring boot 에서 JPA 를 사용할 때 2개 이상의 DB 를 사용할 때가 있다. 그럴 때 이제 트랜잭션 문제가 생길 수 있는데, Spring boot 에서 제공하는 @Transaction 어노테이션에 대한 주석을 살펴보면 다음과 같이 주석이 달려있다.

/**
	 * A <em>qualifier</em> value for the specified transaction.
	 * <p>May be used to determine the target transaction manager,
	 * matching the qualifier value (or the bean name) of a specific
	 * {@link org.springframework.transaction.PlatformTransactionManager}
	 * bean definition.
	 * @since 4.2
	 * @see #value
	 */
	@AliasFor("value")
	String transactionManager() default "";

그렇다면 @Transactional(value = "testTransactionManager") 와 같이 특정 DB 에 대한 transaction 을 각각 사용할 수 있게 되는 것 같다.

우선 필자는 Hikari 를 사용을 했다.


먼저 application.yml 에서 db 설정을 해주도록 하겠다. 물론, 코드로 작성해도 되는데 yml 에서 하는게 더 편하다고 생각한다.

프로젝트에서 사용할 DB 는 각각 school 라는 DB 와 company 라는 DB 이다.

spring:
  datasource:
    hikari:
      school:
        jdbc-url: jdbc:mysql://~~~
        character_set_server=utf8mb4&allowMultiQueries=true&useSSL=false
        username: ~~~
        password: ~~~
        driver-class-name: com.mysql.jdbc.Driver
      company:
        jdbc-url: jdbc:mysql://~~~
        characterEncoding=utf8&allowMultiQueries=true&useSSL=false
        username: ~~~
        password: ~~~
        driver-class-name: com.mysql.jdbc.Driver

먼저 DB 설정을 각각 해줘야 하는데 공통되는 Configuration 부분은 따로 클래스로 빼놓았다. 여기서 메인으로 사용할 DB 를 school로 지정해놓기 위해 추가적으로 @Primary 어노테이션을 달아주었다. 그렇게 된다면 @Transactional 만 사용했을 때는 school db를, company db에 트랜잭션을 걸어줄 때는 @Transactional 에 value 값을 달아주면 된다.

만약 @Primary 를 달아주지 않는 다면 @Transactional 어노테이션에 모두 value 값을 넣어줘야 한다. 그렇지 않으면 어떤 db 에 트랜잭션 걸지 모르기 때문이다.

@Configuration
@EnableConfigurationProperties
public class DataSourceProperties {

    @Bean(name = "schoolDataSource")
    @Qualifier("schoolDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.hikari.school")
    public DataSource schoolDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "companyDataSource")
    @Qualifier("companyDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.hikari.company")
    public DataSource companyDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }
}

이렇게 우선 DataSourceProperties 라는 Configuration 클래스를 하나 만들어 놓았으니 이제 각각의 db 를 셋팅하는 클래스를 만들어주면 된다.


우선 company db 의 설정 클래스를 먼저 살펴보겠다. 여기서 가장 중요한 부분은 Bean 으로 등록한 PlatfromTransactionManager 를 return 해주는 companyTransactionManager(EntityManagerFactoryBuilder builder) 메소드다.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "companyEntityManagerFactory",
        transactionManagerRef = "companyTransactionManager",
        basePackages = "(해당 company 에 대한 repository 패키지)"
)
public class CompanyRepositoryConfig {

    @Autowired
    @Qualifier("companyDataSource")     // 아까 @Bean 으로 등록했던 것을 가져오도록 한다.
    private DataSource companyDataSource;

    @Bean(name = "companyEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean companyEntityManagerFactory(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(companyDataSource)
                .packages("(해당 company에 대한 domain 패키지)")
                .persistenceUnit("company")
                .build();
    }

    // 가장 중요한 부분.
    @Bean("companyTransactionManager")
    public PlatformTransactionManager companyTransactionManager(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(companyEntityManagerFactory(builder).getObject());
    }

}

이제 CompanyRepositoryConfig 클래스처럼 SchoolRepositoryConfig 클래스를 생성해주면 된다. base package 나 @Qualifier 를 school 에 맞게 변경해주면 된다.

그리고 이제 Service 클래스에서 SchoolRepository interface 를 이용해 어떠한 데이터를 가져오는 클래스에서 Transaction 을 걸어주는 코드이다.

// 자동으로 school db 를 바라본다.
@Transactional
public Page<School> findAllSchool(Pageable pageable) {
    Page<School> schoolList = schoolRepository.findAll(pageable)
    
    log.info("[SchoolService.findAllSchool] : {}", schoolList);
    return schoolList;
}
@Transactional(value="companyTransactionManager")
public Page<Company> findAllCompany(Pageable pageable) {
    Page<Company> companyList = companyRepository.findAll(pageable)
    
    log.info("[CompanyService.findAllCompany] : {}", companyList);
    return companyList;
}

이제 transaction 이 각각에 db 맞게 돌 것이다. 이제 검증을 해보면 되는데, 아마 bean 등록만 되면 잘 적용이 된 것으로 보면 될 것 같다.

그래도 코드로 검증을 해보고 싶어서 유닛 테스트 코드를 작성해보았었는데 이 부분은 알아서 해보자. save 하는 테스트 메소드를 만들어서 테스트를 했었는데 필자는 롤백이 잘 됐었다.

반응형