概要
过度
我们前面介绍了 JDBC 的基本使用方法,以及 Spring JDBC 使用方法。我们大概猜测了一下,Spring JDBC 的核心类 JdbcTemplate
其实就是对Connector
和Statement
的一个封装。
我们通过本文对JdbcTemplate
的实现思路进行详细阅读。
内容简介
JdbcTemplate
实现详细介绍。
所属环节
Spring JDBC实现详细介绍。
上下环节
上文: Spring框架介绍,Spring JDBC引入
下文:无
源码解析
入口
我们从上文的示例代码入手:
jdbcTemplate.update();
jdbcTemplate.query();
当然,根据入参的不同,这两个函数有很大的区别,我们从update
入手:
update()
其中update()
有两种接口:
- 一种是接受
PreparedStatementCreator
和PreparedStatementSetter
。我们称之为不带出结果。 - 一种是接受
PreparedStatementCreator
和KeyHolder
。我们称之为带出结果。
对上面的domain的解释如下:
-
PreparedStatementCreator
:可以生成PreparedStatement
【不一定可执行,可能需要填充参数】 -
PreparedStatementSetter
:为上面的PreparedStatementCreator
服务,可以向里面填参数 -
KeyHolder
:用来接受更新列的指定字段
当然,针对第一种,JdbcTemplate
还进行了n多种封装,这里不再赘述,我们主要看这两个基本方法。
不带出结果
我们直接上源码:
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()
函数,根据名字猜测是执行更新,返回更新涉及的行数(Count)。我们进去看一下:
private static int updateCount(@Nullable Integer result) {
Assert.state(result != null, "No update count");
return result;
}
惊诧三连!!!!!!这么说来所有的逻辑都在execute()
和拉姆达表达式里了。
我们后面再细看execute()
。
带出结果
直接上源码:
@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();
}
}
}
});
}
还是同样的情况,我们发现所有的函数都依赖了execute()
我们猜测是在这里实现了对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,打印日志【注意,如果设置了占位符?的话,这里是不会显示替换之后的结果的】
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
// 拿到 Connection
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
// 得到 PreparedStatement
ps = psc.createPreparedStatement(con);
// 防止取出过多数据引起的问题,设置一些边界值
applyStatementSettings(ps);
// 调用传入的回调函数,在这里会完成拼接入参、调用查询数据库、拼接出参
T result = action.doInPreparedStatement(ps);
// 处理报警
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;
// 关闭 Connection,此处和 conHolder.requested() 呼应,减到0之后就 关闭/放回连接池
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
} finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
// 关闭 Statement 【关闭后这个Statement查出的ResultSet就不能继续滚动了】
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
基本看注释就能熟悉整个思路。其中涉及的一些函数罗列如下:
-
DataSourceUtils.getConnection(obtainDataSource())
返回一个可用的连接 -
DataSourceUtils.releaseConnection(con, getDataSource())
释放这个连接
我们进入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());
}
}
照例进行了委托,此函数仅做了一些异常转化操作
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
// 获得一个链接,如果本线程保存的变量中有就返回
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
// 本线程占了一个 Connector,重入次数+1,然后返回
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
// 如果holder中的不可用,就再拿一个
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
// 此线程没有占用过Connector,从ds拿一个
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);
// TODO 当前线程是否配置了事务【同一事务为了回滚,会将所有的数据库操作放在同一个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 这里后面看事务时可以关注一下
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 事务相关的,后面同一看一下
if (conHolder != null && connectionEquals(conHolder, con)) {
// It's the transactional Connection: Don't close it.
conHolder.released();
return;
}
}
// 非事务相关的,调用完成根据情况看是归还线程池还是关闭
doCloseConnection(con, dataSource);
}
其中conHolder.released()
,conHolder.requested()
内部的核心逻辑都是对一个int
值加减,因为即使是事务,也是单一线程做的,不涉及多线程,如此操作足够。
我们画一个流程图同一看一下:
![](https://img.haomeiwen.com/i3821632/fdeb6146b32d9b45.png)
整体思路基本清晰了,我们只需要在对应的回调函数中完成自己的逻辑即可。
总结
现在我们回去看update()
,query()
的回调函数即可,都是基本的JDBC的接口,没什么可说的了。
计划及展望
本文对Spring JDBC的核心类、核心方法进行了介绍,后面不再赘述。
我们后面会继续看看别的基于Spring的框架,因为这些框架我们大部分情况下都只是使用,最多是查一些问题,基本不会另起炉灶。所以我们需要的是大概熟悉一下Spring XXX中对XXX的定制情况,并相应的熟悉一下XXX的基本API,以方便工作时排查各种稀奇古怪的问题。
网友评论