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

时间:2018-05-18 08:47:28

标签: caching nhibernate

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

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类型的对象,当我尝试访问它/它的属性时,我得到一个异常。会话关闭后有没有办法加载这些数据?二级缓存连接到会话工厂,所以也许有办法将该代理实际转换为正确的对象?或者实际上强制要始终加载这些值而不将fetch方法更改为join

1 个答案:

答案 0 :(得分:1)

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

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

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

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

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

自定义代理工厂本身可能主要是StaticProxyFactory的副本,GetProxy使用自定义ILazyInitializer at this line

自定义延迟初始化程序反过来可能是LiteLazyInitializer的副本,但覆盖了Initialize。其实施是here

这是为了简单的部分,直到那里,它没有听起来那么糟糕,这不涉及复制如此多的代码行。

现在要覆盖Initialize,需要检查Session属性并采取相应措施,如果会话可用,则调用base implementation或尝试直接从否则为二级缓存。

在这里,您需要复制更多代码,主要是LoadFromSecondLevelCacheAssembleCacheEntry

您还需要persister,如果您有会话工厂,则很容易获得:sessionFactory.GetEntityPersister(EntityName)。 (ILazyInitializer具有EntityName属性。)

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

  • CacheMode:检查确保为会话启用了缓存,您肯定应该跳过对您的用例的检查。
  • GenerateCacheKey:易于内联,请参阅its code
  • Timestamp:使用sessionFactory.Settings.CacheProvider.NextTimestamp()代替它。
  • Instantiate:改为使用subclassPersister.Instantiate(id)。 (除非你有一个应该委派给它的拦截器。)

其他电话更麻烦 您将不得不放弃循环引用中的保管,因为它使用会话持久性上下文 然后是使用会话的AssembleDeepCopy逻辑。许多情况只是调用它的Factory属性,因此根据实体的属性类型,实际供应工厂的虚拟会话可能会这样做。
如果可能的话,略过只读的东西,否则你还有更多的工作要做 跳过大多数persistenceContext内容:即会话第一级缓存。如果您的实体有一些,那么InitializeNonLazyCollections电话仍然缺少 关于AfterInitialize,当前需要此调用来处理具有延迟属性的实体(不是实体或集合)。所以你可以跳过它。
最后,PostLoadEvent用于实施ILifecycle的实体:再次,如果您不使用ILifecycle,则可以跳过它。

如果您还有一些缓存集合要从没有会话的二级缓存中检索,您需要对集合类型工厂执行类似的工作,可以使用collectiontype.factory_class进行配置,提供您自己的{{1产生覆盖Initialize的集合类型,同样重复the loading from second level cache

祝你好运,如果你试试这个。