带有 Hibernate 和 Ehcache 的 Spring 数据 JPA 不起作用

Posted

技术标签:

【中文标题】带有 Hibernate 和 Ehcache 的 Spring 数据 JPA 不起作用【英文标题】:Spring data JPA with Hibernate and Ehcache not working 【发布时间】:2015-11-26 02:39:48 【问题描述】:

我正在开发一个使用带有 Hibernate 的 Spring Data JPA 的应用程序,并且我正在尝试使用 ehcache 启用二级缓存。我将我的应用程序分成两个项目:

CoreDataFacade:我使用 QueryDSL、Spring Data JPA with Hibernate 和 ehcache 定义数据访问操作。 QueryComponent:是一个spring boot项目,使用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>

application-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
@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 未命中为零?

更新

这就是我运行查询的方式

存储库

我正在使用带有 spring data jpa 的 QueryDSL。我按照 tutorial 使用 QueryDslPredicateExecutor 集成我的 JpaRepository

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

服务

在我的服务中,我使用 queryDLS 谓词和 PathBuilder 执行查询,如这个伟大的article 所示,这样我就可以通过任何字段找到状态或任何其他实体。例如“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);

查询运行得很好,但数据似乎没有被缓存。

【问题讨论】:

添加您正在运行的查询。 Inefficient EhCache Performance的可能重复 @manish,我用有关查询的信息更新了我的问题。 @NassimMOUALEK,我认为这个问题与 EhCache 性能无关,是关于缓存存储的。 EhCache 似乎无法正常工作。 为什么要绕过 Spring Boot 自己配置一切?你使用了正确的@Cacheable注解吗? 【参考方案1】:

实体缓存仅在使用其 id 检索实体时才有效,例如加载(),获取()。如果您使用查询,它不起作用。

要启用缓存查询,您必须使用查询缓存。例如

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 来实现这一点,但希望这将有助于理解 hibernate 2nd lvl cache

L2C 命中和 L2C 未命中等于 0 意味着休眠永远不会从缓存中搜索数据,因为您在未启用查询缓存的情况下使用查询检索记录

L2C 不为零,因为如果您通过实体 id 检索实体,休眠会缓存稍后使用的记录(这与缓存查询结果不同)

【讨论】:

不完全是。创建结果时,它将尝试在二级缓存中找到命中。一个很好的解释 here 虽然有点过时仍然适用。 @MangEngkus,感谢您的时间和解释。不幸的是,由于项目的最后期限,我们决定不实施 ehcache,但我们会在未来这样做。我尝试在演示项目中使用 query.setHint(“org.hibernate.cacheable”, true) 并且它有效,因此我们需要重新调整我们的代码才能使用它。无论如何,我会保持这个问题的开放性。如果有人遇到同样的问题,并想办法将 ehcache 与 QueryDslPredicateExecutor 一起使用并找到解决方案。 @CharlieG 您是否尝试使用:org.hibernate.cache.SingletonEhCacheProviderorg.hibernate.cache.ehcache.SingletonEhCacheRegionFactory 用于 hibernate.cache.provider_classhibernate.cache.region.factory_class 分别?我们能够让缓存与我们项目中使用的 Spring JPA 和 QueryDSL 一起工作。我很快就会发布答案。【参考方案2】:

您的缓存区域配置有错字。在实体类中,您的配置 (@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region="cache_states")) 指向与 ehcache.xml 中定义的区域不同的区域(缓存 name= "cache_estados")

【讨论】:

嗨@R4J,当我编辑我的问题时,我的缓存区域配置是错误的。实际上,我的 ehcache.xml 区域与我在实体配置中的名称相同(“cache_states”)。我更新了我的问题。顺便说一句,我不知道为什么要投反对票,但感谢您的宝贵时间。【参考方案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);


它对我有用。

【讨论】:

这对我有用。我只用了org.hibernate.cacheable【参考方案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");

缓存管理器配置 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
    ...
    ...
    ..

【讨论】:

CacheConfiguration 类的实现在哪里?

以上是关于带有 Hibernate 和 Ehcache 的 Spring 数据 JPA 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 和 EHCache:maxElementsInMemory 是如何工作的?

如何使用 Spring 清除所有 Hibernate 缓存(ehcache)?

Hibernate性能优化之EHCache缓存

Hibernate + Ehcache - IllegalArgumentException 发生调用复合 id 的 getter

hibernate二级缓存

如何在 grails 应用程序中监控 Hibernate 统计信息(缓存命中和未命中)?