Spring данные JPA с Hibernate и Ehcache не работают

Я работаю над приложением, использующим Spring Data JPA с Hibernate, и я пытаюсь включить кеш второго уровня с помощью ehcache. Я разбил свое приложение на два проекта:

  • CoreDataFacade: где я определяю операции доступа к данным с использованием QueryDSL, Spring Data JPA с Hibernate и ehcache.
  • QueryComponent: это проект загрузки Spring, который использует проект CoreDataFacade для доступа к данным.

Конфигурация CoreDataFacade выглядит следующим образом:

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.7.3.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.6.Final</version>
    </dependency>

    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.4.7</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-ehcache</artifactId>
        <version>4.3.6.Final</version>  
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.6.Final</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.33</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-apt</artifactId>
        <version>3.6.0</version>
    </dependency>
    <dependency>
        <groupId>com.mysema.querydsl</groupId>
        <artifactId>querydsl-jpa</artifactId>
        <version>3.6.0</version>
    </dependency>

приложения context.xml

<jpa:repositories
    base-package="com.coredata.services.impl.sql.mysql.repositories" />

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close" p:driverClass="com.mysql.jdbc.Driver"
    p:jdbcUrl="jdbc:mysql://localhost/FOO" p:user="****" p:password="****"
    p:acquireIncrement="5" p:minPoolSize="10" p:maxPoolSize="100"
    p:maxIdleTime="1200" p:unreturnedConnectionTimeout="120" />

<bean id="jpaVendorAdapter"
    class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
    p:database="MYSQL" p:showSql="true" p:generateDdl="true" />


<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
    p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaVendorAdapter"
    p:packagesToScan="com.coredata.services.impl.sql.mysql.model">
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="hibernate.cache.use_query_cache">true</prop>
            <prop key="hibernate.cache.use_second_level_cache">true</prop>
            <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
            <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory
            </prop>
            <prop key="javax.persistence.sharedCache.mode">ENABLE_SELECTIVE</prop>
            <prop key="hibernate.generate_statistics">true</prop>
        </props>
    </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" />

Аннотации кэш-памяти Entity

@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region="cache_states")
@Table(name="states")
public class State implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id_state")
    private int idState;
    ...

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

    <defaultCache maxElementsInMemory="10000" eternal="false"
        timeToIdleSeconds="120" timeToLiveSeconds="120"
        overflowToDisk="false" diskPersistent="false" />

    <cache name="cache_states" maxElementsInMemory="300" eternal="false"
        timeToIdleSeconds="5000" timeToLiveSeconds="5000" overflowToDisk="false">
    </cache>
</ehcache>

Конфигурация конфигурации QueryComponent выше конфигурации и исключает JPA:

@Configuration
@PropertySource("classpath:/component.properties")
@ImportResource({ "classpath:/application-context.xml"})
@EnableAutoConfiguration(exclude = { JpaRepositoriesAutoConfiguration.class })
public class Application {

    public void run(String... args) {   }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Когда ComponentQuery запускается, все в порядке. Когда я выполняю запрос для поиска всех состояний в базе данных, статистика спящего режима выглядит следующим образом:

Hibernate: select count(state0_.id_state) as col_0_0_ from states state0_
Hibernate: select state0_.id_state as id_stat1_5_, state0_.name_state as  name_e2_5_ from states state0_ limit ?
[2015-08-31 18:52:21.402] boot - 1946  INFO [SimpleAsyncTaskExecutor-1]    --- StatisticalLoggingSessionEventListener:    Session Metrics {
    32992 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    238285 nanoseconds spent preparing 2 JDBC statements;
    935976 nanoseconds spent executing 2 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    269717 nanoseconds spent performing 4 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);
    68790 nanoseconds spent executing 2 partial-flushes (flushing a total of 0 entities and 0 collections)
  }

Когда я повторяю тот же запрос, я получил эту статистику:

Hibernate: select count(state0_.id_state) as col_0_0_ from states state0_
Hibernate: select state0_.id_state as id_stat1_5_, state0_.name_state as  name_e2_5_ from states state0_ limit ?
[2015-08-31 19:26:48.479] boot - 1946  INFO [SimpleAsyncTaskExecutor-1]    --- StatisticalLoggingSessionEventListener:     Session Metrics {
    314930 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    356832 nanoseconds spent preparing 2 JDBC statements;
    681615 nanoseconds spent executing 2 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    209324 nanoseconds spent performing 4 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);
    12368 nanoseconds spent executing 2 partial-flushes (flushing a total of 0 entities and 0 collections)
}

Кажется, что каждый запрос помещает результаты (4 состояния в базе данных) в кеш:

    269717 nanoseconds spent performing 4 L2C puts;
    209324 nanoseconds spent performing 4 L2C puts;

Я ожидаю, что второй запрос будет извлекать данные из кеша, но статистические удары и пропуски равны нулю:

    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;

Мой вопрос: почему L2C-хиты и пропуски L2C равны нулю при выполнении второго запроса?

UPDATE

Вот как я выполняю свой запрос

Reposiory

Я работаю с QueryDSL с помощью Spring данных jpa. Я выполнил этот учебник для интеграции моего JpaRepository с помощью QueryDslPredicateExecutor

public interface StateRepository extends JpaRepository<State, Integer>, QueryDslPredicateExecutor<State> {
 }

Сервис

В моей службе я выполняю свой запрос с использованием предикатов queryDLS и PathBuilder, как показано в этой замечательной статье чтобы я мог находить государства или любые другие объекты в любом поле. Например, "StateName = Texas" , "StatePopulation = 26448193" .

@Autowired
StateRepository repo;

public List<State> getStatesByFields(String options, Integer page, Integer pageSize,String order) {
    PredicateBuilder predicateBuilder = new PredicateBuilder().onEntity("State")
    Pattern pattern = Pattern.compile(OPERATION_PATTERN);
    Matcher matcher = pattern.matcher(options + ",");
    while (matcher.find()) {
        predicateBuilder.with(matcher.group(1), matcher.group(2), matcher.group(3));
    }
    PageRequest pag = new PageRequest(page, page_size)
    BooleanExpression predicate = predicateBuilder.build();
    //findAll is provided by QueryDslPredicateExecutor interface
    Page<State> result = repo.findAll(predicate, pag);
}

Запросы выполняются как шарм, но данные, похоже, не кэшируются.

Ответ 1

Кэш сущности работает только в том случае, если объект извлекается с использованием его идентификатора, например. load(), get(). Это не работает, если вы используете запрос.

Чтобы включить кеширование запроса, вы должны использовать Query cache. например.

List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger")
    .setEntity("blogger", blogger)
    .setMaxResults(15)
    .setCacheable(true)
    .setCacheRegion("frontpages")
    .list();

или используя jpa

query.setHint("org.hibernate.cacheable", true);

Я не уверен, как реализовать это с помощью QueryDslPredicateExecutor, но надеется, что это поможет понять спящий второй кеш lvl

Пропуски L2C и пропуски L2C равны нулю, означает, что hibernate никогда не выполняет поиск данных из кеша, потому что вы извлекаете записи с использованием запроса без включения кеша запросов

L2C ставит не ноль, потому что спящий режим кэширует записи, которые будут использоваться позже, если вы получите объект по его идентификатору (это отличается от кэширования результата запроса)

Ответ 2

Конфигурация области кэша имеет опечатку. В классе сущности ваша конфигурация (@Cache (использование = CacheConcurrencyStrategy.READ_ONLY, region = "cache_states" )) указывает на другой регион, чем определенный в ehcache.xml(кеш name= "cache_estados" )

Ответ 3

Попробуйте это

@CacheConfig(cacheNames = "com.abc.domain.State")
public interface StateRepository extends CrudRepository<State, Integer>, QueryDslPredicateExecutor<State> {

    @QueryHints(value = {
            @QueryHint(name = "org.hibernate.cacheable", value = "true"),
            @QueryHint(name = "org.hibernate.cacheMode", value = "NORMAL"),
            @QueryHint(name = "org.hibernate.cacheRegion", value = "CacheRegion")
    })
    Page<State> findAll(Predicate predicate, Pageable pageable);

}

Это сработало для меня.

Ответ 4

Это то, что делается в нашем случае. Фактически, использовались Spring JPA и QueryDSL. С другой стороны, QueryDSL не предотвращает и не имеет никакого отношения к кешированию. Надеюсь, что этот ответ поможет.

Ниже приведена конфигурация кода, которую вам нужно будет выполнить.

Конфигурация менеджера сущностей bean

@Configuration
@EnableJpaRepositories(
    basePackages = { "com.abc.examples.persistence.repository" },
    entityManagerFactoryRef = "entityManagerFactory",
    transactionManagerRef = "transactionManager"
)
@ComponentScan("com.abc.examples.persistence")
public class PersistenceConfig {

    /*Code omitted. Beans configured for other items like testDataSource, transactionManager*/

    @Bean(name = "entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean(
        @Qualifier("testDataSource") DataSource dataSource,
        @Value("${hibernate.show_sql}") String hibernateShowSql,
        @Value("${hibernate.generate_statistics}") String hibernateShowStats
        ) {
        LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setPackagesToScan("com.abc.examples.persistence.entity");
        factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        factoryBean.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);

        Properties hibernateProperties = new Properties();
        hibernateProperties.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.SingletonEhCacheProvider");
        hibernateProperties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
        hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "true");
        hibernateProperties.setProperty("hibernate.cache.use_query_cache", "true");

        factoryBean.setJpaProperties(hibernateProperties);
        return factoryBean;
    }
}

Важные вещи для поиска в приведенной выше конфигурации:

factoryBean.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
...
...
hibernateProperties.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.SingletonEhCacheProvider");
hibernateProperties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "true");
hibernateProperties.setProperty("hibernate.cache.use_query_cache", "true");

Конфигурация Cache Manager bean (мы использовали EhCache)

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean(name = "cacheManager")
    public EhCacheCacheManager ehCacheCacheManager() {
        return new EhCacheCacheManager(ehCache());
    }

    @Bean
    public CacheManager ehCache() {
        CacheManager cacheManager = CacheManager.create();

        Cache sampleEntityCache = new Cache(
            new CacheConfiguration("com.abc.examples.entity.SampleEntity", 500)
                    .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU)
                    .eternal(false)
                    .timeToLiveSeconds(60 * 60 * 24)
                    .timeToIdleSeconds(60 * 60 * 24)
                    .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.NONE))
        );
        cacheManager.addCache(sampleEntityCache);

        return cacheManager;
    }
}

Класс сущностей

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Cache(usage= CacheConcurrencyStrategy.READ_ONLY,
    region="com.abc.examples.entity.SampleEntity") //This name should match with the name used in the CacheConfiguration above.
public class SampleEntity{
    @Id
    ...
    ...
    ..
}