Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate: Change instance of base class to subclass

I would like to change a concrete superclass to one of its subclass. I've included an example below:

@Entity
@Table(name = "employees")
@Inheritance(strategy = InheritanceType.JOINED)
public class Employee {

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

    public Employee( String id ) {
        this.id = id;
    }
 ...
}

@Entity
@Table(name = "managers")
@PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")
public class Manager extends Employee {

    public Manager( String id ) {
        super(id);
    }
 ...
}


@Entity
@Table(name = "history")
public class History {
 ...
/**
 * 
 */
@ManyToOne
@JoinColumn(name = "employee_id")
private Employee employee;
 ...
}

The three classes I'm working with are Employee, Manager and History. All Managers are Employees, but not all Employees are Managers. All Employees (and Managers) have a History. Employees may be promoted to Management. When this happens an Employee's history should be retained by keeping their Employee ID the same. This will allow a Manager's History through employment to be easily found.

Implementing the promotion operation is complicated by constraints withing the database: the database will not allow removing the old Employee and creating a new Manager with the same ID without removing all of the History objects by cascading operation - which Human Resources won't allow, otherwise my job would be easy!

Is it possible to add or attach the Manager (new managers) row to an existing Employee without resorting to custom SQL operation?

I've tried the following solutions without success:

public void promote( Employee employee ) {
    /* copy over the ID of the employee to the manager.  Will not work because employee with ID already exists */
    Manager manager = new Manager(employee.getId());
    this.entityManager.persist( manager );
}

... or ...

public void promote( Employee employee ) {
    /* detach the employee then merge.  Will not work: fails, again, with a NonUniqueObject Exception */
    this.entityManager.detach( employee );
    Manager manager = new Manager(employee.getId());
    this.entityManager.merge( manager );
}

How can I get this done? Am I even on the right track with detach() and merge()?

Is this possible in Hibernate/JPA?

Any ideas will be helpful at this point as I'm stumped!

  • Aaron
like image 790
A. Phillips Avatar asked Sep 17 '11 19:09

A. Phillips


2 Answers

As you're no doubt starting to see, you're rowing against the wind here. Unfortunately, it looks like you have a classic example of using inheritance to represent role. Both Hibernate--an Object-Relational Mapper--and Java--an Object-oriented language--are going to fight you on this one because your object model is wrong. I'd say your best bet is to fix it now with a refactoring and data migration. The end result should be that everyone is an Employee, and some Employees have some kind of "manages" relationship with one or more departments or other Employees.

like image 122
Ryan Stewart Avatar answered Sep 21 '22 02:09

Ryan Stewart


I ran into a similar situation and I don't believe a flawed data model is to blame since the data model matches the real world model quite well.

You can insert the subclass record manually to achieve the desired result. Sometimes it's requisite to go around Hibernate to get something done, so this is what I did in this case as a workaround:

sessionFactory.getCurrentSession().createSQLQuery(
            "insert into manager (employee_id, rank) " +
            "values (:employeeId, :rank) ")
            .setParameter("employeeId", employeeId)
            .setParameter("rank", rank)
            .executeUpdate();
like image 41
Nealvs Avatar answered Sep 21 '22 02:09

Nealvs