如何将 UnitOfWork 模式与 IdentityMap 模式一起使用?

How to use UnitOfWork pattern together with IdentityMap pattern?

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

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

据我了解,IdentityMap 受会话限制,但作者并未提及 UnitOfWork

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

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

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

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

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

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

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

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

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

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

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

正如休眠文档所说 (read me):

The most common pattern in a multi-user client / server application is session-per-request. In this model, a request from the client is sent to the server, where the Hibernate persistence layer runs. A new Hibernate Session is opened, and all database operations are executed in this unit of work. On completion of the work, and once the response for the client has been prepared, the session is flushed and closed. Use a single database transaction to serve the clients request, starting and committing it when you open and close the Session. The relationship between the two is one-to-one and this model is a perfect fit for many applications.

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

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

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

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

Q: Is the UnitOfWork instance bounded by session?

您在 Martin 所著书籍的第 11 章中读到:

“A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you’re done, it figures out everything that needs to be done to alter the database as a result of your work. [...]

“As soon as you start doing something that may affect a database, you create a Unit of Work to keep track of the changes. Every time you create, change, or delete an object you tell the Unit of Work. You can also let it know about objects you’ve read so that it can check for inconsistent reads by verifying that none of the objects changed on the database during the business transaction.”

因此,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 规范中,工作单元可以具有两种不同的性质:事务范围的持久性上下文和扩展持久性上下文,但也可以手动处理并获得应用程序管理的工作单元 ( see this other answer)。您使用哪一个取决于用例。

Q: How many IdentityMap instances do we need?

Martin Fowler 在书中也回答了这个问题。在他关于 IdentityMap 的部分中有一整节内容。

书中写道:

“Here the decision varies between one map per class and one map for the whole session."

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

“If you have multiple maps, the obvious route is one map per class or per table, which works well if your database schema and object models are the same. ”

几段之后,Martin 解释了 IdentityMap:

的放置位置

“Identity Maps need to be somewhere where they’re easy to find. They’re also tied to the process context you’re working in. You need to ensure that each session gets its own instance that’s isolated from any other session’s instance. Thus, you need to put the Identity Map on a session-specific object. If you’re using Unit of Work that’s by far the best place for the Identity Maps since the Unit of Work is the main place for keeping track of data coming in or out of the database. If you don’t have a Unit of Work, the best bet is a Registry that’s tied to the session.”

所以,你知道了,如果你的 UnitOfWork 绑定到一个请求,那么在你的 UnitOfWork 实例中你将有一个或多个 IdentityMaps

So, is the Unit of Work bounded by business transaction?

是的,是的。

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

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

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

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

多个工作单元

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

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

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

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

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

我相信 JPA 的“上下文”概念是如何定义工作单元的一个很好的例子。我建议您研究 their specification,尤其是他们关于 EntityManager.

的部分