Spring源码学习笔记(13)——JDBC

2020-09-03 11:30:33 浏览数 (1)

Spring源码学习笔记(13)——JDBC

Spring提供了JdbcTemplate模板类来操作数据库,JdbcTemplate是对原生JDBC进行了全面的封装,统一处理了数据库连接的获取与释放等操作,使用起来比较方便。本节分析JdbcTemplate的源码。

一. execute()方法

  1. 从简单更新语句入手 使用JdbcTemplate的update()方法可以进行数据库的更新操作,源码如下:
代码语言:javascript复制
public int update(String sql, @Nullable Object... args) throws DataAccessException {
    return update(sql, newArgPreparedStatementSetter(args));
}
  1. 在调用时,创建了一个ArgumentPreparedStatementSetter实例用于封装参数及参数类型。
代码语言:javascript复制
public int update(String sql, @Nullable PreparedStatementSetter pss) throws DataAccessException {
    return update(new SimplePreparedStatementCreator(sql), pss);
}
  1. 创建了一个SimplePreparedStatementCreator对SQL语句进行封装。
代码语言:javascript复制
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();
            }
        }
    }));
}
  1. JdbcTemplate的execute()是一个核心方法,JdbcTemplate的大部分操作最后都是调用execute()方法执行。execute()采用一种回调的模式,首先进行数据库连接的获取与属性赋值,然后执行回调方法,最后处理资源的释放。execute()的实现如下:
代码语言:javascript复制
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());
    }
}
  1. 可以看到,execute()的执行流程还是比较清晰的。下面具体分析每一步的处理:
  2. 获取数据库连接 获取数据库连接的处理在DataSourceUtils的doGetConnection()方法中:
代码语言:javascript复制
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;
}
  1. 在获取数据库连接时,Spring主要考虑了事务的处理,保证同一线程中的数据库操作都是使用同一个事务连接。
  2. 应用用户设定的输入参数
代码语言:javascript复制
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());
}
  1. setFetchSize主要是为了减少网络交互次数而设计的。当访问ResultSet时,如果每次只从服务器读取一条记录,则会操作大量的网络开销。setFetchSize的含义是调用rs.next时,ResultSet会次一些从服务器读取多少条记录,这样下次调用rs.next时,可以直接从内存中获取数据而不需要进行网络交互,这样提升了性能。 setMaxRows将此Statement对象生成的所有ResultSet对象可以包含的最大行数设置为指定值。
  2. 调用回调函数 调用传入的回调对象的doInPreparedStatement()方法。
  3. 处理异常警告
代码语言:javascript复制
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());
    }
}
  1. Spring定义了SQLWarning类描述数据库警告,警告的意思是数据发生了某种错误,但是并不会影响程序的正常执行,因此只是对警告进行打印日志,并没有抛出异常。
  2. 释放资源
代码语言:javascript复制
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返回。

0 人点赞