Spring源码学习笔记(13)——JDBC
Spring提供了JdbcTemplate模板类来操作数据库,JdbcTemplate是对原生JDBC进行了全面的封装,统一处理了数据库连接的获取与释放等操作,使用起来比较方便。本节分析JdbcTemplate的源码。
一. execute()方法
-
从简单更新语句入手
使用JdbcTemplate的update()方法可以进行数据库的更新操作,源码如下:
public int update(String sql, @Nullable Object... args) throws DataAccessException { return update(sql, newArgPreparedStatementSetter(args)); }
在调用时,创建了一个ArgumentPreparedStatementSetter实例用于封装参数及参数类型。
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException { return update(new SimplePreparedStatementCreator(sql), pss); }
创建了一个SimplePreparedStatementCreator对SQL语句进行封装。
protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss) throws DataAccessException { logger.debug("Executing prepared SQL update"); //调用execute()执行逻辑 return updateCount(execute(psc, ps -> { try { if (pss != null) { //设置PreparedStatement所需的参数 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(); } } })); }
JdbcTemplate的execute()是一个核心方法,JdbcTemplate的大部分操作最后都是调用execute()方法执行。execute()采用一种回调的模式,首先进行数据库连接的获取与属性赋值,然后执行回调方法,最后处理资源的释放。execute()的实现如下:
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); logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : "")); } //获取数据库连接 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) { //提前释放数据库连接,避免由于异常转换器没有被初始化而引起的死锁 if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } String sql = getSql(psc); JdbcUtils.closeStatement(ps); ps = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw translateException("PreparedStatementCallback", sql, ex); } finally { //处理资源释放 if (psc instanceof ParameterDisposer) { ((ParameterDisposer) psc).cleanupParameters(); } JdbcUtils.closeStatement(ps); DataSourceUtils.releaseConnection(con, getDataSource()); } }
可以看到,execute()的执行流程还是比较清晰的。下面具体分析每一步的处理:
-
获取数据库连接
获取数据库连接的处理在DataSourceUtils的doGetConnection()方法中:
public static Connection doGetConnection(DataSource dataSource) throws SQLException { Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { conHolder.requested(); if (!conHolder.hasConnection()) { logger.debug("Fetching resumed JDBC Connection from DataSource"); conHolder.setConnection(fetchConnection(dataSource)); } return conHolder.getConnection(); } logger.debug("Fetching JDBC Connection from DataSource"); Connection con = fetchConnection(dataSource); if (TransactionSynchronizationManager.isSynchronizationActive()) { logger.debug("Registering transaction synchronization for JDBC Connection"); //以同步的方式,获取当前事务的连接。 //在事务环境下,数据库连接与当前线程绑定。 ConnectionHolder holderToUse = conHolder; if (holderToUse == null) { holderToUse = new ConnectionHolder(con); } else { holderToUse.setConnection(con); } //引用计数+1 holderToUse.requested(); TransactionSynchronizationManager.registerSynchronization( new ConnectionSynchronization(holderToUse, dataSource)); holderToUse.setSynchronizedWithTransaction(true); if (holderToUse != conHolder) { TransactionSynchronizationManager.bindResource(dataSource, holderToUse); } } return con; }
在获取数据库连接时,Spring主要考虑了事务的处理,保证同一线程中的数据库操作都是使用同一个事务连接。
-
应用用户设定的输入参数
protected void applyStatementSettings(Statement stmt) throws SQLException { int fetchSize = getFetchSize(); if (fetchSize != -1) { stmt.setFetchSize(fetchSize); } int maxRows = getMaxRows(); if (maxRows != -1) { stmt.setMaxRows(maxRows); } DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); }
setFetchSize主要是为了减少网络交互次数而设计的。当访问ResultSet时,如果每次只从服务器读取一条记录,则会操作大量的网络开销。setFetchSize的含义是调用rs.next时,ResultSet会次一些从服务器读取多少条记录,这样下次调用rs.next时,可以直接从内存中获取数据而不需要进行网络交互,这样提升了性能。
setMaxRows将此Statement对象生成的所有ResultSet对象可以包含的最大行数设置为指定值。
-
调用回调函数
调用传入的回调对象的doInPreparedStatement()方法。
-
处理异常警告
protected void handleWarnings(Statement stmt) throws SQLException { //当设置为忽略警告时,只尝试打印日志 if (isIgnoreWarnings()) { if (logger.isDebugEnabled()) { //在日志开启的情况下,遍历所有产生异常的对象,打印警告信息 SQLWarning warningToLog = stmt.getWarnings(); while (warningToLog != null) { logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" + warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]"); warningToLog = warningToLog.getNextWarning(); } } } else { handleWarnings(stmt.getWarnings()); } }
Spring定义了SQLWarning类描述数据库警告,警告的意思是数据发生了某种错误,但是并不会影响程序的正常执行,因此只是对警告进行打印日志,并没有抛出异常。
-
释放资源
public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException { if (con == null) { return; } if (dataSource != null) { //如果当前线程存在事务,则获取线程绑定的ConnectionHolder,调用其released()方法对引用计数-1,而不是直接释放 ConnectionHolder, conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && connectionEquals(conHolder, con)) { conHolder.released(); return; } } logger.debug("Returning JDBC Connection to DataSource"); doCloseConnection(con, dataSource); }
二. 回调函数的处理
try {
if (pss != null) {
//设置PreparedStatement语句执行所需的所有参数
pss.setValues(ps);
}
//调用PreparedStatement执行更新语句
int rows = ps.executeUpdate();
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows");
}
return rows;
}
finally {
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
setValues()的处理实际是由传入的ArgumentPreparedStatementSetter实例执行的:
public void setValues(PreparedStatement ps) throws SQLException {
if (this.args != null) {
for (int i = 0; i < this.args.length; i++) {
Object arg = this.args[i];
doSetValue(ps, i + 1, arg);
}
}
}
三. Query功能的实现
public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException {
//这里同样使用了ArgumentPreparedStatementSetter
query(sql, newArgTypePreparedStatementSetter(args, argTypes), rch);
}
public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
}
public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
//使用SimplePreparedStatementCreator创建PreparedStatement
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
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();
}
}
}
});
}
可以看到query()和update()的处理是类似的,只不过是在回调方法中使用PreparedStatement的executeQuery()方法执行查询逻辑。
最后调用了ResultSetExtractor的extractData()方法解析结果数据,转换成一个POJO返回。