In the last chapters, we connected Java applications to relational databases. But, just as earlier in the course, you have to write lots of boilerplate code just to read and write each property.
Surely there are better ways of doing this?!
Yes, there is!
One way is to map the objects to the relational database - describe which classes and properties in the code map to which tables and columns in the database, and then use a library to do all the copying and translating. In general cases, these libraries are described as object-relational mapping (ORM) tools. In Java, the Java Persistence API (JPA) describes how these libraries can be implemented and how you would write your code to be easily used by them.
You tell these libraries to inspect specific Java objects from your code, usually using configuration files. When you submit instances of those objects to the library to be stored, the library constructs and executes the SQL statements needed to persist them. When you ask the library to search for entries, it constructs and executes the SQL query statements, and then returns object instances with the results in them.
You have to do very little if any, actual SQL statement writing.
There are several ORM tools. We will look at the popular Hibernate, as it's open-source, mature, straightforward, and you'll likely find yourself using it in your career as a Java Developer! Other ORM tools include EclipseLink from the Eclipse community and OpenJPA from the Apache community.
Installing Hibernate
Hibernate ORM is a Java library packaged in several JAR files, some providing specialist support such as for persisting geospatial data, or improving performance.
This library is frequently updated and improved, so take care to follow their up-to-date instructions. Start at https://hibernate.org/orm/ and follow the links to the latest stable release version:
.
Then download to Zip archive:
Download this and unzip it in your download folder.
Then copy all the JARs in the lib/required/
directory to your development environment and make sure they are in your classpath.
In my examples, I have a lib directory in my Eclipse project. I create a subdirectory, hibernate, and copy the JARs into there.
Configure the build path:
Add JARs to select the all the JARs from the hibernate lib folder.
Now let's use Hibernate to persist customer and robot part details.
Persisting Customers Using Hibernate
After installation: Once its JAR files are included in your classpath, Hibernate has to be configured so that it knows which database to connect to using JDBC. The configuration consists (by default) of a hibernate.cfg.xml file, which specifies the database connection, and a set of mapping files which specify which properties are to be stored in which columns and tables.
Since we already have a SQLite database and JDBC connection set up from the previous chapters, we can build on that. Here's how to use Hibernate to store a person object in a relational database:
Let's have a look at some of that in more detail.
Configuring Class/Table Maps
Start with creating a hibernate.cfg.xml
as follows (you can use this as a template for your own code):
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<!-- Database connection settings -->
name="connection.url"jdbc:sqlite:./RobotShop.db
<!-- JDBC connection pool (use the built-in) -->
name="connection.pool_size"1
<!-- SQL dialect -->
name="dialect"org.hibernate.dialect.SQLServerDialect
<!-- Disable the second-level cache -->
name="cache.provider_class"
org.hibernate.cache.internal.NoCacheProvider
<!-- Create the database schema on startup if it's not already there -->
name="hbm2ddl.auto"update
<!-- Echo all executed SQL to stdout -->
name="show_sql"true
resource="com/openclassrooms/persistence/hibernate/RobotShop.hbm.xml"
The key elements are the property connection.url
, which specifies the JDBC connection URL, and the mapping
elements which specify the files that will map the classes and properties to be persisted with the database tables and columns.
Unfortunately, while JDBC tries to provide a single interface to many different databases, the SQL language itself often varies slightly from database to database. These different variations are called dialects, and you need to specify which to use in the dialect property within the configuration file.
The SQLite dialect is not included by default in the Hibernate library of dialects, so we use the SQLServerDialect one, which is close enough for typical use.
The mapping configuration file (in this case given as RobotShop.hbm.xml above) that tells Hibernate which object fields to store looks like this:
package="com.openclassrooms.persistence.hibernate"
name="Person" table="CUSTOMERS"
name="id" column="ID" access="field"
class="increment"
name="name" access="field"
name="address" access="field"
name="email" access="field"
name="telephone" access="field"
This maps the class person to the table CUSTOMERS in the database and then lists the fields of the person class that should be persisted. Unless you specify a column name, they are stored in a column with the same name as the property name (so in this case, Person.name is stored in the name column of the CUSTOMERS table).
Annotating Class/Table Maps
Hibernate offers another way to map classes to tables. As you did with JAXB earlier, you can also annotate classes, and Hibernate reads them using reflection to see what it should do. For example, you might mark up the person class like this:
@Entity
@Table(name="Customers")
public class Person {
@Id
@GeneratedValue
public int id;
@Column(name="FullName", nullable=false)
public String name;
public String address;
public String telephone;
public String email;
}
As with JAXB, you might find this more convenient than maintaining a separate configuration file, but on the other hand, your objects (person) now have code in them that are specific to your persistence mechanism. If you change your persistence technology, you may also need to change your person object.
Although you should know this option exists, in this course, we will use the configuration file approach because later we will be swapping between persistence technologies. To do this cleanly, without changing the person object, we cannot use such annotations.
Persisting
Persistence interactions with Hibernate are based on sessions and transactions. You can think of these as interactions with colleagues: a session is like having a meeting, where you might have several transactions about particular topics, and within each transaction, there may be several conversations. These become important when persisting complex objects in shared, dynamic, and high-performance environments.
To apply this, construct Hibernate's SessionFactory from the configuration file:
File configFile = new File("hibernate.cfg.xml");
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure(configFile)
.build();
SessionFactory sessionFactory = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory();
This looks for the configuration file hibernate.cfg.xml
in the local directory and uses it to create a registry containing details of the context and environment. The code then uses that registry to create a factory for making sessions using that context. The sessions are what you use for saving a customer person instance like this:
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save( aCustomer ) ;
session.getTransaction().commit();
session.close();
Let's break this down:
Open a session.
Within that session, start a transaction.
Tell the library what action you want to do (save the customer).
Then tell Hibernate to carry out those tasks by:
Committing the transaction.
Closing the session.
There's quite a lot of code here for a relatively simple task, so you can wrap it all up in a persister class like this:
public class HibernatePersonPersister {
protected SessionFactory sessionFactory = null;
public HibernatePersonPersister() throws SQLException {
File configFile = new File("hibernate.cfg.xml");
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure(configFile)
.build();
sessionFactory = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory();
}
/** In hibernate 'creating' is the same as 'updating' */
public void createCustomer(Person aCustomer) {
updateCustomer(aCustomer);
}
/** Creates a session, a transaction for that session,
* gives the customer instance to the hibernate library
* to prepare to store, and then commits the transaction
* to persist it */
public void updateCustomer(Person aCustomer) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save( aCustomer ) ;
session.getTransaction().commit();
session.close();
}
/** Creates a session, a transaction for that session,
* instructs hibernate to delete the entry, and then commits the transaction */
public void deleteCustomer(Person aCustomer) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.delete( aCustomer ) ;
session.getTransaction().commit();
session.close();
}
/** Creates a session, a transaction for that session,
* instructs hibernate to find the entry with the given
* unique identifier, and then commits the transaction to
* run the query*/
public Person readCustomer(int id) {
Session session = sessionFactory.openSession();
session.beginTransaction();
Person customer = session.find(Person.class, id);
session.getTransaction().commit();
session.close();
return customer;
}
/** Creates a SQL-like query criteria to list all the
* customers in the database. */
public List<Person> listCustomers() {
Session session = sessionFactory.openSession();
session.beginTransaction();
List<Person> customers = (List<Person>) session.createQuery( "from Person" ).list();
session.getTransaction().commit();
session.close();
return customers;
}
/** A quick and dirty test, and a demonstration of usage */
public static void main(String[] args) throws IOException, SQLException {
Person samjones = new Person();
samjones.name = "Sam Jones";
samjones.address = "12 Letsbe Avenue, Royston Vasey, MK2 3AU";
samjones.email = "sam.jones@openclassrooms.co.uk";
samjones.telephone = "+44 7700 900081";
HibernatePersonPersister persister = new HibernatePersonPersister();
persister.updateCustomer(samjones);
Person storedSam = persister.readCustomer(samjones.id);
if (!storedSam.toString().equals(samjones.toString())) {
throw new RuntimeException("Stored Sam "+storedSam+" is not equal to original Sam "+samjones);
}
}
}
This packages the setup in the constructor, and has the CRUD methods for create, read, update, and delete. The create and update are the same internally.
The listCustomers
method uses Hibernate's query language (HSQL), which is similar to SQL except that it refers to the objects in the code rather than the tables in the database. Here, it selects from person (the mapped class) rather than customers (the database table).
Try It Out for Yourself: Persist Robot Parts Using Hibernate!
Now it's your chance to have a try!
Using the above as a guideline, or working from the Hibernate documentation, write a Hibernate persister wrapper for storing robot parts for our shop. An example implementation is below.
I'll take you through one way of storing robot parts using Hibernate:
For reference, here is some of the code samples from this video.
You will need to add a RobotPart entry to the mapping file RobotShop.hbm.xml:
package="com.openclassrooms.persistence.hibernate"
name="Person" table="CUSTOMERS"
name="id" column="ID" access="field"
class="increment"
name="name" access="field"
name="address" access="field"
name="email" access="field"
name="telephone" access="field"
name="RobotPart" table="ROBOTPARTS"
name="id" column="ID" access="field"
class="increment"
name="name" access="field"
name="description" access="field"
name="supplier" access="field"
The new RobotPart element is structured similarly to the person element; it specifies that the RobotPart class instances be stored in the ROBOTPARTS table, and the fields to be stored in columns of the same name.
Here is an example wrapper persister that provides CRUD operations for robot parts using Hibernate:
public class RobotPartPersister {
protected SessionFactory sessionFactory = null;
public RobotPartPersister() throws SQLException {
File configFile = new File("hibernate.cfg.xml");
final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.configure(configFile)
.build();
sessionFactory = new MetadataSources(registry)
.buildMetadata()
.buildSessionFactory();
}
/** In hibernate 'creating' is the same as 'updating' */
public void createRobotPart(RobotPart aRobotPart) {
updateRobotPart(aRobotPart);
}
/** Creates a session, a transaction for that session,
* gives the robotpart instance to the hibernate library
* to prepare to store, and then commits the transaction
* to persist it */
public void updateRobotPart(RobotPart aRobotPart) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save( aRobotPart ) ;
session.getTransaction().commit();
session.close();
}
/** Creates a session, a transaction for that session,
* instructs hibernate to delete the entry, and then commits
* the transaction */
public void deleteRobotPart(RobotPart aRobotPart) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.delete( aRobotPart ) ;
session.getTransaction().commit();
session.close();
}
/** Creates a session, a transaction for that session,
* instructs hibernate to find the entry with the given
* unique identifier, and then commits the transaction to
* run the query*/
public RobotPart readRobotPart(int id) {
Session session = sessionFactory.openSession();
session.beginTransaction();
RobotPart part = session.find(RobotPart.class, id);
session.getTransaction().commit();
session.close();
return part;
}
/** Creates a SQL-like query criteria to list all the
* customers in the database. */
public List<RobotPart> listRobotParts() {
Session session = sessionFactory.openSession();
session.beginTransaction();
List<RobotPart> parts = (List<RobotPart>) session.createQuery( "from RobotPart" ).list();
session.getTransaction().commit();
session.close();
return parts;
}
/** A quick and dirty test, and a demonstration of usage */
public static void main(String[] args) throws IOException, SQLException {
RobotPart sharklaseradapter = new RobotPart();
sharklaseradapter.name = "Shark Laser Adapter";
sharklaseradapter.description = "Collar and fittings for shark-mounted laser. Shark not included";
sharklaseradapter.supplier = "Dr NoNo";
RobotPartPersister persister = new RobotPartPersister();
persister.updateRobotPart(sharklaseradapter);
RobotPart storedPart = persister.readRobotPart(sharklaseradapter.id);
if (!storedPart.toString().equals(sharklaseradapter.toString())) {
throw new RuntimeException("Stored "+storedPart+" is not equal to original "+sharklaseradapter);
}
}
}
This has the CRUD operations in very much the same layout and structure as the customer persister. The constructor calls the setup methods that check the RobotParts table is in the database and creates the Hibernate SessionFactory using the right configuration file. The main method at the end acts as a simple test for store and retrieval and means you can run this as an application to check that the persister works.
Let's Recap!
In this chapter, you learned to create a persister that wraps the Hibernate library in a class with CRUD operations for both customers and robot parts. You can use this to persist other objects too.
You can see that Hibernate requires quite a lot of code to carry out these simple operations. This is partly because Hibernate is also used to persist complex objects in high-performance environments, and bundling operations into transactions and sessions make such advanced environments much easier to manage.
So far in the course, we have created several persister wrappers that start to look similar in many ways; they have a constructor that sets up the storage mechanisms, some CRUD-like methods, and a quick test in the main method. 🙂
When you find yourself doing similar things, start to think of patterns and use them to guide your development.
We'll start working with the repository pattern in the next part! 😎