Analytics

Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

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:

Sunday, August 23, 2009

One to One shared primary key association in JPA with Hibernate

Overview

One of the less commonly used mappings in JPA and Hiberate is the @OneToOne mapping with a shared primary key. Conceptually this type of mapping is simple to understand. However, recently I tried to implement this relationship between two objects and ran into some petty issues. No clear example was listed on the net, so I decided to post a solution on a blog for possible reference for others.

OneToOne Shared Primary Key

A one to one shared primary key is not the most common mapping pattern, but is still used by many domain object designers. More common is the one to one mapping with a separate foreign key. A one to one shared primary key is simply where two objects have a one to one relationship where they share a primary key.

For example, let's use the prominant Person to Address example.

A Person can have one Address only, and an Address can only have one Person living in it. Let's consider Person the owner of the relationship, meaning its primary key gets generated first and essentially objectwise an Address is "set" in the Person object. A one to one shared primary key relationship would mean that a Person table does not have a FK column to Address, rather the Address table's primary key value is the same as Person, and acts as a foreign key to the Person table.



In contrast a one to one relationship with a foreign key would result in the Person table having a field like ADDRESS_ID, which acts as a foreign key to the Address table. The Person primary key generation is independent of the Address primary key. This means the primary key to Person and Address are not the same generally, but could be by coincidence.


Essentially, one to one with a shared primary key saves a column in the database and usually forces a bidirectional relationship through an ORM such as hibernate.

References and Example

When attempting to implement one to one shared mapping with JPA, I had a little trouble. The official JPA documentation does not give an example with a generated id, but only gives one where the ids are manually set. The Hibernate annotations site does the same. In addition there are a few blog postings which address the issue, but they are muddled and don't specify clearly the implementation details. Thus, I had to experiment and put together knowledge from all over to reach a solution.

In Hiberate Core (without JPA or Annotation), the concept and mapping implementation is explained nicely. 5.1.13 One-to-One.

The most informational resource was the book: Java Persistence with Hibernate - 7.1 Single Valued entity associations, pg278-282. This gave the implemenation with annotations.

In order to use a one to one with a shared primary key, ids on both sides of the object need a @GeneratedValue. The owner can use a generic generator where it gets a fresh number every time, but the other side of the one to one needs a custom hibernate extension, which is from @GenericGenerator. This grabs the primary key from the owner object and places it in the dependent object. In our example, Address will have the hibernate extension @GenericGenerator.

Person class



package com.assarconsulting.addressbook.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "PERSON")
public class Person {

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

@Column(name = "NAME", nullable=false)
private String name;

@Column(name = "AGE", nullable=false)
private int age;

@OneToOne(cascade = CascadeType.ALL, mappedBy = "person")
private Address address;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getStreetNumber() {
return address.getStreetNumber();
}

public String getStreetName() {
return address.getStreetName();
}

public String getCity() {
return address.getCity();
}

public String getState() {
return address.getState();
}

public String getZipCode() {
return address.getZipCode();
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
address.setPerson(this);
}

}



Address Class



package com.assarconsulting.addressbook.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

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

@Id
@GeneratedValue(generator = "person-primarykey")
@Column(name = "PERSON_ID")
private Long id;

@Column(name = "STREET_NUMBER", nullable=false)
private String streetNumber;

@Column(name = "STREET_NAME", nullable=false)
private String streetName;

@Column(name = "CITY", nullable=false)
private String city;

@Column(name = "STATE", nullable=false)
private String state;

@Column(name = "ZIP_CODE", nullable=false)
private String zipCode;

@OneToOne
@PrimaryKeyJoinColumn
private Person person;

public Address() {
}

public Address(String streetNumber, String streetName, String city,
String state, String zipCode) {
super();
this.streetNumber = streetNumber;
this.streetName = streetName;
this.city = city;
this.state = state;
this.zipCode = zipCode;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getStreetNumber() {
return streetNumber;
}

public void setStreetNumber(String streetNumber) {
this.streetNumber = streetNumber;
}

public String getStreetName() {
return streetName;
}

public void setStreetName(String streetName) {
this.streetName = streetName;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getZipCode() {
return zipCode;
}

public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}

public Person getPerson() {
return person;
}

public void setPerson(Person person) {
this.person = person;
}
}

Saturday, August 1, 2009

Why Hibernate does "delete all then re-insert" - its not so strange

Overview

Hibernate allows developers to interact with data from a relational database in an object-oriented fashion. This brings a lot of benefits to maintenance of development. However, hibernate is an abstraction of data which essentially is its function as an ORM, and this usually forces the developer to know hibernate's internals in order to use the tool effectively.

Maintaining collections in the object world is something every application does. Using hibernate you can manipulate colllections by adding and deleting items through the java api, and by way of mapping, hibernate takes care of generating the SQL to augment that data in the database. If the nuances of the mapping is not understood, hibernate may produce what seems like strange results. A particular instance arises when you map a List on the many side of a one to many relationship. When removing an item from the list, hibernate issues a delete statement to delete all elements, and then issue insert statements to insert everything back in except the one removed. This seems ineffecient and odd. Why not just delete the one item? Then if you change the collection type to a Set, only one delete statement is issued by itself. This article tries to explain this behavior and hopefully bring to light hibernate's internals.


Explanation by Example

The scenario described above occurs when mapping a one to many relationship with a association table. The association table stores two attributes, each of which is a foreign key to another table. In addition, the many side object is mapped with a List, but no index column is used. Hibernate recommends when using a list that an index column is used, but why exactly? The example should illuminate why hibernate recommends the index column when mapping a list. The example will show you what happens when using a list without an index column.

Example Setup

Let's set up the example. Let's say I have two classes: A and B. There is a one to many relationship from A to B, where A has many B's in a List. Using hibernate we map B with one to many and leave out the index column.

As instances of these classes, we have an A1 with 3 children and A9 with no children (just for contrasting sake). The figure below illustrates this


In the database, we have three tables. The association table maintains the relationship between A and B. No constraints are in the table for simplicity sake. (We want to focus on hibernate's behavior and worry less about enforcing integrity in the database.) Notice there is not indexed column in AB.


Here is the data in the tables:

Also notice that the list contains two B1's, as a list is capable of containing duplicates. So we code in java to delete an element from A, say the first element in the list which is B1.



a.getBList().remove(0);


So what occurs on hibernate's flush? It first issues an sql to delete all rows in the relationship table where B_ID = 1. Then it inserts all the relationships that are not supposed to be deleted!
This seems strange.
However the reason why this occurs is because hibernate supports the additional feature of a collection that is called a Bag. A bag is a collection which can have duplicates but is unordered. The best feature of a bag is that you can get the number of occurrences of an object through the API with
public int occurrences(Object o) 
With a list, there is no way to do the same without iterating through the whole list.

Thus, when a list is mapped without an indexed collection, hibernate treats in like a bag. In essence, a list which has an indexed column is an "indexed bag.

Since hibernate handles our list as a bag, it believes there could be duplicate entries in the association table. In addition it knows the items in table AB are unordered, so there is no way to tell which row should be deleted from the object in java. We asked earlier, why not just issue one delete statement. If it stopped here we would end up with:


So hibernate needs to continue and re-insert the rows it knows from java that are not supposed to be deleted. In memory, it can just look at the list after remove() was called and insert the remaining relationships. So we end up with what we wanted:



Conclusion

When using a list in hibernate an indexed column is necessary if you want efficient manipulation of adding and deleting items. With an indexed column, when you code a remove(index), hibernate can relate that index to the column in the db and even if there are duplicates, it can distinguish and delete one row. Better yet, if duplicates are not needed in your application, you can use a Set. A set requires no index column and hibernate can distinguish one row because it know a set does not have duplicates.

If you are wondering what is the purpose of a Bag, references below may answer that question.

References

http://www.discursive.com/books/cjcook/reference/collections-sect-bag.html
http://guanyu.info/blog/?p=69
https://forum.hibernate.org/viewtopic.php?t=957657
http://sites.google.com/site/xushengxiaotech/Home/hibernate-tips--efficient-usage-of-bag-for-association