Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inheritance mapping with JPA/Hibernate

This is a fairly lengthy (not overly complex) design question so please bear with me. I'm trying to implement a person/role management system with POJOs and JPA. I'm fairly new to ORM and this is mostly a mapping problem.

I've got this working as POJOs and am comfortable with the caller-level API, but would now like to map it to a database using JPA or Hibernate in a Seam environment.

My implementation is based on the Decorator (GoF) and Person Role Object patterns (Baumer/Riehle et al). All roles are hard-coded and run-time addition of new roles is not supported since it would require code changes to extend behavior. I'd use user groups to implement security and permissions.

There is a Person interface with role management methods such as addRole(), removeRole(), hasRole(), getRole(), getRoles(), among other things. The concrete implementation is provided by a PersonImpl class.

There is an abstract class Role which also implements the Person interface (for decorator substitution equivalence), and a RoleImpl class that extends it. The Role class holds a reference to a person instance, using it to service any method / property calls on the person interface, meaning all subclasses of Role can handle the Person interface. The role constructor takes the person object as a parameter.

These are the interfaces/classes:

public interface Person {
    public String getFirstName();

    public void setFirstName(String firstName);
    .
    .
    .
    public boolean isEnabled();
    public void setEnabled(boolean enabled);
    public Set<Role> getRoles();
    public Role addRole(Class<? extends Role> roleType);
    public void removeRole(Class<? extends Role> roleType);
    public boolean hasRole(Class<? extends Role> roleType);
    public Role getRole(Class<? extends Role> roleType);

    public enum Gender {MALE, FEMALE, UNKNOWN};
}

public class PersonImpl implements Person {
    .
    .
    .
}

public abstract class Role implements Person {
protected PersonImpl person;

    @Transient
    protected abstract String getRoleName();
    protected Role() {}
    public Role(PersonImpl person) {
        this.person = person;
    }
    public String getFirstName() {
        return person.getFirstName();
    }
    public void setFirstName(String firstName) {
        person.setFirstName(firstName);
    }
    public Set<Role> getRoles() {
        return person.getRoles();
    }
    public Role addRole(Class<? extends Role> roleType) {
        return person.addRole(roleType);
    }
    .
    .
    .
}

public abstract class RoleImpl extends Role {
    private String roleName;

    protected RoleImpl() {}

    public RoleImpl(PersonImpl person) {
        super(person);
    }
    . 
    . 
    .
}

public class Employee extends RoleImpl {
    private Date joiningDate;
    private Date leavingDate;
    private double salary;

    public Employee(PersonImpl person) {
        super(person);
    }
    .
    .
    .
}

This diagram shows the class relationships:

(If you can't see the diagram inline, view it here via yUML)

I'd use these classes like so:

Person p = new Person("Doe", "John", Person.MALE, ...);
// assuming Employee extends Role
Employee e = (Employee)p.addRole(Employee.class);

Since the Role class also implements the Person interface, I can also do:

// assuming Parent extends Role
Parent parent = new Parent((PersonImpl)p);

e.addRole(Parent.class);
e.getDateOfBirth();  // handled by the decorated person class

// assuming Manager extends Employee extends Role
Manager m = (Manager)p.getRole(Manager);

if (m.hasRole(Employee.class) { 
    // true since Manager derives from Employee
}

The questions I have are:

(a) Is this implementation needlessly complex and if so what would be a simpler approach? Note that this goes into a non-trivial business application and not a kiddy project, and I think the role subclassing is important to leverage behavior in cases such as Employee, Manager, etc.

(b) How do I map this in JPA/Hibernate?

(c) Can it be mapped so I can also take advantage of Seam Identity Management? (My definition of Role is clearly not analogous to Seam's)

(d) If I go with a table-per-subclass (InheritanceType.JOINED) mapping strategy, I map PersonImpl as PERSONS and RoleImpl as ROLES tables, and map each subclass of RoleImpl (such as Employee and Parent) to their own tables as EMPLOYEES and PARENTS.

I would then have a @ManyToMany relationship between PERSONS and ROLES (on the roles collection in PersonImpl), with the join-table PERSON_ROLES.

Now the problem is that the EMPLOYEES and PARENTS tables, etc. have only the ROLE_ID reference, since the inheritance mapping strategy obviously considers them to be extensions to Roles (ROLES) whereas I need them as addendum to PERSON_ROLES, and require the USER_ID + ROLE_ID key to be resolved properly, or at least the USER_ID.

I would prefer a normalized database with the attendant reliance on extra joins than a denormalized database which will be difficult to maintain and likely bunch up a ton of unused and irrelevant fields, which is why I think table-per-subclass is the way to go.

Or is a table-per-class-hierarchy (InheritanceType.SINGLE_TABLE) with a discriminator column OK (from database maintenance perspective) in this scenario? Note that some roles are likely to have dozens of properties/fields.

(e) Is there a better alternative to this design?

Would very much appreciate any ideas/suggestions.

like image 354
alan-p Avatar asked Jul 13 '10 16:07

alan-p


People also ask

What are the inheritance mapping strategies in Hibernate?

Hibernate supports the three basic inheritance mapping strategies: table per class hierarchy. table per subclass. table per concrete class.

Why do we use inheritance mapping in Hibernate?

Entity inheritance means that we can use polymorphic queries for retrieving all the subclass entities when querying for a superclass. Since Hibernate is a JPA implementation, it contains all of the above as well as a few Hibernate-specific features related to inheritance.

What are inheritance mapping strategies in JPA?

JPA defines three inheritance strategies namely, SINGLE_TABLE , TABLE_PER_CLASS and JOINED . Single table inheritance is default, and table per class is optional so all JPA vendors may not support it.

How many inheritance mapping strategies are there in JPA?

JPA and Hibernate support 4 inheritance strategies which map the domain objects to different table structures.


1 Answers

As said

So please bear with me

So my answer will be based on what you said

All roles are hard-coded... There is an abstract class Role...

If There is an AbstractRole class, so i suppose some properties defined in AbstractRole class are inherited by all of subclasses

@Entity
/**
  * Which strategy should we use ??? keep reading
  */
@Inheritance
public abstract class AbstractRole implements Serializable {

    private Integer id;

    private String commonProperty;
    private String otherCommonProperty;
    private String anotherCommonProperty;

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id;
    }

    // getter's and setter's

}

Now Employee (Same approach used by Parent and Student)

@Entity
public class Employee extends AbstractRole {

    private String specificEmployeeProperty;

    // getter's and setter's

}

But which inheritance strategy To use ???

InheritanceType.JOINED

  • Subclasses have a complex mapping: many properties, other relationships as @OneToMany, @OneToOne, @ManyToMany and so on
  • If you use Hibernate as JPA provider, keep in mind it does not support discriminator column. See here
  • A simple query will UNION ALL all of Table defined for each subclass. Maybe you just want to retrieve just one subclass and you will see performance issues. Here goes an workaround.

InheritanceType.SINGLE_TABLE

  • Subclasses have a simple mapping. Not so many properties and minimal amount of other relationships
  • resolution of concrete class at runtime
  • It deals with a lot of nullable columns once all of subclasses share The same Table
  • Faster performance

Now let's see

There is a Person with role management methods such as addRole(), removeRole(), hasRole(), getRole(), getRoles(), among other things

Well, if i see something like addRole method, i suppose Person has Either @OneToMany or @ManyToMany relationship with AbstractRole class. I know, i know... You have @ManyToMany relationship but i really advice you split your @ManyToMany into @OneToMany - @ManyToOne relationship. See here how To

@Entity
public class Person implements Serializable {

    private Integer id;

    private List<AbstractRole> roleList;

    @Id
    @GeneratedValue
    public Integer getId() {
        return this.id;
    }

    @OneToMany
    public List<AbstractRole> getRoleList() {
        return this.roleList; 
    }

    // getter's and setter's

}

And finally

Can it be mapped so I can also take advantage of Seam Identity Management

Seam Identity Management allows you implement your own behavior regardless of using JPA or not.

@Name("authenticationManager")
public class AuthenticationManager {

   /**
     * Starting with Seam 2.1+, you should use Credentials instead of Identity
     * To collect your username and password
     *
     * Your JSF Form should looks like
     *
     * <h:inputText value="#{credentials.username}"/>
     * <h:inputSecret value="#{credentials.password}"/>
     */
    private @In org.jboss.seam.security.Credentials credentials;

    /**
      * Login method
      *     must take no arguments
      *     must return a boolean indicating whether the credentials could be verified
      *
      * method name does not mind
      */
    public boolean authenticate() {
        /**
          * Logic To verify whether credentials is valid goes here
          */
    }

}

And define your authenticate-method in /WEB-INF/components.xml

<security:identity authenticate-method="#{authenticationManager.authenticate}"/>

About your mapping It depends on business requirement, customer needs and so on... but I Think it can be simpler as shown above

Good lucky!

like image 78
Arthur Ronald Avatar answered Oct 19 '22 04:10

Arthur Ronald