Hibernate ehcache/jcache 自定义过期策略在数百次 select 查询后挂起
Hibernate ehcache/jcache custom expiry policy hangs after a few hundred select queries
findAll_test
方法中的 getResultList
调用在数百次请求后挂起。
public Session openSession() {
return this.sessionFactory.openSession(); // org.hibernate.SessionFactory
}
public <R> R with(Function<Session, R> function) throws SqlException {
try {
Session session = this.openSession();
R result = function.apply(session);
if (session.isDirty())
session.flush();
if (session.isOpen())
session.close();
return result;
} catch (Exception exception) {
throw new SqlException(exception); // SqlException extends RuntimeException
}
}
public final ConcurrentList<T> findAll_test() {
return this.with(session -> {
Class<T> tClass = this.getTClass();
// This is the database model/entity class,
// it's passed in the constructor,
// it's the class with @Entity, @Table annotations
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(tClass);
Root<T> rootEntry = criteriaQuery.from(tClass);
CriteriaQuery<T> all = criteriaQuery.select(rootEntry);
// Concurrent.newList is just a thread-safe list,
// it's not being used downstream for the purposes of this example,
return Concurrent.newList(
session.createQuery(all)
.setCacheable(true)
.getResultList() // This works ~1245 times, then hangs
);
});
}
如果我 运行 上面的方法总共大约 1,245 次,跨越几十 models/entities,它挂在里面 getResultList
.
这是我的 Hibernate/HikariCP/ehcache 属性。
Properties properties = new Properties() {{
// Connection
put("hibernate.dialect", config.getDatabaseDriver().getDialectClass());
put("hibernate.connection.driver_class", config.getDatabaseDriver().getDriverClass());
put("hibernate.connection.url", config.getDatabaseDriver().getConnectionUrl(config.getDatabaseHost(), config.getDatabasePort(), config.getDatabaseSchema()));
put("hibernate.connection.username", config.getDatabaseUser());
put("hibernate.connection.password", config.getDatabasePassword());
put("hibernate.connection.provider_class", "org.hibernate.hikaricp.internal.HikariCPConnectionProvider");
// SQL
put("hibernate.generate_statistics", config.isDatabaseDebugMode());
put("hibernate.show_sql", false);
put("hibernate.format_sql", false); // Log Spam
put("hibernate.use_sql_comments", true);
put("hibernate.order_inserts", true);
put("hibernate.order_updates", true);
put("hibernate.globally_quoted_identifiers", true);
// Prepared Statements
put("hikari.cachePrepStmts", true);
put("hikari.prepStmtCacheSize", 256);
put("hikari.prepStmtCacheSqlLimit", 2048);
put("hikari.useServerPrepStmts", true);
// Caching
put("hibernate.cache.use_second_level_cache", true);
put("hibernate.cache.use_query_cache", true);
put("hibernate.cache.region.factory_class", "org.hibernate.cache.jcache.JCacheRegionFactory");
put("hibernate.cache.provider_class", "org.ehcache.jsr107.EhcacheCachingProvider");
put("hibernate.cache.use_structured_entries", config.isDatabaseDebugMode());
}};
以下是“隐藏”问题的两种情况:
- 如果我删除
hibernate.cache.use_query_cache
(或将其设置为 false),我的应用程序会变得 显着 变慢,但它不再遇到此问题。
- 如果我将过期策略从一分钟的持续时间更改为五分钟,那么它也会将我可以运行上述方法的时间延长 5 倍。
问题出在休眠查询缓存上。禁用时,不会发生此问题。当缓存过期时,查询我的方式(对于所有实体)时似乎出现了死锁。
依赖关系:
- Hibernate 和 HikariCP 5.5。7.Final
- ehcache 3.9.9
- mariadb-java-客户端 2.7.4
- MariaDB 服务器 10.3.28
我遇到的问题源于我如何查询数据库,以及我如何同时使用 hibernate.cache.use_second_level_cache
和 hibernate.cache.use_query_cache
。
二级缓存(hibernate.cache.use_second_level_cache
):二级缓存是应用级缓存,用于存储通过主键查询时的实体数据。
查询缓存 (hibernate.cache.use_query_cache
):查询缓存是一个单独的缓存,只存储查询结果。 (我在 findAll_test
中的代码正在访问此缓存)
问题
问题在于重叠的缓存条目到期。如果同时启用两个缓存,它们缓存的实体对象会重叠,二级缓存的过期策略结束将覆盖查询缓存。将实体设置为在任何低于 never 时过期将使查询缓存在过期时与 re-caching 任何实体分离,从而导致死锁情况。
解决方案
不要同时使用两者。这似乎是一个错误,因为您应该能够指定单独的缓存位置以同时使用两个缓存,但这样做并不能阻止问题的发生。
可能有办法解决这个问题并启用二级缓存,但经过大量测试后我没有找到。
有关详细信息,请参阅 Hibernate Caching。
findAll_test
方法中的 getResultList
调用在数百次请求后挂起。
public Session openSession() {
return this.sessionFactory.openSession(); // org.hibernate.SessionFactory
}
public <R> R with(Function<Session, R> function) throws SqlException {
try {
Session session = this.openSession();
R result = function.apply(session);
if (session.isDirty())
session.flush();
if (session.isOpen())
session.close();
return result;
} catch (Exception exception) {
throw new SqlException(exception); // SqlException extends RuntimeException
}
}
public final ConcurrentList<T> findAll_test() {
return this.with(session -> {
Class<T> tClass = this.getTClass();
// This is the database model/entity class,
// it's passed in the constructor,
// it's the class with @Entity, @Table annotations
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(tClass);
Root<T> rootEntry = criteriaQuery.from(tClass);
CriteriaQuery<T> all = criteriaQuery.select(rootEntry);
// Concurrent.newList is just a thread-safe list,
// it's not being used downstream for the purposes of this example,
return Concurrent.newList(
session.createQuery(all)
.setCacheable(true)
.getResultList() // This works ~1245 times, then hangs
);
});
}
如果我 运行 上面的方法总共大约 1,245 次,跨越几十 models/entities,它挂在里面 getResultList
.
这是我的 Hibernate/HikariCP/ehcache 属性。
Properties properties = new Properties() {{
// Connection
put("hibernate.dialect", config.getDatabaseDriver().getDialectClass());
put("hibernate.connection.driver_class", config.getDatabaseDriver().getDriverClass());
put("hibernate.connection.url", config.getDatabaseDriver().getConnectionUrl(config.getDatabaseHost(), config.getDatabasePort(), config.getDatabaseSchema()));
put("hibernate.connection.username", config.getDatabaseUser());
put("hibernate.connection.password", config.getDatabasePassword());
put("hibernate.connection.provider_class", "org.hibernate.hikaricp.internal.HikariCPConnectionProvider");
// SQL
put("hibernate.generate_statistics", config.isDatabaseDebugMode());
put("hibernate.show_sql", false);
put("hibernate.format_sql", false); // Log Spam
put("hibernate.use_sql_comments", true);
put("hibernate.order_inserts", true);
put("hibernate.order_updates", true);
put("hibernate.globally_quoted_identifiers", true);
// Prepared Statements
put("hikari.cachePrepStmts", true);
put("hikari.prepStmtCacheSize", 256);
put("hikari.prepStmtCacheSqlLimit", 2048);
put("hikari.useServerPrepStmts", true);
// Caching
put("hibernate.cache.use_second_level_cache", true);
put("hibernate.cache.use_query_cache", true);
put("hibernate.cache.region.factory_class", "org.hibernate.cache.jcache.JCacheRegionFactory");
put("hibernate.cache.provider_class", "org.ehcache.jsr107.EhcacheCachingProvider");
put("hibernate.cache.use_structured_entries", config.isDatabaseDebugMode());
}};
以下是“隐藏”问题的两种情况:
- 如果我删除
hibernate.cache.use_query_cache
(或将其设置为 false),我的应用程序会变得 显着 变慢,但它不再遇到此问题。 - 如果我将过期策略从一分钟的持续时间更改为五分钟,那么它也会将我可以运行上述方法的时间延长 5 倍。
问题出在休眠查询缓存上。禁用时,不会发生此问题。当缓存过期时,查询我的方式(对于所有实体)时似乎出现了死锁。
依赖关系:
- Hibernate 和 HikariCP 5.5。7.Final
- ehcache 3.9.9
- mariadb-java-客户端 2.7.4
- MariaDB 服务器 10.3.28
我遇到的问题源于我如何查询数据库,以及我如何同时使用 hibernate.cache.use_second_level_cache
和 hibernate.cache.use_query_cache
。
二级缓存(hibernate.cache.use_second_level_cache
):二级缓存是应用级缓存,用于存储通过主键查询时的实体数据。
查询缓存 (hibernate.cache.use_query_cache
):查询缓存是一个单独的缓存,只存储查询结果。 (我在 findAll_test
中的代码正在访问此缓存)
问题
问题在于重叠的缓存条目到期。如果同时启用两个缓存,它们缓存的实体对象会重叠,二级缓存的过期策略结束将覆盖查询缓存。将实体设置为在任何低于 never 时过期将使查询缓存在过期时与 re-caching 任何实体分离,从而导致死锁情况。
解决方案
不要同时使用两者。这似乎是一个错误,因为您应该能够指定单独的缓存位置以同时使用两个缓存,但这样做并不能阻止问题的发生。
可能有办法解决这个问题并启用二级缓存,但经过大量测试后我没有找到。
有关详细信息,请参阅 Hibernate Caching。