4

在 Martin Fowler 的书中,我读到了UnitOfWorkIdentityMap 模式。

作者提到将 identityMap 放在 UnitOfWork 中是个好主意。但是怎么做呢?

据我所知IdentityMap,受会话限制,但作者没有提到它UnitOfWork

  1. 实例是否UnitOfWork受会话限制?

  2. 假设我们有客户和订单实体。

    public clas Client{
    
         List<Order> orders;
        ...
    }
    

我们收到了更新客户信息(电话号码)并添加新订单的请求:

我们在这里需要多少个 unitOfWork 实例?每个会话?我们应该为客户和订单分开实例吗?

我们这里需要多少个 IdentityMap 实例?对于每个实例?我们应该为客户和订单分开实例吗?

每个 unitOfWork 实例需要多少个 IdentityMap 实例?

如果我们有 2 个并发请求怎么办?

4

2 回答 2

3

unitOfWork 根据您需要管理的业务运营的持续时间需要一个范围。如果您的业务操作扩展到多个请求,但您需要将其视为单个工作单元,则必须以一致的范围(例如,会话)处理实例。

工作单元将跟踪与数据库的所有交互,并通过创建事务并以最佳方式(在短期数据库事务中)进行更改来确认它们。

JPA / Hibernate 默认实现这个模式,所以通常如果你使用它们,你不需要自己实现它。JPA / Hibernate 还在一级缓存中实现了类似身份映射模式的东西。它将实体映射保存在其一级缓存中,避免在会话期间和工作单元期间多次到数据库中查找。

正如休眠文档所说(读我):

多用户客户端/服务器应用程序中最常见的模式是每请求会话。在这个模型中,来自客户端的请求被发送到运行 Hibernate 持久层的服务器。打开一个新的 Hibernate Session,所有的数据库操作都在这个工作单元中执行。工作完成后,一旦为客户端准备好响应,会话就会被刷新并关闭。使用单个数据库事务来服务客户端请求,在您打开和关闭会话时启动并提交它。两者之间的关系是一对一的,该模型非常适合许多应用程序。

因此,您的 IdentityMap 的范围应该与您的业务交易相关联,因此与您的 UnitOfWork 的状态相关联,它将跟踪您的业务交易期间的所有更改。

如果您的应用程序处理短事务(例如每个请求),这将非常简单。如果你有一个跨越多个请求的长期工作单元,你的 unitOfWork 必须存在于会话范围内,并且你的身份映射附加到它上面。

在这种情况下,使用数据库管理良好的锁定策略以避免并发更改问题将是关键(在这种情况下,当提交时间更容易到达时,请求中读取的实体可能已被修改)。

在 java 中实现模式的一个基本示例是(没有深入探讨范围问题):https ://github.com/iluwatar/java-design-patterns/tree/master/unit-of-work

于 2019-10-09T14:01:05.607 回答
2

问:UnitOfWork实例是否受会话限制?

在马丁所讨论的书的第 11 章中,您读到:

“工作单元会跟踪您在可能影响数据库的业务交易期间所做的一切。完成后,它会计算出根据您的工作更改数据库需要做的所有事情。[...]

“一旦你开始做一些可能影响数据库的事情,你就会创建一个工作单元来跟踪变化。每次创建、更改或删除对象时,您都会告诉工作单元。您还可以让它知道您已读取的对象,以便它可以通过验证在业务事务期间数据库上没有任何对象发生更改来检查不一致的读取。”</p>

因此,UnitOfWork不需要绑定到会话。实例存在的程度UnitOfWork取决于您的设计。在同一本书中 Martin 的示例中,我们可以看到 UnitOfOWork 实例是根据 HTTP 请求创建的(我相信这是最经典的用法)。

class UnitOfWorkServlet...

   final protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      try {
         UnitOfWork.newCurrent();
         handleGet(request, response);
         UnitOfWork.getCurrent().commit();
      } finally {
         UnitOfWork.setCurrent(null);
      }
   }

   abstract void handleGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException; 

在 JPA 规范中,工作单元可以具有两种不同的性质:事务范围的持久性上下文、扩展的持久性上下文,但也可以手动处理它并获得应用程序管理的工作单元(请参阅this other answer) . 您使用哪一种取决于用例。

问:我们需要多少个 IdentityMap 实例?

Martin Fowler 在书中也回答了这个问题。在他的部分中IdentityMap有一个完整的部分。

书中写道:

“这里的决定在每个班级一张地图和整个课程一张地图之间变化。”

在这种情况下,您可以将会话理解为UnitOfWork。在本书的后面,他解释道:

“如果您有多个映射,则明显的路线是每个类或每个表一个映射,如果您的数据库模式和对象模型相同,则效果很好。”</p>

几段后,马丁解释了在哪里放置IdentityMap

“身份地图需要放在容易找到的地方。它们还与您正在使用的流程上下文相关联。您需要确保每个会话都有自己的实例,该实例与任何其他会话的实例隔离。因此,您需要将身份映射放在特定于会话的对象上。如果您使用的是工作单元,那么这是迄今为止标识映射的最佳位置,因为工作单元是跟踪进出数据库的数据的主要位置。如果您没有工作单元,最好的选择是绑定到会话的注册表。”</p>

所以,你有它,如果你UnitOfWork被绑定到一个请求,那么你将IdentityMaps在你的那个实例中拥有一个或多个UnitOfWork.

那么,工作单元是否受业务事务的约束?

是的。

现在“业务事务”的范围可能是短暂的,例如绑定到 HTTP 请求的生命周期,如上面 Martin Fowler 的示例,其中 UnitOfWork 似乎绑定到每个请求的线程局部变量。

或者“业务事务”可以包含多个请求,例如,在 JPA 中有一个扩展持久性上下文的概念,其中 JPA 中的持久性上下文对应于一个UnitOfWork模式。在扩展的持久性上下文中,业务事务会延长更长的时间。一个典型的例子是购物车:当用户开始将商品放入她的购物车时,会创建一个上下文/工作单元,并且在用户签出她的购物车(提交更改)之前,上下文不会被清除或者她的会话过期(丢弃工作单元中的所有内容)。

现在,还有应用程序管理上下文的想法,这基本上意味着,在您认为合适时启动您的工作单元,并在您不再需要它时关闭它。例如,假设您有一个桌面应用程序和一个具有隔离并发性的小型数据库(即每个用户只管理自己的数据,而不会与其他用户的数据发生冲突)。假设用户的数据可以完美地放入计算机的内存中。在这种情况下,您可以UnitOfWork在应用程序的开头启动您的应用程序并将其用作数据库的一种缓存。在适当的时候,您可以将 UnitOfWork 刷新到磁盘,例如当用户单击保存按钮时,但仍保持它处于活动状态。当用户关闭应用程序时,UnitOfWork 将被丢弃。

因此,您可以看到“商业交易”的实际含义或UnitOfWork应该存在多长时间存在很多细微差别。

多个工作单元

根据到目前为止的解释,您可以拥有多个工作单元,其中包括以下原因:

  • 每个请求一个UnitWork,如果您的应用程序处理并发请求,那么您将同时拥有多个工作单元实例。
  • 每个扩展事务一个UnitOfWork,因此与用户会话相关联。如果您有多个用户,则可以有多个工作单元。

然而,除此之外,我还发现了您可能希望在同一“业务事务”期间生成新工作单元的其他原因。

当您执行一项业务交易时,您可能希望执行另一项完全独立的交易,这并不罕见。例如,假设要为客户下订单,客户记录必须存在于数据库中,但如果客户记录创建失败(例如,可能因为另一个客户有相同的冲突电子邮件),您仍然想下订单处于待审核状态。

因此,您在这里面临的问题是,如果您在下订单的业务交易期间尝试创建客户并且创建客户失败,那么这会污染您的订单交易并且您的工作单元被迫回滚所有内容。在这种情况下,您可能希望产生一个新的工作单元,因此产生一个新的、单独的数据库事务来创建客户,如果该单独的工作单元未能创建客户,这不会污染您的订单创建单元工作,允许您采取措施在没有客户处于待审核状态的情况下仍然保留您的订单。

我相信 JPA 的“上下文”概念是如何定义工作单元的一个很好的例子。我建议你研究他们的规范,特别是他们关于EntityManager.

于 2019-10-16T15:52:52.767 回答