概要
過度
我們前面介紹了 JDBC 的基本使用方法,以及 Spring JDBC 使用方法。我們大概猜測(cè)了一下,Spring JDBC 的核心類 JdbcTemplate其實(shí)就是對(duì)Connector和Statement的一個(gè)封裝。
我們通過本文對(duì)JdbcTemplate的實(shí)現(xiàn)思路進(jìn)行詳細(xì)閱讀。
內(nèi)容簡(jiǎn)介
JdbcTemplate實(shí)現(xiàn)詳細(xì)介紹。
所屬環(huán)節(jié)
Spring JDBC實(shí)現(xiàn)詳細(xì)介紹。
上下環(huán)節(jié)
上文: Spring框架介紹,Spring JDBC引入
下文:無
源碼解析
入口
我們從上文的示例代碼入手:
jdbcTemplate.update();
jdbcTemplate.query();
當(dāng)然,根據(jù)入?yún)⒌牟煌?,這兩個(gè)函數(shù)有很大的區(qū)別,我們從update入手:
update()
其中update()有兩種接口:
- 一種是接受
PreparedStatementCreator和PreparedStatementSetter。我們稱之為不帶出結(jié)果。 - 一種是接受
PreparedStatementCreator和KeyHolder。我們稱之為帶出結(jié)果。
對(duì)上面的domain的解釋如下:
-
PreparedStatementCreator:可以生成PreparedStatement【不一定可執(zhí)行,可能需要填充參數(shù)】 -
PreparedStatementSetter:為上面的PreparedStatementCreator服務(wù),可以向里面填參數(shù) -
KeyHolder:用來接受更新列的指定字段
當(dāng)然,針對(duì)第一種,JdbcTemplate還進(jìn)行了n多種封裝,這里不再贅述,我們主要看這兩個(gè)基本方法。
不帶出結(jié)果
我們直接上源碼:
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)
throws DataAccessException {
logger.debug("Executing prepared SQL update");
return updateCount(execute(psc, ps -> {
try {
if (pss != null) {
pss.setValues(ps);
}
int rows = ps.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows");
}
return rows;
} finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}));
}
整體思路比較明確,都委托給了updateCount()函數(shù),根據(jù)名字猜測(cè)是執(zhí)行更新,返回更新涉及的行數(shù)(Count)。我們進(jìn)去看一下:
private static int updateCount(@Nullable Integer result) {
Assert.state(result != null, "No update count");
return result;
}
驚詫三連?。。。。。∵@么說來所有的邏輯都在execute()和拉姆達(dá)表達(dá)式里了。
我們后面再細(xì)看execute()。
帶出結(jié)果
直接上源碼:
@Override
public int update(final PreparedStatementCreator psc, final KeyHolder generatedKeyHolder)
throws DataAccessException {
Assert.notNull(generatedKeyHolder, "KeyHolder must not be null");
logger.debug("Executing SQL update and returning generated keys");
return updateCount(execute(psc, ps -> {
int rows = ps.executeUpdate();
List<Map<String, Object>> generatedKeys = generatedKeyHolder.getKeyList();
generatedKeys.clear();
ResultSet keys = ps.getGeneratedKeys();
if (keys != null) {
try {
RowMapperResultSetExtractor<Map<String, Object>> rse =
new RowMapperResultSetExtractor<>(getColumnMapRowMapper(), 1);
generatedKeys.addAll(result(rse.extractData(keys)));
} finally {
JdbcUtils.closeResultSet(keys);
}
}
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
}
return rows;
}));
}
情況基本相似,等后面過完execute()再回頭看。
query()
上源碼:
public <T> T query(
PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
throws DataAccessException {
Assert.notNull(rse, "ResultSetExtractor must not be null");
logger.debug("Executing prepared SQL query");
return execute(psc, new PreparedStatementCallback<T>() {
@Override
@Nullable
public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
ResultSet rs = null;
try {
if (pss != null) {
pss.setValues(ps);
}
rs = ps.executeQuery();
return rse.extractData(rs);
} finally {
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
還是同樣的情況,我們發(fā)現(xiàn)所有的函數(shù)都依賴了execute()我們猜測(cè)是在這里實(shí)現(xiàn)了對(duì)jdbc通用方法的封裝。
execute()
@Override
@Nullable
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc); // 拿到sql,打印日志【注意,如果設(shè)置了占位符?的話,這里是不會(huì)顯示替換之后的結(jié)果的】
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
// 拿到 Connection
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
// 得到 PreparedStatement
ps = psc.createPreparedStatement(con);
// 防止取出過多數(shù)據(jù)引起的問題,設(shè)置一些邊界值
applyStatementSettings(ps);
// 調(diào)用傳入的回調(diào)函數(shù),在這里會(huì)完成拼接入?yún)?、調(diào)用查詢數(shù)據(jù)庫、拼接出參
T result = action.doInPreparedStatement(ps);
// 處理報(bào)警
handleWarnings(ps);
return result;
} catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
// 關(guān)閉 Connection,此處和 conHolder.requested() 呼應(yīng),減到0之后就 關(guān)閉/放回連接池
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
} finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
// 關(guān)閉 Statement 【關(guān)閉后這個(gè)Statement查出的ResultSet就不能繼續(xù)滾動(dòng)了】
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
基本看注釋就能熟悉整個(gè)思路。其中涉及的一些函數(shù)羅列如下:
-
DataSourceUtils.getConnection(obtainDataSource())返回一個(gè)可用的連接 -
DataSourceUtils.releaseConnection(con, getDataSource())釋放這個(gè)連接
我們進(jìn)入DataSourceUtils看一下:
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);
} catch (IllegalStateException ex) {
throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());
}
}
照例進(jìn)行了委托,此函數(shù)僅做了一些異常轉(zhuǎn)化操作
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
// 獲得一個(gè)鏈接,如果本線程保存的變量中有就返回
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 本線程占了一個(gè) Connector,重入次數(shù)+1,然后返回
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
// 如果holder中的不可用,就再拿一個(gè)
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
// 此線程沒有占用過Connector,從ds拿一個(gè)
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
// TODO 當(dāng)前線程是否配置了事務(wù)【同一事務(wù)為了回滾,會(huì)將所有的數(shù)據(jù)庫操作放在同一個(gè)Connector中】
if (TransactionSynchronizationManager.isSynchronizationActive()) {
try {
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
} else {
holderToUse.setConnection(con);
}
holderToUse.requested();// 重入+1
// TODO 這里后面看事務(wù)時(shí)可以關(guān)注一下
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
// 這里保存到線程
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
} catch (RuntimeException ex) {
// Unexpected exception from external delegation call -> close Connection and rethrow.
// 和主邏輯的釋放一樣
releaseConnection(con, dataSource);
throw ex;
}
}
return con;
}
我們接下來看一下釋放:
public static void releaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) {
try {
doReleaseConnection(con, dataSource);
} catch (SQLException ex) {
logger.debug("Could not close JDBC Connection", ex);
} catch (Throwable ex) {
logger.debug("Unexpected exception on closing JDBC Connection", ex);
}
}
public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {
if (con == null) {
return;
}
if (dataSource != null) {
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// TODO 事務(wù)相關(guān)的,后面同一看一下
if (conHolder != null && connectionEquals(conHolder, con)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
return;
}
}
// 非事務(wù)相關(guān)的,調(diào)用完成根據(jù)情況看是歸還線程池還是關(guān)閉
doCloseConnection(con, dataSource);
}
其中conHolder.released(),conHolder.requested()內(nèi)部的核心邏輯都是對(duì)一個(gè)int值加減,因?yàn)榧词故鞘聞?wù),也是單一線程做的,不涉及多線程,如此操作足夠。
我們畫一個(gè)流程圖同一看一下:

整體思路基本清晰了,我們只需要在對(duì)應(yīng)的回調(diào)函數(shù)中完成自己的邏輯即可。
總結(jié)
現(xiàn)在我們回去看update(),query()的回調(diào)函數(shù)即可,都是基本的JDBC的接口,沒什么可說的了。
計(jì)劃及展望
本文對(duì)Spring JDBC的核心類、核心方法進(jìn)行了介紹,后面不再贅述。
我們后面會(huì)繼續(xù)看看別的基于Spring的框架,因?yàn)檫@些框架我們大部分情況下都只是使用,最多是查一些問題,基本不會(huì)另起爐灶。所以我們需要的是大概熟悉一下Spring XXX中對(duì)XXX的定制情況,并相應(yīng)的熟悉一下XXX的基本API,以方便工作時(shí)排查各種稀奇古怪的問題。