前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JDBC ResulSet资源释放和Statement并发调用源码分析

JDBC ResulSet资源释放和Statement并发调用源码分析

作者头像
FunTester
发布2023-12-13 16:03:05
2110
发布2023-12-13 16:03:05
举报
文章被收录于专栏:FunTester

最近喜欢上阅读源码来佐证之前的学到的知识,之前读完了Caffeine源码了解到了Caffeine在部分高并发场景可能存在瓶颈的3个点之后。今天又对Java-MySQL的JDBC产生兴趣。

起源于两个问题:

  • 当一个 ResulSet 被执行方法返回,如果不使用 close() 方法,会怎么样?
  • Statement支持不支持并发调用?

ResulSet资源释放

close() 方法注释中,我们得到该方法是为了释放ResulSet对象占用的各种资源。在 Java 中,ResultSet 是用于表示 SQL 查询结果的对象。ResultSet 对象维护了指向查询结果的光标,可以让你逐行访问查询返回的数据。ResultSetclose() 方法用于关闭该 ResultSet 对象,释放资源并释放与数据库的连接。一旦调用了 close() 方法,该 ResultSet 对象将不再可用,并且不能再使用它来访问查询结果或提取数据。当你完成对 ResultSet 对象的操作后,应该及时调用 close() 方法来释放资源,尤其是当你不再需要访问查询结果或当你需要释放数据库连接时。这可以帮助释放数据库资源、减少内存占用,并允许数据库服务器回收相关资源以供其他请求使用,从而提高系统性能和资源利用率。

但是我在实际使用当中,并没有显式调用过 close() 也从来没发生数据库连接超限导致的异常,这一点让我非常奇怪。

首先我们看一下 close() 的具体内容:

代码语言:javascript
复制
public void close() throws SQLException {  
    try {  
        this.realClose(true);  
    } catch (CJException var2) {  
        throw SQLExceptionsMapping.translateException(var2, this.getExceptionInterceptor());  
    }  
}

我们再看 realClose() 方法,内容太多了,我摘抄了部分内容:

第一部分:

代码语言:javascript
复制
            JdbcConnection locallyScopedConn = this.connection;
            if (locallyScopedConn != null) {
                synchronized(locallyScopedConn.getConnectionMutex()) {

第二部分:

代码语言:javascript
复制
this.rowData = null;  
this.columnDefinition = null;  
this.eventSink = null;  
this.warningChain = null;  
this.owningStatement = null;  
this.db = null;  
this.serverInfo = null;  
this.thisRow = null;  
this.fastDefaultCal = null;  
this.fastClientCal = null;  
this.connection = null;  
this.session = null;  
this.isClosed = true;

第一部分显式获取了当前连接的互斥锁,然后进行一系列操作,说明改部分操作对于一个 java.sql.Connection 使用互斥锁操作是线程安全,也就是串行的。

第二部分是关闭之后对于类成员属性的一些重置。其中看到倒数第三行 this.connection = null; 就是释放当前连接引用,请注意这并不是把连接资源释放了,不同于 Connectionclose() 方法。

然后我们在 com.mysql.cj.jdbc.StatementImpl 类中找到了对应的调用:

代码语言:javascript
复制
protected void closeAllOpenResults() throws SQLException {  
    JdbcConnection locallyScopedConn = this.connection;  
    if (locallyScopedConn != null) {  
        synchronized(locallyScopedConn.getConnectionMutex()) {  
            if (this.openResults != null) {  
                Iterator var3 = this.openResults.iterator();  
  
                while(var3.hasNext()) {  
                    ResultSetInternalMethods element = (ResultSetInternalMethods)var3.next();  
  
                    try {  
                        element.realClose(false);  
                    } catch (SQLException var7) {  
                        AssertionFailedException.shouldNotHappen(var7);  
                    }  
                }  
  
                this.openResults.clear();  
            }  
  
        }  
    }  
}

然后我们找到了 com.mysql.cj.jdbc.StatementImpl#implicitlyCloseAllOpenResults 方法,最终找到了其中一个入口方法 com.mysql.cj.jdbc.StatementImpl#executeQuery ,源码部分如下:

代码语言:javascript
复制
    public ResultSet executeQuery(String sql) throws SQLException {
        try {
            synchronized(this.checkClosed().getConnectionMutex()) {
                JdbcConnection locallyScopedConn = this.connection;
                this.retrieveGeneratedKeys = false;
                this.checkNullOrEmptyQuery(sql);
                this.resetCancelledState();
                this.implicitlyCloseAllOpenResults();

也就是说每一次执行MySQL操作,都会将所有打开的 ResultSet 对象都关闭掉。

所以对于 ResultSet 对象来说,下一次调用都会关闭,即使不手动关闭释放资源也是可以接受的。

Statement并发

虽然 Statement 官方资料中并没有明显说是否支持并发,但我一直认为是不支持并发的,忘记知识的来源了,再去搜索的话,也得到了很多印证。

但是对于一个对象来说,无法禁止并发调用,假如用户自己并发调用了,会怎么样呢?

我写了个Demo测试了一下,内容如下:

代码语言:javascript
复制
        def connection = SqlBase.getConnection("jdbc:mysql://127.0.0.1:3306/funtester", "root", "funtester")
        def statement = SqlBase.getStatement(connection)
        def test = {
            def query = statement.executeQuery("select * from user")
            while (query.next()) {
                println query.getString("name")
                println query.getString("id")
            }
            query.close()
        }
        10.times {
            Thread.startVirtualThread {
                test()
            }
        }
  sleep(1.0)

代码Groovy写的,用上了JDK 21最新的虚拟线程功能,感觉良好,最后加了一行 sleep(1.0) 因为虚拟线程并不会阻塞 JVM 关闭,这一点跟 Golang 的协程 goroutine 一样。

结果就发现了报错:

Exception in thread "" java.sql.SQLException: Operation not allowed after ResultSet closed

我们根据报错信息找到了 com.mysql.cj.jdbc.result.ResultSetImpl#checkClosed 方法,内容如下:

代码语言:javascript
复制
protected final JdbcConnection checkClosed() throws SQLException {  
    JdbcConnection c = this.connection;  
    if (c == null) {  
        throw SQLError.createSQLException(Messages.getString("ResultSet.Operation_not_allowed_after_ResultSet_closed_144"), "S1000", this.getExceptionInterceptor());  
    } else {  
        return c;  
    }  
}

这个 connection 表示的就是与当前对象关联的 JdbcConnection ,但是在问题1中 close() 方法第二部分代码分享,当调用 close() 方法时会将对象的 connection 属性变成 null 。所以就会报异常了。

阅读源码的好处

阅读源代码对工作和个人成长有着广泛而深远的影响。代码是软件工程的核心,阅读源代码不仅是对代码功能的理解,更是对整个软件生态系统的深入探索。当我们深入代码之中,我们不仅仅了解代码是如何工作的,还能感受到代码的背后所蕴含的设计思想、优化策略、团队合作与协作等方面的价值。

首先,阅读源代码能够帮助我们更全面、更深入地理解项目的架构和设计。透过代码,我们能够窥见不同模块、组件之间的交互方式,理解数据流、逻辑和功能实现的关系。通过对代码的解读,我们能够建立起对项目整体结构和工作方式的更深入认识,这对于项目的维护和开发至关重要。

其次,阅读源代码也是一个学习和成长的过程。我们可以从其他人的代码中学习到不同的编码技巧、最佳实践、设计模式和解决问题的方法。这种学习方式让我们接触到各种领域和风格的代码,提高了我们的编程能力和解决问题的能力。

另外,阅读代码也为我们提供了一个优秀的调试和问题解决的平台。通过理解代码的工作原理,当出现问题时能更快地定位和解决。我们能够更准确地判断问题的根源,并采取相应的措施来修复代码中的错误或提升代码的性能。

此外,阅读源代码有助于促进团队协作和沟通。理解其他人的工作方式和风格有助于更好地与团队成员合作,减少代码冲突和理解偏差。更好地理解彼此的工作和贡献,有助于形成更加和谐高效的团队。

总的来说,阅读源代码是一种不断学习、提高编程技能、加深对项目理解的过程。虽然这需要时间和耐心,但它对于个人和团队的成长和发展都有着积极的影响。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 FunTester 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ResulSet资源释放
  • Statement并发
  • 阅读源码的好处
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档