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);
}
二. 回调函数的处理
代码语言:javascript复制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实例执行的:
代码语言:javascript复制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功能的实现
代码语言:javascript复制public void query(String sql, Object[] args, int[] argTypes, RowCallbackHandler rch) throws DataAccessException {
//这里同样使用了ArgumentPreparedStatementSetter
query(sql, newArgTypePreparedStatementSetter(args, argTypes), rch);
}
代码语言:javascript复制public void query(String sql, @Nullable PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
}
代码语言:javascript复制public <T> T query(String sql, @Nullable PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
//使用SimplePreparedStatementCreator创建PreparedStatement
return query(new SimplePreparedStatementCreator(sql), pss, rse);
}
代码语言:javascript复制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返回。