在马丁·福勒(Martin Fowler)的书中,我读到了UnitOfWork
和IdentityMap
模式。
作者提到,将IdentityMap放在UnitOfWork内部是个好主意。但是怎么办呢?
据我所知IdentityMap
受会话限制,但作者并未提及UnitOfWork
UnitOfWork
实例是否受会话限制?
假设我们有客户和订单实体。
public clas Client{
List<Order> orders;
...
}
我们收到了更新客户信息(电话号码)并添加新订单的请求:
这里需要几个unitOfWork实例?每个环节?我们应该为客户和订单分开实例吗?
我们这里需要多少个IdentityMap实例?对于每个实例?我们应该为客户和订单分开实例吗?
每个unitOfWork实例需要多少个IdentityMap实例?
如果我们有2个并发请求怎么办?
答案 0 :(得分:2)
unitOfWork根据您需要管理的业务运营的持续时间需要一个范围。 如果您的业务操作扩展到多个请求,但需要将其视为一个工作单元,则必须以一致的范围(例如,会话)来处理实例。
工作单元将跟踪与数据库的所有交互,并通过创建事务并以最佳方式(在短期数据库事务中)进行更改来确认它们。
JPA / Hibernate默认情况下实现此模式,因此通常如果您使用它们,则无需自己实现它。 JPA / Hibernate在一级缓存中还实现了类似Identity Map模式的功能。它将实体图保存在其一级缓存中,从而避免在会话持续时间和工作单元中向数据库查找多个时间。
正如休眠文档所说(read me):
在多用户客户端/服务器应用程序中,最常见的模式是 每个请求会话。在此模型中,发送了来自客户端的请求 到运行Hibernate持久层的服务器。一个新的 Hibernate Session打开,并执行所有数据库操作 在这个工作单元中。工作完成后,一旦回应 对于已经准备好的客户端,将刷新并关闭会话。 使用单个数据库事务处理客户请求, 在打开和关闭会话时启动并提交它。的 两者之间的关系是一对一的,这个模型是完美的 适合许多应用。
因此,您的IdentityMap的范围应与您的业务交易联系在一起,因此也应与在跟踪业务交易期间所有更改的UnitOfWork的状态联系起来。
如果您的应用程序处理空交易(例如,每个请求),这将非常简单。如果您有一个跨多个请求的长期工作单元,则unitOfWork必须存在于会话范围内,并且您的身份映射也已附加在会话范围内。
在这种情况下,管理数据库的良好锁定策略对于避免并发更改(在这种情况下,更容易到达提交时间的情况下,请求中读取的实体可能已被修改)而言,这是关键。 / p>
在Java中实现模式的一个基本示例是(无需深入探讨范围问题):https://github.com/iluwatar/java-design-patterns/tree/master/unit-of-work
答案 1 :(得分:2)
问:
UnitOfWork
实例是否受会话限制?
在有关马丁书的第11章中,您读到:
“工作单元会跟踪您在业务交易过程中可能影响数据库的所有工作。完成后,它会根据您的工作找出更改数据库所需要做的一切。 [...]
“一旦开始执行可能会影响数据库的操作,就会创建一个工作单元来跟踪更改。每次创建,更改或删除对象时,都将其告知工作单元。您还可以让它知道已读取的对象,以便可以通过验证在业务交易期间数据库上没有更改任何对象来检查不一致的读取。”
因此,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规范中,工作单元可以具有两种不同的性质:事务范围的持久性上下文,扩展的持久性上下文,但是也可以手动处理它并获得应用程序管理的工作单元({{ 3}})。您使用哪一个取决于用例。
问:我们需要多少个IdentityMap实例?
马丁·福勒(Martin Fowler)在书中也回答了这个问题。在他关于IdentityMap
的部分中,有一整节内容。
这本书的内容是:
“这里的决定在每堂课一幅地图和整个课程的一幅地图之间变化。”
在这种情况下,您可以将会话理解为UnitOfWork
。在书的后面,他解释了:
“如果您有多个映射,则显而易见的路由是每个类或每个表一个映射,如果您的数据库模式和对象模型相同,则该方法很好。 ”
稍后几段,马丁解释了将IdentityMap
放在哪里:
“身份地图必须位于容易找到的地方。它们还与您正在使用的流程上下文相关联。您需要确保每个会话都拥有自己的实例,该实例与任何其他会话的实例隔离。因此,您需要将身份映射放在特定于会话的对象上。如果您使用的是工作单元,那么这是迄今为止身份地图的最佳位置,因为工作单元是跟踪数据进出数据库的主要位置。如果您没有工作单位,那么最好的选择是与会议绑定的注册表。”
因此,如果您的UnitOfWork
绑定到一个请求,那么您就已经拥有了,那么在您的IdentityMaps
实例中,您将有一个或多个UnitOfWork
。
那么,工作单位受业务交易限制吗?
是的。
现在,“业务交易”的范围可能是短暂的,例如就像上面的Martin Fowler的示例一样,它绑定到HTTP请求的生命周期,其中UnitOfWork似乎绑定到每个请求的线程局部变量。
或者“业务交易”可以包含多个请求,例如在JPA中,存在一个扩展的持久性上下文的概念,其中JPA中的持久性上下文对应于UnitOfWork
模式。在扩展的持久性上下文中,业务交易会扩展更长的时间。一个典型的例子是购物车:当用户开始将物品放入购物车中时,将创建一个上下文/工作单元,并且直到用户签出她的购物车(提交更改)后,上下文才会被清除。或会话期满(丢弃工作单元中的所有内容)。
现在,也有了应用程序管理上下文的概念,这基本上意味着在您认为合适的时候开始工作,而在不再需要时关闭它。例如,假设您有一个桌面应用程序和一个具有独立并发性的小型数据库(即每个用户仅管理自己的数据,而不会与其他用户的数据发生冲突)。假设用户的数据完全适合计算机的内存。在这种情况下,您可以在应用程序的开头启动UnitOfWork
并将其用作数据库的一种缓存。适当时,您可以将UnitOfWork刷新到磁盘,例如当用户单击保存按钮,但仍保持活动状态时。当用户关闭应用程序时,UnitOfWork将被丢弃。
因此,您可以看到关于“业务交易”的实际含义或UnitOfWork
应该存在多长时间的细微差别。
多个工作单元
根据目前的解释,您可以拥有多个工作单元,原因如下:
UnitWork
,并且如果您的应用程序处理并发请求,那么您将同时具有多个工作单元实例。UnitOfWork
,因此,与用户的会话相关。如果您有多个用户,则可以有多个工作单元。但是,除了这些,我还发现了其他原因,为什么您可能想在同一“业务交易”期间生成一个新的工作单元。
在执行业务交易时,您可能希望执行另一项完全独立的交易,这并不稀奇。例如,假设要为客户下订单,则客户记录必须存在于数据库中,但是如果未能创建客户记录(例如,可能因为另一个客户具有相同的冲突电子邮件),则您仍想下订单订单处于待审核状态。
因此,您面临的问题是,如果您在下订单和创建客户的业务交易过程中尝试创建客户失败,这将污染您的订单交易,并且您的工作单元将被迫回滚所有工作。在这种情况下,您可能希望生成一个新的工作单元,并因此生成一个新的单独的数据库事务来创建客户,并且如果该单独的工作单元无法创建客户,那么这不会污染您的订单创建单元工作,使您能够采取措施在没有客户处于待审核状态的情况下继续保留订单。
我相信JPA的“上下文”概念是如何定义工作单元的一个很好的例子。我建议您研究see this other answer,尤其是他们关于EntityManager
的部分。