NHibernate 可以在会话关闭后从二级缓存加载数据吗?

Can NHibernate load data from 2nd level cache after session is closed?

我正在尝试使用 NHibernate 为我的应用程序制作一些东西。我使用了很多 "dictionaries" 来存储某些对象属性的所有可能值。我试过使用二级缓存来存储那些字典数据。现在我想知道是否有办法在会话关闭后从缓存中加载所需的数据。假设这是我的代码:

public class Class1 {
    public virtual int Id { get; set; }
    public virtual Dic1 Dic { get; set; }
}

public class Dic1 {
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
}

这里是映射:

<class name="Class1" table="class1">
    <id name="Id" column="id">
        <generator class="native" />
    </id>
    <!-- I want to try not to use fetch="join" here -->
    <many-to-one
        name="Dic"
        class="Dic1"
        column="dic1_id"
    />
</class>

<class name="Dic1" table="dic1">
    <cache usage="read-write"/>
    <id name="Id" column="id">
        <generator class="native" />
    </id>
    <property name="Name" column="name" />
</class>

如果我在关闭会话之前获取 Class1.Dic 对象的值,NHibernate 不会将查询发送到数据库,因为该值已被一些较早的查询缓存。

但是假设我已经关闭了会话。在调试会话中,Class1.Dic 是 Dic1Proxy 类型的对象,当我尝试访问 it/it 的属性时出现异常。有没有办法在会话关闭后加载该数据?二级缓存连接到会话工厂,所以也许有办法实际将该代理转换为正确的对象?或者实际上强制始终加载这些值而不将 fetch 方法更改为 join.

您可以在关闭会话前使用 NHibernateUtil.Initialize(class1.Dic);。如果对象实际上不是代理或者它已经加载,它将不执行任何操作,否则它将加载它(如果缓存,则从二级缓存)。

您还可以在保持默认 select 获取模式的同时强制预先获取:在 Dicmany-to-one 映射上将 lazy 设置为 false 属性。加载 Class1 将立即触发 Dic 属性 的加载。如果它在二级缓存中,它应该从二级缓存中获取它。请注意,如果您查询 Class1 的列表并且它们的 Dic 属性 未被缓存,即使您启用了 batching of lazy loads,这也会导致 n+1 加载问题。 =63=]

否则,如果您不想在关闭会话之前对 Dic 属性 进行任何类型的操作,则需要更改代理实现以首先检查二级缓存如果会话已经关闭,则在失败之前。但在我看来,这需要付出太多的努力才值得。 (此外,如果实体在缓存中丢失怎么办?在这种情况下您的应用程序失败是否可以接受?)

NHibernate 允许您使用 proxyfactory.factory_class optional setting 提供您自己的代理工厂工厂 (IProxyFactoryFactory),前提是您使用默认的字节码提供程序。

然后您需要实现自己的 IProxyFactoryFactory,这很可能主要是 StaticProxyFactoryFactory 的副本,其中 BuildProxyFactory 产生自定义代理工厂。

自定义代理工厂本身可能主要是 StaticProxyFactory, with GetProxy using a custom ILazyInitializer at this line.

的副本

自定义惰性初始化器可能是 LiteLazyInitializer, but with an override of Initialize. Its implementation is here.

的副本

这是最简单的部分,直到那里,这并不像听起来那么糟糕,不需要复制那么多行代码。

现在要覆盖 Initialize,它需要检查 Session 属性 并相应地采取行动,如果会话可用则调用它的 base implementation,或者否则尝试直接从二级缓存加载。

这里你会有更多的代码要复制,主要是LoadFromSecondLevelCache and AssembleCacheEntry

您还需要持久化器,如果您有会话工厂就很容易获得它:sessionFactory.GetEntityPersister(EntityName)。 (ILazyInitializer 有一个 EntityName 属性。)

正如您通过检查它们的代码所看到的,这些函数在许多点上使用了会话:

  • CacheMode:已检查以确保为会话启用了缓存,您当然应该为您的用例跳过该检查。
  • GenerateCacheKey:容易内联出session,见its code
  • Timestamp:使用 sessionFactory.Settings.CacheProvider.NextTimestamp() 应该可以做到。
  • Instantiate:改用subclassPersister.Instantiate(id)。 (除非你有一个应该委托给它的拦截器。)

其他调用比较麻烦
您将不得不放弃对循环引用的安全保护,因为它使用会话持久性上下文。
然后是使用会话的 AssembleDeepCopy 逻辑。许多情况下只是调用它的 Factory 属性,因此根据实体的 属性 类型,实际提供工厂的虚拟会话可能会做。
如果可能,请跳过只读内容,否则您还有更多工作要做。
跳过大部分 persistenceContext 内容:即会话一级缓存。仍然有 InitializeNonLazyCollections 调用,如果您的实体有一些调用,它将缺少。
关于 AfterInitialize,当前需要此调用来处理具有惰性属性的实体(不是实体或集合)。所以你可以跳过它。
最后,PostLoadEvent 用于实现 ILifecycle 的实体:同样,您可以跳过它,前提是您不使用 ILifecycle.

如果您还有一些缓存集合要在没有会话的情况下从二级缓存中检索,您需要对集合类型工厂进行类似的工作,可使用 collectiontype.factory_class 进行配置,提供您自己的 ICollectionTypeFactory 生成集合类型覆盖 Initialize, likewise duplicating the loading from second level cache.

祝你好运。