编程

Hibernate: 如何原生查询的控制缓存无效

500 2024-07-14 01:09:00

问题描述:

“有人告诉我,原生查询会从我的二级缓存中删除所有实体。但你仍然在推荐他们。它们不会对性能产生负面影响吗?”

方案:

是的,有些原生查询会使二级缓存失效。但是不用担心,如果你做得正确,它不会对性能产生任何负面影响,也不会改变我对于使用原生查询的建议。

为了更详细地回答这个问题,在讨论微调处理过程之前,我们首先需要讨论哪种原生查询会使二级缓存失效。

哪些原生查询会导致缓存失效?

原生 SQL SELECT 语句不会影响二级缓存,因此你不必担心任何负面性能的影响。不过如果你使用 SQL UPDATE 或 DELETE 语句作为原生查询执行,Hibernate将会使二级缓存失效。这是必要的因为这些 SQL 语句改变了数据库中的数据,通过这种方式,它可能让缓存中的实体失效。默认情况下,Hibernate 不知道哪些记录收到了影响。因此,Hibernate 只会让实体的二级缓存失效。

我们来看看一个例子。

在执行下面的测试之前,id 值为 1 的 Author 实体已经存在于二级缓存之中。然后我在事务中运行了一个 SQL UPDATE 原生查询语句。在后续的事务中,我检测了 Author 实体是否仍在缓存中。

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

log.info("Before native update");

log.info("Author 1 in Cache? " + em.getEntityManagerFactory().getCache().contains(Author.class, 1L));

Query q = em.createNativeQuery("UPDATE Book SET title = title || ' - changed'");

q.executeUpdate();

em.getTransaction().commit();

em.close();

em = emf.createEntityManager();

em.getTransaction().begin();

log.info("After native update");

log.info("Author 1 in Cache? " + em.getEntityManagerFactory().getCache().contains(Author.class, 1L));

a = em.find(Author.class, 1L);

log.info(a);

em.getTransaction().commit();

em.close();

如果你没有提供额外的信息,Hibernate 将使二级缓存失效并从中移除所有实体。你可以看到第二个事务中写入的日志信息。id 为 1 的 Author 实体已不复存在于缓存中,Hibernate 不要使用查询来从数据库中获取该数据。

06:32:02,752 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Before native update

06:32:02,752 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Author 1 in Cache? true

06:32:02,779 DEBUG [org.hibernate.SQL] - UPDATE Book SET title = title || ' - changed'

06:32:02,782 INFO  [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {

    14800 nanoseconds spent acquiring 1 JDBC connections;

    22300 nanoseconds spent releasing 1 JDBC connections;

    201400 nanoseconds spent preparing 1 JDBC statements;

    1356000 nanoseconds spent executing 1 JDBC statements;

    0 nanoseconds spent executing 0 JDBC batches;

    0 nanoseconds spent performing 0 L2C puts;

    0 nanoseconds spent performing 0 L2C hits;

    0 nanoseconds spent performing 0 L2C misses;

    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);

    17500 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)

}

06:32:02,782 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - After native update

06:32:02,782 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Author 1 in Cache? false

06:32:02,783 DEBUG [org.hibernate.SQL] - select author0_.id as id1_0_0_, author0_.firstName as firstNam2_0_0_, author0_.lastName as lastName3_0_0_, author0_.version as version4_0_0_ from Author author0_ where author0_.id=?

06:32:02,784 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Author firstName: Joshua, lastName: Bloch

06:32:02,785 INFO  [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {

    11900 nanoseconds spent acquiring 1 JDBC connections;

    15300 nanoseconds spent releasing 1 JDBC connections;

    18500 nanoseconds spent preparing 1 JDBC statements;

    936400 nanoseconds spent executing 1 JDBC statements;

    0 nanoseconds spent executing 0 JDBC batches;

    256700 nanoseconds spent performing 1 L2C puts;

    0 nanoseconds spent performing 0 L2C hits;

    114600 nanoseconds spent performing 1 L2C misses;

    107100 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 0 collections);

    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)

}

仅使受影响的区域无效

但并一定非要如此。你可以告诉 Hibernate 哪些实体类会受到查询的影响。你 只需要打开 Query 对象以获得特定于Hibernate 的 SqlQuery,并使用类引用调用 addSynchronizedEntityClass 方法。

EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

log.info("Before native update");

log.info("Author 1 in Cache? " + em.getEntityManagerFactory().getCache().contains(Author.class, 1L));

Query q = em.createNativeQuery("UPDATE Book SET title = title || ' - changed'");

q.unwrap(NativeQuery.class).addSynchronizedEntityClass(Book.class);

q.executeUpdate();

em.getTransaction().commit();

em.close();

em = emf.createEntityManager();

em.getTransaction().begin();

log.info("After native update");

log.info("Author 1 in Cache? " + em.getEntityManagerFactory().getCache().contains(Author.class, 1L));

a = em.find(Author.class, 1L);

log.info(a);

em.getTransaction().commit();

em.close();

我的 SQL UPDATE语句修改了 Book 表格中匹配 Book 实体的记录。在提供了这些信息给 Hibernate 后,它只让 Book 实体的区域失效,而 Author 实体仍然保留在二级缓存中。

06:30:51,985 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Before native update

06:30:51,985 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Author 1 in Cache? true

06:30:52,011 DEBUG [org.hibernate.SQL] - UPDATE Book SET title = title || ' - changed'

06:30:52,014 INFO  [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {

    18400 nanoseconds spent acquiring 1 JDBC connections;

    19900 nanoseconds spent releasing 1 JDBC connections;

    86000 nanoseconds spent preparing 1 JDBC statements;

    1825400 nanoseconds spent executing 1 JDBC statements;

    0 nanoseconds spent executing 0 JDBC batches;

    0 nanoseconds spent performing 0 L2C puts;

    0 nanoseconds spent performing 0 L2C hits;

    0 nanoseconds spent performing 0 L2C misses;

    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);

    19400 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)

}

06:30:52,015 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - After native update

06:30:52,015 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Author 1 in Cache? true

06:30:52,015 INFO  [org.thoughts.on.java.model.Test2ndLevelCache] - Author firstName: Joshua, lastName: Bloch

06:30:52,016 INFO  [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {

    10000 nanoseconds spent acquiring 1 JDBC connections;

    25700 nanoseconds spent releasing 1 JDBC connections;

    0 nanoseconds spent preparing 0 JDBC statements;

    0 nanoseconds spent executing 0 JDBC statements;

    0 nanoseconds spent executing 0 JDBC batches;

    0 nanoseconds spent performing 0 L2C puts;

    86900 nanoseconds spent performing 1 L2C hits;

    0 nanoseconds spent performing 0 L2C misses;

    104700 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 0 collections);

    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)

}