μ€νλ§ λ°μ΄ν°μμ€
μκ·λͺ¨ μμ€ν μμλ λ¨μΌ λ°μ΄ν°λ² μ΄μ€μ μμ‘΄νμ§λ§ μ‘°κΈμ© 컀μ§λ μμ€ν μμλ λ°μ΄ν°λ² μ΄μ€ ν΄λ¬μ€ν°μ μ κ·Όνκ±°λ λ€μμ λ°μ΄ν°λ² μ΄μ€μ μ°κ²°λλ κ² κ°λ€. λ³Έ κΈμμλ λ€μ€ λ°μ΄ν°λ² μ΄μ€ μ°κ²°μ μν λ°μ΄ν° μμ€λ₯Ό μ΄λ»κ² κ΄λ¦¬νλμ§λ₯Ό λ€λ£¨μ΄λ³΄κ³ μ νλ€. μλμ μμμμ μ€νλ§ κ°λ°μ Josh Long μ΄ λ°μ΄ν°μμ€λ₯Ό μ΄λ»κ² λ€λ£° μ μλμ§μ λν΄μ λ€μνκ² μ€λͺ νκ³ μλ€.
DataSourceBuilder
μ€νλ§ λΆνΈμμλ μ ν리μΌμ΄μ
νλ‘νΌν° νμΌμ spring.datasource
λ‘ μμνλ μμ±μΌλ‘ λ°μ΄ν°λ² μ΄μ€ μ°κ²°μ λν μ 보λ₯Ό μ€μ νκ³ μλ ꡬμ±μ μ 곡νλ€. μ§μ λ°μ΄ν°μμ€λ₯Ό μμ±νκ±°λ λ€μ€ λ°μ΄ν°λ² μ΄μ€μ μ°κ²°νκΈ° μν΄μ μλ‘ λ€λ₯Έ λ°μ΄ν°λ² μ΄μ€ μ°κ²° μ 보λ₯Ό κ°μ§λ λ°μ΄ν°μμ€λ₯Ό μ¬μ©νκ³ μ νλ κ²½μ°μ DataSourceBuilder
μ DataSourceProperties
λ₯Ό μ¬μ©ν΄λ³Ό μ μλ€.
μ€νλ§ νλ μμν¬μμ κΈ°λ³Έμ μΌλ‘ μ¬μ©λλ 컀λ₯μ ν λΌμ΄λΈλ¬λ¦¬λ HikariCP μ λλ€.
AbstractRoutingDataSource
μ μμμμλ λ©ν°-ν λμ ꡬμ±μΌλ‘ μλ‘ λ€λ₯Έ 리μ μ ꡬμ±νλ€λ©΄ μ€λ λ λ‘컬 λ³μμ 리μ μ 보λ₯Ό κ΄λ¦¬νκ³ λ¦¬μ μ λ°λ₯Έ λ°μ΄ν°λ² μ΄μ€μ μ°κ²°νλ μμλ₯Ό 보μ¬μ£Όκ³ μλ€. λ°μ΄ν°λ² μ΄μ€ ν΄λ¬μ€ν°λ‘ κ³ κ°μ©μ±μ HAλ₯Ό ꡬμ±νλ μΈνλΌμ κ²½μ°μλ AbstractRoutingDataSource λ₯Ό νμ©νμ¬ μ°κΈ° μ μ© ν΄λ¬μ€ν° μλν¬μΈνΈμ μ½κΈ° μ μ© μλν¬μΈνΈλ₯Ό λλμ΄μ μ²λ¦¬ν μ μλ λΌμ°ν° λ°©μμ λ°μ΄ν°μμ€λ₯Ό μμ±ν μ μλ€.
@AllArgsConstructor
@Configuration
public class DatabaseConfiguration {
private final DataSourceProperties dataSourceProperties;
@ConfigurationProperties(prefix = "spring.datasource")
@Bean("writer")
public DataSource writerDataSource() {
return DataSourceBuilder.create().build();
}
@Bean("reader")
public DataSource readerDataSource() {
return DataSourceBuilder.create()
.type(dataSourceProperties.getType())
.url(dataSourceProperties.determineUrl())
.username(dataSourceProperties.determineUsername())
.password(dataSourceProperties.determinePassword())
.driverClassName(dataSourceProperties.determineDriverClassName())
.build();
}
@Primary
@Bean
public DataSource dataSourceRouter() {
ClusterDataSourceRouter dataSourceRouter = new ClusterDataSourceRouter();
dataSourceRouter.setTargetDataSources(Map.of(ClusterType.WRITER, writerDataSource()
, ClusterType.READER, readerDataSource()));
dataSourceRouter.setDefaultTargetDataSource(writerDataSource());
return dataSourceRouter;
}
public enum ClusterType {
WRITER, READER
}
public static class ClusterDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if (readOnly) {
return ClusterType.READER;
}
return ClusterType.WRITER;
}
}
}
λλΆλΆ TransactionSynchronizationManagerμ νΈλμμ μ½κΈ° μμ±μ λ°λΌ μ½κΈ° μ μ© μλν¬μΈνΈμ μ°κ²°λλλ‘ μμ λ₯Ό 곡μ νλ κ² κ°λ€μ.
LazyConnectionDataSourceProxy
μ€νλ§μ νΈλμμ μ²λ¦¬λ @Transactional μ μ§μ νλ κ³Όμ μμ 컀λ₯μ μ°κ²°μ μννλ―λ‘ λ€μ€ λ°μ΄ν°μμ€μ λν ꡬμ±μμλ LazyConnectionDataSourceProxyλ₯Ό μ¬μ©νμ¬ μ»€λ₯μ νλ μμ μ λ¦μΆλ κ²μ΄ μΌλ°μ μ΄λ€.
@Primary
@Bean
public DataSource dataSourceRouter() {
ClusterDataSourceRouter dataSourceRouter = new ClusterDataSourceRouter();
dataSourceRouter.setTargetDataSources(Map.of(ClusterType.WRITER, writerDataSource()
, ClusterType.READER, readerDataSource()));
dataSourceRouter.setDefaultTargetDataSource(writerDataSource());
return new LazyConnectionDataSourceProxy(dataSourceRouter);
}
λ°λμ 컀λ₯μ νλ μμ μ λ¦μΆλ κ²μ΄ μ’μ λ°©λ²μ μλ κ²μ΄κΈ°μ κ°λ°μκ° μμ€ν νκ²½μ λν λΆμκ³Ό νλ¨μ΄ νμν©λλ€.