Question

I'm trying to cache lazy loaded collections with ehcache/hibernate in a Spring project. When I execute a session.get(Parent.class, 123) and browse through the children multiple times a query is executed every time to fetch the children. The parent is only queried the first time and then resolved from the cache.

Probably I'm missing something, but I can't find the solution. Please see the relevant code below.

I'm using Spring (3.2.4.RELEASE) Hibernate(4.2.1.Final) and ehcache(2.6.6)

The parent class:

@Entity
@Table(name = "PARENT")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, include = "all")

public class Parent implements Serializable {
/** The Id. */
    @Id
    @Column(name = "ID")
    private int id;


    @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private List<Child> children;

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Parent that = (Parent) o;
        if (id != that.id) return false;
        return true;
    }

    @Override
    public int hashCode() {
        return id;
    }
}

The child class:

@Entity
@Table(name = "CHILD")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, include = "all")
public class Child {

    @Id
    @Column(name = "ID")
    private int id;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "PARENT_ID")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Parent parent;

    public int getId() {
    return id;
    }

    public void setId(final int id) {
    this.id = id;
    }

    private Parent getParent(){
        return parent;
    }

    private void setParent(Parent parent) {
         this.parent = parent;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
    }
        final Child that = (Child) o;
        return id == that.id;
    }

    @Override
    public int hashCode() {
        return id;
    }
}

The application context:

 <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="annotatedClasses">
        <list>
            <value>Parent</value>
            <value>Child</value>
        </list>
    </property>

    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2008Dialect</prop>
            <prop key="hibernate.hbm2ddl.auto">validate</prop>
            <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
            <prop key="hibernate.connection.charSet">UTF-8</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.use_sql_comments">true</prop>

            <!-- cache settings ehcache-->
            <prop key="hibernate.cache.use_second_level_cache">true</prop>
            <prop key="hibernate.cache.use_query_cache">true</prop>
            <prop key="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>
            <prop key="hibernate.generate_statistics">true</prop>
            <prop key="hibernate.cache.use_structured_entries">true</prop>
            <prop key="hibernate.cache.use_query_cache">true</prop>
            <prop key="hibernate.transaction.factory_class"> org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory</prop>
            <prop key="hibernate.transaction.jta.platform"> org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform</prop>
        </props>
    </property>
</bean>

The testcase I'm running:

@Test
public void testGetParentFromCache() {
  for (int i = 0; i <3 ; i++ ) {
      getEntity();
  }

}

private void getEntity() {
    Session sess = sessionFactory.openSession()
    sess.setCacheMode(CacheMode.NORMAL);
    Transaction t = sess.beginTransaction();

    Parent p  = (Parent) s.get(Parent.class, 123);
    Assert.assertNotNull(p);
    Assert.assertNotNull(p.getChildren().size());
    t.commit();
    sess.flush();
    sess.clear();
    sess.close();
}

In the logging I can see that the first time 2 queries are executed getting the parent and getting the children. Furthermore the logging shows that the child entities as well as the collection are stored in the 2nd level cache. However when reading the collection a query is executed to fetch the children on second and third attempt.

As EHCache was not working we tried infinispan as well (with different concurrency level). Unfortunately we keep experiencing the same problem.

P.S. This problem is also addressed in the EHCache forum: http://forums.terracotta.org/forums/posts/list/8785.page and the hibernate forum: https://forum.hibernate.org/viewtopic.php?f=1&t=1029899

Furthermore my colleague created an example project resembling our project setup and problem at GitHub: https://github.com/basvanstratum/cacheimpl

Was it helpful?

Solution

The problem here lies within your version of Hibernate. The following code will explain what is going wrong here.

public class DefaultInitializeCollectionEventListener
      implements InitializeCollectionEventListener {
  public void onInitializeCollection(InitializeCollectionEvent event)
        throws HibernateException {
    …
    final boolean traceEnabled = LOG.isTraceEnabled();
    …

    final boolean foundInCache = methodReturningTrueWhenInCache();

    if ( foundInCache && traceEnabled ) {
      LOG.trace( "Collection initialized from cache" );
    }
    else {
      if ( traceEnabled ) {
        LOG.trace( "Collection not cached" );
      }
      methodThatExecutesAQuery();
  }
}

This is the link to Hibernate's JIRA ticket concerning this. The solution is to either enable trace logging (yuck - forget I even said this) or upgrade your libraries to version 4.2.2 or higher. https://hibernate.atlassian.net/browse/HHH-8250

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top