[英]Hibernate performs N+1 selects instead of 1 query when using the Query Cache
我被一个问题困住了,我真的很迷茫,不知道该怎么办。 我使用查询缓存 + 二级缓存,我想正确缓存结果 10 秒。 所以这是我的
ehcache.xml :
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<cache name = "TestEntity"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="11"
memoryStoreEvictionPolicy="LRU">
</cache>
<cache name="org.hibernate.cache.internal.StandardQueryCache"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU">
</cache>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToLiveSeconds="120"
maxElementsOnDisk="100"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
</ehcache>
首先,我使用insert()方法填充我的数据库。 然后,我第一次调用我的select()方法来获取数据。 一切正常 - 查询和实体被缓存,如果我在 2 秒后调用select()方法,我将在没有任何数据库请求的情况下获取数据。 然后我等待 12 秒(为了缓存完全过期),再次调用select()和 2 秒后调用select() 。 这就是我得到 n+1 个选择的地方:
2019-02-13 18:52:17,101 [DEBUG] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:92) 选择 testentity0_.id 作为 id1_0_0_,testentity0_.value 作为 value2_0ity_0_testentity0_.value 来自 test_0ity0_0 .id=? 2019-02-13 18:52:17,107 [DEBUG] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:92) 选择 testentity0_.id 作为 id1_0_0_,testentity0_.value 作为 value2_0ity_0_testentity0_.value 来自 test_0ity0_0 .id=? 2019-02-13 18:52:17,108 [DEBUG] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:92) 选择 testentity0_.id 作为 id1_0_0_,testentity0_.value 作为 value2_0ity0_0_0 .id=? 2019-02-13 18:52:17,108 [DEBUG] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:92) 选择 testentity0_.id 作为 id1_0_0_,testentity0_.value 作为 value2_0ity0_0_0 .id=? 2019-02-13 18:52:17,109 [DEBUG] org.hibernate.engine.jdbc.spi.SqlStatementLogger.logStatement(SqlStatementLogger.java:92) 选择 testentity0_.id 作为 id1_0_0_,testentity0_.value 作为 value2_0ity0_0_0 .id=?
我知道发出这些请求是因为查询缓存仅缓存 id,并且二级缓存中似乎缺少这些 id 的实体。 但他们为什么失踪? 当我启用完整日志记录时,我看到在第三次调用select() 后有日志条目,例如
将实体添加到二级缓存:[TestEntity#1]
因此,如果实体被添加到二级缓存并且它们应该只在 11 秒后过期,为什么它们在 2 秒后丢失?
我的pom.xml 的一部分:
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.194</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.7.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.2.7.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>5.2.7.Final</version>
</dependency>
</dependencies>
持久性.xml :
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="main">
<class>TestEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="net.sf.ehcache.configurationResourceName" value="ehcache.xml"/>
</properties>
</persistence-unit>
</persistence>
测试实体.java :
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
/**
* User: Kirill Smirnov (k.smirnov@sirena2000.ru)
* Date: 18.12.18
* Time: 19:20
*/
@Entity
@Table(name = "test")
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class TestEntity {
@Id
@GeneratedValue(generator = "test_seq")
@SequenceGenerator(name = "test_seq", sequenceName="TEST_SEQ")
@Column(name = "id")
private int id;
@Column(name = "value", nullable = false)
private String value;
public TestEntity() {
}
public TestEntity(String value) {
this.value = value;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
主.java :
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import java.util.Properties;
/**
* User: Kirill Smirnov (k.smirnov@sirena2000.ru)
* Date: 14.11.14
* Time: 15:55
*/
public class Main {
public static void main(String[] args) throws Exception {
Properties entityManagerFactoryProperties = new Properties();
entityManagerFactoryProperties.setProperty("javax.persistence.jdbc.driver", "org.h2.Driver");
entityManagerFactoryProperties.setProperty("javax.persistence.jdbc.url", "jdbc:h2:mem:");
entityManagerFactoryProperties.setProperty("javax.persistence.jdbc.user", "sa");
entityManagerFactoryProperties.setProperty("javax.persistence.jdbc.password", "");
entityManagerFactoryProperties.setProperty("hibernate.c3p0.min_size", "" + 1);
entityManagerFactoryProperties.setProperty("hibernate.c3p0.max_size", "" + 1);
entityManagerFactoryProperties.setProperty("hibernate.c3p0.timeout", "" + 5000);
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("main", entityManagerFactoryProperties);
insert(entityManagerFactory);
select(entityManagerFactory);
Thread.sleep(2000);
select(entityManagerFactory);
Thread.sleep(12000);
select(entityManagerFactory);
Thread.sleep(2000);
select(entityManagerFactory);
entityManagerFactory.close();
}
private static void insert(EntityManagerFactory entityManagerFactory) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
try {
entityManager.persist(new TestEntity("1"));
entityManager.persist(new TestEntity("2"));
entityManager.persist(new TestEntity("3"));
entityManager.persist(new TestEntity("4"));
entityManager.persist(new TestEntity("5"));
entityManager.getTransaction().commit();
} catch (Exception e) {
entityManager.getTransaction().rollback();
throw e;
} finally {
entityManager.close();
}
}
private static void select(EntityManagerFactory entityManagerFactory) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
try {
String queryText = "FROM TestEntity";
TypedQuery<TestEntity> query = entityManager.createQuery(queryText, TestEntity.class).setHint("org.hibernate.cacheable", true);
query.getResultList();
entityManager.getTransaction().commit();
} catch (Exception e) {
entityManager.getTransaction().rollback();
throw e;
} finally {
entityManager.close();
}
}
}
PS 我猜这个问题的原因是 Hibernate 中的一个错误。 如果我从 5.2 升级到 5.4,问题就会消失。 但是,我接受 Vlad 的回答,因为它通常包含有用的信息。
这就是臭名昭著的N+1 Query Cache issue
。
您必须确保实体缓存区域的 TTL(生存时间)高于查询缓存或集合缓存的 TTL。
否则,Hibernate 将在查询缓存或集合缓存中找到实体标识符,并假设实体已经存储在实体缓存区域中。 但是如果在实体缓存中找不到实体,那么它们只能从数据库中获取,因此会触发 N+1 查询问题。
现在,回到您的设置。 这是您为实体缓存区域设置的内容:
<cache name = "TestEntity"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU">
</cache>
请注意, timeToLiveSeconds
仅设置为10
秒。
QueryCache 设置如下:
<cache name="org.hibernate.cache.internal.StandardQueryCache"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="10"
memoryStoreEvictionPolicy="LRU">
</cache>
因此timeToLiveSeconds
也设置为10
秒,并确保实体查询缓存未设置为比查询缓存和关联的集合缓存更早到期。
接下来,将TestEntity
的timeToLiveSeconds
提高到60
或120
秒。 或者使它eternal = true
并禁用TTL
因为实体正在使用CacheConcurrencyStartegy.READ_ONLY
。
<cache name = "TestEntity"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="60"
memoryStoreEvictionPolicy="LRU">
</cache>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.