smilin_stan
smilin_stan

Reputation: 1733

InheritanceType.JOINED, @PrimaryKeyJoinColumn and nested subclasses

I am getting started with Hibernate have a question about InheritanceType.JOINED and @PrimaryKeyJoinColumn.

Given the below database tables where employee references person and manager references employee:

create table person (person_id int(10) auto_increment, 
                     name varchar(100),
                     primary key (person_id));

create table employee (employee_id int(10) auto_increment,
                       person_id int(10),
                       salary int(10),
                       primary key (employee_id));

create table manager (manager_id int(10) auto_increment,
                      employee_id int(10),
                      shares int(10),
                      primary key (manager_id));

I can create the first two classes for Person and Employee as follows:

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

    @Id
    @GeneratedValue
    @Column(name="person_id")
    private int personId;

    @Column(name="name")
    private String name;
}


@Entity
@Table(name="employee")
@PrimaryKeyJoinColumn(name="person_id")
public class Employee extends Person {

    @GeneratedValue
    @Column(name="employee_id")
    private int employeeId;

    @Column(name="salary")
    private int salary;
}

When it comes to the Manager class I am not sure which value to use for the @PrimaryKeyJoinColumn. From reading the JPA spec I should be using only one id throughout the superclass/subclass/subclass hierarchy, which I suspect should be person_id.

Should I remove the employee_id column from the manager table and replace it with person_id? The reason I ask is that it seems natural to have "cascading" primary keys in the database (i.e. manager does not directly refer to person) but I believe Hibernate requires that all child subclasses (however "deep") contain a foreign key back to the superclass. Some confirmation on my assertions would be really useful.

Upvotes: 1

Views: 16306

Answers (2)

Deepak
Deepak

Reputation: 1758

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

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
@Table(name="Person")
public class Person implements Serializable{

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;

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

@Column(name="age")
private int age;

Person(String name,int age){
    this.name=name;
    this.age=age;

}

public int getId() {
    return id;
}

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

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}
}


@Entity
@Table(name="Student")
@PrimaryKeyJoinColumn(name="person_id")
public class Student extends Person{

@Column(name="stu_className")
private String className;

@Column(name="stu_collegeName")
private String collegeName;

Student(String name, int age, String className,String collegeName) {
    super(name, age);
    this.className=className;
    this.collegeName=collegeName;
}

public String getClassName() {
    return className;
}

public void setClassName(String className) {
    this.className = className;
}

public String getCollegeName() {
    return collegeName;
}

public void setCollegeName(String collegeName) {
    this.collegeName = collegeName;
}
}


@Entity
@Table(name="Teacher")
@PrimaryKeyJoinColumn(name="person_id")
public class Teacher extends Person{

@Column(name="qualification")
private String qualification;

@Column(name="designation")
private String designation;

Teacher(String name, int age,String qualification,String designation) {
    super(name, age);
    this.qualification=qualification;
    this.designation=designation;
}

public String getQualification() {
    return qualification;
}

public void setQualification(String qualification) {
    this.qualification = qualification;
}

public String getDesignation() {
    return designation;
}

public void setDesignation(String designation) {
    this.designation = designation;
}
}


import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;  
import org.hibernate.cfg.Configuration;

public enum HibernateUtil {

INSTANCE;
HibernateUtil(){
    buildSessionFactory();
}
private SessionFactory sessionFactory=null;

public SessionFactory getSessionFactory() {
    return sessionFactory;
}


public void setSessionFactory(SessionFactory sessionFactory) {
    this.sessionFactory = sessionFactory;
}


private  void buildSessionFactory() {
    Configuration configuration = new Configuration();

    configuration.addAnnotatedClass (Com.InheritenceType.Person.class);
    configuration.addAnnotatedClass (Com.InheritenceType.Teacher.class);
    configuration.addAnnotatedClass (Com.InheritenceType.Student.class);
    configuration.setProperty("connection.driver_class","com.mysql.jdbc.Driver");
    configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/hibernate");                                
    configuration.setProperty("hibernate.connection.username", "root");     
    configuration.setProperty("hibernate.connection.password", "root");
    configuration.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
    configuration.setProperty("hibernate.hbm2ddl.auto", "update");
    configuration.setProperty("hibernate.show_sql", "true");
    configuration.setProperty(" hibernate.connection.pool_size", "10");
    configuration.setProperty(" hibernate.cache.use_second_level_cache", "true");
    configuration.setProperty(" hibernate.cache.use_query_cache", "true");
    configuration.setProperty(" cache.provider_class", "org.hibernate.cache.EhCacheProvider");
    configuration.setProperty("hibernate.cache.region.factory_class" ,"org.hibernate.cache.ehcache.EhCacheRegionFactory");

   // configuration
    StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
       sessionFactory = configuration.buildSessionFactory(builder.build());
       setSessionFactory(sessionFactory);
}


public  static SessionFactory getSessionFactoryInstance(){
    return INSTANCE.getSessionFactory();
}
} 


public class Main {

public static void main(String[] args) {
    HibernateUtil util=HibernateUtil.INSTANCE;
    SessionFactory factory=util.getSessionFactory();
    save(factory);
    retrieve(factory);
}

private static void retrieve(SessionFactory factory) {
    Session session=factory.openSession();

    Query query = session.createQuery("select s.id,s.name, s.age, s.qualification from Teacher s ");

    Iterator sal = query.iterate();
    System.out.println(" person_id \t name \t age \t qualification");
    while(sal.hasNext())
    {
    Object[] obj = (Object[]) sal.next();
    System.out.println(obj[0]+" \t "+ obj[1]+ " \t "+ obj[2]);

    }

}

private static void save(SessionFactory factory) {

    Session session=factory.openSession();
    Student student=new Student("Deepak", 30, "MCA", "Thapar");
    Teacher teacher=new Teacher("Bekaar Teachers", 60, "PHD", "Sr.professor");

    session.beginTransaction();

    session.save(student);
    session.save(teacher);

    session.getTransaction().commit();
    session.close();
}
}

Upvotes: 2

bitkot
bitkot

Reputation: 4504

You can have the column name whatever you want. @PrimaryKeyJoinColumn annotation is used to tell hibernate what is the name of the foreign key column in the joining table.

@Entity
@Table(name = "manager")
@PrimaryKeyJoinColumn(name = "employee_id")
public class Manager extends Employee{
    private String branch;
}

This code means that in manager table you have a column named employee_id which is a foreign key for employee table.

This annotation is optional so if you don't provide it then the foreign key column name will of same as the key name in the base class(super base if multilevel inheritance), in your case if you don't provide then any subclass will have person_id as the foreign key.

If you want to keep the field name as employee_id in manager table then you have to provide like this.

@PrimaryKeyJoinColumn(name="employee_id")

if you don't provide this annotation at all then there will be a column name person_id to be used as foreign key for employee table.

Here's what documentation says.

This annotation specifies a primary key column that is used as a foreign key to join to another table.

It is used to join the primary table of an entity subclass in the JOINED mapping strategy to the primary table of its superclass; it is used within a SecondaryTable annotation to join a secondary table to a primary table; and it may be used in a OneToOne mapping in which the primary key of the referencing entity is used as a foreign key to the referenced entity.

If no PrimaryKeyJoinColumn annotation is specified for a subclass in the JOINED mapping strategy, the foreign key columns are assumed to have the same names as the primary key columns of the primary table of the superclass

Edit

The query being executed by Hibernate when I try to retrieve manager by id.

select
            manager0_.id as id1_4_0_,
            manager0_2_.name as name2_4_0_,
            manager0_1_.employee_id as employee1_1_0_,
            manager0_1_.salary as salary2_1_0_,
            manager0_.branch as branch1_2_0_ 
        from
            manager manager0_ 
        inner join
            employee manager0_1_ 
                on manager0_.id=manager0_1_.id 
        inner join
            person manager0_2_ 
                on manager0_.id=manager0_2_.id 
        where
            manager0_.id=?

As you can see manager has a foreign key of employee and not the person table. I have not created the table. I used the hbm2ddl=create.

This means that Hibernate itself creates table using cascading primary key and not super parent key only.

I understand your confusion, even I could not find anything in the hibernate documentation. It just says that the FK will be pointing to superclass primary key. It does not specify immediate superclass or top superclass. Though I checked other JPA implementation i.e OpenJPA. It does say specifically that the FK points to immediate superclass.

Upvotes: 5

Related Questions