728x90
반응형
서비스를 운영하다 보면 데이터베이스가 여러 개의 노드로 분산되어 Master/Slave 구조로 이루어져 있는 경우가 많습니다.
보통 두가지 방법으로 분기처리가 가능한데요. 본 포스팅에서는 @Transactional 어노테이션을 사용하는 방식을 소개해드리겠습니다.
먼저 Transactional readOnly에 따라 분기하는 CustomRoutingDataSource를 생성합니다.
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master";
}
}
다만 이 경우에는 트랜잭션이 시작하더라도 실제 쿼리 발생 직전에 connection을 생성해야 하기 때문에 AbstractRoutingDataSource를 상속받는 데이터 소스를 LazyConnectionDataSourceProxy로 감싸야합니다.
@Configuration
@EnableJpaRepositories(basePackages = {"com.moonsiri.**.repository"}, entityManagerFactoryRef = "entityManagerFactory", transactionManagerRef = "jpaTxManager")
public class JpaConfiguration {
@Bean("mainDatasource")
public DataSource mainDatasource(@Qualifier("mainJdbcProperties") HikariConfig hikariConfig) {
hikariConfig.setJdbcUrl(mainJdbcProperties.getUrl());
hikariConfig.setUsername(mainJdbcProperties.getUsername());
hikariConfig.setPassword(mainJdbcProperties.getPassword());
hikariConfig.setDriverClassName(mainJdbcProperties.getDriverClassName());
return new HikariDataSource(hikariConfig);
}
@Bean("readDatasource")
public DataSource readDatasource(@Qualifier("readJdbcProperties") HikariConfig hikariConfig) {
hikariConfig.setJdbcUrl(readJdbcProperties.getUrl());
hikariConfig.setUsername(readJdbcProperties.getUsername());
hikariConfig.setPassword(readJdbcProperties.getPassword());
hikariConfig.setDriverClassName(readJdbcProperties.getDriverClassName());
return new HikariDataSource(hikariConfig);
}
@Bean("routingDataSource")
public DataSource routingDataSource(
@Qualifier("mainDatasource") DataSource mainDataSource,
@Qualifier("readDatasource") DataSource readDataSource) {
RoutingDataSource routingDataSource = new RoutingDataSource(); // AbstractRoutingDataSource
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("master", mainDataSource);
dataSourceMap.put("slave", readDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(mainDataSource);
return routingDataSource;
}
@Primary
@Bean("dataSource")
public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
@Bean("entityManagerFactory")
public EntityManagerFactory entityManagerFactory(@Qualifier("dataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setPackagesToScan("com.moonsiri.**.domain");
factory.setDataSource(dataSource);
factory.setPersistenceUnitName("MySQL");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
factory.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
properties.put("hibernate.format_sql", true);
properties.put("hibernate.show_sql", false); // sql은 log4j로 출력 org.hibernate.SQL=DEBUG
properties.put("hibernate.globally_quoted_identifiers", true); // 예약어 컬럼명 사용 허용
factory.setJpaPropertyMap(properties);
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean("jpaTxManager")
public PlatformTransactionManager txManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
return jpaTransactionManager;
}
}
LazyConnectionDataSourceProxy는 트랜잭션 시작 시 Connection Proxy 객체를 리턴하고 실제 쿼리를 실행할 때 가서야 데이터 소스에서 connection을 가져옵니다.
이렇게 모든 설정을 마치면 다음과 같이 readOnly 속성으로 분기처리 할 수 있습니다.
@Transactional(readOnly = true)
[Reference]
https://moonsiri.tistory.com/92
https://mudchobo.github.io/posts/spring-boot-jpa-master-slave
https://mudchobo.github.io/posts/spring-boot-jpa-multiple-database
728x90
반응형
'spring > spring jpa' 카테고리의 다른 글
[QueryDSL] FullText 검색을 위한 Match ... against 절 사용법 (0) | 2023.08.24 |
---|---|
[SpringBoot] QueryDSL org.apache.jasper.JasperException: Unable to compile class for JSP: (0) | 2023.08.24 |
[SpringBoot] JPA Projection (0) | 2021.06.23 |
[Spring] JPA 연관 관계 매핑 및 제거 (0) | 2021.06.18 |
[SpringBoot] Mybatis + JPA + QueryDsl 설정 (0) | 2021.06.10 |
댓글