LazyConnectionDataSourceProxy

dragonappear·2022년 8월 15일
0

DataSource

목록 보기
4/4

출처

제목: "LazyConnectionDataSourceProxy 알아보기"
작성자: github.io(sup2is)
작성자 수정일: 2021년7월8일
링크: https://sup2is.github.io/2021/07/08/lazy-connection-datasource-proxy.html
작성일: 2022년8월16일

LazyConnectionDataSourceProxy란?

  • Spring은 트랜잭션에 진입하는 순간 Database Connection을 가져올까?

  • 결론부터 말하면 스프링에서는 트랜잭션에 진입하는 순간 설정된 데이터소스의 커넥션을 가져온다. 이 결론으로 도달할 수 있는 단점은 아래와 같다.

    • Ehcache같은 Cache를 사용하는 경우 실제 Database에 접근하지 않지만 불필요한 커넥션을 점유
    • Hibernate의 영속성 컨텍스트 1차캐시(실제 Database에 접근하지 않음) 에도 불필요한 커넥션을 점유
    • 외부 서비스(http, etc …)에 접근해서 작업을 수행한 이후에 그 결과값을 Database에 Read/Write하는 경우 외부 서비스에 의존하는 시간만큼 불필요한 커넥션 점유
    • Multi Datasource 환경에서 트랜잭션에 진입한 이후 Datasource를 결정해야할때 이미 트랜잭션 진입시점에 Datasource가 결정되므로 분기가 불가능

위 단점들을 해결할 수 있는 방법이 바로 LazyConnectionDataSourceProxy 이다.

LazyConnectionDataSourceProxy을 사용하면 실제로 커넥션이 필요한 경우가 아니라면 커넥션 풀에서 커넥션을 점유하지 않고 실제로 필요한 시점에만 커넥션을 점유하게 할 수 있다.

먼저 실제 스프링 트랜잭션에 진입한 뒤 아무런 작업을 하지않아도 DB 커넥션을 점유하는지 알아보자.

package me.dragonappear.replicationdatasource.service;

import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.dragonappear.replicationdatasource.entity.Account;
import me.dragonappear.replicationdatasource.repository.AccountJpaRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;


@Slf4j
@RequiredArgsConstructor
@Transactional
@Service
public class AccountService {
    private final AccountJpaRepository accountJpaRepository;
    private final DataSource dataSource;

    public Account get(Long id) {
        logConnectionStatus();
        return null;
        //return accountJpaRepository.findById(id).orElseThrow(() -> new RuntimeException("조회 실패"));
    }

    public Long create(String name) {
	    return null;
        //return accountJpaRepository.save(new Account(name)).getId();
    }

    private void logConnectionStatus() {
        HikariPoolMXBean hikariPoolMXBean = ((HikariDataSource) dataSource).getHikariPoolMXBean();
        log.info("################################");
        log.info("현재 active인 connection의 수 : {}",hikariPoolMXBean.getActiveConnections());
        log.info("현재 idle인 connection의 수 : {}",hikariPoolMXBean.getIdleConnections());
        log.info("################################");
    }

}

아무런 작업을 하지 않아도 트랜잭션에 진입하는 순간 커넥션을 점유하는 것을 확인할 수 있다.


LazyConnectionDataSourceProxy 적용하기

아래와 같이 LazyConnectionDataSourceProxy을 적용해보고 다시 아무런 작업도 하지 않는 AccountService 의 get() 메서드를 실행해보도록 하겠다.

@Configuration
@Profile("local")
public class DataSourceConfig {

    @Bean
    public DataSource lazyDataSource(DataSourceProperties properties) {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl(properties.getUrl());
        dataSource.setUsername(properties.getUsername());
        dataSource.setPassword(properties.getPassword());
        dataSource.setDriverClassName(properties.getDriverClassName());
        return new LazyConnectionDataSourceProxy(dataSource);
    }
}

logConnectionStatus() 수정

    private void logConnectionStatus() {
        HikariPoolMXBean hikariPoolMXBean = ((HikariDataSource) ((LazyConnectionDataSourceProxy) lazyDataSource).getTargetDataSource()).getHikariPoolMXBean();
        log.info("################################");
        log.info("현재 active인 connection의 수 : {}",hikariPoolMXBean.getActiveConnections());
        log.info("현재 idle인 connection의 수 : {}",hikariPoolMXBean.getIdleConnections());
        log.info("################################");
    }

  • JpaTransactionManagerdoBegin() 메서드를 실행하는 부분에서 커넥션을 맺는 과정이 있는데 일반 HikariDataSource 인스턴스는 doBegin() 시점에 커넥션을 실제로 가져오지만
  • LazyConnectionDataSourceProxyProxy 객체로 한번 감싼 인스턴스를 doBegin() 시점에 리턴시켜서 실제 커넥션을 맺지는 않고 프록시 객체를 반환하도록 되어있다.

JpaTransactionManager.doBegin()

HikariDataSource.getConnection() 에서 실제로 커넥션을 가져오는 모습

LazyConnectionDataSourceProxy.getConnection() 에서 프록시를 리턴하는 모습

0개의 댓글