Hibernate: 如何原生查询的控制缓存无效
问题描述:
“有人告诉我,原生查询会从我的二级缓存中删除所有实体。但你仍然在推荐他们。它们不会对性能产生负面影响吗?”
方案:
是的,有些原生查询会使二级缓存失效。但是不用担心,如果你做得正确,它不会对性能产生任何负面影响,也不会改变我对于使用原生查询的建议。
为了更详细地回答这个问题,在讨论微调处理过程之前,我们首先需要讨论哪种原生查询会使二级缓存失效。
哪些原生查询会导致缓存失效?
原生 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)
}