Retired Content‎ > ‎

(Retired): Hibernate 2

This content is not examinable so please don't study it if you come across it!

It is merely a place for me to put perfectly good examples and code that isn't currently in the module syllabus.
This content was retired to make room for sections on Testing and Cloud Computing.  This material is still relevant and important for web development and so has been left here.  However, it will NOT BE EXAMINED ON THE EE417 EXAMINATION.  I have left it in the retired section as it may be found useful by some people who end up working in Web Development and need to learn Hibernate in more detail than shown in the 'Hibernate' section.


Hibernate Query Language (HQL)

The immediate thought of the reader of these notes is probably "Hold on!  I've just learned SQL and now you're giving me something new to learn!".  In some respects this is true.  HQL is indeed a different query language to essentially achieve the same thing, persistent storage and retrieval to/from a database.  However, there are a number of differences between SQL and HQL:

  • Language: SQL and HQL are different languages and , while similar, would need to be learned seperately
  • Database Independence: HQL is independent of the underlying database, whereas SQL (due to non standards compliance) is different for different databases.  This means that we can easily move to a new database and not affect HQL code in any way.
  • Relational vs Object Based: SQL is designed to work with relational database tables.  HQL performs data queries in an object-oriented manner.
  • Relationship with Database Structure: When working with SQL, statements refer explicitly to the table and column names in the database.  With the object-oriented Hibernate approach, this is instead handled via mappings (identified in the Beans).  As a result, structural and name changes are easier to handle under HQL, as the HQL code does not need to be affected in any way.

In the second sentence above, we stated that "In some respects this is true".  By implication, "In some respects this is false".  
How? For most operations in Hibernate we will not even have to write HQL.  Specifically, we will be able to use the Criteria API to perform many of the common HQL queries.  However, before looking at the Criteria API, let us introduce some basic HQL.


Basic HQL

In the previous Hibernate section, we have already covered most of the following basic HQL statements. However, they are here for reference, before we use the Criteria API.


As with any other operation in Hibernate involving a database interaction, executing a Hibernate query starts with the Hibernate Session.  The Hibernate Session has a createQuery method which will take any valid Hibernate Query Language String, from which a Query object can be created. 

One simple query which would retrieve all Customer objects from the underlying database, would be performed as follows:

Session session = HibernateUtil.beginTransaction(); Query query = session.createQuery("from Customer");

As we previous described in the initial Hibernate section; to actually get the results of the query, you invoke the list() method of the Query instance.  The List will be a collection of Customer objects, which you can then loop through.

Session session = HibernateUtil.beginTransaction(); Query query = session.createQuery("from Customer"); List customers = query.list(); for (int i = 0; i < customers.size(); i++) { Customer customer = (Customer) customers.get(i); // Now we can do whatever we want with these objects System.out.println("Customer firstname is " + customer.getFirstname()); }
Selecting Specific Elements

While sometimes it is appropriate to return all of the columns in a database query (the equivalent of SQL: "select *"), we will commonly wish to restrict the query to particular fields.  For example, if we were only interested in obtaining the firstnames from the previous query, then we can use the following:

Query query = session.createQuery("SELECT firstname from Customer"); List names = query.list(); for (int i = 0; i < names.size(); i++) { String name = (String) names.get(i); System.out.println("Name is " + name); }

Important to note here is that we are working with String objects here directly, rather than with Customer objects.  This is because, where only one parameter is specified, the result of such a query is a list of elements matching the data type of the property.  In this situation, since the firstname is defined in the Customer class as a String, the List returned from the query.list() method will be a collection of String objects, not a collection of Customer objects.

If more than one property is selected, the elements of the returned List are arrays, with the array elements matching the type and order of the elements as they appear in the select portion of the HQL statement.

HQL WHERE Clause
Query query = session.createQuery("from Customer where firstname='David'); // List results in the usual manner using Customer objects
HQL WHERE Clause (Unique Result)
Query query = session.createQuery("from Customer where id=1"); Customer customer = (Customer) query.uniqueResult();
Variable Injection with Hibernate

Hibernate uses a mechanism which is quite similar to a JDBC PreparedStatement to perform variable injection into HQL statements. 

Query query = session.createQuery("from Customer where id= :idval"); query.setInteger("idval", 1); // the value would probably come from a variable in reality Customer customer = (Customer) query.uniqueResult();
ORDER BY and HQL Alias

Similar to SQL (where we specify a field), we can specify a specific property of our Bean and tell Hibernate to sort the results of our query by that property.  We can additionally choose either an ascending (ASC) or descending (DESC) manner.  

Query query = session.createQuery("from Customer as c order by c.firstname ASC"); List customers = query.list(); // Enumerate the list etc.

In addition, we have demonstrated the use of an HQL 'alias'. These are similar to aliases or synomyns we have encountered in SQL previously.

GROUP BY and HAVING 

Similar to SQL, we can group the results of a query together using the GROUP BY clause.  In addition, we can provide a logical expression through the use of the HAVING clause.  At this point, we don't have any great examples we can demonstrate using only the Customer object.  However, the following demonstrates the use of GROUP BY and HAVING:

Query query = session.createQuery("from Customer as c GROUP BY c.id HAVING c.id > 2"); List customers = query.list(); // Enumerate the list etc.
Batch Calls

For performing deletes and updates on a large number of records, Hibernate provides a fairly simple syntax.  For example, a batch delete, which would delete every customer in the database with firstname 'John' would be:

Query query = session.createQuery("delete Customer where firstname='John'"); int rowCount = query.executeUpdate(); System.out.println(rowCount + " rows were deleted from the database table");

Likewise, to change all Customer passwords, where the password was previously set to 'password' would be:

Query query = session.createQuery("update Customer set password='xy1234' where password='password'");
Hibernate and Native SQL

While we should be focusing on the use of Hibernate, it is worth noting that it is possible to issue native SQL queries usingsession.createSQLQuery(...sql...).  These queries will return a List of objects which must be manually cast to their appropriate types.    However, there should not be much need to use native SQL requests, when we can construct the majority of appropriate requests via Hibernate HQL and/or the Criteria API.


The Criteria API

The motivation behind using Hibernate and HQL is a strong one.  We eliminate the requirement to learn differring versions of SQL supported by the different database systems and can easily switch between these systems with little work overhead. 

However, it is still a query language (albeit an object oriented one) which we need to learn to use.   While HQL does give us the ability to think of the data we require in the form of the Java objects which make up our data model structure the Criteria API takes the idea to an even more intuitive level.

The Criteria API allows the creation of smart, simple and effective queries without the use of any query language (including HQL).


How it works

To use the Criteria API to perform queries based on our Customer, all you have to do is to create an instance of the Customer class, populate the properties on which you wish to search and pass that populated instance to Hibernate.  Hibernate will automatically generate all of the required SQL, execute the query and package up the results into a List of Customer objects.

Let's take an example using our Customer class below:

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


This JavaBean represents the objects that we wish to store in the relational database system. For a simple bean like this, we would expect this data to be stored in a database table called 'Customer_David123' 

Remember: If you try any of these examples, remember to give your own beans unique table names, so that you are not clashing with other students.

In fact, from the log file listed in the last section, you can see that the SQL generated (for Oracle in this case) looks like:

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))

Let us assume for a moment, that our database table is populated with plenty of data against which we want to run a query.

To obtain the list of all of our customers who have a firstname 'John', we can use the Criteria API in the following way:

Customer customer = new Customer(); customer.setFirstname('John'); // This is the situation we want to search for Example example = Example.create(customer); // We have now created an 'Example' object Criteria criteria = session.createCriteria(Customer.class); // Create a Criteria object for this bean criteria.add(example); // Add the example to the Criteria object List results = criteria.list(); // Obtain the List of Customer objects which matches the criteria

That's really all there is to it!  Queries can be performed in this way, without the need for any SQL, HQL or any form of query language whatsoever!


OK, but with SQL I was able to do more complex queries with many more parameters! Can the Criteria API handle this?

Well let's consider where we want to return all Customers with the surname 'Smith' and firstname 'John'.  Easy, just add one more line to the above example (at line 2).

customer.setSurname('Smith');

What about queries that will only return one unique object?

Also no problem!  The only difference is that instead of returning a List of Customer objects, we will use a line of code like the following:

Customer cust = (Customer) criteria.uniqueResult();

How about returning all of the data in a database (the equivalent in SQL of having SELECT * with no 'WHERE' clause)?

In this situation, using the Criteria API, we simply create no Example object.

Criteria criteria = session.createCriteria(Customer.class); List results = criteria.list();

How about Ordering Results?

The Criteria API makes sorting your results extremely easy by providing users with an addOrder method.  The addOrder method takes an Orderobject as an argument, which is created using either of the static methods asc(String) or desc(String).   Obviously, which one you choose depends on the ordering that you require for your query. 

Criteria criteria = session.createCriteria(Customer.class); Order order = Order.asc("surname"); criteria.addOrder(order); List results = criteria.list();

What about ....?

There are a number of other aspects to the Criteria API, which we will not cover in this module.  At this point, the best approach would be to give an example, demonstrating a few of the Criteria API examples listed above.


Criteria API Example

To provide a code sample, detailing the majority of the Criteria situations listed above, we have generated CriteriaExample.java.  The code for this example can be seen below:

package edu.ee.hibernate; import java.util.List; import edu.ee.beans.Customer; import edu.ee.hibernate.HibernateUtil; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Example; import org.hibernate.criterion.Order; /** * Example Class to show Hibernate Criteria API * @author David Molloy * */ public class CriteriaExample { /** * 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 in the Example class 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.CreateCustomer(session, "John", "Murphy", "murphyj", "mur123", "john.murphy@email.com"); CRUDExample.CreateCustomer(session, "Jim", "Doyle", "doylej", "apass123", "doylej@abc.com"); showCustomersByFirstname("John"); showUniqueCustomerByUsername("mjane"); showAllCustomersOrdered(true); showAllCustomersOrdered(false); HibernateUtil.commitTransaction(); HibernateUtil.closeSession(); } /** * This method will print out all the customers with a specified firstname * @param firstname The firstname we are querying against */ @SuppressWarnings("unchecked") private static void showCustomersByFirstname(String firstname) { Customer c = new Customer(); c.setFirstname(firstname); // The argument we passed to the method Example example = Example.create(c); Criteria criteria = HibernateUtil.getSession().createCriteria(Customer.class); criteria.add(example); List<Customer> results = criteria.list(); for (int i=0; i<results.size(); i++) { Customer customer = (Customer) results.get(i); System.out.println("Customer with firstname " + firstname + ": " + customer.getFirstname() + " " + customer.getSurname()); } } /** * This method will print out the name of the Customer who matches a username * This will be a unique single user as we will assume a unique username */ private static void showUniqueCustomerByUsername(String username) { Customer c = new Customer(); c.setUsername(username); Example example = Example.create(c); Criteria criteria = HibernateUtil.getSession().createCriteria(Customer.class); criteria.add(example); Customer customer = (Customer) criteria.uniqueResult(); System.out.println("\nUnique customer matchining username " + username + " is " + customer.getFirstname() + " " + customer.getSurname()); } /** * This method will show all of the Customers in the database. If the argument is true * then it will return them in an ascending order based on surname. If the argument is * false then it will return them in a descending order based on surname * @param which True for ascending order, false for descending order */ @SuppressWarnings("unchecked") private static void showAllCustomersOrdered(boolean which) { Criteria criteria = HibernateUtil.getSession().createCriteria(Customer.class); Order order; if (which==true) order = Order.asc("surname"); else order = Order.desc("surname"); criteria.addOrder(order); List<Customer> results = criteria.list(); for (int i=0; i<results.size(); i++) { Customer customer = (Customer) results.get(i); System.out.println("Customer:" + customer.getFirstname() + " " + customer.getSurname()); } } }

Source: CriteriaExample.java

There are four methods in the above example:

  • main: The main method is similar to the main method in our previous CRUDExample.java file from the last section.  It sets up the Hibernate Session, recreates the database structure, populates the table with some data and calls the other methods in the class.   It should be noted that we use the method CreateCustomer from the CRUDExample class.  This means that we require CRUDExample to be in the same directory.  While we could have recreated a CreateCustomer method in this class, it is more efficient to simply use the existing one in CRUDExample.
  • showCustomerByFirstname(String firstname): This method shows a typical search, where we wish to return all Customer objects which have a firstname property equal to the 'firstname' string we pass to the method.  As this Criteria query can return multiple rows, we expect the results to be returned as a List<Customer>.
  • showUniqueCustomerByUsername(String username): This method is similar to the previous method, except we are expecting a unique Customer to be returned.  For this reason, we can use criteria.uniqueResult() and return straight to a Customer object.
  • showAllCustomersOrdered(boolean which): This method demonstrates a listing of all Customer objects with ordering based on surname.  Depending on the value of 'which' we will order in an ascending or descending manner.   As we are returning all Customer objects, the query will return a List<Customer>.

To deploy this example, simply copy and paste it into your src directory, right-click and select 'Run As -> Java Application'

While, for ease of demonstation, we are creating all of the Hibernate examples using Java applications, similar methods can be used to deploy Hibernate with servlets and jsps.


The end of the output expected from this CriteriaExample is as follows:

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! 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: 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 this_.id as id0_0_, this_.email as email0_0_, this_.firstname as firstname0_0_, this_.password as password0_0_, this_.surname as surname0_0_, this_.username as username0_0_ from Customer_David123 this_ where (this_.firstname=?) Customer with firstname John: John Smith Customer with firstname John: John Murphy Hibernate: select this_.id as id0_0_, this_.email as email0_0_, this_.firstname as firstname0_0_, this_.password as password0_0_, this_.surname as surname0_0_, this_.username as username0_0_ from Customer_David123 this_ where (this_.username=?) Unique customer matchining username mjane is Mary Jane Hibernate: select this_.id as id0_0_, this_.email as email0_0_, this_.firstname as firstname0_0_, this_.password as password0_0_, this_.surname as surname0_0_, this_.username as username0_0_ from Customer_David123 this_ order by this_.surname asc Customer:Jim Doyle Customer:Mary Jane Customer:John Murphy Customer:Michael Reilly Customer:John Smith Hibernate: select this_.id as id0_0_, this_.email as email0_0_, this_.firstname as firstname0_0_, this_.password as password0_0_, this_.surname as surname0_0_, this_.username as username0_0_ from Customer_David123 this_ order by this_.surname desc Customer:John Smith Customer:Michael Reilly Customer:John Murphy Customer:Mary Jane Customer:Jim Doyle

The output shows the results of the various System.out.println() statements we have included in the code, combined with the SQL which Hibernate has automatically generated on our behalf.

You can turn this option off by modifying the central hibernate.cfg.xml file and setting the 'hibernate.show sql' property to false.




Hibernate Mappings and Annotating Beans

In Hibernate so far we have learned how to annotate a basic bean, configure our Hibernate setup, automatically generate tables and perform basic CRUD operations using both the Hibernate Query Language (HQL) and the Criteria API. 

In a real development situation this will be 90% of what we might need to do in relation to working with our data model.  Unfortunately, there is still one outstanding issue.

In the examples above, we used a very basic JavaBean (Customers.java) which had a direct correlation with a single database table.  While this is fine in principle, in reality we are likely to have more complicated, multi-dimensional data arrays and inheritance relationships.   Traditionally on the SQL side, we would have handled this by creating multiple tables, linked by primary and foreign keys.

However, one of the great benefits of Hibernate is that it frees us from manually handcoding structures and complicated queries relating to these multiple tables.  While Hibernate can free us from some aspects, it cannot magically guess our intended data structures.  For this purpose, we need to annotate our beans with the appropriate mappings

While we could write considerable quantities of course material entirely on mappings, we will introduce a few particular mappings only.

Mapping: One Class to One Table

The easiest mapping of all is the one Java class to one database table mapping.  We have already seen this type of mapping with the Customer class in all of the examples above.  In that situation, the Customer class was mapped directly to the corresponding ('Customer_David123' in the example) table in the database.  The properties of the Customer class were mapped directly to the corresponding table columns. 

Our mapping looked like the following:


On the left-hand side, we can see the Customer class, together with the properties and the corresponding getter and setter methods for each property.  These properties correspond directly with the fields in the table in the database, as shown on the right-hand side. 

In the diagram, the database data types (number and varchar2) have been shown in in light-grey as these are database specific. In our situation, they are the correct data type for an Oracle database system and hence these types are used. However, if we were to change database system, slightly different types could be used for storing the mapped properties.


The annotations for this particular example have already been detailed in the previous listings of Customer.java


Mapping Associations: One to One

Let us again consider our original 'Customer' class.  Let us try to introduce a new class associated with this Customer called CreditCard.  The CreditCard class will contain the type of card that will be used, the credit card number, security code, expiry date and name of the card.

Each Customer3 instance must keep track of its associated CreditCard instance, so the Customer3 class defines a property of type CreditCard (we will show the full annotated bean shortly).  Likewise, the Customer3 class must have the requisite getter and setter methodsgetCreditCard() and setCreditCard(CreditCard)

private CreditCard creditCard;

It is possible to set up this association in two ways:

  • Unidirectional Association: Such as in the above diagram.  The Customer3 instance has an associated CreditCard instance (by defining a property of the type CreditCard).  However, the CreditCard instance has no knowledge of its encapsulating Customer3 (there is no Customer3 property).  Hence, we have no CreditCard.getCustomer3() or setCustomer3(Customer3) methods.
  • Bidirectional Association: Such as in the diagram below.  The Customer3 has an associated CreditCard instance and the CreditCard has an associated Customer3 instance.  Hence, we have the addition of the methods CreditCard.getCustomer3() and CreditCard.setCustomer3(Customer3)

For this example, we will implement a bidirectional association.

To tell Hibernate about this association, we mark the getter method getCreditCard()  with the special @OneToOne annotation.

private CreditCard creditCard; @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) @JoinColumn(name="creditcard_id") public CreditCard getCreditCard() { return creditCard; }

Because, we are linking the two tables together, an additional column containing a foreign key, linking to the primary key of our credit card table.  This is specified by our @JoinColumn(name="creditcard_id") annotation.  This indicates that our foreign key to link the tables will be contained in a column called 'creditcard_id'.  We have nominally chosen a name for this field and you can choose any name you like (excluding duplicate names).

With respect to the cascade and fetch attributes, we will return to these below.

As we are implementing a bidirectional association we must similarly annotate the CreditCard class.

private Customer3 customer3; @OneToOne(cascade=CascadeType.ALL, mappedBy="creditCard") public Customer3 getCustomer3() { return customer3; }

To implement a bi-directional relationship, we simply add an instance property of the type Customer3, along with the appropriate getter() and setter() methods in the CreditCard class.  Then using the special mappedBy attribute we link the enclosed class back to the instance variable used to link it to the enclosing class.  In this situation, our Customer3 class defines an instance of CreditCard called creditCard (note the lower-case c).  Hence, this is the mappedBy attribute setting.

Let us take a look at our expected database structures for these two tables:


So let's take a look at our finalised code for the two beans and a short application to show them in action.  Firstly Customer3.java:

package edu.ee.beans; import javax.persistence.*; /** * Our Customer JavaBean with Annotations (POJO) * @author David Molloy * */ @Entity @Table (name="Customer_David789") public class Customer3 { private int id; private String username; private String password; private String firstname; private String surname; private String email; private CreditCard creditCard; public Customer3() { } @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; } @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY) // We can nominally name this column which will contain the foreign key @JoinColumn(name="creditcard_id") public CreditCard getCreditCard() { return creditCard; } public void setCreditCard(CreditCard creditCard) { this.creditCard = creditCard; } }

Source: Customer3.java


Now let us implement the code for the credit card details:

package edu.ee.beans; import javax.persistence.*; @Entity @Table (name="CreditCard_David789") public class CreditCard { private int id; private String cardNumber; private int securityCode; private String expiryDate; private String cardName; private Customer3 customer3; public CreditCard(int id, String cardNumber, int securityCode, String expiryDate, String cardName) { super(); this.id = id; this.cardNumber = cardNumber; this.securityCode = securityCode; this.expiryDate = expiryDate; this.cardName = cardName; } public CreditCard() { super(); } @Id @GeneratedValue @Column(name="ccid") // Just showing how we can have fields with alternate names public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCardNumber() { return cardNumber; } public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; } public int getSecurityCode() { return securityCode; } public void setSecurityCode(int securityCode) { this.securityCode = securityCode; } public String getExpiryDate() { return expiryDate; } public void setExpiryDate(String expiryDate) { this.expiryDate = expiryDate; } public String getCardName() { return cardName; } public void setCardName(String cardName) { this.cardName = cardName; } // Since the Customer3 class defines an instance of the type CreditCard // named creditCard, our mappedBy attribute is set to 'creditCard' @OneToOne(cascade=CascadeType.ALL, mappedBy="creditCard") public Customer3 getCustomer3() { return customer3; } public void setCustomer3(Customer3 customer3) { this.customer3 = customer3; } }

Source: CreditCard.java


And now some code to show this Hibernate One-To-One Association example:

package edu.ee.hibernate; import edu.ee.beans.CreditCard; import edu.ee.beans.Customer3; import edu.ee.hibernate.HibernateUtil; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Example; /** * Example Class to show Hibernate CRUD Operations * @author David Molloy * */ public class OneToOneAssociationExample { /** * 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 CreditCard creditCard = CreateCreditCardObject("1234567812345678", 777, "03/12", "MR JOHN SMITH"); CreateCustomer3(session, "Michael", "Reilly", "reillym", "password", "michael.reilly@email.com", creditCard); showUniqueCustomer3ByUsername(session, "reillym"); // Display all of our Customer entities HibernateUtil.commitTransaction(); HibernateUtil.closeSession(); } /** * This method creates a new CreditCard entity in the database using the * fields provided as arguments */ public static CreditCard CreateCreditCardObject(String cardNumber, int securityCode, String expiryDate, String cardName) { System.out.println("Creating new CreditCard Entity"); CreditCard creditCard = new CreditCard(); creditCard.setCardNumber(cardNumber); creditCard.setSecurityCode(securityCode); creditCard.setExpiryDate(expiryDate); creditCard.setCardName(cardName); return creditCard; } /** * This method creates a new Customer entity in the database using the * fields provided as arguments */ public static void CreateCustomer3(Session session, String firstname, String surname, String username, String password, String email, CreditCard creditCard) { System.out.println("Creating new Customer2 with Billing"); Customer3 customer3 = new Customer3(); customer3.setFirstname(firstname); customer3.setSurname(surname); customer3.setUsername(username); customer3.setPassword(password); customer3.setEmail(email); customer3.setCreditCard(creditCard); // Note: This next line saves both the Customer3 and the CreditCard because // we annotated with CascadeType.ALL // Otherwise, depending on option, we might have to save both the Customer3 // and the CreditCard seperately session.save(customer3); System.out.println("Customer3 Saved!"); } /** * This method will print out the name of the Customer2 who matches a username * This will be a unique single user as we will assume a unique username */ private static void showUniqueCustomer3ByUsername(Session session, String username) { Customer3 c = new Customer3(); c.setUsername(username); Example example = Example.create(c); Criteria criteria = session.createCriteria(Customer3.class); criteria.add(example); Customer3 customer3 = (Customer3) criteria.uniqueResult(); System.out.println("\nUnique customer matchining username " + username + " is " + customer3.getFirstname() + " " + customer3.getSurname()); System.out.println("\nCustomer3 has credit card number: " + customer3.getCreditCard().getCardNumber() + " and expiry date: " + customer3.getCreditCard().getExpiryDate()); } }
Note: Don't forget to add Customer3.class and CreditCard.class to the HibernateUtil list of annotated classes it is told to handle persistence for!

Source: OneToOneAssociationExample.java


On execution, we can expect the following output (only showing the end section and the relevant bits):

create table CreditCard_David789 (ccid number(10,0) not null, cardName varchar2(255 char), cardNumber varchar2(255 char), expiryDate varchar2(255 char), securityCode number(10,0) not null, primary key (ccid)) 12:16:37,843 DEBUG SchemaExport:377 - create table CreditCard_David789 (ccid number(10,0) not null, cardName varchar2(255 char), cardNumber varchar2(255 char), expiryDate varchar2(255 char), securityCode number(10,0) not null, primary key (ccid)) create table Customer_David789 (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), creditcard_id number(10,0), primary key (id)) 12:16:38,062 DEBUG SchemaExport:377 - create table Customer_David789 (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), creditcard_id number(10,0), primary key (id)) alter table Customer_David789 add constraint FK9A5511C5A4B29EBB foreign key (creditcard_id) references CreditCard_David789 12:16:38,203 DEBUG SchemaExport:377 - alter table Customer_David789 add constraint FK9A5511C5A4B29EBB foreign key (creditcard_id) references CreditCard_David789 Creating new CreditCard Entity Creating new Customer2 with Billing Hibernate: select hibernate_sequence.nextval from dual Hibernate: select hibernate_sequence.nextval from dual Customer2 Saved! Hibernate: insert into CreditCard_David789 (cardName, cardNumber, expiryDate, securityCode, ccid) values (?, ?, ?, ?, ?) Hibernate: insert into Customer_David789 (creditcard_id, email, firstname, password, surname, username, id) values (?, ?, ?, ?, ?, ?, ?) Hibernate: select this_.id as id3_0_, this_.creditcard_id as creditcard7_3_0_, this_.email as email3_0_, this_.firstname as firstname3_0_, this_.password as password3_0_, this_.surname as surname3_0_, this_.username as username3_0_ from Customer_David789 this_ where (this_.username=?) Unique customer matchining username reillym is Michael Reilly Customer has credit card number: 1234567812345678 and expiry date: 03/12



One to Many Associations

Of all mappings, this is perhaps the most common.  Consider real world situations:  a Bank has many BankAccounts, a Student has many Modules, an Exam has many ExamQuestions etc.

Rather than consider an entirely new example, we are going to consider the previous example, but where the Customer (which we're now going to call Customer4 to avoid breaking previous examples) can have multiple CreditCard entities (which we will now call CreditCard2 for the same reason). 

We will preserve the bi-directional relationship from before.  Given a Customer4 we can determine all of the CreditCard2 objects associated with it.  Likewise, given a CreditCard2 we can determine the Customer4 to which it is associated.



CreditCard2.java

package edu.ee.beans; import javax.persistence.*; @Entity @Table (name="CreditCard_David101") public class CreditCard2 { private int id; private String cardNumber; private int securityCode; private String expiryDate; private String cardName; private Customer4 customer4; // This card is registered to this Customer4 public CreditCard2(int id, String cardNumber, int securityCode, String expiryDate, String cardName) { super(); this.id = id; this.cardNumber = cardNumber; this.securityCode = securityCode; this.expiryDate = expiryDate; this.cardName = cardName; } public CreditCard2() { super(); } @Id @GeneratedValue @Column(name="ccid") // Just showing how we can have fields with alternate names public int getId() { return id; } public void setId(int id) { this.id = id; } public String getCardNumber() { return cardNumber; } public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; } public int getSecurityCode() { return securityCode; } public void setSecurityCode(int securityCode) { this.securityCode = securityCode; } public String getExpiryDate() { return expiryDate; } public void setExpiryDate(String expiryDate) { this.expiryDate = expiryDate; } public String getCardName() { return cardName; } public void setCardName(String cardName) { this.cardName = cardName; } @ManyToOne @JoinColumn (name="cust_id") public Customer4 getCustomer4() { return customer4; } public void setCustomer4(Customer4 customer4) { this.customer4 = customer4; } }

Source: CreditCard2.java

When using JPA annotations to map the many side of a relationship to the encapsulating one side we firstly use the @ManyToOne annotation.  However, we also need to indicate the @JoinColumn annotation.  In this example above, we are stating that there are many credit cards to one customer (many CreditCard2 to one Customer4 in this situation).  The @JoinColumn will provide the name of the column which will be used as foreign key to the Customer4 table.  Take a look at the table diagram below the next section of code.


Customer4.java

package edu.ee.beans; import java.util.List; import javax.persistence.*; /** * Our Customer JavaBean with Annotations (POJO) * @author David Molloy * */ @Entity @Table (name="Customer_David101") public class Customer4 { private int id; private String username; private String password; private String firstname; private String surname; private String email; private List<CreditCard2> creditCards; public Customer4() { } @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; } @OneToMany(mappedBy="customer4", targetEntity=CreditCard2.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL) public List<CreditCard2> getCreditCards() { return creditCards; } public void setCreditCards(List<CreditCard2> creditCards) { this.creditCards = creditCards; } }

Source: Customer4.java


The  getCustomer4() method of CreditCard2 class was decorated with the @ManyToOne annotation, so it shouldn't be too surprising that the getCreditCards() method of Customer4 is decorated with the @OneToMany annotation. 

There are a few important attributes that need to be included.  The mappedBy attribute maps to the variable name of the encapsulating class (Customer4) takes in the encapsulated entity (CreditCard2).  In this example, it is refering to the

private Customer4 customer4;

line in CreditCard4.java.  "customer4" is the variable name of the instance of Customer4 in the CreditCard2 class.

Secondly, the targetEntity attribute is required.  Because, the many part of the association may be implemented as any collection class such as List, Vector etc. The targetEntity class indentifies the object type contained in the collection.   In our example, the many part relates to a List<CreditCard2> then our targetEntity has a value of CreditCard2.class.

The other two attributes, fetch and cascade will be discussed in the next section.

So, let us put this example in action.  This code creates a Customer4 and two associated CreditCard2 and saves them in the database.  The transaction is committed and a new transaction opened.  The Customer4 is then loaded from the database and some contents and credit card details printed out.


OneToManyAssociationExample.java

package edu.ee.hibernate; import edu.ee.beans.CreditCard2; import edu.ee.beans.Customer4; import edu.ee.hibernate.HibernateUtil; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.criterion.Example; /** * Example Class to show Hibernate CRUD Operations * @author David Molloy * */ public class OneToManyAssociationExample { /** * 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 Customer4 customer4 = CreateCustomer4("Michael", "Reilly", "reillym", "password", "michael.reilly@email.com"); CreditCard2 cc = CreateCreditCard2Object(customer4, "1234567812345678", 777, "03/12", "MR JOHN SMITH"); CreditCard2 cc_2 = CreateCreditCard2Object(customer4, "9876543298765432", 111, "05/13", "MR JOHN SMITH"); session.save(customer4); session.save(cc); session.save(cc_2); HibernateUtil.commitTransaction(); // Commit the transaction to save all entries in both tables session = HibernateUtil.beginTransaction(); // Do a new transaction now showUniqueCustomer4ByUsername(session, "reillym"); // Display a Customer4 matching this username HibernateUtil.closeSession(); } /** * This method creates a new CreditCard entity in the database using the * fields provided as arguments */ public static CreditCard2 CreateCreditCard2Object(Customer4 customer4, String cardNumber, int securityCode, String expiryDate, String cardName) { System.out.println("Creating new CreditCard Entity"); CreditCard2 creditCard2 = new CreditCard2(); creditCard2.setCardNumber(cardNumber); creditCard2.setSecurityCode(securityCode); creditCard2.setExpiryDate(expiryDate); creditCard2.setCardName(cardName); creditCard2.setCustomer4(customer4); // Associate this credit card with the Customer4 return creditCard2; } /** * This method creates a new Customer4 entity in the database using the * fields provided as arguments */ public static Customer4 CreateCustomer4(String firstname, String surname, String username, String password, String email) { Customer4 customer4 = new Customer4(); customer4.setFirstname(firstname); customer4.setSurname(surname); customer4.setUsername(username); customer4.setPassword(password); customer4.setEmail(email); // We won't initialise the List at this time return customer4; } /** * This method will print out the name of the Customer2 who matches a username * This will be a unique single user as we will assume a unique username */ private static void showUniqueCustomer4ByUsername(Session session, String username) { Customer4 c = new Customer4(); c.setUsername(username); Example example = Example.create(c); Criteria criteria = session.createCriteria(Customer4.class); criteria.add(example); Customer4 customer4 = (Customer4) criteria.uniqueResult(); System.out.println("\nUnique customer matchining username " + username + " is " + customer4.getFirstname() + " " + customer4.getSurname()); System.out.println(customer4.getCreditCards()); // Now let's list all of the CreditCard2 objects associated with this Customer4 if (customer4.getCreditCards()==null) { System.out.println("CreditCards list has not been initialised!"); } else { for (int len = customer4.getCreditCards().size(), i = 0; i < len; i++) { CreditCard2 cc2 = (CreditCard2) customer4.getCreditCards().get(i); System.out.println("\n" + cc2.getCardName() + ", " + cc2.getCardNumber()); } } } }

Output

create table Customer_David101 (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:34:39,578 DEBUG SchemaExport:377 - create table Customer_David101 (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)) alter table CreditCard_David101 add constraint FK8F0E0D46098CE06 foreign key (cust_id) references Customer_David101 15:34:40,281 DEBUG SchemaExport:377 - alter table CreditCard_David101 add constraint FK8F0E0D46098CE06 foreign key (cust_id) references Customer_David101 create sequence hibernate_sequence 15:34:40,421 DEBUG SchemaExport:377 - create sequence hibernate_sequence 15:34:40,468 INFO SchemaExport:268 - schema export complete 15:34:40,531 INFO DriverManagerConnectionProvider:170 - cleaning up connection pool: jdbc:oracle:thin:@136.206.35.131:1521:SSD Creating new CreditCard Entity Creating new CreditCard Entity Hibernate: select hibernate_sequence.nextval from dual Hibernate: select hibernate_sequence.nextval from dual Hibernate: select hibernate_sequence.nextval from dual Hibernate: insert into Customer_David101 (email, firstname, password, surname, username, id) values (?, ?, ?, ?, ?, ?) Hibernate: insert into CreditCard_David101 (cardName, cardNumber, cust_id, expiryDate, securityCode, ccid) values (?, ?, ?, ?, ?, ?) Hibernate: insert into CreditCard_David101 (cardName, cardNumber, cust_id, expiryDate, securityCode, ccid) values (?, ?, ?, ?, ?, ?) Hibernate: select this_.id as id4_0_, this_.email as email4_0_, this_.firstname as firstname4_0_,  this_.password as password4_0_, this_.surname as surname4_0_, this_.username as username4_0_ from Customer_David101 this_ where (this_.username=?) Unique customer matchining username reillym is Michael Reilly Hibernate: select creditcard0_.cust_id as creditcard6_1_, creditcard0_.ccid as ccid1_, creditcard0_.ccid as ccid6_0_, creditcard0_.cardName as cardName6_0_, creditcard0_.cardNumber as cardNumber6_0_, creditcard0_.cust_id as creditcard6_6_0_, creditcard0_.expiryDate as expiryDate6_0_, creditcard0_.securityCode as security5_6_0_ from CreditCard_David101 creditcard0_ where creditcard0_.cust_id=? [edu.ee.beans.CreditCard2@15db13f, edu.ee.beans.CreditCard2@e29820] MR JOHN SMITH, 1234567812345678 MR JOHN SMITH, 9876543298765432

There are a number of other mappings we could spend time discussing but will not cover them in this module. These include: two classes to one table, mapping inheritence, compound primary keys and many-to-many associations.

FetchType

Over the last two examples, we managed to avoid getting into any discussions regarding FetchType and CascadeType. However, they are important attributes and we should know their significance in our JPA annotations.

There are two options for FetchType which determines how associated properties are loaded.
  • FetchType.LAZY:  If the FetchType is set to LAZY, when an encapsulating object is loaded, only the attributes defined directly in the class itself are loaded into memory, and any properties mapped through LAZY associations will not be loaded into memory until they are explicitly requested.
  • FetchType.EAGER: If the FetchType is set to EAGER, when an encapsulating object is loaded, all of the EAGERLY associated objects will be loaded into memory as well.

You have probably read those two options twice over and are currently scratching your head.  So let's consider it using an example.  In fact, let us just use the last example we have just demonstrated.

In our Customer4.java class we defined a FetchType.EAGER:

@OneToMany(mappedBy="customer4", targetEntity=CreditCard2.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL) public List<CreditCard2> getCreditCards() { return creditCards; }

This means that when a Customer4 object is loaded, Hibernate will also perform the necessary database operations to load all of the Customer4 object's credit card details into memory also  (even if those credit card details are never used in our application). 

On the other hand, if we were to set the FetchType to be FetchType.LAZY, then the credit card information for the Customer4 object would not be loaded into memory until code was actually called requesting that data. 

Because the example above was EAGER it meant that in the method showUniqueCustomer4ByUsername() the Customer4.getCreditCards() method simply loaded the List<CreditCard2> from memory, as it had already been initialised.  However, if we changed the Fetch to FetchType.LAZY the database load and loading of the List<CreditCard2> into memory would only occur when it was requested. 

In both situations, the code and output would be identical in this example.  However, the underlying Hibernate operations take place in different ways.   For example, if our client application was never to call 'Customer4.getCreditCards()' then using a FetchType.EAGER would be wasteful of resources.

For performance reasons, it's always preferential to minimise the load placed on a system's memory.  As such, most associations are set to a FetchType of LAZY.  This is especially true for one to many and many to many relationships, where the impact of loading a large number of associated properties can be significant.

For one-to-many and many-to-many relationships the default FetchType is LAZY.  However, for one-to-one mappings the default is EAGER meaning that a class, and all of its one-to-one associations are loaded into memory when it is invoked.    However, these are only default settings and can be changed.


CascadeType

One common mistake when persisting entities to the database is to assume that just because the encapsulating entity is saved, that all of the enclosed or associated entities are also saved.  By default, his is not true.

Consider, our One-to-One mapping example from earlier.  In that example, we had a Customer3 class with an associated CreditCard. However, to properly save the instance of a Customer3 entity and its associated CreditCard entity, we need to do the following:

session.save(customer3); session.save(creditCard);

Merely executing the first statement will not suffice as the credit card details would not be persisted in the second table.  As both Customer3 and CreditCard are seperately JPA annotated beans, the default behaviour is to require all instances to touch the Hibernate Session at some point in order to allowed them to be persisted to the database.

To modify the default behaviour, we have the JPA CascadeType Enumeration

We use an Enumeration rather than a straight name=value attribute pairing, as multiple possible values can be used simultaneously.  Without detailing all of the options, the most important are:

CascadeType.PERSIST

When the cascade attribute is set to CascadeType.PERSIST it means that when the encapsulating entity is persisted, any associated entities will also be persisted.  So in the above example, when the customer3 is persisted, the associated creditCard would also be automatically persisted in the database.  It should be noted that this only applies to the saving of an entity - the other CascadeType values apply to refreshes, deletes and merges.

@OneToOne(cascade=CascadeType.PERSIST) @JoinColumn(name="creditcard_id") public CreditCard getCreditCard() { return creditCard; }
CascadeType.REMOVE

When the cascade attribute is set to CascadeType.REMOVE it means that when the encapsulating entity is deleted, any associated entities will also be deleted.  Again, in this example, when we delete a Customer3 record, then we automatically delete the corresponding CreditCard record from the database.

@OneToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
CascadeType.ALL

If you wish to take advantage of all of the both PERSIST and REMOVE (as well as two other options not discussed here, REFRESH and MERGE) cascade options, you can simply write:

@OneToOne(cascade={CascadeType.ALL})

In fact, CascadeType.ALL is the most commonly used type in Hibernate and JPA Annotated applications.


Hibernate Sample Questions

Sample 1

1(a) Create a JavaBean representing 'Item's for sale in our e-commerce shopping system.  This bean should contain properties representing ID, Name, Description and Price. Annotate the bean appropriately to map to a corresponding database table (with field names ID, Name, Desc and Price).

1(b) Write a short hibernate-based application which will add two 'Item' entities to the database. 

1(c) Create a method, using the Criteria API, which will return a unique 'Item' entity.

-----

Sample 2   
Note: Brackets demonstrate different formulations of the question

2(a) Consider that a 'User' of a system can have a (list of/single) billing addresses.  Create two beans representing the 'User' and 'BillingAddress'.   You should choose 4-5 appropriate properties  for each bean.

2(b) Annotate the beans to map an appropriate (one-to-many/one-to-one) association between the two beans.

2(c) Create a database application to (create the table structures / insert entities / delete entities / update entities) using (HQL/the criteria API).



In Closing on Hibernate

Hibernate provides an effective replacement to the cumbersome method of handwriting JDBC code.  Generally, with complex data structures with encapsulated entities and associations, the SQL code becomes complex and error-prone.  On top of that, the SQL code tends to be specific to one particular brand of database, which removes some aspects of portability from your applications. 

Hibernate solves many of these problems for us, allowing us to work directly using JavaBeans (with some additional annotations) and automating persistence.  While we could have covered a great deal more material on Hibernate, we are a little limited for space in this module.  However, the core concepts using Java Annotations have been covered and the examples should provide the reader with enough knowledge to setup ORM persistence for any data model.

Comments