Spring data JPA with Hibernate and Ehcache not working

CharlieG. picture CharlieG. · Sep 1, 2015 · Viewed 8.5k times · Source

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.

Answer

MangEngkus picture MangEngkus · Sep 3, 2015

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)