-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 
Author Message
 Post subject: No cache hits with bidirectional one-to-one
PostPosted: Sun Aug 21, 2011 6:08 pm 
Newbie

Joined: Sun Aug 21, 2011 5:24 pm
Posts: 7
Hello,

I'm developing an application using hibernate-core-3.3.0.SP1 (hibernate-entitymanager-3.4.0.GA) and ehcache-core-2.2.0 as a second-level cache provider. I have 2 entities with a bidirectional one-to-one association:

Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Person {

   @Id
   @GeneratedValue
   private Long id;

   @OneToOne
   private Car car;

   // Getters and setters...
}


Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Car {

   @Id
   @GeneratedValue
   private Long id;

   @OneToOne(mappedBy = "car")
   private Person person;

   // Getters and setters...
}


As you can see, Person is the owning side of the association and both entities are configured with Cache annotations.

I've tried to run a code similar to the following:

Code:
// Populating the database
Person p = new Person();
this.em.persist(p);
Car c = new Car();
this.em.persist(c);
p.setCar(c);
this.em.flush();

// Populating the second-level cache
this.em.clear();
this.em.find(Person.class, p.getId());
this.em.find(Car.class, c.getId());

// Clearing the first-level cache
this.em.clear();

// At this point, the second-level cache contains both entities and the first-level cache is empty
// I thought there would be no more SQLs after this point
// But the following line generates SQL (misses the second-level cache)
this.em.find(Person.class, p.getId());


Is the behavior I described expected?

If I delete the "person" attribute from the Car entity (creating a unidirectional one-to-one), the same piece of code runs as expected (no SQLs at the last find).

If I keep the bidirectional association, but modify the Car entity as follows, the same piece of code runs as expected (no SQLs at the last find).

Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Car implements FieldHandled {

   @Id
   @GeneratedValue
   private Long id;

   @OneToOne(mappedBy = "car")
   @LazyToOne(LazyToOneOption.NO_PROXY)
   private Person person;

   private FieldHandler fieldHandler;

   // Getters and setters...
}


Is the modification above acceptable? Am I missing something?

Thanks a lot,
Tiago


Last edited by tiagobt on Mon Aug 22, 2011 12:55 am, edited 2 times in total.

Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Mon Aug 22, 2011 12:10 am 
Newbie

Joined: Sun Aug 21, 2011 5:24 pm
Posts: 7
I've done some debugging on the Hibernate code and I found a few things:

On the last call of EntityManager#find, the following happens:

  • DefaultLoadEventListener#loadFromSecondLevelCache is called twice. On the first time, a CacheEntry representing a Person is instantiated. On the second time, a CacheEntry representing a Car is instantiated. However, this Car entry looks incomplete to me. The field that was supposed to hold the Person's primary key seems to be null. Is that correct?
  • A few other methods are called: DefaultLoadEventListener#loadFromSecondLevelCache -> DefaultLoadEventListener#assembleCacheEntry -> CacheEntry#assemble -> TypeFactory#assemble -> OneToOneType#assemble -> OneToOneType(EntityType)#resolve -> OneToOneType(EntityType)#loadByUniqueKey.
  • The SQL code is generated on OneToOneType(EntityType)#loadByUniqueKey("com.company.Person", "car", 1, SessionImpl). The source code of this method includes the following line:
    Code:
    //TODO: implement caching?! proxies?!

Could that be causing the unecessary SQL statements? Is this by any chance a bug?

Tiago


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Tue Aug 23, 2011 1:03 am 
Newbie

Joined: Sun Aug 21, 2011 5:24 pm
Posts: 7
I've just realized that the FieldHandler solution (which forces a lazy association) only works partially. Although it makes Hibernate use the second-level cache when fetching both entities and when navigating from the owning side to the inverse side, it still generates SQL statements when navigating from the inverse side to the owning side. It seems that Hibernate detects that the cache entry still has lazy attributes and decides to initialize them by querying the database (which seems unnecessary considering that the lazy attribute was already present in the second-level cache).

The only solution I've found so far is turning the bidirectional one-to-one association into a bidirectional many-to-one association and configuring collection cache on the inverse side (one-to-many). This way, Hibernate ends up using the second-level cache in all situations (fetching both sides and navigating both directions). My data model, however, becomes slightly distorted.

Any other ideas?


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Tue Aug 23, 2011 7:48 pm 
Newbie

Joined: Tue Aug 23, 2011 5:05 pm
Posts: 4
I am having the same problem with Hibernate 3.6.2-Final and Ehcache 1.5.0. I found out why the inverse side of a bidirectional one-to-one association is not cached properly, which is because org.hibernate.type.OneToOneType.disassemble(Object,SessionImplementor,Object) always returns null. The owning side uses org.hibernate.type.ManyToOneType.disassemble(Object,SessionImplementor,Object) which returns an id. I copied the disassemble, assemble and assembleId methods from ManyToOneType to OneToOneType and now the inverse side is also cached properly. This seems to work for me as long as I do not use LazyToOneOption.NO_PROXY on the inverse side, because then the property is lazy and still not cached properly. A comment in ManyToOneType.assamble says: "TODO: currently broken for unique-key references (does not detect change to unique key property of the associated object)". So if you do this I suspect that the cache of the inverse side is not correct when only the owning side is updated. Luckly when I update the owning side I always increment the version the old and new value. So in the setter of Person.car I would increment the version of the old car, update the car property and increment the version of the new car (this of course assumes there is a version property). This will cause the cache of cars to invalidate and solve that problem.

So I assume OneToOneType is implemented this way, because the implementation of ManyToOneType does not always update the cache correctly for unique key properties (which is a problem for the inverse side of a bidirectional one-to-one association). I also think that this is not a problem anymore when you update the versions of the inverse side, which causes the cache to invalidate and thus always updates the cache. It would be nice if somebody with a little bit more Hibernate knowledge could confirm this.


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Wed Aug 24, 2011 1:59 am 
Newbie

Joined: Sun Aug 21, 2011 5:24 pm
Posts: 7
Interesting. I'll try a similar approach. The downside of your solution, however, is that it involves changing Hibernate code and it requires manual updates of the "version" property. Indeed, it would be nice if someone with more knowledge about Hibernate cache could comment on this solution.

In the meantime, I realized that using @PrimaryKeyJoinColumn also solves the problem. With this mapping, the second-level cache is used on all situations (fetching and navigating from both sides). Unfortunately, the application I'm working on already has production data, so changing the database scheme could introduce a complex data migration problem. For new applications, however, I believe that @PrimaryKeyJoinColumn would be the perfect solution.


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Wed Aug 24, 2011 5:07 am 
Newbie

Joined: Tue Aug 23, 2011 5:05 pm
Posts: 4
@PrimaryKeyJoinColumn is interesting. I tried @JoinColumn and that also seems to work and it also supports null values. That solution is indeed preferable to changing the Hibernate code. It is a shame that it requires changes to the database.


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Wed Aug 24, 2011 9:14 am 
Newbie

Joined: Sun Aug 21, 2011 5:24 pm
Posts: 7
Th3BlackShadow wrote:
@PrimaryKeyJoinColumn is interesting. I tried @JoinColumn and that also seems to work and it also supports null values. That solution is indeed preferable to changing the Hibernate code. It is a shame that it requires changes to the database.


Do you mean @JoinTable? The original code already uses a join column (although I didn't specify the name of the column).


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Wed Aug 24, 2011 1:32 pm 
Newbie

Joined: Tue Aug 23, 2011 5:05 pm
Posts: 4
I meant javax.persistence.JoinColumn, like:

Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Person {

   @Id
   @GeneratedValue
   private Long id;

   @OneToOne
   @JoinColumn(name = "_car")
   private Car car;

   // Getters and setters...
}


Code:
@Entity
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Car {

   @Id
   @GeneratedValue
   private Long id;

   @OneToOne
   @JoinColumn(name = "_person")
   private Person person;

   // Getters and setters...
}

I just realized that the reason this worked is because Hibernate doesn't see this as an one-to-one bidirectional association anymore, but as two unidirectional one-to-one associations. The reason this worked for me is because of the way setters are defined in my project. My setters look like:

Code:
  @Transient boolean carBeingSet = false;
  public void setCar(Car newitem)
  {
    if(!carBeingSet)
    {
      carBeingSet = true;
      Car oldthing = getCar();
      if(oldthing != newitem)
      {
        if(oldthing != null && newitem == null)
        {
          oldthing.setPerson(null);
        }
        else if(oldthing != null && newitem != null)
        {
          newitem.setPerson(this);
          oldthing.setPerson(null);
        }
        else if(oldthing == null && newitem != null)
        {
          newitem.setPerson(this);
        }
      }
      car = newitem;
      carBeingSet = false;
    }
  }


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Wed Aug 24, 2011 3:41 pm 
Newbie

Joined: Sun Aug 21, 2011 5:24 pm
Posts: 7
I see. I thought about that as well. Two unidirectional one-to-one associations instead of a bidirectional association. It does work.

As you mentioned, the problem with this approach is keeping the foreign keys synchronized (since we have one more foreign key than we need). In your case, it might work. But my setters are really simple, so I can't risk having incoherent records in the database.


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Wed Oct 17, 2012 8:25 am 
Newbie

Joined: Wed Oct 17, 2012 7:40 am
Posts: 3
Th3BlackShadow wrote:
I meant javax.persistence.JoinColumn, like:

Code:
  @Transient boolean carBeingSet = false;
  public void setCar(Car newitem)
  {
    if(!carBeingSet)
    {
      carBeingSet = true;


As a sidenote: This part is broken. There is no guarantee the scheduler won't let some other thread execute after you have gone through the if condition but not yet set the variable to true. If you need to make sure only one thread can run the code at the same time, use synchronized block.


Top
 Profile  
 
 Post subject: Re: No cache hits with bidirectional one-to-one
PostPosted: Thu Feb 07, 2013 6:44 am 
Newbie

Joined: Thu Feb 07, 2013 6:42 am
Posts: 1
I'm having the same problem with bidirectional OneToOne relations. Is there a bug reported for this or has it been solved in the mean time?


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.