Solved

Hibernate Session Level Cache

Posted on 2011-02-28
30
273 Views
Last Modified: 2013-11-13
Hi Experts

I have a Hibernate question, first I need to give a little background:

I have a Location table that holds location information like city, state, zip etc.  The data in this table is referenced by lots of other tables in the DB and some tables will have more than one reference to the Location table.

As an example let’s assume there is a PersonInfo table that has two references to the Location table one for the city that person was born in and one for the city that person currently lives in.

I have corresponding Java objects for each table:

@Entity
@Table(name = "LOCATION")
public class Location{
	Long id;	

	
	@Id
	@Column(name = " ID", unique = true, nullable = false, insertable = false, updatable = false)
	public Long getId() {
		return this.id;
	}
}

@Entity
@Table(name = "PERSON_INFO")
public class PersonInfo{
	Location cityOfBirth;
	Location cityOfResidence;


	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "CITY_OF_BIRTH")
	public Location getCityOfBirth() {
		return this.cityOfBirth;
	}

	@ManyToOne(fetch = FetchType.EAGER)
	@JoinColumn(name = "CITY_OF_RESIDENCE")
	public Location getCityOfResidence () {
		return this.cityOfResidence;
	}

}

Open in new window



In the web application I’m writing there is a page that allows a user to enter/update their personal info.  This page contains a single form that is backed by a PersonInfo object.  When a user navigates to the page the system will look up that users PersonInfo object from the DB (Using Hibernate and an hql similar to
from PersonalInfo pi where pi.user = ?

Open in new window

) and uses it as the form backing bean for the form.   The web page allows the user to enter the birth and residency cities by selecting from a predefined list of cities and it passes the id of the selected city back to the server when the user posts the form.  

The place where I’m having an issue is when the user selects the same city for both their birth and residency location, then saves the data and then tries to change one of the locations.  What is happening is that since the birth and residencies properties refer to the same location Hibernate is only creating a single instance of the Location object (pulling from Session Level cache).  So the cityOfBirth and the cityOfResidence properties are pointing to the same object.  If the user then tries to update one of the locations the single instance is updated effectively changing both the cityOfBirth and cityOfResidency properties.

I’m not sure how I can get around this other than doing some kind of check after I load the PersonInfo object and making sure that each Location property is a separate instance. I know you can’t disable Hibernates Session Level cache and I can’t seem to find away around this with out some code that seems kludgy to me.  Does anyone have any ideas?

Thanks
David
0
Comment
Question by:NHBFighter
  • 15
  • 13
30 Comments
 
LVL 92

Expert Comment

by:objects
ID: 35001116
you should not be changing the location referenced by the Location object and instead be changing the Location object refenced by PersonInfo (eg. person.setCityOfBirth())
Location should really be immutable.

0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35001317
I understand what your saying, but that doesn't really work out for me.
My form looks something like this (Using spring form tag)
<form:hidden path="info.cityOfBirth.id"/>
<form:input path="info.cityOfBirth.cityName" id="info.cityOfBirth.cityName"/>
<form:input path="info.cityOfBirth.stateName" id="info.cityOfBirth.stateName"/>
...
<form:hidden path="info.cityOfResidence.id"/>
<form:input path="info.cityOfResidence.cityName" id="info.cityOfResidence.cityName"/>
<form:input path="info.cityOfResidence.stateName" id="info.cityOfResidence.stateName"/>

Open in new window


Then I have a look up function that allows the user to search for a city which automatically populates the various form elements.

Both of the location properties on the PersonInfo object are set to not cascade so regardless of any change I make to the location object the location record in the DB never is affected.
When I change the ID of the cityOfBirth location object and save the PersonInfo object Hibernate will update the PERSON_INFO row setting the CITY_OF_BIRTH column to the new value.  And this works fine except for the situation I mentioned in my question.

I'm using spring binding and as far as I know there would be no easy way for me to change the way it works to make it change the object the PersonInfo is referencing.

Thanks for the comment!

Any more suggestions are really appreciated.
Dave

0
 
LVL 92

Expert Comment

by:objects
ID: 35001452
the way you are using it is not a ManyToOne relationship as every PersonInfo needs its own Location instance, they cannot be shared (or you'll get the problems you are having)
you don't really need to have a separate Location table/class at all.
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35001575
I don't fully understand what you mean by saying its not a ManyToOne relationship.  From a database perspective the relationship seems to be many to one.  One location row can be referenced by lots of PersonalInfo rows.  IE 20 PersonalInfo records can have their cityOfBirth column point to a single row in the Location table, meaning all of them were born in the same city.
0
 
LVL 92

Expert Comment

by:objects
ID: 35001654
>  One location row can be referenced by lots of PersonalInfo rows.

It can, but thats not how you want it to work. Because you allow any PersonInfo to change the details of the associated Location, which changes it for any other PersonInfo instance that is using that location. So its currently working as designed.
0
 
LVL 92

Expert Comment

by:objects
ID: 35001746
>  The web page allows the user to enter the birth and residency cities by selecting from a predefined list of cities and

you mention here they select from a list but in your later comment your using text fields to input location details, which one is it?
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35001783
It can and it does, the current set up allows it to work as many to one, the problem is how I want to update the reference, since I'm using spring binding it not easy to change the reference without the 'kludgy' code I mentioned.  

The way to make it work is to do something like this when I'm binding my parameters:
Location l = new Location();
l.setId(newID);
personalInfo.setCityOfBirth(l);

Open in new window


but with the binding framework I'm using I can't do that.  The only solution I have now is a recursive method that will get all Location objects in an Objects object graph and for any two locations that are the same instance I create a new instance and copy all the properties over.  I do this right after the object is loaded prior to binding.  This solves the problem but it still seems kludgy to me.  

Are the any idea for an alternative solution?
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35001821
The user enters text into the city text box, clicks a button that gives them a list of cities to select from.  When the user selects a city the inputs get populated with the information for the city they selected. (All the the elements in the form except the city name are read only).  So really the only property I care about that gets passed back to the server side is the id of the city.
0
 
LVL 92

Expert Comment

by:objects
ID: 35001830
> but with the binding framework I'm using I can't do that.

you can, by using a select for the two location properties
0
 
LVL 92

Expert Comment

by:objects
ID: 35001847
> The user enters text into the city text box

whats the purpose of that?
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35001877
The user types in a name of a city and the list shows them potential matches. For instance the user types in paris they could get a list like:
Paris       in France
Paris      , Arkansas in the US
Paris      , Idaho in the US.

What do you mean by saying I can use a select for the two location properties?  
0
 
LVL 92

Expert Comment

by:objects
ID: 35001910
> The user types in a name of a city and the list shows them potential matches.

Then that input should not be mapped to your entity

> What do you mean by saying I can use a select for the two location properties?  

you provide a drop down list of available Locations for the user to pick from
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35002145
The only input that needs to get mapped to the Location entity is the ID.  Basically the user is selecting a city from a list and the form is passing back the id of that city, lets call it cityID, then the code that is getting executed is something like this:

PersonalInfo info = (PersonalInfo)hibernateSession.get(PersonalInfo.class,2);
info.getCityOfBirth().setID(cityID);//This is the code that could potentally update the cityOfBirth and the cityOfResidence
hibernateSession.update(info);

Open in new window


Obviously lots of steps are taking out of this code, for example the query is run in one request and the update is done in another and the setting of the city id is done by the spring binding framework, but the idea is the same. After the update the record in the PERSON_INFO table with the id of 2 will will now have its CITY_OF_BIRTH column set to the id of the city the user selected.  

If the CITY_OF_BIRTH and CITY_OF_RESIDENCE columns for that PERSONAL_INFO object refer to the same city then after line 1 in the above code info.getCityOfBirth()==info.getCityOfResidence() will be true. I get this, after it looks up the first location that data is stored in the session level cache which is where it gets the location for the second property.  It makes sense that Hibernate works this way, the == being true is causing my problem and that is what I'm looking for a workaround for. I don't think that providing a drop down list of available Locations for the user to pick from will solve my problem because that is what I'm doing (essentially).  

The only thing I think i can do is make sure that before the user's parameters are bound to the PersonalInfo object the two locations aren't referencing the same instance.  If they are I copy the properties of that instance to a new Instance of a Location object and set it on the PersonalInfo object.  I was hoping someone would know another way.
0
 
LVL 92

Accepted Solution

by:
objects earned 500 total points
ID: 35002206
> The only input that needs to get mapped to the Location entity is the ID.

your mapping the location details here

<form:input path="info.cityOfBirth.cityName" id="info.cityOfBirth.cityName"/>
<form:input path="info.cityOfBirth.stateName" id="info.cityOfBirth.stateName"/>

> info.getCityOfBirth().setID(cityID);//This is the code that could potentally update the cityOfBirth and the cityOfResidence

why are you changing the id of the city?

What you are trying to do is pretty standard, in the simplest case a select would handle it
theres an example here http://www.mkyong.com/spring-mvc/spring-mvc-dropdown-box-example/
0
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 92

Expert Comment

by:objects
ID: 35002360
What can't the two locations reference the same instance? If place of birth and residence are the same then they should reference the same instance, othwrwise you'll have two locations in the database representing the same place.
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35002639
I don’t think I’m doing a good job describing my problem, I’ll try to make it clearer.

When the user navigates to the page the system will retrieve the PersonalInfo object from the database, store it in the HttpSession and use it to build the form.  (The form displays all the data about the cities already selected and that is why those input fields like cityName and stateName are there.  But those fields are not important for this discussion they are only there to display information to the user, they might as well be simple text as far as we are concerned but requirements say they should be text boxes).

The user can then update the information in the form and post the changes.  When the post is made the code will pull the PersonalInfo object from the session and bind the users new data to it.  Then persist it back to the DB.

To answer the question of “why are you changing the id of the city?” the reason is because when the user selects a new city the id of that city is passed back to the server, and the id gets changed by spring binding because of this tag:
<form:hidden path="info.cityOfResidence.id"/>

This would still be the case even if I had a simple select box like:
<form:select path="info.cityOfResidence.id">
   <form:option value="1">Some City Name</form:option>
   <form:option value="2">Some Other City Name</form:option>
   ...
</form:select>

Open in new window


Where the values of the options are the ids of the cities.

This is the behavior I want, because by changing the id of the Location object I’m effectively changing the city that Location object refers to.

So looking at this java
PersonalInfo info = (PersonalInfo)hibernateSession.get(PersonalInfo.class,2);//assume there is already a record in the database with an id of 2
info.getCityOfBirth().setID(cityID);//assume there was already an associated city
hibernateSession.update(info);

Open in new window


I expect this SQL to happen:
update PERSONAL_INFO set CITY_OF_BIRTH=:cityId where id=2;

Open in new window


And it does just like I expect.
But when the cityOfBirth and the cityOfResidency are the same the actual update is more like this:
update PERSONAL_INFO set CITY_OF_BIRTH=:cityId CITY_OF_RESIDENCE=:cityId where id=2;

Open in new window


The problem is that if a user previously entered data such that the cityOfBirth and cityOfResidency were pointing to the same city and then tried to change one of those cities both cities would get updated.  Unless I do what I mentioned before and make sure that before the user's parameters are bound to the PersonalInfo object the two locations aren't referencing the same instance.  If they are I copy the properties of that instance to a new Instance of a Location object and set it on the PersonalInfo object.

It’s quite possible to have two separate location instances that refer to the same record in the data base.  Unless you are using second level caching running the hql query “from Location where id=2” from two different sessions will return 2 separate Location instances.  And in that case locInstance1 == locInstance2 will be false but locInstance1.equals(locInstance2) should be true.



0
 
LVL 92

Assisted Solution

by:objects
objects earned 500 total points
ID: 35003209
>  the reason is because when the user selects a new city the id of that city is passed back to the server,

selects it from where?

> and the id gets changed by spring binding because of this tag:
> <form:hidden path="info.cityOfResidence.id"/>

how is the value of a hidden field getting changed?

> This would still be the case even if I had a simple select box like:

The binding actually changes the location specified in the person. It does *not* change the id of the location entity.
You should never go changing the id of entities that are managed by hibernate.

> info.getCityOfBirth().setID(cityID);//assume there was already an associated city

If thats the hibernate id then you should not be doing that. To change the city you would use:

info.setCityOfBirth(newcity);

0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35003875
It doesn't really matter how the value is getting changed, its changed via javascript on the front end but like I said whether its javascript setting a hidden field or a select box passing the id is irrelevant.   The point is that when the request parameters get bound to the PersonInfo object the info.getCityOfBirth().setID(cityID) call is made and this causes problems if both the cityOfBirth and cityOfResidence refer to the same instance.

The binding changes the ID of the Location entity object that is referenced by the PersonInfo object which is what I want, this process updates the PERSON_INFO table like I want it to. (IE the person info row now references a different location row.)  I'm not using second level caching and I have a new Session object for each request so updating the Location instance's id is only a problem in the situation we've been discussing.   I mentioned before that I know that one way to do this would be to have a new Location instance set on the PersonInfo instance but the binding framework makes that difficult to do during binding.  I also mentioned how I implemented that type of solution but was looking for an alternative.  So rather than having code that does this kind of think prior to binding:
if(info.getCityOfBirth()==info.getCityOfResidence()){
    Location l = new Location();
    l.setId(info.getCityOfResidence().getID());
    info.setCityOfBirth(l);
}

Open in new window


I was looking for an alternative.
0
 
LVL 92

Expert Comment

by:objects
ID: 35003933
It does matter, and by the looks it is the cause of the problem.
You should rarely if ever changed the id of an entity, from memory the JPA spec specifies that its not permitted.

What you do is a very standard thing to do and the example I posted above shows all that is needed.
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35006896
How the request parameter is generated does not matter with this problem.  A request parameter with a name like "info.cityOfBirth.id" has a value of the city id that the user selected.  There is a predefined set of cities and corresponding ids (all stored in the Location table, which is never updated by the front end), I need the user to select a city and then that's citiy's ID needs to be passed back to the server. Using a drop down box like you selected isn't an option that gets me anywhere different from what I have now. I have the id of a location object getting passed to the server from the front end and I don't see how the presentation is pertinent in this case.

The technically correct way would be to get the id of the city selected by the user, do a query to look up the Location object referenced by that id and set that location to on the info object.  But that would mean I couldn't use out of the box spring binding.

Thanks for the comments, it looks like I'll have to go with the solution I was hoping to find an alternative for.
0
 
LVL 92

Expert Comment

by:objects
ID: 35012294
>  I have the id of a location object getting passed to the server from the front end and I don't see how the presentation is pertinent in this case.

Its nothing to do with the presentation, its how the update on the server is handled that is the problem.
You set the location for the person, you don't change the id of the location referenced by the person.
id's are there for hibernate and for you to do lookups. not much else you should need to do with them.
though if you're front end is wired up as in the example above then the update is handled for you by spring.
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35012472
>Its nothing to do with the presentation, its how the update on the server is handled that is the problem.
I agree 100% that way I say it doesn't really matter how the value is getting changed on the front end.

>if you're front end is wired up as in the example above then the update is handled for you by spring.

I don't follow this,  Spring doesn't create a new Location object for me it updates the one that is referenced by the PersonInfo object.  
The example in the link above creates a select box that populates a simple String property of the command object. (Customer in the example) this is not at all what I want.  The property I want to populate is a complex Location object not a string like in the example.

One alternative solution I found was to change the way the ID is getting passed back an to change the binding a bit. In stead of having the binding done on the id of the Location object I have the binding done to the location object itself and I register a custom editor for Location objects.
So instead of something like:

<!--Here the city id is selected from a select box-->
<form:select path="info.cityOfResidence.id">
   <form:option value="1">Some City Name</form:option>
   <form:option value="2">Some Other City Name</form:option>
   ...
</form:select>
or 
<!--Here the city id gets populated via javascript-->
<form:hidden path="info.cityOfResidence.id"/> 

Open in new window


I use something like:
<!--Here the city id is selected from a select box-->
<form:select path="info.cityOfResidence">
   <form:option value="1">Some City Name</form:option>
   <form:option value="2">Some Other City Name</form:option>
   ...
</form:select>
or 
<!--Here the city id gets populated via javascript-->
<form:hidden path="info.cityOfResidence"/>

Open in new window

So instead of a request parameter named info.cityOfResidence.id its now comming across as info.cityOfResidence.  Which references the location property not the id of the location.

Then inside my controller class I register a custom editor for Location objects:
binder.registerCustomEditor(Location.class, new PropertyEditorSupport() {	
	@Override
	public void setAsText(String text) throws IllegalArgumentException {
		if(StringUtils.hasText(text))
			setValue(findLocationById(new Long(text)));
		else
			setValue(null);
	}
				
	@Override
	public String getAsText() {
		Location l = (Location)getValue();
		if(l==null)
			return "";
		return String.valueOf(l.getPK());
	}						
});

Open in new window


This seems like the best solution to me.
0
 
LVL 92

Expert Comment

by:objects
ID: 35012562
works the same whether its a string or an object
in your case the mapping would be something like

<form:select path="country">
    <form:options items="${countries}" itemLabel="name" itemValue="id"/>
</form:select>
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35012665
Thats not quite right, spring would have to know how to convert the String passed from the front end into the Location object.  The select box that gets generated would be something like this:

<select name="country">
   <option value="1">Some City Name</option>
   <option value="2">Some Other City Name</option>
   ...
</select>

Open in new window


So it would pass back a request parameter named 'country'. If the form backing object had a property named country whose type was Location Spring would throw and error because it wouldn't know how to convert the string that it gets into a Location object. The error that gets returned would be something like this

Failed to convert property value of type 'java.lang.String' to required type 'Location' for property 'country'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [Location] for property 'country': no matching editors or conversion strategy found

So with out adding in a custom editor to the data binder the bind would not work

0
 
LVL 92

Expert Comment

by:objects
ID: 35012705
> spring would have to know how to convert the String passed from the front end into the Location object.

no it passes back the id, the string is only for display

> So with out adding in a custom editor to the data binder the bind would not work

I've used similiar many many times :)
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35013551
>no it passes back the id, the string is only for display
I'm sure your aware that all request parameters the browser passes back are strings.  If in the example above I select the first option in the select box (The one with a name of "Some City Name") then post the form back to the server; calling request.getParameter("country") on the server side would return the string "1".   Spring can not bind a string value to a property of type Location (or any other Object besides a String) with out a PropertyEditor.   See the following links for an example and explination:
Registering PropertyEditors’s with spring for custom objects
Spring - binding to an object rather than a String or primitive
Data binding in Spring MVC


>I've used similiar many many times :)
What kind of objects have you bound form elements to that didn't require a property editor?

I'm really sure about this, I just went through the exercise of configuring a property editor for my Location objects, and I know the error message like I posted does occur if you don't have a property editor.

I do appreciate all you comments, I know it takes time out of you day and its all to help a person you don't know.

Dave
0
 
LVL 92

Expert Comment

by:objects
ID: 35013740
sorry, yes you do need a property editor (been a while since i used 2.x) . But all it needs to do is lookup the location in the database


    @Override
    public void setAsText(String id) throws IllegalArgumentException {
        setValue(locationDao.findById(Integer.parseInt(id)));
    }
 
    @Override
    public String getAsText() {
        Location s = (Location) getValue();
        if (s == null) {
            return null;
        } else {
            return s.getId().toString();
        }
    }
0
 
LVL 4

Author Comment

by:NHBFighter
ID: 35014255
Yeah, the property editor I ended up using does do a look up from the DB. Thats what the findLocationById(new Long(text)) does.   I'm using Spring 3.0.3 and it still needs property editors.

Thanks again
0
 
LVL 26

Expert Comment

by:mrcoffee365
ID: 39422757
This question has been classified as abandoned and is closed as part of the Cleanup Program. See the recommendation for more details.
0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

Introduction This article is the last of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article covers our test design approach and then goes through a simple test case example, how …
In this post we will learn how to connect and configure Android Device (Smartphone etc.) with Android Studio. After that we will run a simple Hello World Program.
Viewers will learn one way to get user input in Java. Introduce the Scanner object: Declare the variable that stores the user input: An example prompting the user for input: Methods you need to invoke in order to properly get  user input:
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.

760 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now