programacion - java and hibernate
Hibernate: relaciĆ³n padre/hijo en una sola tabla (3)
Apenas veo ningún puntero sobre el siguiente problema relacionado con Hibernate. Esto se refiere a la implementación de la herencia utilizando una sola tabla de base de datos con una relación padre-hijo consigo misma. Por ejemplo:
CREATE TABLE Employee (
empId BIGINT NOT NULL AUTO_INCREMENT,
empName VARCHAR(100) NOT NULL,
managerId BIGINT,
CONSTRAINT pk_employee PRIMARY KEY (empId)
)
Aquí, la columna managerId puede ser nula o puede apuntar a otra fila de la tabla Employee . La regla empresarial requiere que el Empleado sepa de todos sus informados y que sepa sobre su gerente. Las reglas comerciales también permiten que las filas tengan null managerId (el CEO de la organización no tiene un administrador).
¿Cómo mapeamos esta relación en Hibernate, la relación estándar de muchos a uno no funciona aquí? Especialmente, si deseo implementar mis Entidades no solo como una clase de Entidad de "Empleado" correspondiente, sino varias clases, tales como "Gerente", "Asistente de Gerente", "Ingeniero", etc., heredando cada una de la clase de super entidad "Empleado" , alguna entidad que tenga atributos que no se apliquen realmente a todos, por ejemplo, "Gerente" obtiene Beneficios, otros no (la columna de la tabla correspondiente aceptaría nulo, por supuesto).
El código de ejemplo sería apreciado (tengo la intención de utilizar las anotaciones de Hibernate 3).
Usted está expresando dos conceptos aquí:
- herencia y desea asignar su jerarquía de herencia en una sola tabla.
- una relación padre / hijo.
Para implementar 1., deberá usar la tabla única de Hibernate por estrategia de jerarquía de clases :
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="emptype",
discriminatorType=DiscriminatorType.STRING
)
public abstract class Employee { ... }
@Entity
@DiscriminatorValue("MGR")
public class Manager extends Employee { ... }
Para implementar 2., deberá agregar dos asociaciones de autorreferencia en Employee
:
- muchos empleados tienen cero o un gerente (que también es un
Employee
) - un empleado tiene cero o muchos reporteros
El Employee
resultante puede tener el siguiente aspecto:
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="emptype",
discriminatorType=DiscriminatorType.STRING
)
public abstract class Employee {
...
private Employee manager;
private Set<Employee> reportees = new HashSet<Employee>();
@ManyToOne(optional = true)
public Employee getManager() {
return manager;
}
@OneToMany
public Set<Employee> getReportees() {
return reportees;
}
...
}
Y esto daría como resultado las siguientes tablas:
CREATE TABLE EMPLOYEE_EMPLOYEE (
EMPLOYEE_ID BIGINT NOT NULL,
REPORTEES_ID BIGINT NOT NULL
);
CREATE TABLE EMPLOYEE (
EMPTYPE VARCHAR(31) NOT NULL,
ID BIGINT NOT NULL,
NAME VARCHAR(255),
MANAGER_ID BIGINT
);
ALTER TABLE EMPLOYEE ADD CONSTRAINT SQL100311183749050 PRIMARY KEY (ID);
ALTER TABLE EMPLOYEE_EMPLOYEE ADD CONSTRAINT SQL100311183356150 PRIMARY KEY (EMPLOYEE_ID, REPORTEES_ID);
ALTER TABLE EMPLOYEE ADD CONSTRAINT FK4AFD4ACE7887BF92 FOREIGN KEY (MANAGER_ID)
REFERENCES EMPLOYEE (ID);
ALTER TABLE EMPLOYEE_EMPLOYEE ADD CONSTRAINT FKDFD1791F25AA2BE0 FOREIGN KEY (REPORTEES_ID)
REFERENCES EMPLOYEE (ID);
ALTER TABLE EMPLOYEE_EMPLOYEE ADD CONSTRAINT FKDFD1791F1A4AFCF1 FOREIGN KEY (EMPLOYEE_ID)
REFERENCES EMPLOYEE (ID);
No estoy seguro de que realmente quiera, pero creo que quiere una tabla por jerarquía de clases
En ese caso, cada Entidad está ordenada por DISCRIMINATOR_COLUMN de la siguiente manera
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="EMPLOYEE_TYPE",
discriminatorType = DiscriminatorType.STRING
)
@DiscriminatorValue("EMPLOYEE")
public class Employee {
@Id @GeneratedValue
@Column(name="EMPLOYEE_ID")
private Integer id = null;
}
Y sus hijos se asignan de acuerdo a
@Entity
@DiscriminatorValue("MANAGER")
public class Manager extends Employee {
// Manager properties goes here
...
}
Para probar, hagamos lo siguiente
SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
Session session = sessionFactory.openSession();
/*
insert
into
Employee
(EMPLOYEE_TYPE)
values
(''EMPLOYEE'')
*/
session.save(new Employee());
/*
insert
into
Employee
(EMPLOYEE_TYPE)
values
(''MANAGER'')
*/
session.save(new Manager());
session.clear();
session.close();
Pero, en lugar de la herencia (que puede ver una gran cantidad de columnas NULL debido a que más de una entidad comparten la misma tabla, al usar la estrategia InheritanceType.SINGLE_TABLE), su modelo sería mejor de la siguiente manera
@Entity
public class Employee {
private Employee manager;
private List<Employee> reporteeList = new ArrayList<Employee>();
/**
* optional=true
* because of an Employee could not have a Manager
* CEO, for instance, do not have a Manager
*/
@ManyToOne(optional=true)
public Employee getManager() {
return manager;
}
@OneToMany
public List<Employee> getReporteeList() {
return reporteeList;
}
}
Siéntase libre de elegir el mejor enfoque que satisfaga sus necesidades.
Saludos,
Muchas gracias chicos. Creé mi entidad de empleado de la siguiente manera:
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
name="EMPLOYEE_TYPE",
discriminatorType = DiscriminatorType.STRING
)
@DiscriminatorValue("Employee")
public abstract class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="EMPLOYEE_ID")
private Integer empId = null;
@Column(name="EMPLOYEE_NAME")
private String empName = null;
@Column(name="EMPLOYEE_SECRETARY")
private String secretary;
@Column(name="EMPLOYEE_PERKS")
private int perks;
@ManyToOne(targetEntity = Employee.class, optional=true)
@JoinColumn(name="MANAGER_ID", nullable=true)
private Employee manager = null;
@OneToMany
private Set<Employee> reportees = new HashSet<Employee>();
...
public Set<Employee> getReportees() {
return reportees;
}
}
Luego agregué otras clases de entidades sin cuerpo, solo valores de columnas discriminatorias, como Manager, CEO y AsstManager. Elegí dejar que Hibernate creara la mesa para mí. El siguiente es el programa principal:
SessionFactory sessionFactory = HibernateUtil.sessionFactory;
Session session = sessionFactory.openSession();
Transaction newTrans = session.beginTransaction();
CEO empCeo = new CEO();
empCeo.setEmpName("Mr CEO");
empCeo.setSecretary("Ms Lily");
Manager empMgr = new Manager();
empMgr.setEmpName("Mr Manager1");
empMgr.setPerks(1000);
empMgr.setManager(empCeo);
Manager empMgr1 = new Manager();
empMgr1.setEmpName("Mr Manager2");
empMgr1.setPerks(2000);
empMgr1.setManager(empCeo);
AsstManager asstMgr = new AsstManager();
asstMgr.setEmpName("Mr Asst Manager");
asstMgr.setManager(empMgr);
session.save(empCeo);
session.save(empMgr);
session.save(empMgr1);
session.save(asstMgr);
newTrans.commit();
System.out.println("Mr Manager1''s manager is : "
+ empMgr.getManager().getEmpName());
System.out.println("CEO''s manager is : " + empCeo.getManager());
System.out.println("Asst Manager''s manager is : " + asstMgr.getManager());
System.out.println("Persons Reporting to CEO: " + empCeo.getReportees());
session.clear();
session.close();
El código funciona bien, Hibernate estaba creando una columna "MANAGER_EMPLOYEE_ID" por sí misma, donde almacena el FK. Especifiqué el nombre de JoinColumn para convertirlo en "MANAGER_ID". Hibernate también crea una tabla EMPLOYEE_EMPLOYED, sin embargo, los datos no se conservan allí.
El método getReportees () devuelve un valor nulo, mientras que getManager () funciona correctamente, como se esperaba.