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_second_level_cachehibernate.cache.use_query_cache

二级缓存hibernate.cache.use_second_level_cache):二级缓存是应用级缓存,用于存储通过主键查询时的实体数据。

查询缓存 (hibernate.cache.use_query_cache):查询缓存是一个单独的缓存,只存储查询结果。 (我在 findAll_test 中的代码正在访问此缓存)

问题

问题在于重叠的缓存条目到期。如果同时启用两个缓存,它们缓存的实体对象会重叠,二级缓存的过期策略结束将覆盖查询缓存。将实体设置为在任何低于 never 时过期将使查询缓存在过期时与 re-caching 任何实体分离,从而导致死锁情况。

解决方案

不要同时使用两者。这似乎是一个错误,因为您应该能够指定单独的缓存位置以同时使用两个缓存,但这样做并不能阻止问题的发生。

可能有办法解决这个问题并启用二级缓存,但经过大量测试后我没有找到。

有关详细信息,请参阅 Hibernate Caching