14

Java & Databases: Guide to JDBC, Hibernate and Spring Data

 4 years ago
source link: https://www.marcobehler.com/guides/java-databases-jdbc-hibernate-spring-data
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Java ORM Frameworks: Hibernate, JPA and more.

Java developers are usually more comfortable with writing Java classes, than with writing SQL statements. Hence many (greenfield) projects are written with a Java-First approach, which means that you create your Java class before you create the corresponding database table.

This naturally leads to the object-relational mapping question: How do you map from your newly written Java class to your (yet to be created) database table? And could you even generate your database from those Java classes? At least, initially?

This is where full-blown ORMs, like Hibernate or any other JPA implementation come into play.

What is Hibernate?

Hibernate is a mature ORM (Object-Relational Mapping) library, which has first been released in 2001 (!), with a current stable version of 5.4.X, and version 6.x being in development.

Even though countless books have been written about it, here is the attempt to summarize what Hibernate does well:

  1. It lets you (relatively) easily convert between database tables and java classes, without you having to do much apart from an initial mapping.

  2. It allows you to not have to write any SQL code for (basic) CRUD operations. Think: creating a user, deleting a user, updating a user.

  3. It offers a couple query mechanisms (HQL, Criteria API) on top of SQL to query your databases. For now, let’s say these are "object-oriented" versions of SQL, even though this is a bit meaningless without examples, which will follow later.

Finally, let’s look at some code. Imagine, you have the following database table, which is basically the same table you used in the plain JDBC section.

create table users (
    id integer not null,
    first_name varchar(255),
    last_name varchar(255),
    primary key (id)
)

You also have the following, corresponding Java class.

public class User {

        private Integer id;

        private String firstName;

        private String lastName;

        //Getters and setters are omitted for brevity
}

Also imagine, you downloaded the hibernate-core.jar and added it to your project. How do you now tell Hibernate that your User.java class should be mapped to the Users database table?

That’s where Hibernate’s mapping annotations come into play.

How to use Hibernate’s mapping annotations

Out of the box, Hibernate obviously has no way of knowing which of your classes should be mapped how to database tables. Should the User.java class be mapped to a invoices database table or to a users table?

Historically, to let Hibernate know what it should do, you wrote mapping .xml files.

We will not cover xml mapping in this guide, as for the past couple of years it has been superseded with an annotation-based mapping approach.

You might have encountered some of these annotations already, like @Entity, or @Column, or @Table. Let’s have a look at what our annotated User.java class from above would look like with these mapping annotations.

(Parental Advice: Don’t just blindly copy this code)
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.GeneratedValue;
import javax.persistence.Column;
import javax.persistence.Id;

@Entity
@Table(name="users")
public static class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;

        //Getters and setters are omitted for brevity
}

It would not be in the scope of this guide to go too much into detail of every annotation, but here’s a quick summary:

  1. @Entity : a marker for Hibernate to understand: Map this class do a database table.

  2. @Table : tells Hibernate which database table to map the class to.

  3. @Column : tells Hibernate which database column to map the field to.

  4. @Id and @GeneratedValue : tells Hibernate what the primary key of the table is and that it is autogenerated by the database.

There’s of course a lot more annotations, but you get the picture. With Hibernate you write your Java classes, and then need to make sure to annotate them with the correct annotations.

How to quickstart Hibernate (5.x)

After having annotated your classes, you still need to bootstrap Hibernate itself. Hibernate’s entry point for pretty much everything is the so-called SessionFactory, which you need to configure.

It understands your mapping annotations and allows you to open Sessions. A session is basically a database connection (or more specifically a wrapper around your good, old JDBC connection), with additional goodies on top. You will use these sessions to execute SQL / HQL / Criteria queries.

But first, some bootstrapping code:

In newer Hibernate versions (>5.x), this code looks a bit ugly and libraries like Spring will take care of this bootstrapping code for you. But, if you want to get started with plain Hibernate, this is what you will need to do.

(Parental Advice: Don’t just blindly copy this code)
public static void main(String[] args) {
    // Hibernate specific configuration class
    StandardServiceRegistryBuilder standardRegistry
        = new StandardServiceRegistryBuilder()
            .configure()
            .build();

    // Here we tell Hibernate that we annotated our User class
    MetadataSources sources = new MetadataSources( standardRegistry );
    sources.addAnnotatedClass( User.class );
    Metadata metadata = metadataSources.buildMetadata();

    // This is what we want, a SessionFactory!
    SessionFactory sessionFactory = metadata.buildSessionFactory();
}

(check out this full example for more copy & paste code)

How basic persistence works with Hibernate: An example

Now that you have the mapping set-up and constructed a SessionFactory, the only thing left to do is to get a session (read: database connection) from your SessionFactory and then, for example, save the user to the database.

In Hibernate/JPA terms this is called "persistence", because you persist Java objects to your database tables. In the end, however, it is a very fancy way of saying: Save that Java object to the database for me, i.e. generate some insert SQL statements.

Yes, that is right, you do not need to write the SQL yourself anymore. Hibernate will do that for you.

(Parental Advice: Don’t just blindly copy this code)
Session session = sessionFactory.openSession();
User user = new User();
user.setFirstName("Hans");
user.setLastName("Dampf");
// this line will generate and execute the "insert into users" sql for you!
session.save( user );

Compared with plain JDBC, there is no more messing around with PreparedStatements and parameters, Hibernate will make sure to create the right SQL for you (as long as your mapping annotations are right!).

Let’s have a look at what simple select, update and delete SQL statements would look like.

(Parental Advice: Don’t just blindly copy this code)
// Hibernate generates: "select from users where id = 1"
User user = session.get( User.class, 1 );

// Hibernate generates: "update users set...where id=1"
session.update(user);

// Hibernate generates: "delete from useres where id=1"
session.delete(user);

How to use Hibernate’s Query Language (HQL)

So far, we have only looked at basic persistence, like saving or deleting a User object. But there are of course times, when you need more control and need to write more complex SQL statements. For that, Hibernate offers its own query language, the so called HQL (Hibernate Query Language).

HQL looks similar to SQL,but is focused on your Java objects and actually independent of the underlying SQL database. That means, in theory, the same HQL statements work for all databases (MySQL, Oracle, Postgres etc.), with the drawback that you lose access to all database specific features.

Now what does "HQL is focused on Java" objects mean? Let’s have a look at an example:

(Parental Advice: Don’t just blindly copy this code)
List<User> users = session.createQuery("select from User u where u.firstName = 'hans'", User.class).list();

session.createQuery("update User u set u.lastName = :newName where u.lastName = :oldName")
            .executeUpdate();

Both queries look very much like their SQL equivalents, but note that you are not accessing SQL tables or columns (first_name) in these queries.

Instead, you are accessing properties (u.firstName) of your mapped User.java class! Hibernate will then make sure to convert these HQL statements to proper, database specific SQL statements. And in the case of a select automatically convert the returned rows as User objects.

For detailed information on all HQL capabilities, check out the HQL section in the Hibernate documentation.

How to use Hibernate’s Criteria API

When writing HQL statements, you are essentially still writing or concatenating plain strings (though there is tooling support, from IDEs like IntelliJ). Making your HQL/SQL statements dynamic (think: different where clauses depending on user input) is the interesting part.

For that, Hibernate offers another query language, through its Criteria API. There are essentially two versions of the Criteria API (1 and 2), which exist in parallel. Version 1 is deprecated and will be removed sometime in the Hibernate 6.x release line, but it is much easier to use than version 2.

Writing criteria (v2) queries in Hibernate has a steeper learning curve and it needs some more project setup. You need to setup an annotation processing plugin to generate a "Static Metamodel" of your annotated classes (think: the User from above). And then write some seriously complex queries with generated classes.

Let’s have a look at our HQL select query from above, and how you would convert it to criteria query.

(Parental Advice: Don’t just blindly copy this code)
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery( User.class );
Root<User> root = criteria.from( User.class );
criteria.select( root );
criteria.where( builder.equal( root.get( User_.firstName ), "hans" ) );
List<User> users = entityManager.createQuery( criteria ).getResultList();

As you can see, you basically trade in readability and simplicity for type-safety and flexibility - e.g. feel free to sprinkle in if-elses to construct dynamic where clauses on the fly.

But keep in mind, that for our basic example, you now have six lines of code: For a simple "select * from users where firstName = ?".

What are disadvantages of Hibernate?

Hibernate doesn’t just offer simple mapping and querying features. Real-Life mappings and queries will obviously be much more complex than the ones you found above. In addition, Hibernate offers a ton of other convenience features, like cascading, lazy loading, caching and much more. It truly is a complex piece of software, that you cannot fully grasp by just copy-and-pasting some online tutorial.

This somewhat unexpectedly leads to two major issues.

  1. A fair amount of developers, sooner or later, claim that "Hibernate is doing random magic, that nobody understands" or a variant thereof. Because they lack the background knowledge of what Hibernate is doing.

  2. Some developers think that you do not need to understand SQL anymore, when using Hibernate. The reality is, the more complex your software gets, the more SQL skills you will need, to verify the SQL statements that Hibernate generates and optimize them.

To combat these two issues, you only have one choice: To use Hibernate efficiently, you need (to get) good knowledge of Hibernate and SQL.

What are good tutorials or books on Hibernate?

A great book is Java Persistence with Hibernate. It has 608 pages, which already shows the complexity of it all, but your Hibernate skills will benefit greatly from reading it.

If you want more advanced information on Hibernate, make sure to check out Vlad Mihalcea’s or Thorben Janssen’s sites. Both are experts on Hibernate and regularly publish awesome Hibernate content.

If you like to watch video courses, you can also check out the Hibernate screencasts on this site. They are not the brand-newest anymore, but give you a super-fast quickstart into the Hibernate universe.

What is the Java Persistence API (JPA)?

So far, we only talked about plain Hibernate, but what about JPA? How does it compare to a library like Hibernate?

JPA is merely a specification, not an implementation or library So, JPA defines a standard what features a library must support to be JPA compliant. And there’s a couple of libraries, like Hibernate, EclipseLink or TopLink that all implement the JPA specification.

In simpler words: If your library supports (e.g.) saving objects to the database in a certain way, supports mapping and querying capabilities (like the criteria API etc.) and much more, then you can call it fully JPA compliant.

So, instead of writing Hibernate specific code, or EclipseLink specific code, you write JPA specific code. And then it is just a matter of adding some libraries (Hibernate) and configuration file to your JPA project, and you can access your database. In practice, that means JPA is another abstraction on top of Hibernate.

What are current JPA versions?

  • JPA 1.0 - approved in 2006

  • JPA 2.0 - approved in 2009

  • JPA 2.1 - approved in 2013

  • JPA 2.2 - approved in 2017

There’s multiple blogs that sum up the changes in those specifications for you, but Vlad Mihalcea and Thorben Janssen do it best.

What is the actual difference then between Hibernate and JPA?

In theory, JPA allows you to disregard what persistence provider library (Hibernate, EclipseLink etc) you are using in your project.

In practice, as Hibernate is by far the most popular JPA implementation, features in JPA are often "a heavily-inspired-subset" of Hibernate features. For example: JPQL is basically HQL with fewer features. And while a valid JPQL query is always a valid HQL query, this does not work the other way around.

So, as the JPA specification process itself takes time and the output is "just" a common denominator of existing libraries, the features it offers are only a subset of e.g. all the features that Hibernate offers. Otherwise Hibernate and EclipseLink and TopLink would be exactly the same.

Should I use JPA or Hibernate?

In real-life projects, you essentially have two choices:

  • You either use JPA as much as possible, sprinkled in with Hibernate specific features, where the JPA specification is lacking behind.

  • Or use plain Hibernate all the way (my personal, preferred choice).

Both ways are fine IF you know your SQL.

How basic persistence works with JPA

In JPA, the entry point to all database code is the EntityManagerFactory , as well as the EntityManager.

Let’s have a look at the example from above, where we saved Users with the JDBC and Hibernate API. Only this time, we save the users with the JPA API.

(Parental Advice: Don’t just blindly copy this code)
EntityManagerFactory factory = Persistence.createEntityManagerFactory( "org.hibernate.tutorial.jpa" );

EntityManager entityManager = factory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist( new User( "John Wayne") );
entityManager.persist( new User( "John Snow" ) );
entityManager.getTransaction().commit();
entityManager.close();

Apart from different namings (persist vs save, EntityManager vs Session), this reads exactly like the plain Hibernate version.

Hence, if you look at Hibernate’s source code, you’ll find this:

package org.hibernate;

public interface Session extends SharedSessionContract, EntityManager, HibernateEntityManager, AutoCloseable {
  // methods
}

// and

public interface SessionFactory extends EntityManagerFactory, HibernateEntityManagerFactory, Referenceable, Serializable, java.io.Closeable {
    // methods
}

To sum things up:

  • A Hibernate SessionFactory IS a JPA EntityManagerFactory

  • A Hibernate Session IS a JPA EntityManager

Simple as.

How to use JPA’s query language: JPQL

As already mentioned before, JPA comes with its own query language, the JPQL. It is essentially a heavily-inspired-subset of Hibernate’s HQL, with JPQL queries always being valid HQL queries - but not the other way around.

Hence, both versions of the same query will literally look the same:

// HQL
int updatedEntities = session.createQuery(
        "update Person " +
        "set name = :newName " +
        "where name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();

// JPQL
int updatedEntities = entityManager.createQuery(
        "update Person p " +
        "set p.name = :newName " +
        "where p.name = :oldName" )
.setParameter( "oldName", oldName )
.setParameter( "newName", newName )
.executeUpdate();

How to use JPA’s Criteria API

In comparison to HQL vs JPQL, JPA’s Criteria API is essentially completely different from Hibernate’s own Criteria API. We already covered the criteria API in the Hibernate Criteria section.

What other JPA implementations are there?

There are more JPA implementations out there, than just Hibernate. Two primarily come to mind: EclipseLink (see Hibernate vs Eclipselink) and the (older) TopLink.

They lack behind in market-share compared to Hibernate, but you’ll also find projects using them in corporate settings. There are also other projects like BatooJPA, but you will find in most cases that these libraries are abandoned and not maintained anymore, because it is quite some effort to maintain a fully JPA compliant library.

You most certainly need an active community to support further development of your library and Hibernate probably has the most active community as of 2020.

QueryDSL

How does a library like QueryDSL fit into the JPA section of this guide? Up until now, we constructed either HQL/JPQL queries by hand (read: string concatenation), or through the rather complex Criteria (2.0) API.

QueryDSL tries to give you the best of both worlds. Easier construction of queries than with the Criteria API, and more type-safety and less fumbling around than with plain strings.

It should be noted, that QueryDSL was unmaintained for a while, but starting 2020, has picked up steam again. And that it does not only support JPQ, but also NoSQL databases like MongoDB or Lucene.

Let’s look at some example QueryDSL code, which runs a "select * from users where first_name = :name" SQL statement.

(Parental Advice: Don’t just blindly copy this code)
QUser user = QUser.user;
JPAQuery<?> query = new JPAQuery<Void>(entityManager);
List<User> users = query.select(user)
  .from(user)
  .where(user.firstName.eq("Hans"))
  .fetch();

Where does the QUser class come from? QueryDSL will automatically create that for you from your JPA/Hibernate-annotated User class, during compile time - with the help of an appropriate annotation processing compiler plugin.

You can then use these generated classes to execute type-safe queries against the database. Don’t they read much nicer than the JPA Criteria 2.0 equivalent?

ORM frameworks in Java: A Summary

ORM frameworks are mature and complex pieces of software. The major caveat is to think one does not need to understand SQL anymore, when you are working with any of the mentioned JPA implementation.

It is true, ORMs offer you a fast start when trying to map basic classes to database tables. But coupled with a lack of basic knowledge about how they work, you will run into major performance and maintenance challenges later in your project.

Main Takeaway

Make sure you get a good foundation on how e.g. Hibernate works AND how SQL and your database works. Then you are going to be on the right way.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK