JDBC 基本概念、池化和线程化

Posted

技术标签:

【中文标题】JDBC 基本概念、池化和线程化【英文标题】:JDBC fundamental concepts, Pooling and Threading 【发布时间】:2010-11-19 08:05:18 【问题描述】:

我一直在单线程环境的 JavaSE 中使用 JDBC。但是现在我需要使用一个连接池并让许多线程与数据库(MSSQL 和 Oracle)进行交互,我很难做到这一点,因为我似乎对 api 缺乏一些基本的理解。

连接并记录Connection 后的AFAIK 表示与数据库的物理tcp/ip 连接。它创建Statement(s),可以看作是通过Connection与数据库进行SQL交互。

事务和回滚从何而来?是在Connection 还是Statement 级别。 “一个”Connection 创建 N 条语句并将其提供给不同的线程以便让每个人都拥有对 Statement 的使用是否安全?

如果没有,在配置池之后是这样的:

OracleDataSource ods = new OracleDataSource(); 
ods.setURL("jdbc:oracle:thin:@tnsentryname");
ods.setUser("u");
ods.setPassword("p");

顺便说一句,我在哪里设置连接池大小?

为了正确使用连接,我会在每个线程中这样做吗?

//头运行方法

Connection conn = ods.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("the sql");
// do what I need to do with rs
rs.close();
int updateStatus = stmt.executeUpdate("the update");
stmt.close();
conn.close();

//线程运行方法结束

如果池的任何物理连接以某种方式崩溃或断开连接,池是否会自动尝试重新连接并将新连接注入池中,以便后续 pool.getConnection() 将获得健康连接?

非常感谢,请原谅我的英语不好。

【问题讨论】:

【参考方案1】:

    事务发生在连接级别。

    没有。通常,JDBC 驱动程序会确保您不能在同一连接上执行第二条语句,而另一个语句处于活动状态。

如果您需要连接池,请尝试DBCP framework。它提供了相当不错的故障处理(比如注意到过时的连接和客户端代码没有返回的连接)。

至于您的代码:始终将代码包装在try...finally...

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try 
     conn = ds.getConnection ();
     stmt = ...
     rs = ...

finally 
     rs = close (rs);
     stmt = close (stmt);
     conn = close (conn);


public static Connection close (Connection conn) 
    if (conn != null) 
        try 
            conn.close ();
        
        catch (SQLException e) 
            e.printStackTrace(); // Log, don't rethrow!!
        
    
    return null;

此代码将确保所有连接等始终正确关闭,并且关闭期间的任何异常都不会隐藏以前的错误。

【讨论】:

天哪,我喜欢 java,一切都有一个框架 :D 感谢 Aaron 的回答。但是,关于框架对陈旧连接的作用,难道不是任何池实现(oracle 或 mssql 驱动程序)都应该默认做的吗? “应该”!=“确实”!=“做得很好”。您可以在 DBCP 中设置一个超时,上面写着“如果一个小时后没有返回连接,则客户端中存在错误,所以让我们重用它”。至于“陈旧”,您需要提供自定义 SQL 来检查连接是否已死。如果 JDBC 会定义它会很好,但它没有,并且大多数 DB 供应商甚至不费心支持很多 JDBC 规范。例如,在 Oracle 中,TIMESTAMP 不是 java.sql.Timestamp。【参考方案2】:

我认为您应该从连接池的 Sun tutorial 开始。除此之外,还有很多连接池的实现,一些开源的,包括one from Apache。您应该真正从那里开始,而不是在这里重新发明***。

【讨论】:

Em,对不起,我不是想重新发明一些东西。本教程建议使用连接包装器 JDCConnectionManager。但是我使用的驱动程序已经有了它们的池化数据源实现。 您使用的是什么驱动程序?另外,你看过 Apache 的实现吗?如果您需要的不仅仅是基本的 JDBC,那可能会为您解决问题。 ojdbc14.jar 和 jtds-1.2.2.jar。如果驱动程序已经提供了它,我为什么要尝试 apache 的池实现? 好吧,您有两个不同的数据库正在连接,Apache 实现(或可能的其他实现)将使您能够拥有一个与其中任何一个交互的池。但我不再清楚你的问题。起初,您似乎正在编写自己的池实现(如 Sun 教程)。现在您似乎想使用现有的。我不知道 Oracle 有什么,但 jTDS 只是给你一个适合池的连接,它没有给你实际的池实现。【参考方案3】:

连接池使用自己的包装器实现来装饰 Connection 和 Statement 实例。当您在连接上调用 close 时,您实际上只是将其释放回池中。当您在准备好的语句上调用 close 时,您实际上只是将其释放回连接的语句缓存。当您准备语句时,您可能只是从连接中获取缓存的语句实例。所有这些都隐藏在视图之外,因此您不必担心。

当一个连接被提供给一个客户端时,它不再可供任何其他客户端使用,直到该连接被释放回池中。您通常只在需要时获取连接,然后在完成后立即返回它们。由于连接在池中保持打开状态,因此获取和释放连接的开销很小。

您应该像使用单个 JBDC 连接一样使用池中的连接,并遵循有关关闭资源的最佳实践,以免泄漏任何连接或语句。请参阅其他一些答案中的 try/catch/finally 示例。

池可以管理连接资源并在将它们分发给客户端之前对其进行测试,以确保它们不会过时。此外,池将根据需要创建和销毁连接。

【讨论】:

哇!,感谢teabot 的另一件事,一旦你有一个连接。为什么 api 将要在数据库中进行的操作拆分为 Statement 对象而不是仅使用连接对象?对于准备好的陈述,我理解这一点。但是对于正常的语句,是否只是为了让开发者说N个可能执行或不执行的操作,或者可能执行多次,只是为了这个? 只是猜测,但是:正如您显然意识到的那样,PreparedStatements 必须与 Connections 分开,因为每个 Connection 可能有多个 PreparedStatement。所以我想 Java 人把 Statement 分开来保持它们与 PreparedStatements 平行。否则,您将在 Connection 中有一大堆“语句函数”,然后在 PreparedStatement 中有一大堆。那会弄乱 Statement 和 PreparedStatement 的继承树。正如我所说,只是猜测。如果有人对此有权威消息来源,我会很高兴听到它。【参考方案4】:

您只能在任何给定的连接上保持一个语句打开。使用连接池创建多个连接并没有那么困难,尽管要走的路是使用最常用的连接之一。

另外,如果你打算使用标准 JDBC,我建议使用 PreparedStatement 而不是 Statement。

我一直在使用 iBatis,开箱即用非常好。还带来了一些其他的东西。

【讨论】:

【参考方案5】:

看看this (+:

【讨论】:

【参考方案6】:

额外的位:

    应用服务器倾向于提供连接池,它可以变得相当聪明。如果您使用的是应用服务器,请在添加自己的任何内容之前仔细调查开箱即用的内容。

    交易:如果你有

    开始交易

    获取连接 工作 关闭连接 // 意味着返回池

    获取连接(具有相同的隔离级别等) // 您将获得 SAME 连接,池为您的交易保留它

    work // 发生在同一个事务中 关闭连接

    提交事务 // 提交所有工作

    连接和错误

池实现可以很聪明。如果池中的任何一个连接遇到某些错误,表明数据库服务器已反弹,则池可以选择丢弃所有池成员。

【讨论】:

【参考方案7】:

如果您已经掌握了单线程 JDBC,那么使用多线程和连接池应该没什么大不了的。您需要做的不同是: 1. 当您需要连接时,从池中获取它,而不是直接获取。 2. 每个线程都应该有自己的连接。

澄清第 2 点:如果您获得一个连接,然后将其传递给多个线程,则可能有两个线程尝试同时针对同一个连接执行查询。 Java 将对此抛出异常。每个连接只能有一个活动语句,每个语句只能有一个活动查询(即结果集)。如果两个线程都持有同一个 Connection 对象,它们很可能会立即违反此规则。

另一个警告:使用连接池时,请务必非常小心,在完成后始终关闭连接。池管理器没有明确的方法可以知道您何时完成连接,因此如果您未能关闭连接,它会在那里悬空很长时间,可能永远取决于池管理器。我总是总是在每个“getConnection”后面加上一个 try 块,并在 finally 块中关闭连接。然后我知道我已经在函数退出之前关闭了它。

除此之外,一切都应该和你习惯的一样。

【讨论】:

不知道是不是版本问题,“Java会在这个上面抛出异常”在最近的JDBC中不是真的。 @yuxh 对不起,我想我的陈述模棱两可。我不是说,如果你尝试这样做,Java 会立即自动抛出异常。我的意思是,你有可能创造一种会导致异常的情况。您可能会在任何给定应用程序的任何给定运行中找到一种方法。

以上是关于JDBC 基本概念、池化和线程化的主要内容,如果未能解决你的问题,请参考以下文章

Netty实战-EventLoop和线程模型

卷积层池化和激活函数的顺序

二叉树1. 二叉树的基本概念

最大值池化与均值池化比较分析

线程池源码解析

内存池进程池线程池