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

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

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

一. execute()方法

  1. 从简单更新语句入手

    使用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()的执行流程还是比较清晰的。下面具体分析每一步的处理:

  2. 获取数据库连接

    获取数据库连接的处理在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主要考虑了事务的处理,保证同一线程中的数据库操作都是使用同一个事务连接。

  3. 应用用户设定的输入参数

    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对象可以包含的最大行数设置为指定值。

  4. 调用回调函数

    调用传入的回调对象的doInPreparedStatement()方法。

  5. 处理异常警告

    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类描述数据库警告,警告的意思是数据发生了某种错误,但是并不会影响程序的正常执行,因此只是对警告进行打印日志,并没有抛出异常。

  6. 释放资源

    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返回。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张申傲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值