12
votes

I'm working on an application using Spring Data JPA with Hibernate and I'm trying to enable second level cache using ehcache. I broke up my application into two projects:

  • CoreDataFacade: where I define data access operations using QueryDSL, Spring Data JPA with Hibernate and ehcache.
  • QueryComponent: is a spring boot project that uses CoreDataFacade project to access data.

CoreDataFacade's configuration is as follows:

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 Cache annotations

@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's configuration imports above configuration and exclude 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);
    }
}

When ComponentQuery starts everything is OK. When I execute a query to find all states in database, hibernate statistics are as follows:

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)
  }

When I repeat the same query I got this statistics:

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)
}

It seems that every query is putting results (4 states in database) in cache:

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

I expect second query to retrive data from cache but it statistics hits and misses are zero:

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

My question is: why L2C hits and L2C misses are zero when I execute second query?

UPDATE

This is how I'm running my query

Reposiory

I'm working with QueryDSL with spring data jpa. I followed this tutorial to integrate my JpaRepository using QueryDslPredicateExecutor

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

Service

In my service I execute my query using queryDLS predicates and PathBuilder as shown in this great article so that, I can find States or any other entity by any field. E.g "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);
}

Queries runs like charm, but data seems not be cached.

4
Add the queries you are running.manish
@manish, I updated my question with information about queries.CharlieG.
@NassimMOUALEK, I think this question is not related with EhCache performace, is about cache storing. EhCache seems not being working.CharlieG.
Why are you bypassing Spring Boot and configure everything yourself? Have you used the correct @Cacheable annotation?M. Deinum

4 Answers

7
votes

Entity cache only works if the entity is retrieved using its id e.g. load(), get(). It doesn't work if you use query.

To enable caching the query you have to use Query cache. e.g.

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

or using jpa

query.setHint(“org.hibernate.cacheable”, true);

I'm not sure how to implement this with QueryDslPredicateExecutor, but hopes this will help to understand hibernate 2nd lvl cache

L2C hits and L2C misses are equal to zero means that hibernate never search the data from the cache because you are retrieving records using query without enabling the query cache

L2C puts not zero because hibernate cache the records to be used later if you retrieve the entity by its id(this is different than caching the query result)

2
votes

Your cache region configuration has a typo. In the entity class, your configuration (@Cache(usage = CacheConcurrencyStrategy.READ_ONLY, region="cache_states")) points to a different region than one defined in ehcache.xml (cache name="cache_estados")

2
votes

Try this

@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);

}

It worked for me.

0
votes

This is what is done in our case. In fact, Spring JPA and QueryDSL were used. On an other note, QueryDSL is not preventing nor has anything to do with caching. Hope this answer helps.

Following are the code config you would need to work it out.

Entity manager config 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;
    }
}

Important things to look for in the above config are

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 config bean (we used 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;
    }
}

Entity class

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