Scriptico

Hibernate One-to-One Bi-Directional Mapping

In the following article, I would like to show an approach how to map one-to-one bi-directionall two entities in Hibernate.
First of all, let’s take a look at the relation between two tables in a database:

tables

As you can see, there is the one-to-one relation between user and user_profile tables and the user_profile table has a primary key that in fact, it is a foreign key to the id property of the user table. In other words, there is a simple use-case: one user record can have only one related user_profile record.

What the mapping looks like?

There are two classes. The first, parent, class is User:

package com.scriptico.domain.entities;

import java.io.Serializable;
import java.util.Set;

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

import org.hibernate.annotations.GenericGenerator;

@Entity
@Table(name = "user")
public final class User implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(generator = "system-uuid")
	@GenericGenerator(name = "system-uuid", strategy = "uuid")
	@Column(name = "id", unique = true)
	private String id;

	@OneToOne(mappedBy = "user")
	private UserProfile profile;

	public String getId() {
		return id;
	}

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

	public UserProfile getProfile() {
		return profile;
	}

	public void setProfile(UserProfile profile) {
		this.profile = profile;
	}
}

The second, child, class is UserProfile:

package com.scriptico.domain.entities;

import java.io.Serializable;

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;

import org.hibernate.annotations.GenericGenerator;

@Entity
@Table(name = "user_profile")
public final class UserProfile implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(generator = "foreign")
        @GenericGenerator(
            name = "foreign",
            strategy = "foreign",
            parameters = {@org.hibernate.annotations.Parameter(name = "property", value = "user")})
	@Column(name = "user_id")
	private String userId;

	@Column(name = "first_name", nullable = true, length = 50)
	private String firstName;

	@Column(name = "last_name", nullable = true, length = 50)
	private String lastName;

	@Column(name = "display_name", nullable = true, length = 50)
	private String displayName;

	@OneToOne
	@PrimaryKeyJoinColumn(name = "user_id", referencedColumnName = "id")
	private User user;
	

	public String getUserId() {
		return userId;
	}

	public void setUserId(String userId) {
		this.userId = userId;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
}

Now few words about the code above. The parent class has a mapped UserProfile child class and as you can see, I use the following annotation:

@OneToOne(mappedBy = "user")

the mappedBy property defines the field in the child class that owns the relationship and in our case, it is the user field in the UserProfile class.

Now, a few cents about the UserProfile class. First of all, I did a trick to define the primary key. The UserProfile class must have an identifier and as a result, the userId property is marked by @Id annotation. If the class has no identifier, Hibernate will throw the following exception:

org.hibernate.AnnotationException: No identifier specified for entity: com.scriptico.domain.entities.UserProfile

Now, the identifier property should to be marked by the @GeneratedValue annotation. The @GeneratedValue annotation provides for the specification of generation strategies for the value of the primary key. Although you may not specify the @GeneratedValue annotation for the identifier, the following attempt to save the User entity:

private void create() {
       User user = new User();

       UserProfile profile = new UserProfile();
       user.setProfile(profile);
       profile.setUser(user);

       userDao.createUser(user);
}

will throw an exception:

org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save(): com.scriptico.domain.entities.UserProfile

Now, if you just mark your identifier by the @GeneratedValue annotation and try to run the following method

private void create() {
       User user = new User();

       UserProfile profile = new UserProfile();
       profile.setUserId(user.getId()); // an attempt to initialize the identifier
       user.setProfile(profile);
       profile.setUser(user);

       userDao.createUser(user);
}

Hibernate will throw the following exception:

org.hibernate.AssertionFailure: null id in com.scriptico.domain.entities.UserProfile entry (don’t flush the Session after an exception occurs)

So, I defined my own generator and specified the strategy for the @GeneratedValue annotation as shown below:

@GeneratedValue(generator = "foreign")
@GenericGenerator(
     name = "foreign",
     strategy = "foreign",
     parameters = {@org.hibernate.annotations.Parameter(name = "property", value = "user")})

The user property is marked by @OneToOne and @PrimaryKeyJoinColumn annotations. The @OneToOne annotation defines a single-valued association to another entity that has one-to-one multiplicity. The @PrimaryKeyJoinColumn annotation specifies a primary key column that is used as a foreign key to join to another table.

That is it. Now, tables are mapped and Hibernate saves records perfectly!

I do really appreciate your comments and if you see any problems with the approach, please let me know.

Resources

Category: Hibernate, Java

Tagged:

3 Responses

  1. Foster says:

    Thanks for sharing this tutorial.

    I managed to do my mapping as directed, but I wonder if it is possible to do the same using a Long as primary key instead a String.

  2. […] few months ago, I wrote an article how to map the one-to-one bi-directional relations between two entities and now is the time for a simple example how to map many-to-many relation. You […]

Leave a Reply

*