Saturday, April 18, 2009

JPA/Hibernate - One to One Mapping Quirks

Introduction

I have been working on a product where I am currently using Hibernate implementation of JPA (Java Persistence Architecture) as data persistence mechanism. While working, I came across an interesting finding about one-to-one mapping using annotations. In this short article, I would like to explain my findings that may be of help to some novice JPA/Hibernate developers.

Technical Details

Let us take Users.java and Contacts.java as domain examples to explain the scenario. In this case, User object has certain attributes and Contact object also has its own specific attributes. Assume a user can have only one contact, then one of the common ways of expressing one-to-one relationships is to have the dependent object share the same primary key as the controlling object. In UML, it is called "compositional relationships".

User.java

@Entity
@Table (name="Users")
public class Users implements Serializable{

private static final long serialVersionUID = -3174184215665687091L;

/** The cached hash code value for this instance. Setting to 0 triggers re-calculation. */
@Transient
private int hashValue = 0;

/** The composite primary key value. */
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column (name="id")
private Long Id;

/** The value of the simple username property. */
@Column(name = "username", unique=true, nullable=false)
private String username;

/** The value of the contacts association. */
@OneToOne(cascade = {CascadeType.ALL}, fetch=FetchType.LAZY)
@PrimaryKeyJoinColumn
@JoinColumn (name="id", nullable=false)
private Contacts contacts;

.....

public Users(){}

/**
* @return Id
*/
public Long getId(){
return this.Id;
}
/**
* @param id - The Id to set
*/
public void setId(Long id){
this.hashValue = 0;
this.Id = Id;
}
/**
* @return Contacts
*/
public Contacts getContacts(){
return this.contacts;
}
/**
* @param Contacts - The contacts to set
*/
public void setContacts(Contacts contacts){
this.contacts = contacts;
}

....
}

Contacts.java

@Entity
@Table (name="Contacts")
public class Contacts implements Serializable{

private static final long serialVersionUID = -1079313023058953957L;

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

/** The value of the users association. */
@OneToOne
@JoinColumn (name="id")
private Users users;

....

public Contacts (){}

/**
* @return Id
*/
public Long getId(){
return this.Id;
}
/**
* @param id - The Id to set
*/
public void setId(Long id){
this.hashValue = 0;
this.Id = id;
}
/**
* @return Users
*/
public Users getUsers(){
return this.users;
}
/**
* @param users - The users to set
*/
public void setUsers(Users users){
this.users = users;
}

....
}


The above set up will result in an error message that says like the following:

Exception raised - org.hibernate.id.IdentifierGenerationException:
ids for this class must be manually assigned before calling save(): com.vbose.Contacts

The problem is that Hibernate is confused in this case. It thinks that it should look at the Id column to determine the primary id of the table. In reality, we should have told Hibernate that the Users object is the thing that provides the id. In order to help Hibernate understand how to properly resolve the id, we need to annotate the id column of the Contacts with a special "Generator", like so:

The one below is an enhanced Contacts.java domain object:

@Entity
@Table (name="Contacts")
public class Contacts implements Serializable{

private static final long serialVersionUID = -1079313023058953957L;

@Id
@GeneratedValue(generator="foreign")
@GenericGenerator(name="foreign", strategy = "foreign", parameters={
@Parameter(name="property", value="users")
})
@Column (name="id")
private Long Id;

/** The value of the users association. */
@OneToOne
@JoinColumn (name="id")
private Users users;

....

public Contacts (){}

/**
* @return Id
*/
public Long getId(){
return this.Id;
}
/**
* @param id - The Id to set
*/
public void setId(Long id){
this.hashValue = 0;
this.Id = id;
}
/**
* @return Users
*/
public Users getUsers(){
return this.users;
}
/**
* @param users - The users to set
*/
public void setUsers(Users users){
this.users = users;
}

....
}


The additional annotation at the "Id" property says that the generated value of the Id column comes from a special generator that simply reads a foreign key value off of the users object. Please note that contacts object has to be set in Users and vice versa before setting other properties of each objects. For e.g.

Users users = new Users();
Contacts contacts = new Contacts();
users.setContacts(contacts);
contacts.setUsers(users);

Conclusion

The above said quirk is not documented in Hibernate reference document. I hope it will help any novice JPA/Hibernate developers who are facing problems with one-to-one mapping using annotations.