Wednesday, September 15, 2010

Starting with Hibernate

This article is about Hibernate, the java library for data persistence. I will attempt to explain some things, and provide some reasonably simple examples which demonstrate its use.

Basic concepts

Hibernate is all about data persistence. That is, the storage of information for later retrieval. Persistence can be handled in a very manual way via direct usage of jdbc through sql calls (java.sql package) or through a more managed way via a data persistence library. Hibernate is one of these libraries
that provides a means to persist data to and from the database.

Database dialects

Hibernate works with practically any database you want to throw at it. You just have to provide the jdbc library to work with it, and the connection strings, and let hibernate know what type of database it is, so that it knows in which way it needs to modify the SQL syntax for that particular flavour/dialect of database.

These configurations are in the hibernate.cfg.xml file, unless you are using instead the properties file to configure your hibernate application, which should be located on the classpath of your application. In these file you should ensure the following properties are correctly configured for your database:

hibernate.connection.url
hibernate.connection.driver_class
hibernate.connection.username
hibernate.connection.password
hibernate.dialect

Because I am using derby (7) (8) for my examples, my file contains the following information:



Data Mapping

Data-mapping is the process of mapping the fields in your classes to columns in your database tables so that the data can be persisted. It involves determining the correct multiplicity of the objects in relation to one another to give you the appropriate cardinality of the associations between the resulting tables. Understanding the concepts of one-to-one, one-to-many, many-to-many is essential to being able to design a non-trivial application. The technical points to data-mapping for hibernate are discussed in great detail in chapter 5, and chapter 7 of the jboss documentation for hibernate.

Hibernate queries

Queries are an essential component of the functionality provided by the hibernate framework. The topic will be expanded on here soon....

Automatic table creation and update

Hibernate can be configured to automatically create and update your schema to the database on each run. This is done using the 'hbm2ddl.auto' property (12). However, while this option is fun to play with, and may save you some time in development, there is general consensus that this should not be used in production situations (13).

Advanced concepts

Lazy loading

Lazy loading of data is enabled (by default) to improve the performance of an application. The idea is only to retrieve data when and if it is needed. If lazy loading is turned off, all associations will be loaded together in one action. Lazy loading is controlled through a mapping attribute, 'lazy', of the class, property, many-to-one, one-to-one, component, subclass, joined-subclass and union-subclass elements. The default for these attributes is 'true' unless the default is otherwise specified through the 'default-lazy' attribute of the 'hibernate-mapping' element.

The valid values for all of the above attributes is simply 'true|false', with the exception of the many-to-one and one-to-one elements, which allows 'proxy|no-proxy|false'.

Lazy loading is great but introduces some additional problems. When a field is lazily loaded, it is done through a a proxy. The proxy, which in hibernate is a subclass of the field being lazily loaded, holds the data that is loaded from the database. The main difference is some functionality that hibernate needs to do the loading. When a field is not lazily loaded, the proxy is not used. The fact that the object that we end up with is a proxy (i.e., class b extends a) and not the expected class (class a) can result in confusing problems (1) if you are not aware of this situation. Problems related to proxies are more fully discusssed here http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/. In this document, we will do some easy tests which demonstrate these issues.

Field Access

Field access is specified through the 'access' attribute of the various configuration elements. This element allows values 'field, property, or ClassName'. By default, property access is used, unless the default is configured otherwise via the default-access attribute of the hibernate-mapping element.
The jboss website says (5) that “The access attribute allows you to control how Hibernate accesses the property at runtime. By default, Hibernate will call the property get/set pair. If you specify access="field", Hibernate will bypass the get/set pair and access the field directly using reflection.”.
This means in fact that you don't even need the getter and setter for your object to be populated with all required data. However, there is a catch, especially when it comes to lazy-load proxy. In the case of lazy loading, we see the lazily loaded field, when directly inspected, always being 'null'. So, in these cases a getter must be used. “The proxy will be loaded when the getter is invoked and the data will be accessible.” (1).

Hibernate 'Hello World'

To start a very basic hibernate application, one needs at least the hibernate dependencies and a basic idea of how the data needs to be structured. In our 'hello world' example, we shall simply store the details (surname, firstname) of a 'User'. This will require no associations with any other objects besides the standard String type which is represented simply by a field in the table. Once we have stored the details of our 'User', we will retrieve them again from the database. Almost without using a single keyword of SQL.

First, in such a simple example, we can write our POJO type class, our User, and once we have done that, we need to show how this needs to map to the database.

File: User.java

package sv.java.contacts;

/**
* This class represents a single user of the system, 
* @author sean
*
*/
public class User
{
    int id;

    String firstNames="";
    String surname="";

    /**
    * default constructor (with at least package visibility) required for hibernate;
    * http://docs.jboss.org/hibernate/core/3.3/reference/en/html/persistent-classes.html#persistent-classes-pojo-constructor
    */
    User()
    {
        super();
    }

    public User(String surname, String firstnames)
    {
        this.surname = surname;
        this.firstNames = firstnames;
    }

    public int getId()
    {
        return id;
    }

    /**
    * gets the first names of this person
    * @return
    */
    public String getFirstnames()
    {
        return firstNames;
    }

    /**
    * get the surname of this person
    * @return
    */
    public String getSurname()
    {
        return surname;
    }
}




As mentioned above, the User class needs the corresponding User data mapping. This we provide in its own User.hbm.xml file

File: User.hbm.xml




As you can see from the very basic mapping, we specify that the firstNames member of class User should map to the 'firstNames' column of the SimpleUser table. The surname member of the class similarly maps to the surname column of the table. We also have an id member which maps to an column called 'UserId', which is an automatically generated ('native'=by the database) integer.

Once we have our class, and its mapping, we just need to use it! Our Hello World is the beginning of our 'Example 2' and will eventually be a contacts manager. Therefore, our main class is called 'ContactManager'.

File: ContactManager.java

package sv.java.simple;



import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import sv.java.contacts.User;


public class ContactManager
{
    public static void main(String[] args)
    { // start our session 
        SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
        Session session = sessionFactory.openSession();

        // we start our database 'save' type transaction, creating a record in the database
        session.beginTransaction();

        User user = new User("Task", "Nancy");

        session.save(user);
        session.flush();
        session.getTransaction().commit();
        session.close();

        // now, we can easily retrieve it again because we know the exact id of the user still (given to us by the save operation)
        session = sessionFactory.openSession();
        User storedUser = (User)session.get(User.class, new Integer(user.getId()));

        System.out.print("User: " + storedUser.getFirstnames() + " " + storedUser.getSurname() +"\n");

        session.close();
    }
}



This is about as simple as a hibernate example can get and still do something potentially useful.

As you can see from the main class, operations occur as follows:

first, we build a session factory, which in turn allows us to create a session, and then open the session.

Second, We begin a transaction. A transaction is a single atomic action according to the database. Until it is committed, nothing is fixed in the database. This allows us to ensure data integrity in the case of errors that may occur half-way through an operation. If we want to, before the commit, we can rollback all operations that have occurred during a transaction.

Third, we create and save our user. The data is now in the database, but not firmly. As mentioned before, until the transaction is commited, the data can still be lost!

Forth, we commit the transaction. The data is now secure in the database.

Fifth, using the id that we have stored in our existing User object (updated by the save operation), we get the object from the database. To prove it, we print out the details.

As mentioned, this is a very simple example. Such a situation will probably not exist in real-life. Usually the schema is much more complicated, and requires data be stored in several different tables, with different associations between the various tables, various multiplicities. The next example explores and solves some of these issues.

Full source for the 'Hello World' project is available here.
All dependencies for both the Hello World project, and the next example are available here.


Example 2

Our next example furthers the idea of the Contacts Manager. This time, our application starts to look a bit more realistic, and a bit more complicated. It shows the implementation of a couple of associations types; one-to-one, one-to-many, and how they can be used in different ways.

The story...

As before, we have a 'User'. This user has many contacts, of which have many different types of contact details (land-line, mobile, home address, etc..). We also allow our user to store his main contact detail (only one!). And we store details regarding his current employment. Even if he is unemployed, we want to know this, therefore, we also have one entry for a users employment-details. One. No more, no less.

The relationships can be seen here, in this quick sketch up of an Entity Relationship Diagram:





Where we have one-to-many relationships, we need to, in practice, have a joining table to allow the association. For example, our user has many contacts. Therefore, we need a UserPerson table, a ContactPerson table, and the joining Users_Contacts table. This need is reflected in our mapping file which you will see soon. See in the next diagram how the relationships are translated to a real-world database schema.







As already mentioned, our mapping files need to contact the information required for hibernate to create the associations between entities. In our example, we have a couple of one-to-one relationships, and a couple of one-to-many relationships.

one-to-one

The simplest of the associations is the one-to-one relationship. And the simplest of these cases is where the primary-key (id) of table A is used to associate with a record in table B using the primary-key (id) in that associating table. Such is the case with the relationship between the 'User' and the 'EmploymentDetail'. In this case, there is no real foreign-key used in the association, just the matching primary-keys.

The association is mapped, in hibernate, in the following way:



Another example of a one-to-one relationship is the association between the 'User' and his single allowed 'ContactDetail'. That is, for example, the users own telephone number or whatever he chooses to store here as his main contact type. In this case however, we cannot use the 'User' table' primary-key to directly identify the ContactDetail, because ContactDetail has its own primary-key which is completely unrelated to the primary-key of the 'User' table. To map the association in the simplistic way as with the 'CurrentEmployment' association above would cause incorrect results, often silently. This could cause you disaster if you do not map correctly, and this mistake is not caught in testing, so be careful! Instead, we need to use another mapping tool, which will give us a foreign-key to the ContactDetail in our 'User' table. In this case, we actually use the 'many-to-one' element, and constrain the foreign-key to be unique ensuring a one-to-one relationship.





one-to-many

The only other association type we use in this example is a 'one-to-many' association. This is implemented as a 'Set' in the java code. Our User object can hold a Set (a HashSet in fact) of Contact objects. A correct mapping for this in hibernate is shown below:



Please note, that there are different ways to implement a one-to-many association (using a bag, or a list).


Full source for 'Example 2' is available here.
All dependencies for both the Hello World project, and example 2 are available here.


References:
1.
advanced hibernate/proxy pitfalls
http://blog.xebia.com/2008/03/08/advanced-hibernate-proxy-pitfalls/
2.
debate: field access vs. property access
http://java.dzone.com/articles/jpa-implementation-patterns-7
3.
field vs. property (annotations)
http://javaprogrammingtips4u.blogspot.com/2010/04/field-versus-property-access-in.html
4.
Why getter and setter methods are evil
http://www.javaworld.com/javaworld/jw-09-2003/jw-0905-toolbox.html?page=1
5.
Hibernate mapping
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html
6.
cheat sheet
http://ndpsoftware.com/HibernateMappingCheatSheet.html
7.
Practicing Geek
http://practicinggeek.blogspot.com/2008/12/using-apache-derby-with-hibernate.html
8.
Java Lobby
http://java.dzone.com/articles/hibernatecfgxml-settings-derby
9.
Xebia – JPA Implementation patterns: Lazy loading
http://blog.xebia.com/2009/04/27/jpa-implementation-patterns-lazy-loading/
10.
Generic Data Access Objects
http://community.jboss.org/wiki/GenericDataAccessObjects
11.
Don't repeat the DAO!
http://www.ibm.com/developerworks/java/library/j-genericdao.html
12.
Miscellaneous Properties
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/session-configuration.html#configuration-optional
13.
Should hbm2ddl.auto be used in production
http://stackoverflow.com/questions/221379/hibernate-hbm2ddl-autoupdate-in-production

No comments:

Post a Comment

Post a Comment