Hibernate выполняет N + 1 выбор вместо 1 запроса при использовании Query Cache - PullRequest
0 голосов
/ 13 февраля 2019

Я застрял с проблемой, я чувствую себя действительно потерянным и не знаю, что делать.Я использую кеш запросов + кэш второго уровня и хочу правильно кешировать результаты в течение 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 () , чтобы получить данные в первый раз.Все работает нормально - запрос и сущности становятся кэшированными, и если я вызову метод select () через 2 секунды, я получу данные без каких-либо запросов к базе данных.Затем я жду 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_0_0_ из теста testentity0_, где testentity0_.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 в качестве value__0_0_00000.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 в качестве value__0_0_0000.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 в качестве value__0_0_0000.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 в качестве value__0_0_0000.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>

persistence.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>

TestEntity.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;
    }
}

Main.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, проблема исчезнет.Однако я принимаю ответ Влада, потому что он содержит полезную информацию в целом.

1 Ответ

0 голосов
/ 14 февраля 2019

Это проблема N + 1 кеша запросов .

Необходимо убедиться, что TTL (время жизни) для области Entity Cache выше, чем TTLQuery Cache или Collection Cache.

В противном случае Hibernate найдет идентификаторы сущностей в Query Cache или Collection Cache и предположит, что сущности уже были сохранены в области Entity Cache.Но если сущности не найдены в Entity Cache, их можно получить только из БД, что вызывает проблему с запросом N + 1.

Теперь вернемся к вашим настройкам.Это то, что вы устанавливаете для области Entity Cache:

<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 секунд.

Итак, как я объяснил в этой статье , необходимо убедиться, что срок действия Entity Query Cache не истекает раньше, чем Query Cache и связанный Collection Cache.

Итак, увеличьте timeToLiveSeconds для TestEntity до 60 или 120 секунд.Или сделайте его eternal = true и отключите TTL, поскольку сущность использует READ_ONLY CacheConcurrencyStartegy.

<cache name = "TestEntity"
       maxElementsInMemory="100"
       eternal="false"
       timeToLiveSeconds="60"
       memoryStoreEvictionPolicy="LRU">
</cache>
...