LazyConnectionDataSourceProxy
spring database
LazyConnectionDataSourceProxy
Proxy for a target DataSource, fetching actual JDBC Connections lazily. This DataSource proxy allows to avoid fetching JDBC Connections from a pool unless actually necessary.
- 스프링은 트랜잭션 시작시 커넥션의 실제 사용여부와 무관하게 커넥션을 확보한다.
- 단점
- Cache 를 사용하는 경우 Database 에 접근하지 않음에도 불필요한 커넥션을 점유
- Hibernate 의 영속성 컨텍스트 1차 캐시에도 불필요한 커넥션을 점유
- 외부 서비스에 접근해서 작업을 수행한 이후 그 결과값을 Database 에 Read/Write 하는 경우 외부 서비스에 의존하는 시간 만큼 불필요한 커넥션을 점유
- Multi DataSource 환경에서 트랜잭션 진입한 이후 DataSource 를 결정 해야할 때, 이미 트랜잭션 진입 시점에서 DataSource 가 결정되므로 분기 불가능
- 단점
- 이로인해 트랜잭션 시작 후 커넥션과 무관한 다른 작업으로 많은 시간이 지체되면 그 시간 동안 해당 트랜잭션의 커넥션은 사용불가 상태가 되어, 데이터소스에 커넥션 풀이 부족해지는 사태를 유발할 수도 있다.
- LazyConnectionDataSourceProxy 를 사용하면 트랜잭션이 시작되어도 실제로 커넥션이 필요한 경우에만 데이터소스에서 커넥션을 반환한다.
getConnection()
When asked for a Statement (or PreparedStatement or CallableStatement).
HikariPoolMXBean 으로 Connection Monitoring 하기
- LazyConnectionDataSourceProxy 설정을 하지 않은 경우에는 트랜잭션에 진입하는 순간 커넥션이 할당된다.
- LazyConnectionDataSourceProxy 설정을 한 경우에는 When asked for a Statement (or PreparedStatement or CallableStatement) 일때 커넥션이 할당된다.
@Service
class AuthService(
private val dataSource: DataSource,
private val repository: AuthRepository
) {
private val log = LoggerFactory.getLogger(this.javaClass)
@Transactional
fun createToken(request: TokenDto.CreateReqeust) {
printConnectionStatus()
}
private fun printConnectionStatus() {
val hikari = datasource as HikariDataSource
val hikariPoolMXBean = hikari.getHikariPoolMXBean()
log.info("Active Connections: $hikariPoolMXBean.getActiveConnections()")
log.info("Idle Connections: $hikariPoolMXBean.getIdleConnections()")
}
}
Config
@Configuration
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);
}
}
Master/Slave
- Master/Slave 로 구성된 환경에서 LazyConnectionDataSourceProxy 와 dynamicDataSource 를 설정하여, 런타임에 어떤 커넥션을 맺을지 정할 수 있다.
<bean id="abstractDataSource"
abstract = "true"
class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="maxPoolSize" value="30" />
<property name="minPoolSize" value="10" />
<property name="autoCommitOnClose" value="false" />
<property name="checkoutTimeout" value="10000" />
<property name="acquireRetryAttempts" value="2" />
</bean>
<!--Master /Slave DataSource-->
<bean id="master" parent="abstractDataSource">
<!--connection pool attrs-->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.master.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="slave" parent="abstractDataSource">
<!--connection pool attrs-->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.slave.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--Configure Dynarmic Data Routing Data Source -->
<bean id ="dynamicDataSource" class= "com.example.o2o.dao.split.DynamicDataSource">
<!-- Map<Object, Object> targetDataSources; -->
<property name="targetDataSources">
<map>
<entry value-ref="master" key="master"></entry>
<entry value-ref="slave" key="slave"></entry>
</map>
</property>
</bean>
<!--Evaluate DataSource when it is needed at runtime, lazy connection/evaluation-->
<bean id ="dataSource" class ="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource">
<ref bean="dynamicDataSource"></ref>
</property>
</bean>