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개의 댓글

관련 채용 정보