๐Ÿ”ฎSpring

[ Spring Multi DataSource ] Read / Write Dynamic DataSource

harry.93 2021. 10. 7. 22:06
๋ฐ˜์‘ํ˜•

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
๋ฐ˜์‘ํ˜•