Course Content‎ > ‎

Section 10: Hibernate


Object-Relational Persistence - Introduction

The concept of Persistence should be well known to readers at this stage. The vast majority of server-side applications (and indeed client-side for that matter) require persistent data. If an information system didn't persist data when the server machine were shutdown, then the information system would be of little practical use. As discussed previously, database management systems (DBMS) are the standard mechanism for handling persistence in server-side systems.

Relational database management systems are designed to be understandable in a human way, viewing information in tabular form. They are not specific to Java, nor indeed to a particular application. Relational technology provides a way of sharing data among different applications or among different technologies that form part of the same application. Hence, the relational data model is often the common enterprise-wide representation of business entities.

However, if you are working with object-oriented programming and relational databases, you will surely have noticed that these are two very different technologies. The relational model deals with tabularised sets of relational data, in rows and columns. The object-oriented model deals with objects, their attributes and their associations to each other. As soon as you want to make objects persisent using an RDBMS you will notice: there is a rift between these two models, the so called object-relational gap.

Let's take a practical example with some code snippets. First let's consider a simple JavaBean:

/** * Customer JavaBean */ public class Customer { private String ID; private String surname; private String firstname; private String email; // Constructor public Customer(long ID, String surname, String firstname, String email) { this.ID = ID; this.surname = surname; this.firstname = firstname; this.email = email; } // Now the get methods public String getID() { return ID; } public String getName() { return firstname + " " + surname; } public String getSurname() { return surname; } public String getFirstname() { return firstname; } public String getEmail() { return email; } // And some set methods public void setID(String value) { ID = value; } public void setSurname(String value) { surname = value; } public void setFirstname(String value) { firstname = value; } public void setEmail(String value) { email = value; } }

If we wanted to store this data persistently in our RDBMS, we could create a table called DBCUSTOMERS with four columns: ID, FIRSTNAME, SURNAME, EMAIL. We could then create a utility class which executes our SQL via JDBC to populate, update and retrieve information from this table. It might have seperate methods such as below:

public boolean addCustomer(Customer customer) { // JDBC Connection and statement setup..... PreparedStatement pstmt = con.prepareStatement("INSERT INTO CUSTOMERS (ID,SURNAME,FIRSTNAME,EMAIL) VALUES (?,?,?,?)"); pstmt.clearParameters(); // Clears any previous parameters pstmt.setString(1, customer.getID()); pstmt.setString(2, customer.getFirstname()); pstmt.setString(3, customer.getSurname()); pstmt.setString(4, customer.getEmail()); pstmt.executeUpdate(); // handle closing down of connections etc. }

In this rather simple example, we map the object into a relational format by taking all of the fields of the object and storing them in the DBCustomers table columns. Pretty easy!

This may be easy for an object with a small number of fields - but consider this for an object with many seperate fields. Now take it a step further: what about associations? For example, perhaps we wish to extend our Customer object to include a Vector of Customer billing addresses.

... private String email; private Vector billingAddresses; .... public Vector getBillingAddresses() { return billingAddresses; } public void setBillingAddresses(Vector value) { billingAddresses = value; } ....

So now we have a 2-dimensional data type (Vector) which we will need to store in our database system. How do we handle this? Perhaps, we might take the billing address information and store it within a nested table field in a column called BILLINGADDRESSES. Alternatively, we could create a seperate table called CUSTOMERBILLINGADDRESSES, which has fields CBA_ID, ADDRESS, CUST_ID. CUST_ID in this scenario would be a foreign key to the CUSTOMERS table. As rapidly becomes apparent, the conversion between our objects and relational storage starts to become more cumbersome.

In fact, the example above could still be considered simplistic. In reality the Customer object, might have associated Order objects. How are these represented in the database? Most likely seperate tables, with a primary-foreign key relationship. However, if your Customer object is to be stored, do we also store the Orders? Automatically? Manually? The same applies for loading - let's say we load the Customer object from the database, do we also load all of the Orders? Perhaps, those Orders in turn have their own object associations. Would we end up, very inefficiently, loading huge quantities of information from the database, where all we were looking for was the customer's name?

As you can see, the object-relational gap quickly becomes very wide if you have large object models. In fact, there have been studies that showed that about 35% of an application code was produced by the mapping between application data and the datastore. So let's look at our options!


Approaches to Handling Data Storage

Hand-Coding a Persistence Layer with SQL/JDBC

Arguably the most common approach to Java persistence is for developers to handwrite the JDBC code to interact with the database. After all, most developers are familiar with DBMS systems (as you are hopefully!), SQL, database relationships and JDBC. Moreover, it is possible to use design patterns, such as DAO (not covered here!) to hide much of the complex JDBC code from the business logic.

However, the work involved in manually coding persistence for each class is considerable, particularly where the object structure is complicated or where multiple SQL dialects need to be supported. The intial setup involved in this work, can take up a large part of the overall development effort. Additionally, if changes are later made to objects requiring persistence, a hand-coded solution requires more attention and maintainance effort.


Using Serialization

Java has a built-in persistence mechanism: Serialisation provides the ability to write objects to a byte-stream, which may then be persisted in a file or database. However, serialised objects can only be accessed as a whole; it's impossible to retrieve any data from the stream without deserialising the entire stream. Thus, this form of persistence is not suitable for search operations or arbitrary data retrieval. For example, with the Customer object, we would not be able to change the email address for the Customer without deserialising the entire object, making the change and serialising the Customer again.

Clearing, serialisation is inadequate as a persistence mechanism for high concurrency web and enterprise applications.


Object-oriented database Systems

Since we work with objects in Java, it would be ideal if there was a way to store those objects in a database without having to bend and twist the object model at all. In the mid-1990s, new object-oriented database systems gained attention. An object-oriented database management system (OODBMS) is more like an extention to the application environment than an external data store. OODBMS offer seamless integration into the object-oriented application environment. This is different from the model used by today's relational databases, where interaction with the database occurs via intermediate SQL.

However, OODBMS systems really taken off, beyond some minor niche markets such as Computer Aided Design/Modelling and scientific computing. Rather, than look at the reasons, we will just note at this point that OODBMS are not a popular solution and the majority of developers will be working with Relational Database Systems.


Object/Relational Mapping (ORM)

Object/Relational Mapping (ORM) is the automated (and transparent) persistence of objects in a Java application to the tables in a relational database. This is achieved using metadata that describes the mapping between the objects and the database. This mapping can be achieved in either direction (OO App->Database or Database->OO App). With a good ORM, you have to define the way you map your classes to tables once - which property maps to which column, which class to which table etc. After this, you should be able to do things like this:

orm.save(myCustomer);

This will automatically generate all the SQL needed to store the object. An ORM allows you to load your objects just as easily:

myCustomer = orm.load(Customer.class, customerID);

ORM implementations are very complex, with quite a considerable learning curve. Much of the development effort moves from generating JDBC object-relational, manual-mapping statements to setting up ORM implementation configuration files. Why should we introduce another new complex infrastructural element into our system?

  • Productivity - Persistence related code can be the most tedious code in a Java application. Hibernate eliminates much of the grunt work and let's you concentrate on the business problem -> Reduced development time.

  • Maintainability - Fewer lines of code makes the system more understandable since it emphasises business logic rather than plumbing. More importantly, a system with less code is easier to modify based on changing external specifications. In hand-coded persistent systems, a change to objects always involve changes on the DBMS (and vice versa). However, automated object/relational persistence substantially reduces the number of lines of code and encourages code mofication.

  • Performance - A common claim is that hand-coded persistent can always be at least as fast, and often faster, than automated persistence. While this could be considered true, in the sense that assembly code can always be at least as fast as Java code. However, in a project with time contraints, hand-coded persistence does not always allow such optimisations. Hibernate allows many more optimisations to be used all of the time automatically.

  • Vendor Independence - An ORM abstracts your application away from the underlying SQL database and SQL dialect. If the ORM mechanism supports a number of different databases (most do!), then this confers a certain level of portability on your application.

Hibernate is by far the most popular ORM available on the market today (and it's free!) - it is this ORM upon which we will focus the rest of this section.


Hibernate

Hibernate is currently the most mature and complete open-source, object relational mapper available. It was developed by a team of Java software developers around the world. It provides an easy to use framework for mapping a object-oriented domain model to a traditional relational database. Hibernate not only takes care of the mapping from Java classes to database tables (and from Java data types to SQL data types), but also provides data query and retrieval facilities and can significantly reduce development time otherwise spent with manual data handling in SQL and JDBC.

So, just how easy is it to use Hibernate?

Let us again consider the JavaBean shown in the previous section, representing a Customer of one of our developed applications:

package edu.ee.beans; /** * Our Customer JavaBean (POJO) * @author David Molloy * */ public class Customer { // The fields - type these in first before doing the automatic // code generation steps below private int id; private String username; private String password; private String firstname; private String surname; private String email; // Constructor using all of the fields // Note: You can automatically generate these by Right-Clicking // and selecting: Source -> Generate Constructor using Fields public Customer(int id, String username, String password, String firstname, String surname, String email) { super(); this.id = id; this.username = username; this.password = password; this.firstname = firstname; this.surname = surname; this.email = email; } // A blank constructor.. we can alternatively use this and set our // fields using the setter methods public Customer() { } // Now all of the getter and setter methods // Note: You can automatically generate these by // Right-clicking and selecting: Source->Generate Getters/Setters public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }

Utilising a mysterious object known as the Hibernate Session, persisting our Java object from the last section is quite simple to handle.

Customer customer = new Customer(); customer.setUsername("smithj"); customer.setPassword("mypass"); customer.setFirstname("John"); customer.setSurname("Smith"); customer.setEmail("john.smith@dcu.ie"); Session hibernateSession = HibernateUtil.getSession(); hibernateSession.save(customer);

This final line of code saves the state of our new Customer object to the database. Naturally, there is a little more plumbing work that we will need to do behind the scenes (What database are we using? Which table are we storing in? etc.) We will also need to modify the Customer.java bean above slightly. However, this is in essence how simple it is (once we set everything up!).


But I just learned a bunch of SQL! Where is it in this example?

If you go down the Hibernate route you’re not going to use it directly any more! However, to use Hibernate effectively, a solid understanding of the relational model and SQL is still required. You need to understand the relational model, primary/foreign key relationships and data integrity – all topics we have covered earlier.

In addition, the SQL is still working behind the scenes. Our relational database still expects “commands” in the form of SQL. Effectively, Hibernate will automatically generate the following SQL (and communicate it to the database):

insert into CUSTOMERS (ID, USERNAME, PASSWORD, FIRSTNAME, SURNAME, EMAIL) values (1, ‘smithj’, ‘mypass’, ‘John’, ‘Smith’, ‘john.smith@dcu.ie’);

You mentioned plumbing work. What else is involved to get this working?

There are a few other steps involved, such as downloading and using the Hibernate JAR files and setting up the HibernateUtil class we are going to use. In addition, we are going to need a global configuration file to describe the database details for all our classes (database address, type of database, username, password etc). However, we will return to this configuration file later, when we do a practical example.

Of more interest right now, is the object relational mapping configuration.


Object relational mapping? What do we need this for?

Put simply, our Java application can’t entirely guess exactly what database structure we want. However, it can certainly do a lot of the work for us. However, we might need to specify the name of the table we wish to store our object in, the individual names of the columns, their lengths and also indicate some of the relationships.

Previous to Hibernate Version 3.0, Hibernate mappings were handled via XML files, which described the tabular structures that Java objects should map to. A pre 3.0 configuration file might look similar to the following:

<hibernate-mapping> <class name=”edu.ee.Customer table=”Customer”> <id name=”id” column=”id”> <generator class=”native”/> </id> <property name=”username” column=”username”> <property name=”password” column=”password”> <property name=”firstname” column=”firstname”> <property name=”surname” column=”surname”> <property name=”email” column=”email”> </class> </hibernate-mapping>

This configuration file tells Hibernate how to map our Customer object to the corresponding Customer table. It indicates that all fields should be stored in columns of the same name as the field. In addition, it indicates that “id” should be the primary key and should be automatically generated.

As can be seen, these configuration files are cumbersome, require an extra, error-prone file to be created and get very large when a large number of Java classes need to be persisted. More recent versions have utilised JPA (Java Persistence API) Annotations, which were introduced in Java 5.

Basically, an annotation allows you to add detail and information about a Java class, without damaging, disturbing or changing any of the code. Rather than having a separate mapping configuration file, we can “annotate” our JavaBeans by directly putting the mapping information around our code.

Consider again our JavaBean:

package edu.ee.beans; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; /** * Our Customer JavaBean with Annotations (POJO) */ @Entity @Table (name="Customer_David123") public class Customer { private int id; private String username; private String password; private String firstname; private String surname; private String email; public Customer(int id, String username, String password, String firstname, String surname, String email) { super(); this.id = id; this.username = username; this.password = password; this.firstname = firstname; this.surname = surname; this.email = email; } public Customer() { } @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getSurname() { return surname; } public void setSurname(String surname) { this.surname = surname; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }

Source: Customer.java


The JavaBean is identical to before, with three additional lines.  The @Entity@Id and @GeneratedValue tags are JPA annotations.  @Entity tells Hibernate that it will be responsible for persisting this JavaBean.  @Id identifies the primary key and @GeneratedValue indicates that the primary key will be automatically generated.  By default and unless otherwise configured, all fields are assumed to be matched to columns of the same name (hence there are very few annotations in this example).

In short, in Hibernate 3.0 onwards, those three entity tags provide the exact same configuration as all the configuration in the previous XML file (with the added benefit of one less file).  Instead of using an external file, each Java class maintains its own mapping information, which is much more natural, and much easier to maintain on a class by class basis.  We will look at JPA annotations in greater detail later.


But what exactly does all this mean? Where is the benefit? Working with JDBC wasn’t all that bad… was it?

Like so many other Java technologies, Hibernate will initially take a little time to learn.  The temptation to simply continue manually writing JDBC SQL code is a difficult practice to refrain from.  However, in the medium term, Hibernate will greatly enhance your ability to write database-backed applications in Java. 

Want to change to a different database system (RDBMS) which has a considerably different implementation of SQL?  
Hand-written JDBC Code: Big problem!  Rewrite of most embedded SQL.
Hibernate: No problem! Change three lines in a Hibernate configuration file! 

Fed up manually creating table structures using the ‘CREATE TABLE’ statement?  
Hand-written JDBC Code: Keep doing it (or use a UI)
Hibernate: ONE line of code can be used to create ALL of your tables automatically, in whichever database dialect you have configured.

Tired of constantly getting database errors because you are moving between Java objects and relational database tables? 
Hand-written JDBC Code: No other option
Hibernate: work with objects 100% of the time and never write SQL manually again!


Alright alright! I’ll do it! Just show me how!

The following section provides a full example on getting a Hibernate-based application running using Eclipse and Tomcat.


Getting Started

There are a number of requirements for this Hibernate example. Fortunately, you should have most of these already.

  1. A JDBC compliant database: Hibernate will work with all popular databases. 
  2. JDK 5.0 or better: as annotations didn’t exist before Java 5. 
  3. JDBC Drivers for your database: the JDBC drivers are used by Hibernate to connect to the database. 
  4. Eclipse: our development environment
  5. Hibernate: we require the various JAR files and libraries which form Hibernate.
  6. A hibernate.cfg.xml file on your classpath
  7. HibernateUtil.java: this is simply a helper class we can use as a broker to use Hibernate.  It will save us lines of code when we regularly use Hibernate in our code.  Contains a number of methods for starting up Hibernate sessions, recreating database structures, handling transactions etc.

All of the steps are provided below in further detail.  Following these steps is a Hibernate Template Eclipse Project file and instructional video to explain how to put everything together.


Step 1: JDBC Compliant Database

The very purpose of using Hibernate is to store application information into a relational database. Participants of this module should use the existing database and schema, which were already used in the ‘Databases’ and ‘JDBC’ chapters.

Database IP: 136.206.35.131

Username: ee_user

Password: ee_pass

Schema: ssd

For participants who would like to set up their own database, I would recommend using MySQL. MySQL is free, installation is easy and there are some pretty useful UI tools for administering MySQL databases.


Step 2: JDK 5.0 or better

The details for installing the JDK have already been described can be found the downloads section under: Installing JSE and JEE. This should already have been installed from earlier chapters.


Step 3: JDBC Drivers

While we have already installed Java and have a database ready for use, we require JDBC drivers to enable our Java applications to “talk to” the database. In order for a Java program to connect to a database, the Java environment needs access to the appropriate JDBC drivers. 

Since there are a huge range of available databases, we need to specifically download the JDBC drivers relevant to our database and version of Java. However, we have already done this, in the ‘JDBC’ chapter. We will also contain the latest JDBC driver for Oracle in the ‘Hibernate Template Project’ that will be provided at the end of these steps.

Manual Setup Link: ojdbc6.jar     (no need to download unless you are setting up manually)


Step 4: Eclipse

Eclipse is the development environment we have been using for this module and this continues to be the case. We will be creating web applications in a manner identical to before and the Eclipse environment has already been setup for this purpose. For more detailed steps on installing and setting up Eclipse please see: Installing Eclipse

Steps 3 and 5 require the inclusion of JAR libraries files in our projects. This is also handled as before.

  1. Place the driver JAR file in the WEB-INF/lib directory of your application
  2. Right-click your project name -> Properties -> Java Build Path -> Libraries (tab) -> Add JARs -> Navigate and select your JAR to include -> OK
We will be providing a ‘Hibernate Template Project’ at the end, so you may not be required to manually include JAR files unless you wish to do so.


Step 5: Hibernate

In order to use Hibernate we require a number of JAR files, which contain both Hibernate and Hibernate Annotations support. The benefit of using annotations should be apparent from the previous example and so we will need some additional libraries to use this functionality. 

At the time of initially writing these notes, the most recent non-alpha versions of these JAR files where used.  These were available from http://www.hibernate.org .  We are using:

  • Hibernate Core Version 3.6.3 (central Hibernate libraries)
Hibernate Annotations is bundled with Core from version 3.5.x onwards.  If you were using an older version of Hibernate Core you would require a separate download of Hibernate annotations.  


In addition, we will require some support JARs that Hibernate in turn utilises. There can be quite a number of these, although it is possible that some of these might already be used for other reasons in an application.  For example, slf4j (Simple Logging Facade For Java) is commonly used for logging and would often by used by general applications for the purpose of logging.  These JAR files are also bunded with the Core package downloadable from the Hibernate website (you will find them in a directory such as .....Hibernate-3.3.2/lib/required).  The one exception is the sl4j-simple-1.6.1.jar file which was not bundled with Hibernate and needed to be downloaded from the site http://www.slf4j.org  (I am unsure why it is not bundled with Hibernate directly - a similar slf4j file is included, but this does not contain all of the classes we require).

In fact, to get Hibernate "up and running" we are going to use about a dozen JAR files including the Hibernate libraries, support libraries and our JDBC driver.  However, to keep things simple and to avoid versioning problems, we will provide a blank 'Hibernate Template Project' which we will use to get started.

We will return to this completed template at the end of the steps.  In addition, a video will be provided to show you how this was all completed, in case you feel like manually working through the steps.


Step 6: A working 'hibernate.cfg.xml'

The next step we require is the overall global configuration file we will use for Hibernate. This is a special configuration file that tells Hibernate:

· where our database is

· what database driver to use to connect to the database

· what type of database "dialect" to use

· what the connection URL is

· what the database username and password are 

There are some other aspects which can be configured, but these are the principal configurations in the hibernate.cfg.xml file. A sample configuration file is contained in the tutorial, which comes bunded with the Hibernate Core module. 

However, instead we will provide a completed configuration file we up for our particular needs. 

You should not try to learn this configuration file – you will not be asked this in an examination. However, you could be asked to indicate what configuration is supplied by it.

hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class"> oracle.jdbc.driver.OracleDriver </property> <property name="connection.url"> jdbc:oracle:thin:@136.206.35.131:1521:SSD </property> <property name="connection.username">ee_user</property> <property name="connection.password">ee_pass</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">0</property> <!-- SQL dialect --> <property name="dialect"> org.hibernate.dialect.Oracle10gDialect </property> <property name="transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </property> <!-- Enable Hibernate's current session context --> <property name="current_session_context_class">thread</property> <!-- Echo all executed SQL to stdout --> <property name="hibernate.show_sql">true</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> </session-factory> </hibernate-configuration>
Source: hibernate.cfg.xml

Step 7:HibernateUtil

We do not actually need a HibernateUtil helper class. However, it saves us writing a great deal of repeated code in other classes as much of the work is centralised here.


HibernateUtil is a support or helper class that we will write to directly interact with Hibernate. It contains a number of methods, which in turn call the appropriate Hibernate methods.  Let us first take a look at the code for this HibernateUtil.java class:

package edu.ee.hibernate; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; import org.hibernate.tool.hbm2ddl.SchemaExport; import edu.ee.beans.Customer; public class HibernateUtil { private static SessionFactory factory; public static Configuration getInitializedConfiguration() { Configuration config = new Configuration(); /* add all of your JPA annotated classes here!!! */ config.addAnnotatedClass(Customer.class); config.configure(); return config; } public static Session getSession() { if (factory == null) { Configuration config = HibernateUtil.getInitializedConfiguration(); factory = config.buildSessionFactory(); } Session hibernateSession = factory.getCurrentSession(); return hibernateSession; } public static void closeSession() { HibernateUtil.getSession().close(); } public static void recreateDatabase() { Configuration config; config = HibernateUtil.getInitializedConfiguration(); new SchemaExport(config).create(true, true); } public static Session beginTransaction() { Session hibernateSession; hibernateSession = HibernateUtil.getSession(); hibernateSession.beginTransaction(); return hibernateSession; } public static void commitTransaction() { HibernateUtil.getSession().getTransaction().commit(); } public static void rollbackTransaction() { HibernateUtil.getSession().getTransaction().rollback(); } public static void main(String args[]) { HibernateUtil.recreateDatabase(); } }

Source: HibernateUtil.java


These methods are:

  • getInitializedConfiguration(): This method initialises and configures the Configuration object.  This is the single, central place where all of our JPA annotated classes are added to the Hibernate configuration.  Even if we decorate our JavaBeans with JPA annotations, if those beans are not added to the Hibernate Configuration object before the SessionFactory is instantiated, then Hibernate will not manage persistence for those beans.  
  • recreateDatabase(): After the configuration of our Configuration object, Hibernate can generate a database create script based on the various JPA annotated classes that we have added.  In simple terms, we can use this method to automatically generate the various 'CREATE TABLE' statements required to create the database structure to persist our data.  
WARNING: This is a dangerous, but useful method. It will drop and create anew all of the tables for each of our annotated beans. This could be considered ‘dangerous’ as if it were run on a live table, containing data, it would result in the data being lost.
  • getSession(): The key to performing database operations is the magical Hibernate Session object. This lightweight component is instantiated by a SessionFactory. When we are calling Hibernate operations, we will always open a session.
  • beginTransaction(): With the exception of a few query calls, database manipulations are performed within the scope of a transaction. This facilitates our code to either commit or rollback any changes made within the transaction. Commit and rollback have already been discussed in the ‘Databases’ and ‘JDBC’ chapters and the same principles apply.
  • commitTransaction(): Committing a transaction is achieved by calling the commit() method on the Hibernate Session’s transaction.
  • rollbackTransaction(): In the situation where something might go wrong, the transaction may be rolled back. 
  • closeSession(): When we have completed our Hibernate operations, we close the Session object using this method.
You should not attempt to “learn off” the HibernateUtil.java file. If you were asked a question requiring the use of HibernateUtil then you will be provided this exact class in its full form. However, you should understand each of the various methods to the level that they have been described above.


You now have all of the tools, libraries and helper classes to run the examples in the rest of this chapter. More importantly, you have the explanation of all of the components we will be utilising during these examples. 


Getting Started - Here's one I prepared earlier!

While the most masochistic of students may prefer to attempt to manually create the infrastructure described in the seven steps above, our main concern is in relation to getting working with Hibernate as quickly as possible. For this purpose, we have provided an Eclipse project file, which contains all of the appropriate jar files, the HibernateUtil.java file, the hibernate.cfg.xml file, the sample annotated Customer.java bean and build path configuration for Eclipse. 

Importing this project to your Eclipse Workspace
  • Download the hibernate_template.zip to your local harddrive
  • In Eclipse, select File -> Import -> General -> Existing Projects into Workspace -> Next
  • Select Archive File -> Browse to the hibernate.zip file -> Select the Project -> Next
Note: The project will not be shown if it already exists in your workspace. If this happens you will receive a message such as “Some projects were hidden because they exist in the workspace directory”.

We now have all the infrastructure in place to start implementing some practical Hibernate examples. For most of the following examples, we will simply be using normal Java classes (with main methods) for demonstration purposes. However, we could just as easily embed the same code in a servlet or JSP.

Important: Personalising your Tables

There is one last step which must be done. As you are all using a shared database and you are all using the same annotation configuration (Customer.java is mapped to a table called ‘Customer_David123’ in this project zip), you should ensure that you map your own Customer.java bean to a unique table name. Otherwise, you will all be simply overwriting each others data, dropping and recreating tables and generally causing each other some confusion.  The solution to this is simple. In your project, navigate to WEB-INF/src/edu.ee.beans and edit Customer.java. Change the line:

@Table (name=”Customer_David123”)

To something unique of your own.


Testing the Hibernate Template Project

You may already have noticed that the HibernateUtil.java class contains a main method which simply makes a call to ‘HibernateUtil.recreateDatabase()’.

Hence, if we “run” HibernateUtil as an application, we expect it to drop and create our database tables for any beans we have added to the Configuration. This project contains only one bean, our Customer.java class. 

After modifying the @table annotation in Customer.java (as mentioned above), right-click HibernateUtil.java and select ‘Run As’ -> ‘Java Application’.

In your console, you should see a considerable amount of logging information, after which point you should be able to try the following using the SQL query tool.

select * from Customer_David123 (change this to your table name)

If your query returns an empty table, then your project worked successfully!  It should be empty as we have not yet added any rows to our database table!

If your query returns an error such as 

ORA-00942: table or view does not exist

then you have a problem -> check the log in the console for errors which might give some indication.


It didn’t work for me!

The most common cause of failure is an exception such as:

“java.sql.SQLException: The Network Adapter could not establish the connection”. This is caused by Hibernate failing to connect to the database. 

There are a number of reasons why a connection might fail:

  • The database might be down. Check the query tool – if this is working then the database is not down. If it is not working, then this is almost certainly the cause.
  • Firewalling: if you are behind a firewall you might not be letting out connections to “strange” ports such as 1521 (the port that the Oracle listener listens on). Check your home or work firewalls to see if the port is being blocked. 
  • DCU Wireless Network: At the time of writing these notes, the DCU wireless network has port 1521 blocked for security reasons. You will not get this example running on your laptop on the DCU Laplan network.
  • Configuration: you may have changed something in the configuration, which has caused a problem. If you have been playing with certain files, then try overwriting them with the original version from the zip file provided.

If you continue to have problems, then feel free to use the mailing list. Please ensure to copy the contents of your console log to the bottom of your email. 



CRUD – Creating, Reading, Updating and Deleting

All database driven applications revolve around the four basic CRUD operations: Create, Retrieve (Read?), Update and Delete (Destroy?).  We have covered these operations previously in SQL with the corresponding INSERT, SELECT, UPDATE AND DELETE statements.  Of course, with Hibernate we are not concerned about the formatting of these SQL statements (in the various different proprietary database dialects) as all such statements are generated automatically. 

For the demonstration of these operations, we will continue to use the Customer.java bean that we created earlier (and included in the template).  We will provide the code snippets for each of the four CRUD operations, followed by an example using all four operations.

Despite, the somewhat complicated setup, hopefully the benefits of Hibernate will immediately become apparent when viewing the following code.  In addition, it is worth considering that with a template, such as that provided, you can quickly get up and running on any project where Hibernate is used for persistence.  


Creating

Creating a new Customer entry in our database table is a relatively straightforward process.  Simply create a new Customer object, populate it with the appropriate data, start a Hibernate session and 'save' the object. 

Session hibernateSession = HibernateUtil.beginTransaction(); Customer customer = new Customer(); customer.setUsername("smithj"); customer.setPassword("mypass"); customer.setFirstname("John"); customer.setSurname("Smith"); customer.setEmail("john.smith@dcu.ie"); hibernateSession.save(customer); HibernateUtil.commitTransaction();
Note: Don't confuse Hibernate Session objects with HttpSession objects from the servlets chapter of the module. The Hibernate 'Session' refers to our communication with Hibernate, whereas our HttpSession refers to the storage of transient information on a client using HTTP to access our web applications.

Retrieving

How do we query and obtain all of the information in our database tables?  As can be expected (and already witnessed in the Databases chapter), querying a database is a little bit more of an involved process than simply creating a new record. 

Let's start with a code example:

Session hibernateSession = HibernateUtil.beginTransaction(); List<Customer> allCustomers = null; Query queryResult = hibernateSession.createQuery("from Customer"); allCustomers = (List<Customer>) queryResult.list(); for (int i = 0; i < allCustomers.size(); i++) {    Customer customer = (Customer) allCustomers.get(i);     System.out.println("Customer name is : " + customer.getFirstname() + " " + customer.getSurname()); } HibernateUtil.commitTransaction();

In this example, we begin a transaction as normal, which obtains a Hibernate Session.  We then declare an object of type java.util.List which will contain all of the results from our query. 

Then, the createQuery method is invoked on the Hibernate Session, with the String "from Customer" being passed in as the argument. 

HOLD ON A SECOND!  This looks like SQL!

While it looks a little like SQL, it certainly is not.  The literal String "from Customer"  is a special type of query statement that is derived from a Hibernate specific query language, HQL (Hibernate Query Language).  We will talk more about HQL later.  But for now, the "from Customer" can be thought of as the object oriented equivalent of "select * from Customer_David123".  

Note: When the HQL statement references the Customer, it is actually referencing the Java class named Customer and not the corresponding database table, in this case Customer_David123. Unlike SQL this is case sensitive - hence 'from customer' will give an error.

Calling the list() or iterate() method of the Hibernate Query object will trigger a database call that will execute the given query, and return a collection of JavaBeans that match the type and criteria of the HQL statement.  As can be seen in the code, the queryResult does not contain a number of rows (such as we were used to dealing with in the JDBC Chapter), but rather contains a List of Customer objects!  The 'for loop' simply enumerates through the List, looks at each individual Customer object and prints out the firstname and surname of each Customer.

Retrieving a Unique Entity

While there are certainly situations where one might want to list all of the data in a particular database table, there are also situations where this would be considered heavy-handed and resource inefficient.

Consider a situation where our table contains a million rows and we know the primary key (in this case, the Customer ID) of the row we wish to obtain.  If we were to return all million rows and proceed to enumerate through them to print out the data we required, there would be major delays, server load problems and network bandwith issues.

The primary key of the Customer, named id, is a unique value that can't be duplicated in the Customer_David123 table.  We defined this primary key constraint when we created the Customer.java bean at the beginning of this chapter.   Hence, if we were to query the Customer_David123table on the id alone, we should never receive more than one record in return  (Note: we could return zero).  

Hibernate uses a syntax that is very similar to that of a JDBCPreparedStatement when it comes to variable injection.  In JDBC PreparedStatements, our code might have looked something like the following:

// All of the code to create connection above here String mySQLQuery = "select * from customer_david123 where id = ?"; PreparedStatement ps = con.prepareStatement(mySQLQuery); ps.setString(1, idVariable); ResultSet rs = ps.executeQuery(); if (rs.next()) { // continue code here.. manually create the Customer object using the various row components... // print out the name of the Customer

In Hibernate, rather than putting a question mark into the HQL String, we use a variable name with a preceding colon.

Session hibernateSession = HibernateUtil.beginTransaction(); String queryString = "from Customer where id = :id"; Query query = session.createQuery(queryString); query.setInteger("id", idVariable); Customer customer = (Customer) query.uniqueResult(); System.out.println("Customer Name = " + customer.getFirstname() + " " + customer.getSurname()); HibernateUtil.commitTransaction();

When we expect a single row to be returned from the database, we use the query object's uniqueResult() method  (multiple rows we use list()as before).


Updating

The process of updating a record is fairly straightforward.  All you have to do is get a Customer object, update some of its information and then pass that updated Customer JavaBean to the Hibernate Session.  As an example, let us update a password which would be a common operation in any online system involving users or customers.

To make the example slightly more difficult, let us provide an example where we change all Customer objects to password 'password'.  To do this, we are really just combining some of the code that we've seen above under 'Retrieving' with a few two extra lines of code.

Session hibernateSession = HibernateUtil.beginTransaction(); List<Customer> allCustomers = null; Query queryResult = hibernateSession.createQuery("from Customer"); allCustomers = (List<Customer>) queryResult.list(); for (int i = 0; i < allCustomers.size(); i++) {    Customer customer = (Customer) allCustomers.get(i); // Now for the extra two lines of code customer.setPassword("password"); hibernateSession.update(customer); } HibernateUtil.commitTransaction();
Note: So far we have seen three similar methods for inserting/updating records in the database. save(), update(), saveOrUpdate(). It can be a little confusing regarding when each one should be used. As a general rule, when you have an entity that does not have a primary key which needs to be saved as a new entry to the database, you should use save(). When you are updating an entity with an existing entry in the database you should use update(). Finally, saveOrUpdate() intelligently combines the saveand update functionality, and automates the process making a decision on which to apply using the general rule.

Deleting

The final CRUD operation is delete (or destroy).  In short, deleting a record is similar to the coding of an update, with the exception of the fact that the word update() gets replaced with the word delete.  If we were to consider the previous update example above and were to change the line:

hibernateSession.update(customer); to hibernateSession.delete(customer);

we would delete all of the customer entities from our table.

More commonly, we are likely to wish to delete one single Customer entity from our database.  The only really important piece of information the Customer needs to contain is the primary key of the record to be deleted.

The following code will delete the Customer entity with a primary key id value of 1:

Session hibernateSession = HibernateUtil.beginTransaction(); Customer customer = new Customer(); customer.setId(1); hibernateSession.delete(customer); HibernateUtil.commitTransaction()

Hibernate CRUD Example

To properly demonstrate Hibernate's CRUD abilities, let us consider a more detailed example, which will show Creating, Retrieving, Updating and Deleting operations.  To deploy this, create a new class called CRUDExample in your WEB-INF/src directory and copy/paste the following text to it. 

It is important that you familiarise yourself with this example. You should play with the code, modify it and try to implement your own CRUD operations. You could be asked to write code like this (after being provided with HibernateUtil and the API documentation) in an examination situation.


The following class (CRUDExample.java) firstly creates the database structure, using HibernateUtil.recreateDatabase().  Following is a brief explanation of the methods:

  • main: The main method is called first.  This method begins a Hibernate transaction and subsequently calls each of the other methods to create, update, delete and retrieve our data.  The Hibernate Session is passed to each of the methods, to save us retrieving the Session object in every method.  After all other methods are called, the transaction is committed and the Session closed.
  • CreateCustomer: This takes in as arguments each of the fields required to create a new Customer entity (apart from the id which is our primary key and is automatically generated by Hibernate).  The method itself simply sets each of these fields and makes a call tosession.save(customer)
  • ChangePassword: This method performs a retrieval firstly to obtain the Customer object which has a username matching the argument passed to the method.  We use variable injection to call the query to return a unique Customer object  (Note: We are assuming that username is unique, even though it is not the primary key).  Once we have obtained the Customer entity, we simply callcustomer.setPassword() and session.update(customer) to complete the change of password field in the database table.
  • DeleteCustomer: This method operates similarly to the ChangePassword() method, firstly retrieving a specific Customer entity.  Instead of doing an update(), we instead perform a session.delete(customer) to delete the Customer entity from the database table.
  • DisplayCustomers: This method lists all of the Customer entities remaining in our database table (there are two, as we created three and deleted one).  This is achieved using a basic HQL statement:  session.createQuery("from Customer"). The for loop simply enumerates through the List of Customer objects and prints out the value in each case.
import java.util.List; import edu.ee.beans.Customer; import edu.ee.hibernate.HibernateUtil; import org.hibernate.Query; import org.hibernate.Session; /** * Example Class to show Hibernate CRUD Operations * @author David Molloy * */ public class CRUDExample { /** * Main method which runs first... we can modify the methods * that are called for different results */ public static void main(String[] args) { Session session = HibernateUtil.beginTransaction(); // Comment this next line if you want to stop dropping and recreating tables every execution HibernateUtil.recreateDatabase(); // First create some customer entities using a method we have created CRUDExample.CreateCustomer(session, "Michael", "Reilly", "reillym", "password", "michael.reilly@email.com"); CRUDExample.CreateCustomer(session, "John", "Smith", "smithj", "somepass", null); // leaving email blank CRUDExample.CreateCustomer(session, "Mary", "Jane", "mjane", "mypass", null); CRUDExample.ChangePassword(session, "smithj", "newpass!"); // Now do an update Customer 'smithj' CRUDExample.DeleteCustomer(session, "mjane"); CRUDExample.DisplayCustomers(session); // Display all of our Customer entities HibernateUtil.commitTransaction(); HibernateUtil.closeSession(); } /** * This method creates a new Customer entity in the database using the * fields provided as arguments */ public static void CreateCustomer(Session session, String firstname, String surname, String username, String password, String email) { System.out.println("Creating new Customer"); Customer customer = new Customer(); customer.setFirstname(firstname); customer.setSurname(surname); customer.setUsername(username); customer.setPassword(password); customer.setEmail(email); session.save(customer); System.out.println("Customer Saved!"); } /** * This method will change the password for a particular username * It also demonstrates both Retrieval and Update operations */ public static void ChangePassword(Session session, String aUsername, String newPass) { // First Retrive the particular customer...let's assume we know the username String queryString = "from Customer where username = :username"; Query query = session.createQuery(queryString); query.setString("username", aUsername); Customer customer = (Customer) query.uniqueResult(); // We now have the Customer entity... so let's change the password customer.setPassword(newPass); session.update(customer); System.out.println("Customer Username = " + customer.getUsername() + " has password " + customer.getPassword()); } /** * This method will delete the customer with the specified username * It also demonstrates both Retrieval and Delete operations */ public static void DeleteCustomer(Session session, String aUsername) { // First Retrive the particular customer...let's assume we know the username String queryString = "from Customer where username = :username"; Query query = session.createQuery(queryString); query.setString("username", aUsername); // Note: We'll assume username is unique so query.uniqueResult is appropriate Customer customer = (Customer) query.uniqueResult(); // We now have the Customer entity... So now let's delete it! session.delete(customer); System.out.println("Customer Deleted!"); } /** * Final method to print out all of our Customers with all their fields * Demonstrates Retrieval only */ @SuppressWarnings("unchecked") public static void DisplayCustomers(Session session) { List allCustomers = null; Query queryResult = session.createQuery("from Customer"); allCustomers = (List) queryResult.list(); for (int i = 0; i < allCustomers.size(); i++) { Customer customer = (Customer) allCustomers.get(i); System.out.println(customer.getId() + ", " + customer.getFirstname() + ", " + customer.getSurname() + ", " + customer.getUsername() + ", " + customer.getPassword() + ", " + customer.getEmail()); } } }

Source: CRUDExample.java


While it is strongly recommended that you deploy and test the example above, some of the output of the Hibernate operations are listed below. 

15:53:07,953 INFO DriverManagerConnectionProvider:109 - connection properties: {user=ee_user, password=****} drop table Customer_David123 cascade constraints 15:53:08,031 DEBUG SchemaExport:377 - drop table Customer_David123 cascade constraints drop sequence hibernate_sequence 15:53:08,078 DEBUG SchemaExport:377 - drop sequence hibernate_sequence create table Customer_David123 (id number(10,0) not null, email varchar2(255 char), firstname varchar2(255 char), password varchar2(255 char), surname varchar2(255 char), username varchar2(255 char), primary key (id)) 15:53:08,093 DEBUG SchemaExport:377 - create table Customer_David123 (id number(10,0) not null, email varchar2(255 char), firstname varchar2(255 char), password varchar2(255 char), surname varchar2(255 char), username varchar2(255 char), primary key (id)) create sequence hibernate_sequence 15:53:08,234 DEBUG SchemaExport:377 - create sequence hibernate_sequence 15:53:08,234 INFO SchemaExport:268 - schema export complete 15:53:08,250 INFO DriverManagerConnectionProvider:170 - cleaning up connection pool: jdbc:oracle:thin:@136.206.35.131:1521:SSD Creating new Customer Hibernate: select hibernate_sequence.nextval from dual Customer Saved! Creating new Customer Hibernate: select hibernate_sequence.nextval from dual Customer Saved! Creating new Customer Hibernate: select hibernate_sequence.nextval from dual Customer Saved! Hibernate: insert into Customer_David123 (email, firstname, password, surname, username, id) values (?, ?, ?, ?, ?, ?) Hibernate: insert into Customer_David123 (email, firstname, password, surname, username, id) values (?, ?, ?, ?, ?, ?) Hibernate: insert into Customer_David123 (email, firstname, password, surname, username, id) values (?, ?, ?, ?, ?, ?) Hibernate: select customer0_.id as id0_, customer0_.email as email0_, customer0_.firstname as firstname0_, customer0_.password as password0_, customer0_.surname as surname0_, customer0_.username as username0_ from Customer_David123 customer0_ where customer0_.username=? Customer Username = smithj has password newpass! Hibernate: update Customer_David123 set email=?, firstname=?, password=?, surname=?, username=? where id=? Hibernate: select customer0_.id as id0_, customer0_.email as email0_, customer0_.firstname as firstname0_, customer0_.password as password0_, customer0_.surname as surname0_, customer0_.username as username0_ from Customer_David123 customer0_ where customer0_.username=? Customer Deleted! Hibernate: delete from Customer_David123 where id=? Hibernate: select customer0_.id as id0_, customer0_.email as email0_, customer0_.firstname as firstname0_, customer0_.password as password0_, customer0_.surname as surname0_, customer0_.username as username0_ from Customer_David123 customer0_ 1, Michael, Reilly, reillym, password, michael.reilly@email.com 2, John, Smith, smithj, newpass!, null

It is useful to look at the automatically generated SQL and compare it to your own knowledge of SQL from the earlier chapter.   Most of it (apart from perhaps the sequences used to automatically generated primary keys) should be fairly familiar to you at this point.  Our own System.out.println statements are here, interspersed with the Hibernate-generated SQL statements, which we elected to show to the logs when we choose this option in our hibernate.cfg.xml file.

Worth mentioning again at this point is the fact that this generated SQL would be entirely different if we were to move to an alternative database.  
In fact, this might be a good time to mention the steps involved in doing this both with and without Hibernate.  Imagine we have two applications, one written in handwritten SQL/JDBC code and the other written using Hibernate. 

Moving Database - Handwritten SQL

  • Rewrite all 'CREATE TABLE' statements and any other data definition statements we require
  • Download and set up the JDBC library jar file for the new database
  • Rewrite potentially every method which contains SQL in any code files
  • Debug and test SQL

Moving Database - Hibernate

  • Download and set up the JDBC library jar file for the new database
  • Edit 5 lines in hibernate.cfg.xml and use HibernateUtil.recreateDatabase() to set up data structure

Both of these situations assume that the new database, schema and login details are set up already.  Using Hibernate, it is possible to port entire applications to a new database in literally minutes.  The SQL code is optimised by Hibernate to be both efficient and correct.

Comments