-->
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.  [ 6 posts ] 
Author Message
 Post subject: Second-level caching for optional one-to-one association?
PostPosted: Wed Jan 02, 2008 4:19 pm 
Newbie

Joined: Wed Jan 02, 2008 3:54 pm
Posts: 2
Location: California
If a data model includes one-to-one relationships that are optional/nullable, what's the best way to represent those so that the second-level cache will do the right thing?

For example, a Car may have zero or one Stereo, but a Stereo will always belong to a Car:

Code:
  @Entity
  public class Car {
    ...
    @OneToOne(optional = true)
    @PrimaryKeyJoinColumn
    private Stereo stereo;
    ...
  }

  @Entity
  public class Stereo {
    ...
    @OneToOne(optional = false, mappedBy = "stereo")
    @PrimaryKeyJoinColumn
    private Car car;
    ...
  }


I've tried a lot of variations on this, and I've found that the second level cache (EHCache) handles this association right when the owner (Car) and the owned (Stereo) both exist. If the Car has no Stereo, however, I see a query for the missing Stereo every time I pull it out of the cache. I.e. it doesn't seem like the cache can store a null to say "this Car has no Stereo, so don't bother asking about it ..."

The caching works right for one-to-one relationships where optional=false.

The only way I've found to get the right behavior for this case is to model the relationship as a one-to-many where my business logic just maintains it as a one-to-one. When I do this, I can set up the cache for the entities and the collection, and I don't see extra queries for the missing child. This works ok, but feels like a hack. Is there a cleaner way to model this?[/code]


Top
 Profile  
 
 Post subject: Re: Second-level caching for optional one-to-one association
PostPosted: Wed Jan 02, 2008 6:08 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
engberg wrote:
The only way I've found to get the right behavior for this case is to model the relationship as a one-to-many where my business logic just maintains it as a one-to-one. When I do this, I can set up the cache for the entities and the collection, and I don't see extra queries for the missing child. This works ok, but feels like a hack. Is there a cleaner way to model this?



The problem here is that the relation is defined on primary keys so even though a Car instance is in cache HB has no way to understand if the car has a stereo. You will not have this problem if the relation is mapped with a foreign key. In addition, you will have to dictate select fetching or, as per my understanding, hibernate ignores your cache settings. On top of that, you should advice hibernate to cache the relationship as well as the entities. All I said can be put in the following example:


Code:
package test.model.data.jpa;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity
@Table(name = "CAR")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Car
{
    @Id @GeneratedValue
    @Column(name = "Id")
    public Long id;

    @Column(name = "Name")
    public String name;

    @OneToOne(optional = true)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "CarStereo")
    @Fetch(value = FetchMode.SELECT)
    @JoinColumn(name="STEREO_ID")
    public Stereo stereo;
}


and

Code:
package test.model.data.jpa;

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

import javax.persistence.*;
import java.util.List;
import java.util.ArrayList;

@Entity
@Table(name = "STEREO")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Stereo
{
    @Id @GeneratedValue
    @Column(name = "Id")
    public Long id;

    @Column(name = "Name")
    public String name;

    @OneToOne(mappedBy = "stereo")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    @Fetch(value = FetchMode.SELECT)
    public Car car;
}


I hope this helps.

Farzad-


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 02, 2008 6:27 pm 
Newbie

Joined: Wed Jan 02, 2008 3:54 pm
Posts: 2
Location: California
Thanks for the help, this is very useful.

So this approach requires that the "owner" table (Car) be modified so that it has a foreign key column that points to the child (CarStereo). Hibernate knows that a Car has no CarStereo because its FK column is null.

Is there any way to get the same behavior without adding a FK column? If Cars were always retrieved using an outer join against CarStereo, Hibernate would know immediately when a Car has no CarStereo. It would be nice if it could store this boolean information on the Car in a way that could be cached. I.e. if the serialized representation in EHCache could have a flag that says "this association is null".

Thanks again


Top
 Profile  
 
 Post subject:
PostPosted: Wed Jan 02, 2008 6:33 pm 
Expert
Expert

Joined: Wed Apr 11, 2007 11:39 am
Posts: 735
Location: Montreal, QC
engberg wrote:
Thanks for the help, this is very useful.

So this approach requires that the "owner" table (Car) be modified so that it has a foreign key column that points to the child (CarStereo). Hibernate knows that a Car has no CarStereo because its FK column is null.

Is there any way to get the same behavior without adding a FK column? If Cars were always retrieved using an outer join against CarStereo, Hibernate would know immediately when a Car has no CarStereo. It would be nice if it could store this boolean information on the Car in a way that could be cached. I.e. if the serialized representation in EHCache could have a flag that says "this association is null".

Thanks again


I would like to see this too as caching comes as a mystery to me each time but as far as I know hibernate does not serialize objects into cache but copies the values and where possible only ids. So, in the case of this example it copies a hypothetical car object as [1, "A Car", 1], the last 1 being the id of a Stereo object in my example. Having used the primary key hibernate does not store the last 1 (only my guess, I actually like to verify this). However, your point is still valid here and to the best of my knowledge selects that don't return a result are a little bit costly and can cause lock escalations. So there is definitely an interest to solve this. I will probably examine the issue in more details and open an issue if there is a room for improvement.


Farzad-


Top
 Profile  
 
 Post subject: Re: Second-level caching for optional one-to-one association?
PostPosted: Sun Jan 23, 2011 6:03 am 
Newbie

Joined: Tue Dec 06, 2005 10:13 am
Posts: 17
Hi,
I know this is not very related to this post, but I am having a problem using OneToOne relation with a code very similar to the one posted here.
I switched to Hibernate 3.6 and ever since I started getting this exception:

Caused by: org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.todacell.ui.model.bo.campaign.TargetDays#756]
at org.hibernate.impl.SessionFactoryImpl$2.handleEntityNotFound(SessionFactoryImpl.java:433)
at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:233)
at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
at org.hibernate.impl.SessionImpl.internalLoad(SessionImpl.java:1038)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:630)
at org.hibernate.type.EntityType.resolve(EntityType.java:438)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:139)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
at org.hibernate.loader.Loader.doQuery(Loader.java:857)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
at org.hibernate.loader.Loader.loadEntity(Loader.java:2037)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:86)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:76)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3268)
at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:496)
at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:477)
at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:285)
at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
at org.hibernate.impl.SessionImpl.get(SessionImpl.java:1005)
at org.hibernate.impl.SessionImpl.get(SessionImpl.java:998)
at com.todacell.ui.model.dao.BaseDAO.getObject(BaseDAO.java:62)
... 69 more

Does anyone has a clue why Hibernate 3.6 gives this exception even though I am using "optional=true"?
Is there a workaround for this problem?


Top
 Profile  
 
 Post subject: Re: Second-level caching for optional one-to-one association?
PostPosted: Thu Aug 04, 2011 10:53 am 
Newbie

Joined: Tue Jan 20, 2009 3:37 pm
Posts: 1
@bashan: Were you able to solve this problem. I have many-one mapping and this worked fine until I switch to Hibernate-3.6.
Following is the exception while accessing the object:
Code:
org.hibernate.ObjectNotFoundException: No row with the given identifier exists:[dependent.type#12345678]
   at org.hibernate.impl.SessionFactoryImpl$2.handleEntityNotFound(SessionFactoryImpl.java:435)
   at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:233)
   at org.hibernate.event.def.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:269)
   at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:152)
   at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
.......


But in the database there is a record for corresponding ID#12345678. Also, when I change mapping record to a different Id, I am able to access the object without any exceptions.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 6 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.