spring-boot-starter-data-jdbc 또는 spring-boot-starter-data-jpa 의존성을 사용하면 자동으로 Hikari CP를 의존하게 된다.public static Connection getMySQLConnection() {
try {
return DriverManager.getConnection(
MysqlDBConnectionConstant.URL,
MysqlDBConnectionConstant.USERNAME,
MysqlDBConnectionConstant.PASSWORD
);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (result != null) {
try {
result.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
if(pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@RequiredArgsConstructor
public class SimpleJdbcCrudRepository implements SimpleCrudRepository {
private final DataSource dataSource;
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
private void closeConnection(Connection c, Statement s, ResultSet r) {
JdbcUtils.closeConnection(c);
JdbcUtils.closeStatement(s);
JdbcUtils.closeResultSet(r);
}
...
closeConnection() 메서드를 활용해 CP에 반환해주는 것을 의도한 코드이다. 그런데 JdbcUtils.closeConnection() 내부 코드를 살펴보면..public static void closeConnection(@Nullable Connection con) {
if (con != null) {
try {
con.close();
} catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
} catch (Throwable ex) {
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
}
close()를 해버리는거 아닌가? 아니 분명 CP에 반환한다며. 재사용한다며? 왜 close()만 해주고 반환은 안해주지? 의문이 생겼다.처음에 Datasource 을 주입받을 때 Spring은 기본적으로
HikariDataSource을 주입해주는데, 여기서 만들어주는 Connection은HikariProxyConnection이다. 여기에는 close()를 오버라이딩해서 단순히 닫아주는게 아니라 CP에 반환하는 로직이 작성되어 있다고 한다!
@Test
@DisplayName("hikari")
void test2() throws Exception {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(MysqlDBConnectionConstant.URL);
hikariDataSource.setUsername(MysqlDBConnectionConstant.USERNAME);
hikariDataSource.setPassword(MysqlDBConnectionConstant.PASSWORD);
hikariDataSource.setMaximumPoolSize(5);
Connection conn = hikariDataSource.getConnection();
System.out.println("conn class = " + conn1.getClass());
conn class = class com.zaxxer.hikari.pool.HikariProxyConnection
HikariProxyConnection 가 실제로 반환되는 것을 확인했다.HikariProxyConnection 은 ProxyConnection 추상 클래스를 상속받고 있고, ProxyConnection 에서 close() 메서드를 확인할 수 있었다.public final void close() throws SQLException {
this.closeStatements();
if (this.delegate != ProxyConnection.ClosedConnection.CLOSED_CONNECTION) {
this.leakTask.cancel();
try {
if (this.isCommitStateDirty && !this.isAutoCommit) {
this.delegate.rollback();
LOGGER.debug("{} - Executed rollback on connection {} due to dirty commit state on close().", this.poolEntry.getPoolName(), this.delegate);
}
if (this.dirtyBits != 0) {
this.poolEntry.resetConnectionState(this, this.dirtyBits);
}
this.delegate.clearWarnings();
} catch (SQLException e) {
if (!this.poolEntry.isMarkedEvicted()) {
throw this.checkException(e);
}
} finally {
this.delegate = ProxyConnection.ClosedConnection.CLOSED_CONNECTION;
this.poolEntry.recycle();
}
}
}
this.poolEntry.recycle(); 이다. 여기서 재활용을 해준다는 것을 확인했다.getConnection()public Connection getConnection() throws SQLException {
if (this.isClosed()) {
throw new SQLException("HikariDataSource " + this + " has been closed.");
} else if (this.fastPathPool != null) {
return this.fastPathPool.getConnection();
} else {
HikariPool result = this.pool;
if (result == null) {
synchronized(this) {
result = this.pool;
if (result == null) {
this.validate();
LOGGER.info("{} - Starting...", this.getPoolName());
try {
this.pool = result = new HikariPool(this);
this.seal();
} catch (HikariPool.PoolInitializationException pie) {
if (pie.getCause() instanceof SQLException) {
throw (SQLException)pie.getCause();
}
throw pie;
}
LOGGER.info("{} - Start completed.", this.getPoolName());
}
}
}
return result.getConnection();
}
}
getConnection() 을 진행한다. 하지만 Hikari 개발자만 개발용으로 만들어 둔 것으로 추측된다고 한다… 실상은 사용이 안됨. 기본생성자로 HikariDataSource 을 생성하면 fastPathPool == null 이 된다.result.getConnection(); 가 호출되어 아래 코드가 실행된다. HikariPool 에서 getConnection() 메서드이다.public Connection getConnection(long hardTimeout) throws SQLException {
this.suspendResumeLock.acquire();
long startTime = ClockSource.currentTime();
try {
long timeout = hardTimeout;
do {
PoolEntry poolEntry = (PoolEntry)this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);
if (poolEntry == null) {
break;
}
long now = ClockSource.currentTime();
if (!poolEntry.isMarkedEvicted() && (ClockSource.elapsedMillis(poolEntry.lastAccessed, now) <= this.aliveBypassWindowMs || !this.isConnectionDead(poolEntry.connection))) {
this.metricsTracker.recordBorrowStats(poolEntry, startTime);
Connection var10 = poolEntry.createProxyConnection(this.leakTaskFactory.schedule(poolEntry));
return var10;
}
this.closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? "(connection was evicted)" : "(connection is dead)");
timeout = hardTimeout - ClockSource.elapsedMillis(startTime);
} while(timeout > 0L);
this.metricsTracker.recordBorrowTimeoutStats(startTime);
throw this.createTimeoutException(startTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SQLException(this.poolName + " - Interrupted during connection acquisition", e);
} finally {
this.suspendResumeLock.release();
}
}
PoolEntry poolEntry = (PoolEntry) this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);
connectionBag 의 경우 커넥션을 보관하고 있는 장소라고 이해하면 될듯.borrow() 메서드는 사용 가능한 커넥션이 있다면 꺼내고, 없으면 timeout 만큼 기다리다가 초과되면 예외를 반환해주는 듯 하다.Connection var10 = poolEntry.createProxyConnection(this.leakTaskFactory.schedule(poolEntry));
poolEntry 의 경우 커넥션 풀을 감싸고 있는 wrapper 객체이다. 내부에 Connection 을 들고 있다.HikariProxyConnection 을 반환해준다.우리는 HikariProxyConnection 을 Connection 인터페이스로 편하게 사용하고 있는 것이다. 그래서 close()을 호출해도 재정의된 close()가 실행되는 것이다!