make-delivery 프로젝트

[#7] TransactionManager가 DataSource정하는 로직을 늦추기 - LazyConnectionDataSourceProxy

sunggook lee 2020. 12. 5. 16:08

tjdrnr05571.tistory.com/14?category=876333

 

Mysql Replication Spring에서 Master/Slave 이중화 with Docker

이글에선 단일서버에서 Mysql Replication을 port를 나누어 하는 방법을 다룹니다. 목차 - 내 프로젝트에서 Mysql Replication을 사용해야 하는 이유 - Mysql Replication의 동작 원리 - Docker로 Mysql 컨테이너..

tjdrnr05571.tistory.com

 

위 링크에서처럼 Mysql Replication을 Spring에서 구현하던 중 @Transactional로 트랜잭션을 시작 시켜줄 때 DataSource가 무조건 기본 Master DataSource로만 쿼리가 가는 문제가 생겼습니다. 이는 스프링에서 트랜잭션을 시작할 때 바로 DataSource를 정하고 그 DataSource로 트랜잭션 메소드 내 모든 쿼리 수행을 하기 때문입니다. 현재 저의 프로젝트에서는 AOP로 쿼리 메소드에 @SetDataSource로 마스터나 슬레이브 데이터소스를 정해주는 로직이기 때문에 위의 문제가 발생하면 무조건 Master DataSource로만 쿼리가 가는 문제가 발생했던 것입니다.

 

1. Spring은 기본적으로 트랜잭션을 시작할 때 쿼리가 실행되기도 전에 DataSource를 정해놓습니다.

(TransactionManager 식별 -> DataSource에서 Connection 가져오고 -> Transaction 동기화(Synchronization)

2. Transaction이 시작되면 같은 DataSource만을 이용합니다.

 

    @SetDataSource(dataSourceType = DataSourceType.SLAVE)
    UserDTO selectUserById(String id);

    @SetDataSource(dataSourceType = DataSourceType.MASTER)
    void deleteUser(String id);

 

따라서 위 스샷처럼 쿼리 메소드에 AOP로 DataSource를 정하는 로직이 가능하게 하려면 쿼리를 실행할 때 DataSource를 정할 수 있도록

DataSource 연결을 늦춰주도록 구현하여야 합니다. 이는 밑 스샷과 같이 LazyConnectionDataSourceProxy를 이용하여 현재 RoutingDataSource를 감싸서 구현 가능합니다.

 

    @Bean
    public DataSource lazyRoutingDataSource(
        @Qualifier(value = "routingDataSource") DataSource routingDataSource) {
        return new LazyConnectionDataSourceProxy(routingDataSource);
    }

    @Bean
    public PlatformTransactionManager transactionManager(
        @Qualifier(value = "lazyRoutingDataSource") DataSource lazyRoutingDataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(lazyRoutingDataSource);
        return transactionManager;
    }

 

routingDataSource를 LazyConnectionDataSourceProxy로 감싸면 

TransactionManager 식별 -> LazyConnectionDataSourceProxy에서 Connection Proxy 객체 획득 -> Transaction 동기화(Synchronization) -> 실제 쿼리 호출시에 RoutingDataSource.getConnection()/determineCurrentLookupKey() 호출 과 같이 트랜잭션 매니저의 로직이 변경됩니다.

 

또한 routingDataSource의 빈 이름이 DataSource가 아니라면 TransactionManager에 DataSource를 새로 지정해주어야합니다.

 

이렇게 설정을 변경해준다면 @Transactional 이 걸린 메소드에서 ReadOnly 설정을 해주면 Master 데이터소스를 사용하고 설정을 해주지않으면 Slave 데이터소스를 사용하도록 다음과 같이 변경해준다면 트랜잭션내 실제 쿼리가 실행되는 메소드일 때 트랜잭션의 상태를 파악해서 데이터소스를 정해줍니다.

 

    AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
        @Override
        protected Object determineCurrentLookupKey() {
            DataSourceType dataSourceType = RoutingDataSourceManager.getCurrentDataSourceName();

            if (TransactionSynchronizationManager
                .isActualTransactionActive()) {
                boolean readOnly = TransactionSynchronizationManager
                    .isCurrentTransactionReadOnly();
                if (readOnly) {
                    dataSourceType = DataSourceType.SLAVE;
                } else {
                    System.out.println("not-readOnly");
                    dataSourceType = DataSourceType.MASTER;
                }
            }

            RoutingDataSourceManager.removeCurrentDataSourceName();
            return dataSourceType;
            }

 

 

 

프로젝트 url

github.com/f-lab-edu/make-delivery

 

f-lab-edu/make-delivery

구매자에게 음식 배달을 제공하는 서비스입니다. Contribute to f-lab-edu/make-delivery development by creating an account on GitHub.

github.com

 

참고

egloos.zum.com/kwon37xi/v/5364167

 

Java 에서 DataBase Replication Master/Slave (write/read) 분기 처리하기

대규모 서비스 개발시에 가장 기본적으로 하는 튜닝은 바로 데이터베이스에서 Write와 Read DB를 Replication(리플리케이션)하고 쓰기 작업은 Master(Write)로 보내고 읽기 작업은 Slave(Read)로 보내어 부하

egloos.zum.com

d2.naver.com/helloworld/5812258