[ Spring Multi DataSource ] Read / Write Dynamic DataSource

    ๋ฐ˜์‘ํ˜•

    AWS ์‚ฌ์šฉ ์ค‘, READ ์™€ READ/WRITE DB๊ฐ€ ๋ถ„๋ฆฌ๋˜๋ฉด์„œ, ํ˜„์žฌ ์‚ฌ์šฉ์ค‘์ธ 1๊ฐœ์˜ DataSource ๋ฅผ ๋ถ„๋ฆฌํ•˜์—ฌ READ์ „์šฉ ds ์™€ READ/WRITE ์ „์šฉ ds ๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ๋˜์—ˆ๋‹ค.

    ๊ตฌ๊ธ€๋ง์„ ํ•˜๋ฉด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ธ€์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ž‘์„ฑํ•˜์˜€๋‹ค.

    https://taes-k.github.io/2020/03/11/sprinig-master-slave-dynamic-routing-datasource/

     

    Spring, master-slave dynamic routing datasource ์‚ฌ์šฉํ•˜๊ธฐ

    DB Replication ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•˜๋ฉด์„œ DB์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ DB Replication์„ ํ†ตํ•ด ์ฟผ๋ฆฌ์˜ ๋Œ€๋ถ€๋ถ„์„ ์ฐจ์ง€ํ•˜๋Š” read ์ž‘์—…์„ Slave DB๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ๋” ํ•˜์—ฌ ๋ถ€ํ•˜๋ฅผ ๋ถ„์‚ฐ ์‹œํ‚ฌ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Read์˜ ๋ถ€ํ•˜

    taes-k.github.io

    https://mudchobo.github.io/posts/spring-boot-jpa-master-slave

     

    Spring Boot JPA - master slave ๋ถ„๊ธฐ ์ฒ˜๋ฆฌ - transactional ๋ฐฉ์‹ - mudchobo devlog

    ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ JPA๋Š” ๊ธฐ๋ณธ ์…‹ํŒ…์€ 1๊ฐœ์˜ datasource๋งŒ ์„ค์ •ํ•˜๊ฒŒ ๋˜์–ด ์žˆ๋‹ค. ํ•˜์ง€๋งŒ, master / slave replication์ด ๋˜์–ด ์žˆ๋Š” ๋””๋น„๋ฅผ ๋‘˜ ๋‹ค ์—ฐ๊ฒฐํ•˜๊ณ  ์‹ถ์„ ๋•Œ์—๋Š” ์กฐ๊ธˆ ๊นŒ๋‹ค๋กญ๊ฒŒ ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. ๋‘ ๊ฐ€์ง€ ๋ฐฉ

    mudchobo.github.io

     

    1. SQLConfig.java

    package com.test;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.sql.DataSource;
    
    import com.zaxxer.hikari.HikariDataSource;
    
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jdbc.DataSourceBuilder;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.context.annotation.Primary;
    import org.springframework.core.io.Resource;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @Configuration
    @MapperScan(basePackages = "com.test.repo.postgreSQL")
    @EnableTransactionManagement
    public class PostgreSQLConfig {
    
      @Bean
      @ConfigurationProperties("spring.datasource.hikari.master")
      public DataSource dataSourceMaster() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
      }
    
      @Bean
      @ConfigurationProperties("spring.datasource.hikari.slave")
      public DataSource dataSourceSlave() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
      }
    
      @DependsOn({"dataSourceMaster","dataSourceSlave"})
      @Bean
      public DataSource routingDataSource(@Qualifier("dataSourceMaster") DataSource dataSourceMaster,
          @Qualifier("dataSourceSlave") DataSource dataSourceSlave) {
    
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("master", dataSourceMaster);
        dataSourceMap.put("slave", dataSourceSlave);
    
        PostgreSQLRoutingDataSource routingDataSource = new PostgreSQLRoutingDataSource();
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(dataSourceMaster);
        return routingDataSource;
      }
    
      @Primary
      @DependsOn({"routingDataSource"})
      @Bean
      public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
        return new LazyConnectionDataSourceProxy(routingDataSource);
      }
    
      @Bean
      public SqlSessionFactory sqlSessionFactory(@Qualifier("routingDataSource") DataSource dataSource) throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath:mybatis/mapper/postgreSQL/*.xml"));
        Resource myBatisConfig = new PathMatchingResourcePatternResolver()
            .getResource("classpath:mybatis/config/mybatis-config.xml");
        sessionFactory.setConfigLocation(myBatisConfig);
    
        return sessionFactory.getObject();
      }
    
      @Bean
      public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) throws Exception {
        final SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        return sqlSessionTemplate;
      }
    
    }

    ์ž‘์„ฑํ•˜๋‹ค๊ฐ€ ์–ด๋…ธํ…Œ์ด์…˜์— ๋Œ€ํ•œ ๊ฒƒ๋„ ์ƒˆ๋กœ์ด ์•Œ๊ฒŒ๋˜์—ˆ๋‹ค.

    - Qualifier : ๋™์ผํ•œ ํƒ€์ž…์„ ๊ฐ–๋Š” ๋นˆ ๊ฐ์ฒด๋ฅผ ์ฃผ์ž… ์‹œ, ์Šคํ”„๋ง์—์„œ๋Š” ์•Œ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋ช…์‹œํ•ด์ฃผ๋Š” ๊ฒƒ.

    - DependsOn : ์Šคํ”„๋ง ์ˆœํ™˜ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ, ์˜์กด์„ฑ ์ˆœ์„œ๋ฅผ ์ง€์ •ํ•˜์—ฌ ์ˆœํ™˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๋ช…์‹œ, DependsOn ๋’ค์— ์˜ค๋Š” Name ์„ ๊ฐ–๋Š” ๋นˆ ์ฃผ์ž… ํ›„, ๋‹ค์Œ ์ˆœ์„œ๋กœ ์ฃผ์ž…๋˜๊ฒŒ๋” ํ•จ.

     

    2. RoutingDataSource.java

    package com.test.config;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    import org.springframework.transaction.support.TransactionSynchronizationManager;
    
    public class PostgreSQLRoutingDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
            if (isReadOnly) {
                System.out.println("๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ");
                System.out.println("๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ DB Connection now is - slave - ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ");
                System.out.println("๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ");
                return "slave";
            } else {
                System.out.println("๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ");
                System.out.println("๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ DB Connection now is - master - ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ");
                System.out.println("๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ๏ผƒ");
                return "master";
            }
        }
    
    }

     

    3. application.yml

    ...
    datasource: 
       hikari:
        master:
          jdbc-url: jdbc:log4jdbc:postgresql://{url}
          driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
          username: master
          password: master
        slave:
          jdbc-url: jdbc:log4jdbc:postgresql://{url}
          driver-class-name: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
          username: slave
          password: slave
    ...

     

    ๊ฐ„๋‹จํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์ž๋ฉด, Service ์— ์„ ์–ธ๋œ @Transactional ํƒœ๊ทธ์— readOnly ๊ฐ€ true ๋ƒ false ๋ƒ ์— ๋”ฐ๋ผ ์ ์šฉ๋˜๋Š” ds๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ํ˜•ํƒœ์ด๋‹ค.

    @Transactional
    
    or 
    
    @Transactional(readOnly = true)

    ๋ฏธ๋ฆฌ ์ƒ์„ฑ๋œ master ์™€ slave ds ๊ฐ์ฒด๋ฅผ ๋งต ํ˜•ํƒœ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€, ํ˜ธ์ถœ ์‹œ readOnly ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜์—ฌ ์ ์ ˆํ•œ ds ๋ฅผ ๋ฆฌํ„ดํ•˜์—ฌ sqlSession์„ ๊ตฌ์„ฑํ•˜๊ฒŒ ๋œ๋‹ค.

    * DependsOn ์€ ์ œ๊ฑฐ ํ›„ ๋นŒ๋“œ ์‹œ, ์Šคํ”„๋ง ์ˆœํ™˜ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์ด๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ํ•˜์˜€๋‹ค. ๊ทผ๋ฐ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉํ•˜๋ฉด ๋นŒ๋“œ ์‹œ, ์—๋Ÿฌ๊ฐ€ ๋‚˜์ง€ ์•Š์•„ ํ•ด๋‹น ๊ตฌ๋ฌธ์„ ๊ฐ€์ ธ๊ฐˆ ์ง€, ๊ณ ์ณ์•ผํ•  ์ง€ ๊ณ ๋ฏผ ์ค‘์ด๋‹ค.

    728x90
    ๋ฐ˜์‘ํ˜•

    ๋Œ“๊ธ€