SpringJDBC解析2-execute方法

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
日志服务 SLS,月写入数据量 50GB 1个月
云解析 DNS,旗舰版 1个月
简介: 大家都使用过JDBCTEMPLATE的execute方法,execute作为数据库操作的核心入口,将大多数数据库操作相同的步骤统一封装,而将个性化的操作使用参数PreparedStatementCallback回调。

大家都使用过JDBCTEMPLATE的execute方法,execute作为数据库操作的核心入口,将大多数数据库操作相同的步骤统一封装,而将个性化的操作使用参数PreparedStatementCallback回调。

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(getDataSource());  
    PreparedStatement ps = null;  
    try {  
        Connection conToUse = con;  
        if (this.nativeJdbcExtractor != null &&  
                this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) {  
            conToUse = this.nativeJdbcExtractor.getNativeConnection(con);  
        }  
        ps = psc.createPreparedStatement(conToUse);  
        //应用用户设定的输入参数  
        applyStatementSettings(ps);  
        PreparedStatement psToUse = ps;  
        if (this.nativeJdbcExtractor != null) {  
            psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);  
        }  
        //调用回掉函数  
        T result = action.doInPreparedStatement(psToUse);  
        handleWarnings(ps);  
        return result;  
    }  
    catch (SQLException ex) {  //释放数据库连接避免当异常转换器没有被初始化的时候出现潜在的连接池死锁  
        if (psc instanceof ParameterDisposer) {  
            ((ParameterDisposer) psc).cleanupParameters();  
        }  
        String sql = getSql(psc);  
        psc = null;  
        JdbcUtils.closeStatement(ps);  
        ps = null;  
        DataSourceUtils.releaseConnection(con, getDataSource());  
        con = null;  
        throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);  
    }  
    finally {  
        if (psc instanceof ParameterDisposer) {  
            ((ParameterDisposer) psc).cleanupParameters();  
        }  
        JdbcUtils.closeStatement(ps);  
        DataSourceUtils.releaseConnection(con, getDataSource());  
    }  
} 

以上方法对常用操作进行了封装,包括如下几项内容:

  1. 获取数据库连接
  2. 应用用户设定的输入参数
  3. 调用回调函数
  4. 警告处理
  5. 资源释放

获取数据库连接

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(dataSource.getConnection());  
        }  
        return conHolder.getConnection();  
    }  
    logger.debug("Fetching JDBC Connection from DataSource");  
    Connection con = dataSource.getConnection();  
    //当前线程支持同步  
    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);  
        }  
        //记录数据库连接  
        holderToUse.requested();  
        TransactionSynchronizationManager.registerSynchronization(  
                new ConnectionSynchronization(holderToUse, dataSource));  
        holderToUse.setSynchronizedWithTransaction(true);  
        if (holderToUse != conHolder) {  
            TransactionSynchronizationManager.bindResource(dataSource, holderToUse);  
        }  
    }  
  
    return con;  
}  

在数据库连接方面,Spring主要考虑的是关于事务方面的处理。基于事务处理的特殊性,Spring需要保证线程中的数据库操作都是使用同一个事务连接

应用用户设定的输入参数

protected void applyStatementSettings(Statement stmt) throws SQLException {  
    int fetchSize = getFetchSize();  
    if (fetchSize > 0) {  
        stmt.setFetchSize(fetchSize);  
    }  
    int maxRows = getMaxRows();  
    if (maxRows > 0) {  
        stmt.setMaxRows(maxRows);  
    }  
    DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());  
}  

setFetchSize最主要是为了减少网络交互次数设计的。访问ResultSet时,如果它每次只从服务器读取一行数据,则会产生大量的开销。setFetchSize的意思是ResultSet会一次性从服务器上取得多少行数据回来,这样在下次rs.next时,它可以直接从内存中获取数据而不需要网络交互,提高了效率。这个设置可能会被某些JDBC驱动忽略,而且设置过大也会造成内存的上升。setMaxRows将此Statement对象生成的所有ResultSet对象可以包含的最大行数设置为给定数。

调用回调函数

处理一些通用方法外的个性化处理,也就是PreparedStatementCallback类型的参数的doInPreparedStatement方法的回调。

T result = action.doInPreparedStatement(psToUse);

警告处理

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());  
    }  
}  

这里用到了一个类SQLWarning,SQLWarning提供关于数据库访问警告信息的异常。这些警告直接链接到导致报告警告的方法所在的对象。警告可以从Connection、Statement和ResultSet对象中获得。试图在已经关闭的连接上获取警告将导致抛出异常。类似地,试图在已经关闭的语句上或已经关闭的结果集上获取警告也将导致抛出异常。注意,关闭语句时还会关闭它可能生成的结果集。

最常见的警告DataTruncation:DataTruncation直接继承SQLWarning,由于某种原因意外地截断数据值时会以DataTruncation 警告形式报告异常。对于警告的处理方式并不是直接抛出异常,出现警告很可能会出现数据错误,但是,并不一定会影响程序执行,所以用户可以自己设置处理警告的方式,如默认的是忽略警告,当出现警告时只打印警告日志,而另一种方式只直接抛出异常。

资源释放

数据库的连接并不是直接调用了Connection的API中的close()方法。考虑到存在事务的情况,如果当前线程存在事务,那么说明在当前线程中存在共用数据库的连接,这种情况下直接使用ConnectionHolder中的released方法进行连接数减一,而不是真正的释放连接。

public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException {  
    if (con == null) {  
        return;  
    }  
    if (dataSource != null) {  
        //当前线程存在事务的情况下说明存在共用数据库连接  
        //直接使用ConnectionHolder中的released方法进行连接数减一而不是真正的释放链接  
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
        if (conHolder != null && connectionEquals(conHolder, con)) {  
            // It's the transactional Connection: Don't close it.  
            conHolder.released();  
            return;  
        }  
    }  
    logger.debug("Returning JDBC Connection to DataSource");  
    //在此方法中判断是否需要关闭连接,然后再进行关闭  
    doCloseConnection(con, dataSource);  
}  

 

目录
相关文章
|
3月前
|
安全 Java UED
深度解析Java中方法内的异步调用实践与应对方案
深度解析Java中方法内的异步调用实践与应对方案
93 1
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
【人工智能】Foxmail邮箱在人工智能领域的应用方法及代码解析
Foxmail邮箱作为一款流行的邮件客户端软件,主要用于个人和企业的邮件收发、管理等功能。虽然它与人工智能(AI)技术有着潜在的融合点,但直接关于Foxmail邮箱在人工智能方面的应用代码并不是常规的讨论内容,因为邮箱客户端本身并不直接包含复杂的AI算法或代码。
134 58
|
1天前
|
存储 关系型数据库 MySQL
技术解析:MySQL中取最新一条重复数据的方法
以上提供的两种方法都可以有效地从MySQL数据库中提取每个类别最新的重复数据。选择哪种方法取决于具体的使用场景和MySQL版本。子查询加分组的方法兼容性更好,适用于所有版本的MySQL;而窗口函数方法代码更简洁,执行效率可能更高,但需要MySQL 8.0及以上版本。在实际应用中,应根据数据量大小、查询性能需求以及MySQL版本等因素综合考虑,选择最合适的实现方案。
13 6
|
20天前
|
vr&ar
简单易懂的 全景图高清下载方法以及原理简要解析(支持下载建E、720yun、酷雷曼、景站、酷家乐、百度街景原图)
这篇文章介绍了一种简单易懂的全景图高清下载方法,使用在线网站全景管家,支持下载包括建E、720yun、酷雷曼等多个平台的全景图原图,并简要解析了全景图的原理和制作方法。
简单易懂的 全景图高清下载方法以及原理简要解析(支持下载建E、720yun、酷雷曼、景站、酷家乐、百度街景原图)
|
18天前
|
项目管理 敏捷开发 开发框架
敏捷与瀑布的对决:解析Xamarin项目管理中如何运用敏捷方法提升开发效率并应对市场变化
【8月更文挑战第31天】在数字化时代,项目管理对软件开发至关重要,尤其是在跨平台框架 Xamarin 中。本文《Xamarin 项目管理:敏捷方法的应用》通过对比传统瀑布方法与敏捷方法,揭示敏捷在 Xamarin 项目中的优势。瀑布方法按线性顺序推进,适用于需求固定的小型项目;而敏捷方法如 Scrum 则强调迭代和增量开发,更适合需求多变、竞争激烈的环境。通过详细分析两种方法在 Xamarin 项目中的实际应用,本文展示了敏捷方法如何提高灵活性、适应性和开发效率,使其成为 Xamarin 项目成功的利器。
34 1
|
22天前
|
监控 安全 iOS开发
|
29天前
|
域名解析 运维 监控
网络故障排查的常用工具与方法:技术深度解析
【8月更文挑战第20天】网络故障排查是一项复杂而重要的工作,需要网络管理员具备扎实的网络知识、丰富的实践经验和灵活的问题解决能力。通过掌握常用工具和方法,遵循科学的排查流程,可以显著提高故障排查的效率和准确性。希望本文能为读者在网络故障排查方面提供有益的参考和启示。
|
18天前
|
安全 数据安全/隐私保护 架构师
用Vaadin打造坚不可摧的企业级应用:安全性考虑全解析
【8月更文挑战第31天】韩林是某金融科技公司的架构师,负责构建安全的企业级应用。在众多Web框架中,他选择了简化UI设计并内置多项安全特性的Vaadin。韩林在其技术博客中分享了使用Vaadin时的安全考虑与实现方法,包括数据加密、SSL/TLS保护、结合Spring Security的用户认证、XSS防护、CSRF防御及事务性UI更新机制。他强调,虽然Vaadin提供了丰富的安全功能,但还需根据具体需求进行调整和增强。通过合理设计,可以构建高效且安全的企业级Web应用。
28 0
|
18天前
|
测试技术 数据库
确保数据访问层的可靠性:详细解析使用Entity Framework Core进行隔离的单元测试方法
【8月更文挑战第31天】在软件开发中,单元测试是确保代码质量的关键。本文通过一个在线商店的商品查询功能案例,介绍了如何使用EF Core和Moq框架实现数据访问层的隔离测试。通过模拟`ApplicationDbContext`,我们能够在不访问真实数据库的情况下对`ProductService`进行单元测试,提高测试效率并保证测试稳定性。这种方法是实现高效、可靠单元测试的重要手段。
25 0
|
18天前
|
前端开发 JavaScript 开发者
React生命周期方法完全指南:深入理解并高效应用每个阶段的钩子——从初始化到卸载的全方位解析
【8月更文挑战第31天】本文详细介绍了React组件生命周期方法,包括初始化、挂载、更新和卸载四个阶段的关键钩子。通过探讨每个阶段的方法,如`componentDidMount`和`componentWillUnmount`,帮助开发者在正确时机执行所需操作,提升应用性能。文章还提供了最佳实践,指导如何避免常见错误并充分利用最新钩子。
28 0

热门文章

最新文章

推荐镜像

更多