![](/img/trans.png)
[英]How to avoid stalled DB connections with TomEE's JDBC connection pool?
[英]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! 耶!
您希望“例外”做很多事情:
throw new RuntimeException("uncaught", e)
- 你应该更新你的 IDE 模板,这是正确的默认 catch 块内容),但更好的是让 SQLException 发生. 只是让所有东西都抛出 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,您正在寻找的工具是JOOQ或JDBI 。 这些意味着你的复杂:
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.