μ†Œκ·œλͺ¨ μ‹œμŠ€ν…œμ—μ„œλŠ” 단일 λ°μ΄ν„°λ² μ΄μŠ€μ— μ˜μ‘΄ν•˜μ§€λ§Œ μ‘°κΈˆμ”© μ»€μ§€λŠ” μ‹œμŠ€ν…œμ—μ„œλŠ” λ°μ΄ν„°λ² μ΄μŠ€ ν΄λŸ¬μŠ€ν„°μ— μ ‘κ·Όν•˜κ±°λ‚˜ λ‹€μˆ˜μ˜ λ°μ΄ν„°λ² μ΄μŠ€μ— μ—°κ²°λ˜λŠ” 것 κ°™λ‹€. λ³Έ κΈ€μ—μ„œλŠ” 닀쀑 λ°μ΄ν„°λ² μ΄μŠ€ 연결을 μœ„ν•œ 데이터 μ†ŒμŠ€λ₯Ό μ–΄λ–»κ²Œ κ΄€λ¦¬ν•˜λŠ”μ§€λ₯Ό λ‹€λ£¨μ–΄λ³΄κ³ μž ν•œλ‹€. μ•„λž˜μ˜ μ˜μƒμ—μ„œ μŠ€ν”„λ§ 개발자 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);
}

λ°˜λ“œμ‹œ 컀λ„₯μ…˜ νšλ“ μ‹œμ μ„ λŠ¦μΆ”λŠ” 것이 쒋은 방법은 아닐 것이기에 κ°œλ°œμžκ°€ μ‹œμŠ€ν…œ ν™˜κ²½μ— λŒ€ν•œ 뢄석과 νŒλ‹¨μ΄ ν•„μš”ν•©λ‹ˆλ‹€.