所有ORM应用程序都使用持久化管理器来执行数据库写入、读取和查询等操作。在NHibernate中,这些操作通过Session、Transaction、Criteria等接口来完成。每个会话必须首先开启一个事务,所有对象的活动都在这个事务的边界内进行(这里用“事务”这个词来表示,是为了简化理解。在ORM中,它通常被称为行动单元,而不一定是事务,但这个词能让对“行动单元”有一个大致的概念,而不必深入解释“行动单元是什么”,这是一个在ORM和NHibernate开发中非常有趣且复杂的话题)。
每个会话都与一个持久化上下文(PersistenceContext)相关联。持久化上下文提供了诸如维护唯一对象身份(称为身份范围)、可重复读取、避免在对象图中递归加载对象、自动事务性写入、脏检查等功能。熟悉ORM代码的人通过阅读持久化上下文提供的功能,就能立即理解它的含义。是的,它就是那个熟悉的IdentityMap。对于其他人,在这里解释这个概念,这是理解本文讨论的基础。
如前所述,对于ORM软件来说,第一步是为持久化对象确定对象身份,以便它能够唯一地匹配到数据库中的一行。下一步是确保这个身份是唯一的,并且只有一个持久化对象代表特定的数据库行。这避免了歧义。通常为了确保对象的唯一性,会使用一个内存中的哈希表,数据库主键作为哈希表的“键”字段,持久化对象作为“值”。每当ORM软件在执行查询时查找数据库中的持久化对象,或者通过ID(即主键值)获取它时,它首先会在哈希表中使用“主键”查找持久化对象,如果存在,就直接从哈希表返回对象。否则,ORM软件会根据执行数据库查询获得的值创建一个对象,将其添加到哈希表中,然后最终将对象返回给客户端代码。客户端代码使用的每个持久化对象都存储在这个哈希表中。这是ORM软件的第一级缓存。当重复请求一个对象时,只有第一次是对数据库进行查询。其余的时间都是从这个缓存或哈希表中获取的。这被称为可重复读取。主要的问题是在哪里保持这个哈希表。如果在进程级别保持这个哈希表,并在进程中存储所有对象,那么需要从多个线程同步访问哈希表。所以通常的想法是在线程级别保持这个哈希表,并且在会话和事务中修改的所有持久化对象都保持在这个线程的哈希表中。这被称为持久化上下文缓存(大致给出了ORM库是如何开发的)。
持久化上下文提供的下一个功能是脏检查。脏检查这个名字是自解释的。它的意思是发现一个对象在一次事务的范围内是否被修改了。ORM软件使用两种方法来实现它:1. 在事务开始时单独存储持久化对象的快照,并在事务结束时进行比较;2. 保持一个IsDirty标志,并在对象被修改时设置它。NHibernate必须使用第一种脏检查方法,因为在修改持久化对象时不设置任何标志,也不从一个明确定义的NHibernate接口继承对象,这是第二种方法所必需的。那么脏检查有什么用呢?在事务结束时,如果事务成功,NHibernate会将所有修改过的脏持久化对象的更改同步到数据库。不需要做任何事情。持久化对象由NHibernate管理。快照方法是由像NHibernate这样的复杂库所偏好的,因为可以根据实际修改的字段而不是对象的所有字段来微调生成的SQL更新数据库。
现在已经熟悉了持久化上下文。这个持久化上下文及其缓存与每个会话分别关联。图1给出了线程级持久化上下文缓存的大致概念。
在NHibernate中,持久化对象存在于四种状态:瞬态、持久、已删除和分离对象。使用“new”运算符实例化的对象称为瞬态对象。它们没有数据库ID。当这个瞬态对象保存到数据库时,它与数据库ID关联,并成为一个持久对象。从数据库加载的对象也有数据库ID,并且只被称为持久对象。这是ORM中两种主要类型的对象。NHibernate引入了第三种对象状态,称为分离状态。理解这个分离状态是非常重要的,只有理解了这个分离状态,才能理解本文背景中介绍的持久化上下文缓存的主题。
当会话关闭时,与会话关联的持久化上下文缓存也会关闭。然而,持久化对象的引用可能在创建它的会话生命周期之外存在。这些持久化对象在持久化上下文缓存范围之外的状态被称为分离状态。