This article is an exploration of composition and inheritance relationships with Hibernate and Spring. The article begins with boilerplate project setup using Maven2 and presents individual examples of one-to-one, one-to-many and many-to-many relationships, as well as one way to map inheritance to a database schema. JUnit tests emphasize the interesting features of these relationships. By the end of the tutorial, we will have a framework and the tools for building more complex domains from these simple models.
Our initial project setup is essentially identical to that of the previous post in the series, where we use the M2 plug-in for Eclipse to create a simple Maven2 project. For this example, we specify our dependencies (Spring, Hibernate, JUnit), create a Spring application context for Hibernate session management, create a Hibernate mapping file to register our annotated domain and create a pristine database schema (in Oracle).
The pom.xml contains our Hibernate 3.4, Spring 3.0, JUnit 4.4 and Oracle driver dependencies.
The src/main/resources/applicationContext.xml conatins our Hibernate connection and session configuration and our transaction manager.
The src/main/resources/hibernate.cfg.xml is our registry for annotated Hibernate domain Objects.
We can create a pristine database schema from sqlplus.
With that infrastructural boilerplate out of the way, we can begin defining our domain. For this example, our domain consists of libraries, books and authors.
A library can contain any number of books, and books can be present in any number of libraries. A book is written by an author. An author can write any number of books.
One to One
Suppose that a Library has an Address, and the Address must be unique per Library. That is, there cannot be more than one Library for a given Address. We can say that the Library and the Address have a one-to-one relationship.
One way to representing such a relationship with tables in a relational database would be for these two entities to share a primary key. That is, the primary key of one table can have a foreign-key reference to another table.
We first need to declare that our domain will contain a Library and an Address in the src/main/resources/hibernate.cfg.xml.
Suppose we decide that the Library's primary key depends on the primary key of the Address. Our Address object can have a sequence-generated primary key, and the corresponding id of the Library can have a foreign-key dependency on the Address id. You might be asking yourself what would happen if the relationship were reversed, if it would make any functional difference or if the difference is only semantic.
The primary key for Library.java is also specified as a generated value, but it uses a foreign-key generator instead of a sequence. We also need to specify that the primary key is a join column with the Address. This foreign-key id generator and the declaration that the Address can be joined to the Library by their primary keys is the essence of this relationship mapping. You now might be asking yourself what would happen if this relationship were reversed and if it would make sense for an Address to have a dependency on a Library.
We can verify that exactly one unique Address can be associated with a Library with a test case.
One to Many
As previously noted, each Book has a single Author, and an Author can write any number of Books. Such a requirement (while simplistic because in actuality a Book could have multiple Authors) can be mapped to a one-to-many relationship for the purpose of this tutorial.
In our database, this type of compositional relationship can best be represented by a foreign-key dependency from the many side to the one side. Whereas, in our domain Object, an inuitive way to demonstrate this relationship would be for each Author to reference the books she has written and for each Book to have a reference to its Author, the tabular relationship would be indicated as a field only in the composed entity (Book).
Again, we will declare that Books and Authors are part of our domain in src/main/resources/hibernate.cfg.xml.
An Author.java writes Books, and to indicate this relationship, we can use the @OneToMany declaration.
Similarly, a Book.java has an Author, so we can declare this relationship as @ManyToOne. We will also specify how the database resolves this reference, so we declare our @JoinColumn here, similar to how the foreign-key reference is in the Book table in the database.
We do not explicitly reference the foreign-key dependency in the Book table except in the meta-data. In a test case, we can execute a SQL query to get the value from this foreign-key column and compare it to the id that it references in the Author table.
Many to Many
A Library has many Books and a Book can be found in any number of Libraries. In a database, this type of complex relationship cannot be captured by using foreign-key references from Books to Libraries or vice versa. Most problems in computer science can be solved by adding a layer of indirection, and this is no exception. Many-to-many relationships can be represented in a relational database with a cross-reference table.
Fortunately, Hibernate will automatically create such a cross-reference table, so we do not need to register any other Objects with our domain. We can simply annotate our existing domain Objects to indicate that Libraries and Books have a @ManyToMany relationship and that the database can use a @JoinTable to represent this relationship.
With a LibraryBookTest.java, we can demonstrate the difference between how joins are performed between HQL and SQL.
While relational databases represent flat data structures very well, Object inheritance relationships do not naturally map. Suppose we want our domain to contain regular People as well as Authors. An Author is a Person who has written any number of Books. A Person is not necessarily an Author, however.
One way to represent this relationship in a database is to use a single table to represent all types of People and to use a discriminator to differentiate among the various types of people. Such a table would contain all the attributes for all the types of people in the domain, even if the given attributes do not apply to a particular type of person. For our example because we have such a small number of types of People in our system, the SingleTableInheritance model will suffice. For domains that contain deep or broad inheritance trees, this mapping may not be the best and other mappings should be explored.
People are now part of the domain, and we can update our src/main/resources/hibernate.cfg.xml registry.
Here, all types of Person.java will be present in the same database table because of the configured @Inheritance strategy. Different People can be distinguished with a @DiscriminatorType in a @DiscriminatorColumn.
The updated Author.java must also register a @DiscriminatorValue but does not need to specify a primary key or table mapping because they are inherited from the base class.
We can demonstrate the relationship between Person and Author in our AuthorTest.java. These test cases show that an Author is a Person, but a Person is not necessarily an Author.
We now have a framework for easily registering new inter-related entities in our domain and for verifying the relationships between those entities.