Dainius
Dainius

Reputation: 1852

Hibernate not caching my OneToOne relationship on the inverse side

I have code like:

@Entity
@Table(name = "A")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class A
{
  @OneToOne(cascade={CascadeType.ALL}, fetch=FetchType.EAGER, mappedBy="a")
  public B getB() {};
}

@Entity
@Table(name = "B")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class B
{
  @OneToOne(cascade={}, fetch=FetchType.LAZY)
  @JoinColumn(name="A_ID")
  public A getA() {};
}

each time when A is loaded there is query for B. Why is A.getB() not cached after A is loaded and is it possible to cache it?

Upvotes: 15

Views: 2749

Answers (4)

Alexander Petrov
Alexander Petrov

Reputation: 9492

I feel that the original answer does not cover entirly why this is happening.

Why OneToOne is not cached ?

It is not cached because class A is not the owner of the relationship and does not contain the @JoinColumn inside its table. Therefore there is no way for class A to tell what is the ID of class B. This is why when trying to retrieve class A it needs to send a query for class B to figure out what the ID of class B is, but when it sends the query the class B is already loaded so there is no need for it to actualy retrieve it from the cache.

When OneToOne will be cached ?

Now if you navigate the opposite way from class B to class A then you will hit the cache straight away :)

Why is @OneToMany(cascade={}, fetch=FetchType.EAGER, mappedBy="a") working ?

In hibernate collections are cached in their dedicated region known as collection cache. Hibernate caches the primary keys of the entities that make up the collection. Not the entities themselves; i.e. there is no Set stored somewhere in the second level cache.

Once the primary key for is retrieved from the collection cache region it falls back to the regular entity cache to retrieve the actual object. Therefore the @OneToMany hack works for you.

Upvotes: 0

Jay
Jay

Reputation: 452

It may be a little more work, but you could try making the fetchType Lazy, and do the fetching of B explicitly. That way you could check whether the instance of B has already been loaded or not?

On a side note, have you seen this post? I think the problem is similar:

https://forum.hibernate.org/viewtopic.php?p=2378461

Upvotes: 0

Dainius
Dainius

Reputation: 1852

Workaround that work for me is create additional method with @OneToMany

@OneToMany(cascade={}, fetch=FetchType.EAGER, mappedBy="a")
public Set<B> getBSet() {};

@Transient
public B getB() { return b.iterator().next(); }

I'm not very happy with this solutions, but it works and I can't find other way.

Upvotes: 2

Alex Gitelman
Alex Gitelman

Reputation: 24722

Try putting @Cache annotation on getB() getter as well. My observations are that if you cache the object, it's associations may not be considered cached.

Upvotes: 0

Related Questions