65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 NHibernate 进行对象关系映射 (ORM) - 第 8 部分(共 8 部分)编码获取和持久化对象

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2013年1月20日

CPOL

8分钟阅读

viewsIcon

22466

包含 8 部分的完整系列文章,展示使用 NHibernate 进行一对一、多对一、多对多关联映射,使用 NHibernate 处理集合,使用 NHibernate 处理继承关系,使用 NHibernate 处理延迟初始化/获取。

第一部分

第 2 部分

第 3 部分

第 4 部分

第 5 部分 A

第 5 部分 B

第 6 部分

第 7 部分

引言

在本文系列的前 7 部分中,我们已经完成了使用 NHibernate 编码各种类型的关联。这是八部分文章系列的最后一篇。在本文中,我们将解决第一篇文章末尾提出的问题,“如何在不同会话之间管理持久化对象?”。本文的背景非常重要,因为它为理解本文的讨论铺平了道路并介绍了重要的主题。

背景  

所有 ORM 应用程序都使用持久化管理器来实现数据库写入、读取和查询等功能。在 NHibernate 中,这由 Session、Transaction、Criteria 等接口完成。每个会话必须首先打开一个事务,并且对象上的所有活动都必须在此事务边界内(将其称为事务是一种高度简化的说法。在 ORM 中,它通常被称为操作单元,不一定是事务,但你可以通过“事务”这个词来理解,这使我避免了深入解释“什么是操作单元?”。操作单元本身是 ORM 中一个详尽且有趣的主题,在应用程序开发中绝对不需要使用 ORM 软件,但对于开发 NHibernate 这样的 ORM 库至关重要)。

每个会话都被称为与 PersistenceContext 相关联。PersistenceContext 提供维护对象标识(称为标识范围)、可重复读取、避免对象图的递归加载、自动事务写入、脏检查等功能。那些使用过 ORM 代码的人,在阅读它提供的功能时,会立即知道 Persistence Context 的含义。是的,它就是那个熟悉的 IdentityMap。对于其他人,我在此解释一下这个概念,这对于理解非常重要。请注意,这里表达的想法基于我们之前开发 ORM 库的经验,而不是 NHibernate 本身,但这个想法对于大多数 ORM 库来说几乎是相同的。

如前所述,对于 ORM 软件,第一步是为持久化对象确定对象标识,使其能够唯一地匹配数据库行。下一步是确保该标识是唯一的,并且只有一个持久化对象代表特定的数据库行。这避免了歧义。因此,通常为确保这种对象唯一性所做的是,使用一个内存哈希表,以数据库主键作为哈希表的“键”字段,以持久化对象作为“值”。每当 ORM 软件在执行查询或按 ID(即主键值)获取对象时,查找数据库中的持久化对象时,它会首先使用“主键”(作为哈希表的键字段)在该哈希表中查找持久化对象。如果存在,它将直接从哈希表中返回对象。否则,ORM 软件会基于执行数据库查询获得的值创建一个对象,将其添加到哈希表中,然后最终将对象返回给客户端代码。客户端代码使用的每个持久化对象都存储在此哈希表中。这是 ORM 软件的第一级缓存。当你反复请求一个对象时,只有第一次会与数据库进行查询。其余时间都会从这个缓存或哈希表中获取。这被称为可重复读取。最重要的是你把这个哈希表放在哪里。如果你将此哈希表放在进程级别,并将进程中的所有对象都存储在那里,那么你需要同步多个线程对哈希表的访问。因此,通用的做法是将此哈希表放在线程级别,在该线程内,在会话和事务中修改的所有持久化对象都将在此每个线程的哈希表中维护。这被称为持久化上下文缓存(大致描绘了 ORM 库的开发方式)。持久化上下文提供的下一项功能是脏检查。脏检查这个名字不言而喻。它意味着你在一个事务范围内找出对象是否被修改过。ORM 软件通过两种方式来实现:1. 在事务开始时单独存储持久化对象的快照,并在事务结束时进行比较;2. 保持一个 IsDirty 标志,并在对象被修改时设置它。NHibernate 显然必须使用第一种脏检查方法,因为我们在修改持久化对象时不会设置任何标志,并且我们没有继承自定义定义的 NHibernate 接口的对象,这对于第二种方法是必需的。那么脏检查的用途是什么?在事务结束时,如果事务成功,NHibernate 会将所有已修改的脏持久化对象更改同步到数据库。你无需执行任何操作。持久化对象由 NHibernate 管理。更复杂的库(如 NHibernate)更倾向于快照方法,因为你可以微调为更新数据库生成的 SQL,使其仅包含实际修改过的字段,而不是对象的所有字段。现在我们熟悉了持久化上下文。这个持久化上下文及其缓存分别与每个会话相关联。图 1 中的图表大致说明了线程级别的持久化上下文缓存。

 

图 1 - 持久化上下文缓存 - 缓存中已有的对象直接从哈希表或缓存中检索,分为两步。对于不在缓存中的对象,需要三步,因为首先必须将项目放入缓存,然后才能将其返回给客户端。session.getbyid(primarykey) 方法仅用于说明目的,以解释客户端代码如何使用标识符检索对象。

使用代码

NHibernate 中的持久化对象存在四种状态:瞬态 (Transient)、持久化 (Persistent)、移除 (Removed) 和分离 (Detached) 对象。使用“new”运算符实例化的对象称为瞬态对象。它们没有数据库 ID。当这个瞬态对象保存到数据库时,它将与数据库 ID 相关联并成为持久化对象。从数据库加载的对象也有数据库 ID,并且也被称为持久化对象。这是 ORM 中的两种主要对象类型。NHibernate 引入了第三种对象状态,称为分离状态。理解这种分离状态至关重要,而正是为了理解这一点,我们在本文的背景部分介绍了持久化上下文缓存的主题。

当会话关闭时,与该会话相关联的持久化上下文缓存也会关闭。然而,持久化对象的引用可能在其创建的会话生命周期之外仍然存在。这些持久化对象在持久化上下文缓存范围之外的状态被称为分离状态。

最后一种较少使用的状态是对象的“移除”状态,它是对象在会话中从数据库删除但调用删除的事务尚未完成的中间状态。因此,在事务完成之前,该对象处于称为移除的状态,之后会过渡到瞬态状态。下面的图 2 大致描绘了对象的不同状态。

 

图 2 - 对象的不同状态

现在,第一篇文章末尾的获取问题应该对每个人来说都很容易理解了。问题在于,我们使用 DBRepository 类中的一个辅助方法获取了一个对象。该 DBRepository 实例在其 getItemById 方法中使用了一个会话来从数据库检索对象,并在方法结束时关闭该会话。当我们稍后尝试使用该对象时,我们遇到了一个异常。我们之所以得到异常,是因为默认情况下 NHibernate 的 lazy 设置为 true,这意味着在检索对象时,对象的所有关联都不会随对象一起发送,而只会保留一个代理。这就是 NHibernate 的延迟加载功能。因此,当在会话对象关闭后访问此代理时,它会引发异常。通过将 lazy 设置为 false,我们获得了完整的对象而不是代理的关联。这是解决方案之一,但并非在所有地方都适用。但是,现在在给出的背景和我们对不同对象状态的当前知识之后,我们可以理解真正的问题。真正的问题是,当 DBRepository 在返回我们需要的对象后关闭会话时,与该会话关联的持久化上下文缓存也会关闭。因此,我们手中持有的是一个分离的对象。由于这个分离的对象在其他会话的持久化上下文中不存在,因此不能直接在任何其他会话中使用。因此,它必须通过一个称为“重新附加”的过程被带入另一个会话的持久化上下文。调用它的方法是:

newsession.update(object);

这会将对象重新附加到新会话。对于未修改的持久化对象,也可以使用 lock() 方法进行重新附加。在需要将新会话中已加载的具有与你要加载到新会话的持久化上下文缓存中的旧对象相同的数据库标识的实例时,要使用的方法是,

new_object = newsession.merge(old_object);

这个“merge”方法会将 old_object 的状态复制到 new_object 中。在此调用之后,客户端应该只使用 new_object。解决我们在第一篇文章中遇到的问题的办法是使用这些对象重新附加技术。因此,通过使用对象重新附加,我们现在知道了跨会话使用对象的技术。

关注点

结论
这标志着八部分文章系列的结束。在接下来的几个月里,当我找到时间时,我将撰写更多关于使用 NHibernate 进行查询、NHibernate 中的获取策略以及使用重新附加进行对象转换的完整示例的文章。在那之前,请享受 NHIBERNATE。

第一部分

第 2 部分

第 3 部分

第 4 部分

第 5 部分 A

第 5 部分 B

第 6 部分

第 7 部分

© . All rights reserved.