在一篇关于 Hibernate 的文章中,我读到如果你想使用多线程,那么为每个 CRUD 操作创建一个新会话。也就是说,例如,我的 DAO 类中的保存操作如下所示:
public void save(User user) {
try (Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession()) {
Transaction tx = session.beginTransaction();
session.save(user);
tx.commit();
}
}
但这就是问题所在。今天我看到另一篇文章说上面描述的动作是一种反模式。逐字:
这是一种关于在一个线程中打开和关闭对数据库的每个操作的 Session 对象的反模式。就数据库事务而言,这也是一种反模式。将您的呼叫分组到一个预定的序列中。此外,不要在每个 SQL 语句上自动提交事务。Hibernate 关闭,或期望应用程序服务器立即关闭自动提交模式。
相反,这篇文章建议Паттерн сессия-на-запрос,它被描述为:
最常见的交易模式。这里的术语“请求”应该在系统响应来自用户/客户端的一系列请求的上下文中理解。Web 应用程序是此类系统的主要示例,但它们当然不是唯一的。在请求处理开始时,应用程序打开一个 Session 对象,启动一个事务,执行所有与数据相关的工作,完成事务,并关闭 Session。该模式的本质是事务和会话之间的一对一关系。
在那之后,我出现了认知失调。首先,因为事实证明,根据这篇文章,我以前做错了,为每个操作创建一个会话,其次,因为我不明白第二种模式的含义。解释哪个是真的,哪个是假的。使用什么,忘记什么?
如果您转向 Hibernate 文档,那么根据它,每个操作的会话被正式声明为antipattern。
至于会话,它的真正含义表明您可以并且如果可能的话,应该将一个或多个事务中有意义的相关操作组合到一个会话中。同时,如果您不需要执行除此操作之外的任何操作,那么您的会话只能包含一个操作这一事实并没有错。
为了理解第一种和第二种的区别,我举个例子:假设有另一个类与User关联,例如UserHistory(让它存储更改用户名的历史)。我们假设在更改 User 中的名称时,UserHistory 类也必须更新。
现在让我们想象一下,我们通过每会话操作完成了所有操作。在这种情况下,我们将有两个不同的会话和两个事务,它们将相互独立地更新 User 和 UserHistory。当其中一个事务没有到达时(例如,由于网络上的突然丢失),就会出现问题,这将不可避免地导致数据库中数据的完整性受到破坏。
是的,当不通过 pools使用数据库时,通过每个操作创建和关闭与数据库的连接是非常耗费资源的。
事实上,正如 Hibernate 的文档中所写的那样:
请注意,它说的是新会话,实际上建议使用当前会话,即:
也就是说,如果没有当前会话,那么我们创建它,如果有,我们就取当前会话。
为每个操作创建一个会话是一种反模式,但不是因为将在另一个会话中进行更新或事务不会在那里结束(DBMS 事务管理器完全能够处理这些事情),而是因为创建会话的成本 - 事实上,它是与数据库的新连接,是一项极其昂贵且资源密集型的操作。