Analytics

Sunday, March 28, 2010

One to One Shared Primary Key is Eagerly Fetched in Hibernate

One to One Shared Primary Key is Eagerly Fetched
 
Every so often Hibernates presents some peculiarity that, at first observance, doesn't make much sense.  However, once you investigate the peculiarity thoroughly, you see why hibernate behaves the way it does. 
 
An instance occurs when mapping a one-to-one relationship with a shared primary key.  In certain instances, even if this mapping is declared to fetch lazily, hibernate will eagerly fetch the association.  The reason is because due to the nature of a shared primary key, hibernate does not know whether to initialize the association to null or not without actually joining against the associated table. 
 
Explore through an Example
 
Let's set up an example to further explain the situation.  Let's say we want to map a one-to-one association with a shared primary key for two basic entities: a Passenger and a AirlineTicket.  A Passenger can only have one Airline ticket, and an Airline ticket can only have one passenger.  For our example, the relationship will be bidirectional from Passenger to AirlineTicket.
 




We can map the objects with JPA and hibernate with the code snippets below. Note that we have marked the association from Passenger to AirlineTicket as LAZY.

....
@Entity
@Table(name = "PASSENGER")
public class Passenger {

    @Id
    @GeneratedValue
    @Column(name = "PASSENGER_ID")
    private Long id;

    @Column(name = "NAME")
    private String name;
    
    @Column(name = "BIRTHDATE")
    private int age;
    
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "passenger")
    private AirlineTicket airlineTicket;
... 
}  


...
@Entity
@Table(name = "AIRLINE_TICKET")
@org.hibernate.annotations.GenericGenerator(name="passenger-primarykey", strategy="foreign",
 parameters={@org.hibernate.annotations.Parameter(name="property", value="passenger")
})
public class AirlineTicket {

    @Id
    @GeneratedValue(generator = "passenger-primarykey")
    @Column(name = "PASSENGER_ID")
    private Long id;
    
    @Column(name = "SEAT_NUMBER")
    private int seatNumber;
    
    @Column(name = "CLASS")
    private String clazz;
    
    @OneToOne
    @PrimaryKeyJoinColumn
    private Passenger passenger;
    
    public AirlineTicket() {
    }
...
}  

However, when we load the entity with a entityManager.find(..), hibernate issues a join in the generated sql to eagerly load the AirlineTicket!  If the Passenger entity was loaded with a ejql statement such as "from Passenger", hibernate will issue two separate sql statements back to back: one to load the passenger, and then another to load the airline ticket (which essentially is an eager fetch.)
 
Why does this peculiarity occur?  When hibernate loads the Passenger object, it has to initialize its attributes.  AirlineTicket is an association which is mapped with a shared primary key.  Therefore, in order to find out whether the AirlineTicket is null or not, hibernate must issue a join to the AIRLINE_TICKET table to check if a row exists with the same primary key as the Passenger object.  If hibernate abstained from issuing a join to AIRLINE_TICKET and proceeded to instantiate a proxy for AirlineTicket, the Passnger object would contain a AirlineTicket, even if there was no real association in the database.
 
Solution
 
In order to take advantage of lazy loading when you have a shared primary key one-to-one relationship, you can use the optional=false setting on the relationship. For example, the AirlineTicket reference in the Passenger object would have optional=false.  This conveys to hibernate that the there will always be an AirlineTicket for a Passenger, thus it can create a proxy and it is guarenteed to have a corresponding object.
 
Design Considerations
 
So what are the situations to use a shared primary key in a one-to-one?  This begs the question of when is it best to use a one-to-one relationship?  One-to-One relationships are modeled in the domain when one object instance has an exclusive relationship with another object instance.  Simple examples would be [Person, Heart], [Husband, Wife], [HeadCoach, NflTeam]. (Of course, you could make silly arguments to debunk this, but you get the drift). 
 
The easiest mapping for one-to-one relationship is to have a foreign kep from the owning entity to the other object.  In our Passenger example, the PASSENGER table would have a foreign key column AIRLINE_ID. This approach has no peculiarities with lazy loading, as hibernate can figure out if the association exists without joining to another table. All it has to do is check the foreign key for null. 
 
A shared primary key for one-to-one mapping is appropriate only if the association is non-optional.  Meaning each end of the association has to exist.  If one side exists, so does the other.  If you map without this symbiotic relationship, you will run into the peculiarities above and possibly have to eager fetch for no reason.  This could lead to performance issues.  Shared primary keys save a column on the database, but the mapping is a little more complicated and peculiarities exist.  Hardware is cheap, knowledge is expensive.  Consider this approach carefully.

Source Code 
one-to-one.zip
 
References:

Monday, March 22, 2010

5 Ways to Think Wisely in Development

Recently I have been reading some popular and interesting social psychology books.  The contents are based on empirical evidence and scientific research, and often provide stories about how society operates, and why people behave the way they do.  Some of the books in this genre include: Freakonomics, The Tipping Point, Outliers and Kluge

The most recent book I read is Kluge: The Haphazard Construction of the Human Mind, by Gary Marcus.  Marcus argues that the human mind is not the elegantly designed organ that we conventionally perceive it as, rather it is a cobbled together contraption which is a product of evolution. He offers explanations on why our minds do clumsy things, such as forget where our car is parked, or why we can't remember what we ate for breakfast.

Without getting into the details, he points out several characteristics of the human brain that are products of evolution. Our cognitive makeup contains several bugs which can be referenced by psychological terms, some of which include: context driven memory, confirmation bias, motivated reasoning, and framing.  (I'll leave the explanation of these terms to the book itself.) He also gives recommendations on how to overcome these mental pitfalls.  It is a fascinating for the laymen psychologist. 

So what does all this have to do with developing software?  From Marcus' exploration of the mind, I see several recommendations that can help us become better software developers.  Many of the technical and social decisions we make as part of a software team are often afflicted by the "kluges" of the mind.  Some basic and common sense tactics can help offset these imperfections, not to mention a help us becomes clearer thinkers, wiser developers, and better teammates. 

1. When possible, consider alternative hypotheses.

Often when we have an idea, we get stuck on it and want to see it through to the finish, just for the satisfaction of feeling good about ourselves. It could be a design pattern we see for a problem, or it could be some performance enhancement we think needs done.  We tend to not evaluate our own ideas in a dispassionate or objective way.  One of the simplest things we can do to improve our capacity to think and come up with good solutions is to consider an alternative track.  Contemplate on the opposite and counter your own initial ideas.  This can go a long way on improving your own initial thoughts or could lead to an entirely better solution.


2. Imagine your decisions will be spot-checked.

Research has shown that people who believe that they will have to justify their answers are less biased than people who don't.  Hold yourself accountable for any decisions you make,  technical or otherwise.   If we do this, we will tend to invest more cognitive effort and make correspondingly better decisions based on analysis, not just feelings or habits.  A good practice would be to write down rationale for any sophisticated decision made and make sure the reasoning is sound.  This could be notes for yourself, or published to the software team in a collaborative tool such as a wiki.


3.  Always weigh benefits against costs.

There is always some feature or tool that is cool to use or enticing to learn.  We should always weigh the benefits versus the costs before we proceed down a certain route.  The feature may be cool to the developers, but how much business value does it provide? Does it help the business save money?  The new ORM tool looks great and has some added benefits, but what cost will it incur versus the technical savings? 

The inverse argument should be considered just as much.  A refactoring may incur some cost to implement upfront, but will payoff in the long run by resulting in more maintainable and defect free code.  The new integration testing tool may require a week of investment, but could reap dividends by allowing the team to write automated tests and eliminate the painstaking manual and repeated tests.  Sometimes the initial pain is worth enduring to get a long term benefit.

4.  Whenever possible, don't make important decisions when you are tired or have other things on your mind.

Marcus describes how we have two portions of the brain: the reflexive and the deliberative. The reflexive portion of the brain evolved early on and controls our bodily motions. It also controls ours emotions and fight or flight responses.  The deliberative portion of the brain is the most recently evolved and controls rational thinking and logic.

When making decisions related to software, make sure you are well rested and not stressed.  Get enough sleep and keep your hunger under control.  When your health is not optimal, your reflexive portion of the brain activates and overrides the rational part.  This inhibits rational thinking and can especially inhibit complicated problem solving.  In order to make the best technical and team decisions, keep in a rested state to leverage the rational part of the mind.

5. Distance yourself.

Our mind is set up to ponder the near and defer future decisions for a later time.  It's always about the now and the urgency of the present.  The release needs to be done immediately, and we get into fire fighter  mode where everything is an emergency. Or...The debate is on about the new design and we must engage in the battle and win the argument!

It's always best to take a step back and distance yourself from the situation. Imagine you are watching from afar and try to judge the situation objectively.  Of course, the here and now is always important, but it's also important to balance situation by distancing yourself.  Doing so will help assess the situation fairly.  It can also let the reflexive portion of the brain simmer down and let the deliberative mind step in to take control when needed.

Even though we are rational people in a technical field, we are human after all.  We are products of our ancestors and emotions and rationality are both part of our makeup, although not always in the correct proportion. However, simple mindful steps can be taken to offset any shortcomings we have.