【问题标题】:nHibernate second level cache not working on query overnHibernate 二级缓存不适用于查询
【发布时间】:2025-12-08 23:20:10
【问题描述】:

我有以下问题。 我在 nHibernate(使用 Postgres)上启用了二级缓存系统,如下所示 配置

cfg.SessionFactory().Caching.Through<RtMemoryCacheProvider>().WithDefaultExpiration(28800);

我只启用实体缓存,因为此时我不需要查询缓存。

在我的实体中,这是我的设置(它们有些是读写的,有些是只读的,此时可以将更多设置为只读)

<id name="StudentID" access="property" column="`StudentID`">
  <generator class="native" />
</id>

<property name="Name" column="`Name`" >
<property name="Address" column="`Address`" />
<property name="IsActive" column="`IsActive`" />
<property name="DateCreated" column="`DateCreated`" />
<property name="DateLastUpdated" column="`DateLastUpdated`" />
<property name="LastUpdatedBy" column="`LastUpdatedBy`" />

<set name="Projects" inverse="true" mutable="false">
  <cache usage="read-only"/>
  <key column="`StudentID`" />
  <one-to-many class="Project" />
</set>

<set name="Classes" inverse="true" mutable="false">
  <cache usage="nonstrict-read-write"/>
  <key column="`StudentID`" />
  <one-to-many class="Class" />
</set>

<set name="Books" inverse="true" mutable="false">
  <cache usage="nonstrict-read-write"/>
  <key column="`StudentID`" />
  <one-to-many class="Book" />
</set>
</class>

在对我的解决方案进行单元测试时 - 我首先预先获取学生列表,然后尝试生成 缓存命中

public bool PreLoadStudents()
{
    using (ISession session = NHibernateHelper.OpenSession())
    {
        IList<Student> results = session.QueryOver<Student>()
                                 .Fetch(d => d.Projects).Eager
                                 .Fetch(d => d.Classes).Eager
                                 .Fetch(d => d.Books).Eager
                                 .TransformUsing(Transformers.DistinctRootEntity)
                                 .List<Student>();
     }
}

[Test]
public void GetByIdTest()
{
   bool bLoaded = testBLL.PreLoadStudents();

   var student1 = testBLL.GetByID("123");
   var student2 = testBLL.GetByID("123");

   long cacheHit = testBLL.GetSessionFactory().Statistics.SecondLevelCacheHitCount;

   Assert.That(cacheHit,Is.EqualTo(2));    
}

我尝试了两种不同的“GetByID”实现,其中一种使用 约定“get”方法,另一种使用与 fetch 语句类似的查询方法 到 PreLoadStudents 学生方法。

在“get”方法的情况下,缓存命中发生并且测试通过。 在“查询结束”的情况下,不会发生缓存命中或未命中,而是执行了 2 个查询。

这是我使用“Get”方法用于“GetByID”方法的代码

var student = session.Get<Student>(studentId); 

我不喜欢这种方法,因为我无法获取延迟加载的子集合

这是我使用“QueryOver”方法用于“GetByID”方法的代码

 var student = session.QueryOver<Student>()
                                          .Where(d => d.studentId == currentStudentId)
                                          .Fetch(d => d.Projects).Eager
                                          .Fetch(d => d.Classes).Eager
                                          .Fetch(d => d.Books).Eager
                                          .SingleOrDefault();

有什么想法为什么“get”方法会产生命中而query over 方法没有?

【问题讨论】:

  • 您是否启用了查询缓存?二级缓存和查询缓存是有区别的。此外,您必须在 QueryOver 查询中使用 .Cacheable()
  • 是的 - 经过几次尝试,我确实启用了查询缓存和实体缓存。可以说我现在了解查询与获取。另外,我了解到查询缓存将使用二级缓存中的数据填充结果

标签: c# postgresql caching nhibernate


【解决方案1】:

在阅读并进行了一些实验测试之后,这是针对我的问题出现的解决方案。

我最初的问题 - 关于为什么“get”方法产生命中而查询方法没有产生命中的任何想法?是一个错误的问题,原因如下:

  1. nHibernate 中的二级缓存 (L2) 缓存实体的值
  2. nHibernate中的查询缓存缓存了搜索结果的索引

因此

  1. Get 允许我们在知道 id 的情况下快速检索实体。如果 我们不知道 id 那么我们需要做一个查询 如果之前没有运行过数据库。另一种方法是从缓存中加载所有内容,然后在其上运行 LINQ。
  2. Query over 允许我们将查询结果存储在可以从 L2 缓存中填充的查询缓存中。

经验教训

  1. 如果要缓存查询,请同时打开二级缓存和查询缓存,没有二级缓存的查询缓存可能不会提高性能
  2. 只能使用缓存查询或通过 GET 访问 L2

【讨论】:

    【解决方案2】:

    二级缓存仅在您有事务时才有效,无论如何这也是查询的好习惯。

    using (ISession session = NHibernateHelper.OpenSession())
    using (ITransaction tx = session.BeginTransaction())
    {
        IList<Student> results = session.QueryOver<Student>()
                                 .Fetch(d => d.Projects).Eager
                                 .Fetch(d => d.Classes).Eager
                                 .Fetch(d => d.Books).Eager
                                 .TransformUsing(Transformers.DistinctRootEntity)
                                 .List<Student>();
    
        tx.Commit();
    }
    

    【讨论】:

    • 我的问题与提交事务无关。二级缓存也可以用于查询。