Problem

Mapping One to One association with JPA is pretty straitforward. The following entities:

Employee.java

package com.application.onetoone.model;

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

@Entity
@Table(name = "EMPLOYEES")
public class Employee  {

  private Integer employeeId;
  private Desk desk;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @JoinColumn(name="desk_id", unique=true)
  public Desk getDesk() {
    return desk;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setDesk(Desk desk) {
    this.desk = desk;
  }

}

and

Desk.java

package com.application.onetoone.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "DESKS")
public class Desk {

  private Integer deskId;
  private String description;
  private Employee employee;

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  @Column(name="desk_id")
  public Integer getDeskId() {
    return deskId;
  }

  @Column(name="description")
  public String getDescription() {
    return description;
  }

  @OneToOne(mappedBy="desk", fetch=FetchType.EAGER)
  public Employee getEmployee() {
    return employee;
  }

  public void setDeskId(Integer deskId) {
    this.deskId = deskId;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public void setEmployee(Employee employee) {
    this.employee = employee;
  }

}

generate the following database schema:
One-To-One
The owning entity – Employee – contains additional column – desk_id, which maps to the ownded entity – Desk. Having the configuration above I can:

assign a desk to employee:

    Employee employee = new Employee ();

    desk = deskDao.load(2);
    if (null == desk.getEmployee()) {
      employee.setDesk(desk);
    }

ask where a given employee is sitting:

    String description = employee.getDesk().getDescription();

or ask who is sitting at given desk:

    Integer employeeId = desk.getEmployee().getEmployeeId();

and, since they are not glued, an employee can empty his current desk and get a new one:

    Employee employee = new Employee ();

    desk = deskDao.load(4);
    if (null == desk.getEmployee()) {
      employee.setDesk(desk);
    }

A relation between employee and a seat is temporal. An employee can leave, a seat can be freed and reassing to someone else. There are however relations, which are everlasting and can not be changed over the time of entity life. Let’s say, I want to identify an employee – I give him a first name, last name and a date of birth. In order to to this I crete a separate entity:

EmployeeDetails.java

package com.application.onetoone.model;

import java.util.Calendar;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "EMPLOYEE_DETAILS")
public class EmployeeDetails {

  private Integer employeeId;
  private String firstName;
  private String lastName;
  private Calendar dateOfBirth;
  private Employee employee;

  @Id
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @Column(name="first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name="last_name")
  public String getLastName() {
    return lastName;
  }

  @Temporal(TemporalType.DATE)
  @Column(name="date_of_birth")
  public Calendar getDateOfBirth() {
    return dateOfBirth;
  }

  @OneToOne(mappedBy="employeeDetails", fetch=FetchType.EAGER)
  public Employee getEmployee() {
    return employee;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

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

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

  public void setEmployee(Employee employee) {
    this.employee = employee;
  }

}

which I want to share a primary key with it’s parent entity – employee, which I need to amend:

package com.application.onetoone.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEES")
public class Employee  {

  private Integer employeeId;
  private Desk desk;
  private EmployeeDetails employeeDetails;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @JoinColumn(name="desk_id", unique=true)
  public Desk getDesk() {
    return desk;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @PrimaryKeyJoinColumn
  public EmployeeDetails getEmployeeDetails() {
    return employeeDetails;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setDesk(Desk desk) {
    this.desk = desk;
  }

  public void setEmployeeDetails(EmployeeDetails employeeDetails) {
    this.employeeDetails = employeeDetails;
  }

}

The entities above generate the following database schema:
One-To-One
As you can see, there is no foreign key generated between Employee and EmployeDetails. This is default Hibernate behaviour and was already raised as a bug, however, it is not fixed as of now. Additionally, the configuration above does not assign any key to EmployeeDetails and the following code:

    Employee employee = new Employee ();

    Desk desk = new Desk ();
    desk.setDescription("T114A45");
    employee.setDesk(desk);

    EmployeeDetails employeeDetails = new EmployeeDetails ();
    employeeDetails.setFirstName("John");
    employeeDetails.setLastName("Doe");
    employee.setEmployeeDetails(employeeDetails);      

    employeeDao.save(employee);

throws a HibernateSystemException with a following message: ids for this class must be manually assigned before calling save()
.
How do I assign it ?

Solution one – simple and ugly

Without going beyond the JPA annotation there is nothing smart that can be done. employee_id is not known before the Employee record is actually persisted, so it seems that the only way to get it right is:

    Employee employee = new Employee ();

    Desk desk = new Desk ();
    desk.setDescription("T114A45");
    employee.setDesk(desk);

    // save it
    employee = employeeDao.save(employee); 

    EmployeeDetails employeeDetails = new EmployeeDetails ();
    employeeDetails.setFirstName("John");
    employeeDetails.setLastName("Doe");
    // set employee_id
    employeeDetails.setEmployeeId(employee.getEmployeeId());
    employee.setEmployeeDetails(employeeDetails);      

    // save again
    employeeDao.save(employee);

The part of the code responsilbe for a proper mapping is left to the client. Obviously, it does the job, but this looks like workaround rather than solution.

Solution two – Hibernate GenericGenerator

JPA does not provide support for custom key generators, but Hibernate is closing the gap. First, we have to tell Hibernate, where the source of the primary key. Hibernate @GenericGenerator annotations provides the solution:

EmployeeDetails.java

package com.application.onetoone.model;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;

@Entity
@Table(name = "EMPLOYEE_DETAILS")
public class EmployeeDetails {

  private Integer employeeId;
  private String firstName;
  private String lastName;
  private Date dateOfBirth;
  private Employee employee;

  @Id
  @GeneratedValue(generator = "foreign")
  @GenericGenerator(name = "foreign", strategy = "foreign",
    parameters = {@Parameter(name = "property", value = "employee")}
  )
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @Column(name="first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name="last_name")
  public String getLastName() {
    return lastName;
  }

  @Temporal(TemporalType.DATE)
  @Column(name="date_of_birth")
  public Date getDateOfBirth() {
    return dateOfBirth;
  }

  @OneToOne(mappedBy="employeeDetails", fetch=FetchType.EAGER)
  public Employee getEmployee() {
    return employee;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

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

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

  public void setDateOfBirth(Date dateOfBirth) {
    this.dateOfBirth = dateOfBirth;
  }

  public void setEmployee(Employee employee) {
    this.employee = employee;
  }

}

@GenericGenerator with strategy set to foreign tells Hibernate to obtain a primary key value from one of its properties.
The last thing left to do is to make sure, the property used to generate id value is not null. Since EmployeeDetails is an owned entity, it makes perfect sense to change the the owning entity – Employee:

package com.application.onetoone.model;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name = "EMPLOYEES")
public class Employee  {

  private Integer employeeId;
  private Desk desk;
  private EmployeeDetails employeeDetails;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return employeeId;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @JoinColumn(name="desk_id", unique=true)
  public Desk getDesk() {
    return desk;
  }

  @OneToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
  @PrimaryKeyJoinColumn
  public EmployeeDetails getEmployeeDetails() {
    return employeeDetails;
  }

  public void setEmployeeId(Integer employeeId) {
    this.employeeId = employeeId;
  }

  public void setDesk(Desk desk) {
    this.desk = desk;
  }

  public void setEmployeeDetails(EmployeeDetails employeeDetails) {
    this.employeeDetails = employeeDetails;
    // for @GenericGenerator
    employeeDetails.setEmployee(this);
  }

}

Now we can create an employee:

    Employee employee = new Employee ();      

    EmployeeDetails employeeDetails = new EmployeeDetails ();
    employeeDetails.setFirstName("John"); employeeDetails.setLastName("Doe");
    employee.setEmployeeDetails(employeeDetails);

    Desk desk = new Desk ();
    desk.setDescription("T114A45");
    employee.setDesk(desk);

    employeeDao.save(employee);

Ask where employee is sitting:

    String desk = employeeDetailsDao.find("lastName", "Doe").get(0).getEmployee().getDesk().getDescription();

or who is sitting here:

    String employeeName = deskDao.find("description", "T114A45").get(0).getEmployee().getEmployeeDetails().getLastName();

Problem

Most likely, each DAO class implements basic CRUD operations. Since these are the same for all possible entities, it makes perfect sense to have one generic DAO class which implements common CRUDs and extend it when necessary adding anything which is required by a specific entity instance.

Simple generic DAO should implement the following interface:

SimpleDao.java

package com.application.sample;

import java.io.Serializable;

public interface SimpleDao <PK extends Serializable, E extends AbstractEntity <PK>> {
  public E create(E entity);
  public E read(PK id);
  public E update(E entity);
  public void delete(PK id);
}

When implementing generic DAO using Hibernate as JPA provider we are facing the problem. Hibernate load and get operations require information about object being retrieved. Either object class, class name or the object instance has to be provided before actual read operation can be executed. None of these information is accessible before generic DAO is extended and instantiated. I can think of two possible solutions, an obvious one – asking implementation to deliver required information, and nice one – using java.lang.reflection.ParameterizedType.

Lets assume, I have an abstract entity, which defines fields common to all entities in my application:

AbstractEntity .java

package com.application.sample;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import javax.persistence.Version;

@MappedSuperclass
public abstract class AbstractEntity <PK extends Serializable> implements Serializable {
  private static final long serialVersionUID = -6991226731574845156L;

  public AbstractEntity () {
  }

  private PK primaryKey;
  private Integer version;

  @Transient
  public PK getPrimaryKey () {
    return primaryKey;
  }

  @Version
  @Column(name="version", nullable=false)
  public Integer getVersion() {
    return version;
  }

  public void setPrimaryKey (PK primaryKey) {
    this.primaryKey = primaryKey;
  }

  public void setVersion(Integer version) {
    this.version = version;
  }

}

And Employee entity:

Employee.java

package com.application.sample;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.Index;

@Entity
@Table(name = "EMPLOYEES")
@org.hibernate.annotations.Table(
  indexes = {@Index(name="lastNameIdx", columnNames={"last_name"} )},
  appliesTo = "EMPLOYEES"
)
public class Employee extends AbstractEntity <Integer> {
  private static final long serialVersionUID = -1693502528557287614L;

  private String firstName = null;
  private String lastName = null;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name="employee_id")
  public Integer getEmployeeId() {
    return getPrimaryKey();
  }

  @Column(name = "first_name")
  public String getFirstName() {
    return firstName;
  }

  @Column(name = "last_name")
  public String getLastName() {
    return lastName;
  }

  public void setEmployeeId(Integer employeeId) {
    setPrimaryKey(employeeId);
  }

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

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

}

For an Employee entity I want to have a possibility to find employees by their last name, so I add the required method the specification:

EmployeeDao.java

package com.application.sample;

import java.util.List;

public interface EmployeeDao extends SimpleDao<Integer, Employee> {

  public List<Employee> readEmployeeByLastName(String lastName);

}


Solution one – pass the buck.

Generic DAO implementation:

AbstractPassTheBuckDao.java

package com.application.sample;

import java.io.Serializable;

import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class AbstractPassTheBuckDao<PK extends Serializable, E extends AbstractEntity <PK>>
       extends HibernateDaoSupport implements SimpleDao <PK, E>   {

  public AbstractPassTheBuckDao() {
  }

  protected AbstractPassTheBuckDao(SessionFactory sessionFactory) {
    this();
    this.setSessionFactory(sessionFactory);
  }

  // Ask the implementation for entity class
  abstract protected Class<E> getPersistentClass ();

  public E create(E entity) {
    this.getHibernateTemplate().save(entity);
    return entity;
  }

  @SuppressWarnings("unchecked")
  public E read(PK id) {
    E entity = (E) this.getHibernateTemplate().get(getPersistentClass(), id);
    return entity;
  }

  public E update(E entity) {
    this.getHibernateTemplate().merge(entity);
    return entity;
  } 

  public void delete(PK id) {
    this.getHibernateTemplate().delete(read(id));
  }

}

Employee DAO implementation:

EmployeePassTheBuckDao.java

package com.application.sample;

import java.util.List;

public class EmployeePassTheBuckDao
       extends AbstractPassTheBuckDao <Integer, Employee> implements EmployeeDao {

  // Deliver entity class
  protected Class<Employee> getPersistentClass() {
    return Employee.class;
  }

  @SuppressWarnings("unchecked")
  public List<Employee> readEmployeeByLastName(String lastName) {
    String queryString = "from Employee where lastName = :lastName";
    String paramName = "lastName";
    return (List<Employee>) this.getHibernateTemplate().findByNamedParam(queryString, paramName, lastName);
  }

}


Solution two – do it yourself.

Generic DAO implementation:

AbstractDoItYourselfDao.java

package com.application.sample;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;

import org.hibernate.SessionFactory;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public abstract class AbstractDoItYourselfDao<PK extends Serializable, E extends AbstractEntity <PK>>
       extends HibernateDaoSupport implements SimpleDao <PK, E>   {

  private Class<E> persistentClass;

  @SuppressWarnings("unchecked")
  public AbstractDoItYourselfDao() {
    // retrieve entity class
    ParameterizedType paramType = null;
    Type type = getClass().getGenericSuperclass();
    if (type instanceof ParameterizedType) {
      paramType = (ParameterizedType) type;
    } else {
      paramType = (ParameterizedType) getClass().getSuperclass().getGenericSuperclass();
    }

    if (paramType.getActualTypeArguments().length == 2) {
      if (paramType.getActualTypeArguments()[1] instanceof TypeVariable) {
        throw new IllegalArgumentException("Could not persistent entity class using reflection");
      } else {
        persistentClass = (Class<E>) paramType.getActualTypeArguments()[1];
      }
    } else {
      persistentClass = (Class<E>) paramType.getActualTypeArguments()[0];
    }

  }

  protected AbstractDoItYourselfDao(SessionFactory sessionFactory) {
    this();
    this.setSessionFactory(sessionFactory);
  }

  // deliver entity class
  protected Class<E> getPersistentClass () {
    return persistentClass;
  }

  public E create(E entity) {
    this.getHibernateTemplate().save(entity);
    return entity;
  }

  @SuppressWarnings("unchecked")
  public E read(PK id) {
    E entity = (E) this.getHibernateTemplate().get(getPersistentClass(), id);
    return entity;
  }

  public E update(E entity) {
    this.getHibernateTemplate().merge(entity);
    return entity;
  } 

  public void delete(PK id) {
    this.getHibernateTemplate().delete(read(id));
  }

}

Employee DAO implementation:

EmployeeDoItYourselfDao .java

package com.application.sample;

import java.util.List;

public class EmployeeDoItYourselfDao
       extends AbstractDoItYourselfDao <Integer, Employee> implements EmployeeDao {

  @SuppressWarnings("unchecked")
  public List<Employee> readEmployeeByLastName(String lastName) {
    String queryString = "from Employee where lastName = :lastName";
    String paramName = "lastName";
    return (List<Employee>) this.getHibernateTemplate().findByNamedParam(queryString, paramName, lastName);
  }

}

As seen above, the EmployeeDao implementation implements only Employee related operations and there is no need to deliver Employee class to generic DAO superclass.


© 2007 Toss a coin | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress