繁体   English   中英

如何避免 jdbc postgres 的多个连接

[英]How to avoid multiple connections for jdbc postgres

我需要根据之前的查询结果查询一个postgresql database 现在我为每个查询都有一个新的DriverManager连接,这对我来说似乎不是最佳实践(我是 Java 新手)。

public static Integer item(DataModel params) {

    Psqlcon psql = new Psqlcon();
    
    try (Connection con = DriverManager.getConnection(psql.url, psql.user, psql.password);
         PreparedStatement pst = con.prepareStatement("SELECT * FROM table1 WHERE var=? "
        pst.setObject(1, params.getID());
       
        ResultSet find_id = pst.executeQuery();{

            while (find_id.next()) {

                id = find_id.getInt(1);
            }
            pst.close();
        }

    } catch (SQLException ex) {

        Logger lgr = Logger.getLogger(something.class.getName());
        lgr.log(Level.SEVERE, ex.getMessage(), ex);
    }

    try (Connection con = DriverManager.getConnection(psql.url, psql.user, psql.password);
             PreparedStatement pst = con.prepareStatement("SELECT something FROM table2 WHERE id=? "
            
        pst.setObject(1, id);
       
        ResultSet find_next = pst.executeQuery();{

            while (find_next.next()) {

                id2 = find_next.getInt(1);
            }
            pst.close();
        }
   
        ....on so on

这是一个过于简单的例子。 查询实际上要复杂一些。 有一个connection并只关闭prepareStatement不是更好吗? 如何实现只有一个连接?

您的问题归结为 state 跟踪。 整个项目中的大量代码都需要使用 DB,而 DB基本上是面向 session 的概念。 因此,您将需要更改您的代码流——任何需要对 DB 执行任何操作的代码不能只是 go:“嗯,好吧,我将 go 建立连接”。 它需要获得提供给它的现有连接,因为“每次都建立新连接”是行不通的。 不仅因为这很慢,而且主要是因为交易是一回事。

这就是注入框架的用武之地:它们都是为了让 state 存在于项目的大量位置。 但是,您也可以“手动”完成,可以这么说:传递一个Connection object 作为方法参数。

这段代码中有一些代码异味。 这些问题正在引导您提出这个问题。

异常处理

“发生异常时,将一些内容打印到日志并继续运行”是您的代码现在所做的。 鉴于我是这样说的,希望现在很明显,这是一个非常糟糕的主意。 应该声明一个从根本上做数据库事情的方法throws SQLException 整个“代码流”(最终被调用以执行特定作业的每个方法)都需要是ALL声明为throws SQLException的方法,此时您可以摆脱所有 try/catch 块。 在使代码变得更好的同时,您将大大减少代码 LOC! 耶!

您希望“例外”做很多事情:

  • 停止执行。 当您在您不理解的 state 中时,您不希望代码继续运行。 SQLException 有很多原因,因此,您不知道发生了什么。 继续下去是不好的。 一个简单的解决方法是让 catch 块重新抛出一些东西( throw new RuntimeException("uncaught", e) - 你应该更新你的 IDE 模板,这是正确的默认 catch 块内容),但更好的是让 SQLException 发生.
  • 您希望尽可能多地保存相关信息:堆栈跟踪、因果链、确切的异常类型、消息以及异常 object 具有的任何特殊信息 - SQLException 有很多这样的信息,例如 postgres 错误 state非常重要的数字,并不总是包含在消息中。

只是让所有东西都抛出 SQLException 就可以完成所有这些 - 很少有地方(所有顶级调用)可以编写处理它的代码,而不是编写 83951 次lgr.log(Level.SEVERE)

每次都会创建连接

您不想这样做 - 您希望传递连接。 通常,您需要更强大的设置来跟踪交易。 所有数据库的一切都始终处于事务形式。 即使你有 autocommit true (这就是为什么它被称为'AutoCommit'而不是'transactionless'):这只是意味着数据库假设你打算在每次查询后执行db.commit()

甚至读取查询也是事务性的。 想象一家银行需要为合伙企业的收购提供银行担保。 为此,它需要总结合伙企业中每个成员的余额。 你可以简单地做:

int sum = 0;
for (Partner p : partners) {
  try (var pst = con.prepareStatement("SELECT balance FROM table2 WHERE id=?")) {
    ... 
    sum += rs.getInteger(1);
  }
}

return sum > requiredGuarantee;

但是,上面的代码被巧妙地破坏了 想象一下,就在这段代码的中间,一个合伙人将一大笔钱转移给了另一个合伙人。 您碰巧在转账之前读取了将现金转出的合作伙伴,但在其之后读取了接收合作伙伴的余额。 您的代码现在有错误的总和。 事情是所有合伙人账户中的总金额远高于实际金额,银行提供担保,交易失败。 银行刚刚损失了1000万美元。 哎呀,真是个bug? 不是吗?

解决方案是交易。 以及正确的事务级别。 在正确的级别(SERIALIZABLE 是唯一可以正确执行此操作的级别),如果您在单个事务中执行所有这些 SELECT,则不会发生上述情况。 Postgres 会让你像往常一样查询和总结,你可能确实得到了错误的总和,但是.commit()调用(请注意,在这个事务中我们还没有写任何东西!)会失败 这是你重新开始的提示。

连接与事务处于同一级别:如果要同时运行 2 个事务,则必须有 2 个连接。 单个连接在事务中。 因此,传递连接 object传递事务相同,并且您必须具有如上面代码所示的事务(想象银行担保最终由在“担保”表中插入新行来表示。你想要这一切都在一个事务中,所以毕竟,如果由于在两者之间发生了一些其他事务而求和不一致,那么你.commit()和 postgres 会抛出一个异常(你写的那一行从未发生过)这改变了一个或多个合作伙伴的平衡。

这是写入“一致”数据 model 的唯一正确方法。

对于某些项目,一致性成本太高。 然而,除非你的名字是真正的 google,否则你永远不会到达那里,如果你必须拥有这个,它比仅仅选择较低的事务隔离级别要复杂得多。 所以,为了一致性,go。

如果您希望Connection con成为所有基于 db 的方法的第一个参数,或者您想创建一个 object 并拥有一个类型为“con”的字段,这取决于您(但请记住,它们必须被关闭,所以创建此类 object 的代码必须使用 try-with-resources 来关闭该连接,或者您的 class 具有字段Connection con需要为AutoClosable ,并且必须使用 try-with-resources 创建。) - 或如果你想使用像 spring 或 dagger 或 guice 这样的注入框架。

使用抽象

JDBC 主要设计用于公开每个关系数据库系统可能具有的每个功能,并成为一致的“底层”。 这意味着作为一个 API 直接在其上编写应用程序,这太可怕了 它不适合你。

使用为此目的设计的库。 它将建立JDBC 之上。 您正在有效地编写直接机器代码。 这很少是一个好主意。

您可以使用 JPA(对象持久性框架),如 Hibernate,但这是一个全新的蠕虫罐。 如果您喜欢 SQL,您正在寻找的工具是JOOQJDBI 这些意味着你的复杂:

pst.setObject(1, id);
       
        ResultSet find_next = pst.executeQuery();{

            while (find_next.next()) {

                id2 = find_next.getInt(1);
            }
            pst.close();

都变成了简单的单行。

连接池

创建连接 object 真的很昂贵。 如果您想要多个同时交易,这是您必须支付的不可避免的成本,但如果您有多个连续交易,您真的不想支付它。 这在一定程度上取决于 DB 引擎和 JDBC 驱动程序(有些甚至有内置的池系统,有些则没有)。 但一个简单的解决方案是添加一个抽象层:您可以请求连接池,而不是要求 DriverManager 为您获取连接。 它为您提供了一个虚拟化连接 object ,它通过所有调用,除了close() ,它实际上并没有关闭连接,而只是使虚拟 object 无效并将实际连接传递回“池”。

在您获得大量流量之前,这不是必需的,但是如果您想做好充分的准备,那么您可以看看它。 HikariCP这样的库使这变得容易。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM