Tuesday, April 14, 2009

Hibernate Bi-Directional One to One mapping using Annotations

It took me more than few minutes to find how to do one to one mapping using Hibernate Annotations, so I though I will share it.

I think Hibernate Annotations is best explained by example. Actually, I am not sure I have ever read anything about Hibernate Annotation mappings beside looking at examples…

This example is built from 2 classes: Person and PersonDetails, corresponding to 2 database tables: person and person_details. The 2 classes will demonstrate the usage of one to one mapping from both directions.

I am not sure this example is so close to the real world, but I believe it is enough to get the idea.

The person table contains the following columns:

  • person_id – used as primary key with auto increment
  • first_name
  • last_name
  • birth_date

The person_details table contains the following columns:

  • person_id – used as primary key and foreign key to person table (person_id column)
  • weight
  • height
  • hair_color
  • eyes_color

Note that there may be persons with no person details. That means that having a row in person table, but no row in person_details table is a trivial case. Of course that there may be at the most one person details row for each person.

This is how the Person class looks like using Hibernate Annotations:

package com.bashan.hibernate;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotNull;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
@Entity
@Table(name="person")
public class Person implements Serializable {
  @Id
  @GeneratedValue(strategy= GenerationType.AUTO)
  @Column(name="person_id")
  private Integer personId;
  @Column(name="first_name")
  @NotNull
  @Length(max = 50)
  private String firstName;
  @Column(name="last_name")
  @NotNull
  @Length(max = 50)
  private String lastName;
  @Column(name="birth_date")
  private Date birthDate;
  @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person")
  @JoinColumn(name="person_id")
  private PersonDetails personDetails;
  public Integer getPersonId() {
    return personId;
  }
  public void setPersonId(Integer personId) {
    this.personId = personId;
  }
  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;
  }
  public Date getBirthDate() {
    return birthDate;
  }
  public void setBirthDate(Date birthDate) {
    this.birthDate = birthDate;
  }
  public PersonDetails getPersonDetails() {
    return personDetails;
  }
  public void setPersonDetails(PersonDetails personDetails) {
    this.personDetails = personDetails;
  }
}

And the PersonDetails class:
package com.bashan.hibernate;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import javax.persistence.*;
@Entity
@Table(name="person_details")
public class PersonDetails {
  @Id
  @GeneratedValue(generator="foreign")
    @GenericGenerator(name="foreign", strategy = "foreign", parameters={
      @Parameter(name="property", value="person")
    })
  @Column(name="person_id")
  private Integer personId;
  @OneToOne(fetch = FetchType.LAZY, optional=true)
  @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
  @PrimaryKeyJoinColumn
  private Person person;
  @Column(name="weight")
  private Integer weight;
  @Column(name="height")
  private Integer height;
  @Column(name="hair_color")
  private Integer hardColor;
  @Column(name="eyes_color")
  private Integer eyesColor;
  public Integer getPersonId() {
    return personId;
  }
  public void setPersonId(Integer personId) {
    this.personId = personId;
  }
  public Person getPerson() {
    return person;
  }
  public void setPerson(Person person) {
    this.person = person;
  }
  public Integer getWeight() {
    return weight;
  }
  public void setWeight(Integer weight) {
    this.weight = weight;
  }
  public Integer getHeight() {
    return height;
  }
  public void setHeight(Integer height) {
    this.height = height;
  }
  public Integer getHardColor() {
    return hardColor;
  }
  public void setHardColor(Integer hardColor) {
    this.hardColor = hardColor;
  }
  public Integer getEyesColor() {
    return eyesColor;
  }
  public void setEyesColor(Integer eyesColor) {
    this.eyesColor = eyesColor;
  }
}

That’s pretty much it… I hope it will save you time…

16 comments:

  1. Hello.

    I wondering how you made the save, because I have the following problem.

    I have 2 entitys fund and fund_details, each one mapped as you, but when hibernate try to save it, it first save the fund_details and then fund, but for that moment the fund id doesn't exist.

    To save I made :

    fund.setFundDetails(details);
    details.setFund(fund);

    The error I get is:
    util.JDBCExceptionReporter SQL Error: 0, SQLState: 23503
    [ERROR] util.JDBCExceptionReporter ERROR: insert or update on table "fund_details" violates foreign key constraint "fkd8ef6e256fba07b1"

    Thx.

    ReplyDelete
  2. Hi Juan,

    I believe the "mappedBy" property on the parent class should do the work. Try to annotate your classes as close as you can to my example (it is example taken from a working code).

    ReplyDelete
  3. Thanks! It helped to save time :)

    ReplyDelete
  4. hi,
    I wondering how you made the save, because I have the following problem.
    org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property: mainParent

    ReplyDelete
  5. Hi,
    It should work OK.
    Just make sure you put all the annotations properly.

    ReplyDelete
    Replies
    1. I have the following problem.
      org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property: mainParent

      Delete
    2. Hi,
      I think you might have this problem since you didn't set your primary key in the parent table to be "Auto Increment" (My Sql) or identity column (SQL Server). If you use another database you have to set this column to automatically generate the value of the key or otherwise, change in Hibernate the declaration: @GeneratedValue(strategy= GenerationType.AUTO) to be something else that will tell Hibernate to generate the value of the primary key.

      Delete
  6. Hi,
    I want to know why in Person class you are using @JoinColumn(name="person_id").Even without JoinColumn it works.

    ReplyDelete
  7. It has been too long to remember why I did it this way. If it works without the "JoinColumn" then thanks for the update. It might help to others.

    ReplyDelete
  8. HI Guy Bashan,

    Thanks for your kindness to answer each of all questions.
    I am new to java, and I have a dummy question.

    I want to use "hibernate mapping using annotation" like above in your code. The question is : which IDE should I use? Netbeans or Eclipse? and what did you use?

    thanks so much if you could help!

    ReplyDelete
  9. Hi Shengmao Li,
    Which IDE to use is a hard question. It's a matter of taste and habit. I personally use IntelliJ. It gives nice solutions for Hibernate. It is kind of "Hibernate aware". You can see problems in mappings and even the HQL is highlighted and validated. If you want to choose between Eclipse or Netbeans I would recommend Eclipse. Not from my experience, but from what I have seen around it is more widely adopted.

    ReplyDelete
  10. When I try to insert I got this: null id generated for:class com.itym.pojo.elementos.radioenlaces.RadioenlaceDatosSupervisionPojo

    It seems it's trying to insert the child before the parent. Does anyone know why or how to resolve that?

    ReplyDelete
  11. Hi Krusher,
    It is hard to tell why you get it without looking at the code.

    ReplyDelete
  12. I have problem in updation of record
    following error generated
    -> Record Updated org.hibernte.id.IdentifierGenerationException: attempted to assign id from null one-to-one property: studentobj
    -> my update method code
    public boolean getRecordUpdated(StudentBean sb){
    boolean flag=false;
    try {
    System.out.println("user " +sb.getLoginobj().getUsername());
    sf=MysqlConnection.getConnection();
    s=sf.openSession();
    s.getTransaction().begin();

    s.update(sb);

    s.getTransaction().commit();
    flag=true;
    s.close();


    } catch (Exception e) {
    System.out.println("Record Updated " +e);
    e.printStackTrace();
    }
    return flag;
    }

    ReplyDelete
  13. i have problem in updation of record
    following error arises
    ->error
    Record Updated org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property: studentobj
    ->my studentBean
    @Entity
    @Table(name="student")
    public class StudentBean implements Serializable {
    private int studentId;
    private String sname,saddress,scontact;
    private LoginBean loginobj;
    @OneToOne(cascade= CascadeType.ALL,fetch= FetchType.LAZY,mappedBy="studentobj")
    @JoinColumn(name="studentId")
    public LoginBean getLoginobj() {
    return loginobj;
    }

    public void setLoginobj(LoginBean loginobj) {
    this.loginobj = loginobj;
    }
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    @Column(name="studentId")
    public int getStudentId() {
    return studentId;
    }

    public void setStudentId(int studentId) {
    this.studentId = studentId;
    }
    -my loginbean code
    @Entity
    @Table(name="Login")
    public class LoginBean implements Serializable{
    private int loginId;
    private String username,password;
    private StudentBean studentobj;

    @OneToOne(cascade= CascadeType.ALL,fetch= FetchType.LAZY,optional=true)
    @Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
    @PrimaryKeyJoinColumn
    public StudentBean getStudentobj() {
    return studentobj;
    }

    public void setStudentobj(StudentBean studentobj) {
    this.studentobj = studentobj;
    }
    @Id
    @Column(name="loginId")
    @GeneratedValue(generator="myGenValue")
    @GenericGenerator(name="myGenValue",strategy="foreign",parameters=@Parameter(name="property",value="studentobj"))
    public int getLoginId() {
    return loginId;
    }

    public void setLoginId(int loginId) {
    this.loginId = loginId;
    }
    ->my update method
    public boolean getRecordUpdated(StudentBean sb){
    boolean flag=false;
    try {
    System.out.println("user " +sb.getLoginobj().getUsername());
    sf=MysqlConnection.getConnection();
    s=sf.openSession();
    s.getTransaction().begin();

    s.update(sb);

    s.getTransaction().commit();
    flag=true;
    s.close();


    } catch (Exception e) {
    System.out.println("Record Updated " +e);
    e.printStackTrace();
    }
    return flag;
    }

    ReplyDelete
  14. Hi TedyBear,
    I believe Hibernate has gone quite a few improvements since I last wrote this blog, therefore it will be hard for me to help you... Anyway, back then it used to work quite well for me...

    ReplyDelete