![]() | This page and the inheritance features in Cayenne are a work in progress. Features are described here which are not yet available in a release of Cayenne. They are subject to change before final release. |
Inheritance is a powerful feature and can significantly simplify your code, but it does take a little time to fully understand how it works within an object mapping framework like Cayenne.
We will use a simple example from school database to illustrate the concepts: a Person could be either a Student or a Teacher and so in Java you'd want to express these as a Person class with Student and Tutor as two subclasses. The Java class structure (the object entities) looks roughly like this:
public abstract class Person extends PersistentObject {} public class Teacher extends Person {} public class Student extends Person {}
Some possibilities are immediately obvious: you may wish to write a messaging system which sends emails to both Students and Teachers. Rather than deal with the two classes separately, now you can move the emailAddress attribute to the Person superclass. Your messaging framework can now work with Person, irrespective if they are students, tutors or any other subclasss of Person. Or perhaps you want to be able to link book loans to both Students and Teachers. You can do this by moving the relationship with book loans into the Person superclass.
Notice that in the example above, Person is an abstract class. That is, you cannot commit a person who is neither a Student nor a Teacher. But sometimes it is useful to make the superclass concrete and not abstract. This allows you to store a Person without yet giving them attributes from either subclass. Perhaps they are simply a person for whom we wish to store some contact information. Maybe later on they will become a Student and we will add those attributes. Because Java does not have multiple inheritance, it is not possible for a person to be a Student and a Teacher at the same time. Interfaces are probably a good way to address that type of model and will be discussed later.
Cayenne supports three types of inheritance at the database level. Each is useful for different reasons depending on storage criteria, database optimisation and so on. But it is important to note that for the most part, these choices don't change the way your classes are modelled in Java.
One database table or view (dbEntity) is used for all the subclasses and the superclass. So Person, Student and Teacher are all mapped against the one dbEntity. If an attribute appears in Student but not in Teacher (for example the hasScholarship attribute) then that column in the database is set to NULL for all Teachers committed. Naturally, the columns found only in the subclasses cannot be restricted to 'NOT NULL' in the database.
This type of inheritance works well when there are only a few attributes which are different between the two subclasses and you don't mind having all the records from the subclasses combined in one table.
In Cayenne modeler you create this type of inheritance by pointing the superclass and subclass both to the same dbEntity. You must also define a class designator column which allows Cayenne to determine which subclass any particular record in the table belongs to. This column can only be NULL if the superclass is not abstract.
Superclass: abstract or concrete
Class designator column: required
Primary key: single column shared by superclass and subclass
Here you will need one table per subclass. So your database will have Student and Teacher tables. Any attributes in the Person object entity will need to be present in both the database tables.
This type of inheritance is best used when there are few attributes which are found in the superclass. Because the superclass is abstract you cannot have a relationship to other entities. For instance if you want to relate both Students and Teachers to library Books borrowed, then it would be useful to have a single relationship from Person to Books. That is possible in vertical inheritance.
Normally if you use horizontal inheritance you will set your superclass to be abstract, but this is not a requirement. If it is a concrete class, then it is important to remember that you cannot create and commit to the database object entities of this superclass, since Cayenne would not know which table to add the record to. However you are able to create and commit Students and Teachers, fetch and edit them as concrete Person objects and then commit them back the database as Person objects, since Cayenne now 'knows' what subclass they belong to.
Superclass: abstract or concrete (usually abstract)
Class designator column: not required
Primary key: in each subclass (unique across all subclasses)
This final approach requires one table per subclass plus one table for the superclass. All attributes found in the superclass are stored in this additional table. This is particularly useful when you have lots of common attributes or relations to other entities. Perhaps Person is subclassed by Student, Teacher, Parent, AdminStaff, Visitor and Applicant. But all these entities are allowed to borrow books from the library. Now, rather than creating 6 relationships between each of these tables to the Loan table, you can create a single relationship between Person and Loan.
Superclass: abstract or concrete
Class designator column: required
Primary key: in superclass. Copied into subclass to form the one-to-one join.
Different words are used sometimes to describe the implementations of inheritance:
WebObjects | Hibernate | JPA |
---|---|---|
Single table | table-per-class-hierarchy | single table |
Horizontal | table-per-concrete-class | table per class |
Vertical | table-per-subclass | joined |
Sometimes you don't need inheritance but rather you want Cayenne to give you a simple way to define an interface for your classes. The regular Student and Teacher classes both need to implement emailAddress attributes so that you have a consistent way to access that attribute.
public interface Address { public String getEmailAddress(); } public class Teacher extends PersistentObject implements Address {} public class Student extends PersistentObject implements Address {}