Thursday 31 October 2013

[Java] Mapping relational concepts with Hibernate 4

It is crucial to know that O/R mapping using Hibernate isn't about transforming every table you want to use in your application into a Java class. Notice that relational and object-oriented are two different paradigms. It would be more appropriate to re-think the design of the database tables using Java and object-oriented programming. In this tutorial, which is aimed for first-timers, we will tackle basic O/R mapping using Hibernate 4, in other words, we will see how to join tables with their respective object-oriented representations. I assume that you are able to make and run a basic Hibernate example, otherwise please read the first tutorial "Hello Hibernate 4 with Eclipse Kepler" (http://peace-and-code.blogspot.com/2013/10/java-hello-hibernate-4-with-eclipse.html). In this tutorial, we use Hibernate 4.2.5 (http://sourceforge.net/projects/hibernate/files/
hibernate4/4.2.5.Final/
).

1. Component fields
In object-oriented, it is pretty common to design a class that aggregates a set of attributes and methods to a component object. Developers are highly encouraged to follow this approach for many motives, including but not limited to re-use and consistency of class semantics and roles. Obviously, unless the developer is willingly writing dirty code for any reason whatsoever, it makes no sense using object-oriented to make only a single or no objects at all since slicing the domain into interacting objects consists the essence of the object-oriented paradigm to begin with. For that purpose, Hibernate offers the possibility to define and use component elements to define a primary key or any other field (see Mapping 2).

2. Mapping primary keys
Hibernate uses the value of the primary key to identify the row attached to an entity instance in order to perform an update or a delete on the row, which makes primary keys, in this context, a liaison between data on the database and its object representation. On certain cases though, Hibernate could be instructed via mapping to override this default behavior.
In a mapping file, three elements are used to identify a row among which two are used to map primary keys:
  • <id>: maps a single column primary key. In terms of assignment, this field falls into one of the following three categories:
    • automatically generated using one of the predefined generators[1];
    • manually assigned by the user on the transition from the Transient state to the Persistent state (for more information on object states check the second tutorial "Hibernate 4 object states" (http://peace-and-code.blogspot.com/2013/10/java-hibernate-4-object-states.html);
    • automatically generated using a custom generator specified by the developer.
  • <composite-id>: maps a multiple columns primary key. The identifier must be manually assigned.
  • <natural-id>: regroups a field or a set of fields which is non null and unique for each row in the table, and presumably immutable, unless declared otherwise. This element serves as an alternative to identify a row mostly in case of a database-level generated primary key (select generator class).

2.1. Identifier generator classes
There are several predefined generator classes and you can even define your own generator by implementing org.hibernate.id.IdentifierGenerator[1]. Some of the identifier generator classes include:
  • native: picks a standard generator based on the DBMS;
  • select[2]: retrieves the primary key from the database upon insert, the primary key being generated by a trigger, you need to feed the class with a natural-id;
  • assigned[3]: this is the default class if no generator is specified, it transfers the responsibility of setting the identifier to the application;
  • foreign[4]: uses a foreign key as an identifier
2.1.1. Automatically generated identifiers
In the following example, let a Person entity.
We can opt for a native identifier generator. In that case, using PostgreSQL 9.1.9, Hibernate defines a sequence named hibernate_sequence to assign values to the identifier.
Mapping 1. native identifier generator

In the realm of automatically generated identifiers, it is possible to define a method directly in the database, which generates primary key values, e.g. a trigger, and exempt Hibernate from the responsibility of defining the generator. Then, we declare a select identifier generator, in which case, we should specify a natural-id. Here we use a component to define the natural-id.
Mapping 2. select identifier generator

2.1.2. Custom identifier generators
The user of Hibernate can define his own identifier generator. He has to specify the custom class in the mapping file.
Mapping 3. Custom identifier generator

Then, he has to implement org.hibernate.id.IdentifierGenerator[1].
Java Listing 1. Custom identifier generator

2.2. Assigned identifiers
In case the identifier can't be generated and needs to be manually specified, its generator could be declared as assigned, or not since assigned is the default value for the generator class. Here, the application is responsible for filling the identifier's value before saving.
Mapping 4. assigned identifier generator

2.3. Composite identifiers
If the primary key is composite, it could be mapped to a component declared by a composite-id element. The component class must implement the java.io.Serializable interface. It is possible as well to directly use fields of the entity class. In this last case, the whole entity class will be considered as the identifier and thus have to implement java.io.Serializable as well.
Mapping 5. Composite identifier

For this example, Hibernate generates the follwing SQL code:
SQL Listing 1. Composite primary key


3. Mapping integrity constraints
The example below shows some basic mappings of unique, check (on both class and property level), default and not-null. Note that more constraints can be defined using Hibernate.
Mapping 6. Constraints

The SQL code for creating this table would look like the following:
SQL Listing 2. Constraints


4. Mapping foreign keys and associations
A foreign key is defined as a sign of the association between two relations, for that reason I delayed its mapping to this section.
In Hibernate, associations could be classified, regarding their cardinalities into 3 classes:
  • one-to-one: if the one-to-one is a perfect 1:1 association, its two ends are better be reduced to a single table;
  • one-to-many or many-to-one;
  • many-to-many: this class of associations includes a third relation which each of its rows references the primary keys of the associated rows from both tables.
If we consider the direction of navigation from an entity class to another, associations could be classified into 2 classes:
  • uni-directional;
  • bi-directional.
This last feature is purely object-oriented and has to be considered in the design of the entity classes, even though its irrelevance to SQL. In the example below, we have the following tables:
  • books;
  • authors;
  • publishers;
  • editions;
  • books_authors.
The associations between these tables are:
  • At least one edition for a book {1:1..*}.
  • At most one sequel for a book {1:0..1}.
  • At most one publisher for a book, and a publisher can publish several books. If no publisher is specified, then we assume that the book is self-published. {0..*:0..1}
  • At least one book by an author, and at least one author for a book. This association must be specified by a join table which is in our case books_authors. {1:1..*}
Assuming that the associations are unidirectional, the mapping for this specification would look like this:
Mapping 7. Uni-directional associations

Notice that we put all the classes in a single mapping file.
Since the navigation is specified to be directed in one direction from UniBook towards its associates, UniBook is the only entity class that have references or collections of references to the other entity classes to which it is associated.
Java Listing 2. UniBook class members

In the bi-directional version, the mapping should make the two parties aware of their association.
Mapping 8. Bi-directional associations

Notice that inverse attribute is specified in set element in the case of one-to-many or many-to-many association types. Along with this mapping, the entities must reference each of its associates to fully implement the bi-direction navigation.
After a session in which we inserted two books having several editions, the tables will have the same content regardless of the navigation direction in the mappings.
books
     isbn      |      title      | publisher_id |   sequel_isbn   
---------------+-----------------+--------------+---------------
 9781256983569 | Genesis Project |            2 | 
 9786895321312 | Natura Project  |            2 | 9781256983569

authors
 author_id | first_name | last_name 
-----------+------------+-----------
         1 | Lea        | Munus
         5 | Aeon       | Fruit

books_authors
   book_isbn   | author_id 
---------------+-----------
 9786895321312 |         1
 9781256983569 |         1
 9781256983569 |         5

publishers
 publisher_id | name | address |   phone   |    fax    
--------------+------+---------+-----------+-----------
            2 | AS   | My Land | 858695326 | 459656313

editions
 edition_id | number | edition_date | pages_count |   book_isbn   
------------+--------+--------------+-------------+---------------
          3 |        | 2014-07-29   |         350 | 9786895321312
          4 |        | 2015-08-29   |         355 | 9786895321312
          7 |        | 2017-10-29   |         365 | 9781256983569
          6 |        | 2015-09-29   |         360 | 9781256983569
          8 |        | 2019-11-29   |         366 | 9781256983569

---
External links:

[1] http://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/id/IdentifierGenerator.html
[2] http://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/id/SelectGenerator.html
[3] http://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/id/Assigned.html
[4] http://docs.jboss.org/hibernate/orm/4.2/javadocs/org/hibernate/id/ForeignGenerator.html

No comments:

Post a Comment