Guide to JPA with Hibernate: Basic Mapping

Introduction

The Java Persistence API (JPA) is the persistence standard of the Java ecosystem. It allows us to map our domain model directly to the database structure and then giving us the flexibility of only manipulating objects in our code. This allows us not to dabble with cumbersome JDBC components like Connection, ResultSet, etc.

We'll be making a comprehensive guide to using JPA with Hibernate as its vendor. In this article, we'll be exploring the configuration and basic mapping in Hibernate:

  • Guide to JPA with Hibernate: Basic Mapping (you're here)
  • Guide to JPA with Hibernate: Relationships and Inheritance Mapping (coming soon!)
  • Guide to JPA with Hibernate: Querying (coming soon!)

What's JPA?

Java Persistence API

JPA is an API that aims at standardizing the way we access a relational database from Java software using Object Relational Mapping (ORM).

It was developed as part of the JSR 220 by an EJB 3.0 software expert group, though it's not only devoted to EJB software development.

JPA is no more than an API and thus doesn't provide any implementation but solely defines and standardizes the concepts of ORM in Java.

Therefore, in order to use it, we must provide an implementation of the API. Happily for us, we're not tied to write it ourselves, there are already implementations, called vendors, available:

Each vendor, in addition to implementing the API, also provides some specific features. In this article we're going to use Hibernate as our vendor, though we won't look at its peculiarities.

Object Relational Mapping

Object Relational Mapping is a technique used to create a mapping between a relational database and objects of a software - in our case, Java objects. The idea behind this is to stop working with cursors or arrays of data obtained from the database, but rather directly obtain objects representing our business domain.

To achieve that, we use techniques to map our domain objects to the database tables so that they are automatically filled with the data from the tables. Then, we can perform standard object manipulation on them.

Our Example

Before getting started, we'll introduce the example that we'll use throughout the series. The idea is to map the model of a school with students taking courses given by teachers.

Here is what the final model looks like:

domain model

As we can see, there are a few classes with some properties. And those classes have relationships between them. By the end of this series, we'll have mapped all those classes to database tables and be able to save and retrieve data from the database using them.

Getting Started

Let's get straight to the point with a working, though minimalist, example. First of all we'll need to import the JPA/Hibernate dependency. Using Maven, let's add the necessary dependencies to our pom.xml:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>${version}</version>
</dependency>

We'll also need a database to work with. H2 is lightweight and simple, so we'll go with that:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>${version}</version>
</dependency>

Then, we'll have to create a persistence.xml file in our classpath, under a META-INF directory. This file is used to configure JPA, telling what the vendor is, which database we're going to use and how to connect to it, what are the classes to map, etc.

For now, it'll look like this:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">
    <persistence-unit name="guide-to-jpa-with-hibernate">
        <class>com.fdpro.clients.stackabuse.jpa.domain.Student</class>

        <properties>
            <!-- Database configuration -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/>
            <property name="javax.persistence.jdbc.user" value="user"/>
            <property name="javax.persistence.jdbc.password" value="password"/>

            <!-- Schema configuration -->
            <property name="javax.persistence.schema-generation.database.action" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

We won't bother much with the meaning of all this for now. Finally, we're going to map our first class, Student:

@Entity
public class Student {
    @Id
    private Long id;

    public Long id() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

This means that this class will be an entity in our database. Hibernate now knows that it should map this entity into a database table, and that we'll be populating instances of this class with the data from the table. The mandatory @Id will serve as a primary key of the matching table.

Now, let's see how to manipulate this entity:

EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("guide-to-jpa-with-hibernate");
EntityManager entityManager = entityManagerFactory.createEntityManager();

entityManager.getTransaction().begin();

Student student = new Student();
student.setId(1L);
entityManager.persist(student);

entityManager.getTransaction().commit();
entityManager.clear();

Student foundStudent = entityManager.find(Student.class, 1L);

assertThat(foundStudent).isEqualTo(student);

entityManager.close();

Again, let's not bother with everything here as it'll get a lot simpler. This is a bit crude, but a proof-of-concept approach to checking if we can access the entity programmatically.

All we have to know for the moment is that this code allows us to save a Student entity to the database and then retrieve it. The assertThat() statement passes as the foundStudent genuinely is the one we're searching for.

That's all for our first steps with the Java Persistence API. We'll have the chance to dive deeper into the concepts we used here in the rest of the tutorial.

Configuration

It's now time to dive deeper into the API, starting with the persistence.xml configuration file. Let's see what we have to put in there.

Namespace, Schema, and Version

First of all, here is the opening tag:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">

Here we can see we're defining the namespace, http://xmlns.jcp.org/xml/ns/persistence, and the schema location, http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd (note the version).

Also, though we already did mentioned it in the schema location, we are mentioning the version again.

So, here we're working with the version 2.2 of JPA.

Persistence Unit

Then, right after the opening tag, we declared a <persistence-unit> tag:

<persistence-unit name="guide-to-jpa-with-hibernate">

A persistence unit defines a set of entities managed by an application and being found in a given database. It must have a name, that'll be used later. All of the following configuration will be within this persistence unit as it refers to that single database.

If we were to have multiple different databases, and therefore different sets of entities, we would have to define multiple persistence units, all with different names.

Mapped Classes

Then, the first thing we notice in the persistence unit is a <class> tag with the qualified name of our Student class:

<class>com.fdpro.clients.stackabuse.jpa.domain.Student</class>

That's because we must manually define every mapped class in the persistence.xml file.

Frameworks like Spring made this process much simpler by introducing us with the packagesToScan property, which automatically scans entire packages for annotations.

Database

After that, there are the properties, starting with the database configuration:

<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/>
<property name="javax.persistence.jdbc.user" value="user"/>
<property name="javax.persistence.jdbc.password" value="password"/>

There are a few lines here, let's go through them one after the other:

  • javax.persistence.jdbc.driver: The qualified name to the driver needed to communicate with the database.
  • javax.persistence.jdbc.url: The URL of the database, here we indicate that we want to communicate with an in-memory instance of H2.
  • javax.persistence.jdbc.user: The user to connect to the database. It doesn't actually matter what we put there since the H2 instance does not have a specific user. We would even have been able to omit this line.
  • javax.persistence.jdbc.password: The password matching the user. Same thing applies here for the H2 instance, we can omit this or put whatever we want.

Schema

Finally, we tell JPA to create our schema on start-up. We do that mainly because we're using an in-memory database and so the schema is lost every time the database is stopped.

<property name="javax.persistence.schema-generation.database.action" value="create"/>

In a production application with a persistent database we would probably not rely on this mechanism to create our database schema.

Mapping Classes

Now that our minimal configuration has been covered, let's get to the main topic: mappings. As a reminder, mapping is the mechanism of binding our Java classes to database tables.

So, the first thing we must do to map a class to a database table is annotate it with the @Entity annotation:

@Entity
public class Student {}

If we stop right there, then JPA will deduce the table name from the name of the class: STUDENT. Database tables are not case sensitive, but for clarity we're going to use uppercase when referring to them.

But now, what if we want to map that class to a table named differently, like STUD? Then we have to use the @Table annotation, which takes a name attribute:

@Entity
@Table(name = "STUD")
public class Student {}

Now, our class is mapped to the STUD table instead of STUDENT. This comes particularly handy when working with a legacy database, which may have table names that are abbreviations or cumbersome names. Then, we can give proper names to our classes, even if the database table names are much different.

Mapping Fields

Now, let's get to mapping our fields to database columns. Depending on the fields, there are a few techniques available.

Basics

Let's start with the easy ones. There are a bunch of types that are automatically handled by JPA:

  • Primitives
  • Primitives wrappers
  • String
  • BigInteger, BigDecimal
  • Dates (their mapping could require some configuration though, so they'll have their own section)

When we put one field of one of those types in our classes, they are automatically mapped to a column of the same name.

So, if we were to add last and first names to our Student:

public class Student {
    private String lastName;
    private String firstName;
}

Then, these fields would be mapped to columns named LASTNAME and FIRSTNAME, respectively.

Again, we would certainly like to customize our columns names. In order to do that we would have to use the @Column annotation and its name attribute:

public class Student {
    private String lastName;

    @Column(name = "FIRST_NAME")
    private String firstName;
}

Just like that, our firstName field is mapped to a FIRST_NAME column.

Let's see if this works by retrieving a student from the database. First of all, let's create a dataset file, data.sql, which we'll put at our classpath's root:

insert into STUD(ID, LASTNAME, FIRST_NAME) values(2, 'Doe', 'John');

Then, let's tell JPA to load this dataset. That's done using the javax.persistence.sql-load-script-source property in our persistence.xml:

<property name="javax.persistence.sql-load-script-source" value="data.sql"/>

Finally we can write a test asserting that we retrieve our student and its data are correct:

Student foundStudent = entityManager.find(Student.class, 2L);

assertThat(foundStudent.id()).isEqualTo(2L);
assertThat(foundStudent.lastName()).isEqualTo("Doe");
assertThat(foundStudent.firstName()).isEqualTo("John");

Ids

Now, let's rapidly talk about IDs. There is a lot to say about them, though we'll only get into the basics here. In order to declare an ID, we need to use the @Id annotation:

public class Student {
    @Id
    private Long id;
}

But what is an ID exactly? It's the mapping of our table's primary key - that is, the column identifying our rows. Sometimes, we want our primary keys to be auto generated. To do that in JPA, we must then use the @GeneratedValue annotation alongside the @Id one:

public class Student {
    @Id
    @GeneratedValue
    private Long id;
}

There are multiple value generation strategies, which you can specify by setting the strategy flag:

@GeneratedValue(strategy = GenerationType.TYPE)

Without setting the strategy, Hibernate will pick the one best suiting our database provider.

Dates

We mentioned dates earlier, saying they were naturally handled by JPA, but with some peculiarities.

So, first of all, let's remember Java provides us with two date and time representations: The one in the java.util package (Date, Timestamp, etc.) and the one in the java.time package (LocalDate, LocalTime, LocalDateTime, etc.).

The former are handled through the usage of the @Temporal annotation, while the latter are handled out of the box, but only since the version 2.2 of JPA. Before that we would have had to use converters, which we'll see later on in this article for legacy projects.

Let's start with mapping a Date field, let's say the birth date of a student:

public class Student {
    @Temporal(TemporalType.DATE)
    private Date birthDate;
}

We can notice that the @Temporal annotation takes an argument of type TemporalType. This needs be specified in order to define the type of the column in the database.

Will it be holding a date? A time? A date and a time?

There is an enum value for each of these possibilities: DATE, TIME, and TIMESTAMP, respectively.

We need to do that because a Date object holds date and time together, meaning we must specify what part of the data we really need.

The new Java time representation made that easier for us, as there is a specific type for date, for time, and for datetime.

Thus, if we want to use a LocalDate instead of a Date, we can simply map the field without the @Temporal annotation:

public class Student {
    private LocalDate birthDate;
}

And as simply as that, our field is mapped!

Enums

Another kind of field that needs specific attention are enums. Out of the box, JPA offers an annotation to map enums - @Enumerated. This annotation takes an argument of type EnumType, which is an enum offering the values ORDINAL and STRING.

The former maps the enum to an integer representing its declaration position, which makes it forbidden then to change the order of the enum constants. The latter uses the enum constants names as the corresponding value in the database. With this solution, we can't rename the enum constants.

Also, if we're working with a legacy database, we might be forced to use names already stored in for our enum constants, which we might not want if those names are not meaningful. The solution then would be to give the enum a field representing the database value, letting us choose whatever constant name we see fit, and use a converter to map the enum type. We'll see converters in the very next section.

So, what does it all say about our Student example? Let's say we want to add gender to the student, which is represented by an enum:

public enum Gender {
    MALE,
    FEMALE
}

public class Student {
    private Gender gender;
}

Then, we must add the @Enumerated annotation to our gender field for it to be mapped:

public class Student {
    @Enumerated
    private Gender gender;
}

But what about the argument we talked about earlier? By default, the selected EnumType is ORDINAL. We might want to change that to STRING though:

public class Student {
    @Enumerated(EnumType.STRING)
    private Gender gender;
}

And there we are, students' genders will now be mapped as MALE and FEMALE in the database.

Converters

This section will be about the converters we talked much about earlier. Converters are to be used when we want a database column to be mapped to a type that's not handled out of the box by JPA.

Let's say, for example, we have a column telling us if a student wants to receive the school newsletter or not, but the data stored in this column are Y and N for "yes" and "no", respectively. Then we have multiple possibilities:

  • Map the column to a String, but that will be cumbersome to use in the code.
  • Map the column to some kind of YesNo enum, but that seems like an overkill.
  • Map the column to a Boolean, and now we're getting somewhere!

So, how do we achieve that last one? By using a converter. First of all, we must create a YesNoBooleanConverter class, which implements the AttributeConverter interface:

public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> {
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return null;
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        return null;
    }
}

We notice then that there are two methods to implement. The first one converts our boolean to a String to be stored in the database while the other one converts a database value to a boolean. Let's implement them:

public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> {
    @Override
    public String convertToDatabaseColumn(Boolean attribute) {
        return attribute ? "Y" : "N";
    }

    @Override
    public Boolean convertToEntityAttribute(String dbData) {
        return dbData.equals("Y");
    }
}

Here, we consider our column will always hold a value, no matter what, and that this value will always be Y or N. We might have to write a bit more code in more complex cases (to handle null values, for example).

Now, what do we do with that? We'll map our student field with a @Convert annotation, which takes our class as an argument:

public class Student {
    @Convert(converter = YesNoBooleanConverter.class)
    private boolean wantsNewsletter;
}

Notice how we mapped our field as a primitive boolean, not a wrapper type. We can do that because we know our column will always hold a value and that the converter we wrote never returns null as a value.

But, we're not finished yet. We must still add the converter to our persistence.xml file:

<class>com.fdpro.clients.stackabuse.jpa.domain.converters.YesNoBooleanConverter</class>

And now it works. However, what can we do if we have a bunch of yes/no columns in our database and we find it tiring to repeat the @Convert annotation for those types all the time? Then we can add a @Converter annotation to our YesNoBooleanConverter class and pass it the autoApply = true argument.

Then every time we have a String value in the database we want to map as a Boolean in our code, this converter will be applied. Let's add it:

@Converter(autoApply = true)
public class YesNoBooleanConverter implements AttributeConverter<Boolean, String>

And then remove the @Convert annotation from the `Student' class:

public class Student {
    private boolean wantsNewsletter;
}

Embedded

Finally, let's talk about embedded types. What are they for? Let's imagine our STUD table contains the students address information: street, number, and city. But, in our code we would like to use an Address object, making it reusable and, most of all, an object (cause we're still doing object-oriented programming!).

Now, let's do that in the code:

public class Address {
    private String street;
    private String number;
    private String city;
}

public class Student {
    private Address address;
}

Of course, it won't work like that yet. We must tell JPA what it has to do with this field. That's what the @Embeddable and @Embedded annotations are for. The first one will go on our Address class and the second one on the field:

@Embeddable
public class Address {
    private String street;
    private String number;
    private String city;
}

public class Student {
    @Embedded
    private Address address;
}

Let's see our dataset again:

insert into STUD(ID, LASTNAME, FIRST_NAME, BIRTHDATE, GENDER, WANTSNEWSLETTER, STREET, NUMBER, CITY)
    values(2, 'Doe', 'John', TO_DATE('2000-02-18', 'YYYY-MM-DD'), 'MALE', 'Y', 'Baker Street', '221B', 'London');

It has evolved a bit since the beginning. You can see here that we added all the columns from the previous sections as well as the street, number, and city. We've done this as if the fields belong to the Student class, not the Address class.

Now, is our entity still mapped correctly? Let's try it out:

Student foundStudent = entityManager.find(Student.class, 2L);

assertThat(foundStudent.id()).isEqualTo(2L);
assertThat(foundStudent.lastName()).isEqualTo("Doe");
assertThat(foundStudent.firstName()).isEqualTo("John");
assertThat(foundStudent.birthDateAsDate()).isEqualTo(DateUtil.parse("2000-02-18"));
assertThat(foundStudent.birthDateAsLocalDate()).isEqualTo(LocalDate.parse("2000-02-18"));
assertThat(foundStudent.gender()).isEqualTo(Gender.MALE);
assertThat(foundStudent.wantsNewsletter()).isTrue();

Address address = new Address("Baker Street", "221B", "London");
assertThat(foundStudent.address()).isEqualTo(address);

It's still working well!

Now, what if we want to reuse the Address class for other entities, but the column names are different? Let's not panic, JPA has us covered with the @AttributeOverride annotation.

Let's say the STUD table columns for the address are: ST_STREET, ST_NUMBER, and ST_CITY. It might seem like we're getting creative, but let's be honest, legacy code and databases are definitively creative places.

Then we must tell JPA that we override the default mapping:

public class Student {
    @AttributeOverride(name = "street", column = @Column(name = "ST_STREET"))
    @AttributeOverride(name = "number", column = @Column(name = "ST_NUMBER"))
    @AttributeOverride(name = "city", column = @Column(name = "ST_CITY"))
    private Address address;
}

And there we have it, our mapping is fixed. We should note that, since JPA 2.2, the @AttributeOverride annotation is repeatable.

Before that we would have had to wrap them with the @AttributeOverrides annotation:

public class Student {
    @AttributeOverrides({
        @AttributeOverride(name = "street", column = @Column(name = "ST_STREET")),
        @AttributeOverride(name = "number", column = @Column(name = "ST_NUMBER")),
        @AttributeOverride(name = "city", column = @Column(name = "ST_CITY"))
    })
    private Address address;
}

Conclusion

In this article, we dove into what JPA and Hibernate are and their relationship. We've configured Hibernate in a Maven project and dove into basic object-relational mapping.

The code for this series can be found over on GitHub.