Java persistence with JPA and Hibernate, Part 1: Entities and relationships
The Java Persistence API (JPA) is a Java specification that bridges the gap between relational databases and object-oriented programming. This two-part tutorial introduces JPA and explains how Java objects are modeled as JPA entities, how entity relationships are defined, and how to use JPA's EntityManager with the Repository pattern in your Java applications.
Note that this tutorial uses Hibernate as the JPA provider. Most concepts can be extended to other Java persistence frameworks.
See "What is JPA? Introduction to the Java Persistence API" to learn about the evolution of JPA and related frameworks, including EJB 3.0. and JDBC.
Relational databases have existed as a means for storing program data since the 1970s. While developers today have many alternatives to the relational database, this type of database is scalable and well understood, and is still widely used in small- and large-scale software development.
Java objects in a relational database context are defined as entities. Entities are placed in tables where they occupy columns and rows. Programmers use foreign keys and join tables to define the relationships between entities--namely one-to-one, one-to-many, and many-to-many relationships. We can also use SQL (Structured Query Language) to retrieve and interact with data in individual tables and across multiple tables, using foreign key constraints. The relational model is flat, but developers can write queries to retrieve data and construct objects from that data.
You may be familiar with the term object-relations impedance mismatch, which refers to the challenge of mapping data objects to a relational database. This mismatch occurs because object-oriented design is not limited to one-to-one, one-to-many, and many-to-many relationships. Instead, in object-oriented design, we think of objects, their attributes and behavior, and how objects relate. Two examples are encapsulation and inheritance:
Association, aggregation, composition, abstraction, generalization, realization, and dependencies are all object-oriented programming concepts that can be challenging to map to a relational model.
The mismatch between object-oriented design and relational database modeling has led to a class of tools developed specifically for object-relational mapping (ORM). ORM tools like Hibernate, EclipseLink, and iBatis translate relational database models, including entities and their relationships, into object-oriented models. Many of these tools existed before the JPA specification, but without a standard their features were vendor dependent.
First released as part of EJB 3.0 in 2006, the Java Persistence API (JPA) offers a standard way to annotate objects so that they can be mapped and stored in a relational database. The specification also defines a common construct for interacting with databases. Having an ORM standard for Java brings consistency to vendor implementations, while also allowing for flexibility and add-ons. As an example, while the original JPA specification is applicable to relational databases, some vendor implementations have extended JPA for use with NoSQL databases.
The first release of JPA, version 1.0, was published in 2006 through the Java Community Process (JCP) as Java Specification Request (JSR) 220. Version 2.0 (JSR 317) was published in 2009, version 2.1 (JSR 338) in 2013, and version 2.2 (a maintenance release of JSR 338) was published in 2017. JPA 2.2 has been selected for inclusion and ongoing development in Jakarta EE.
The Java Persistence API is a specification, not an implementation: it defines a common abstraction that you can use in your code to interact with ORM products. This section reviews some of the important parts of the JPA specification.
You'll learn how to:
In order to define an entity, you must create a class that is annotated with the @Entity annotation. The @Entity annotation is a marker annotation, which is used to discover persistent entities. For example, if you wanted to create a book entity, you would annotate it as follows:
By default, this entity will be mapped to the Book table, as determined by the given class name. If you wanted to map this entity to another table (and, optionally, a specific schema) you could use the @Table annotation to do that. Here's how you would map the Book class to a BOOKS table:
If the BOOKS table was in the PUBLISHING schema, you could add the schema to the @Table annotation:
With the entity mapped to a table, your next task is to define its fields. Fields are defined as member variables in the class, with the name of each field being mapped to a column name in the table. You can override this default mapping by using the @Column annotation, as shown here:
In this example, we've accepted the default mapping for the name attribute but specified a custom mapping for the isbn attribute. The name attribute will be mapped to the name column, but the isbn attribute will be mapped to the ISBN_NUMBER column.
The @Column annotation allows us to define additional properties of the field/column, including length, whether it is nullable, whether it must be unique, its precision and scale (if it's a decimal value), whether it is insertable and updatable, and so forth.
One of the requirements for a relational database table is that it must contain a primary key, or a key that uniquely identifies a specific row in the database. In JPA, we use the @Id annotation to designate a field to be the table's primary key. The primary key is required to be a Java primitive type, a primitive wrapper, such as Integer or Long, a String, a Date, a BigInteger, or a BigDecimal.
In this example, we map the id attribute, which is an Integer, to the ID column in the BOOKS table:
It is also possible to combine the @Id annotation with the @Column annotation to overwrite the primary key's column-name mapping.
Now that you know how to define an entity, let's look at how to create relationships between entities. JPA defines four annotations for defining entities:
The @OneToOne annotation is used to define a one-to-one relationship between two entities. For example, you may have a User entity that contains a user's name, email, and password, but you may want to maintain additional information about a user (such as age, gender, and favorite color) in a separate UserProfile entity. The @OneToOne annotation facilitates breaking down your data and entities this way.
The User class below has a single UserProfile instance. The UserProfile maps to a single User instance.
The JPA provider uses UserProfile's user field to map UserProfile to User. The mapping is specified in the mappedBy attribute in the @OneToOne annotation.
The @OneToMany and @ManyToOne annotations facilitate both sides of the same relationship. Consider an example where a Book can have only one Author, but an Author may have many books. The Book entity would define a @ManyToOne relationship with Author and the Author entity would define a @OneToMany relationship with Book.
In this case, the Author class maintains a list of all of the books written by that author and the Book class maintains a reference to its single author. Additionally, the @JoinColumn specifies the name of the column in the Book table to store the ID of the Author.
Finally, the @ManyToMany annotation facilitates a many-to-many relationship between entities. Here's a case where a Book entity has multiple Authors:
In this example, we create a new table, BOOK_AUTHORS, with two columns: BOOK_ID and AUTHOR_ID. Using the joinColumns and inverseJoinColumns attributes tells your JPA framework how to map these classes in a many-to-many relationship. The @ManyToMany annotation in the Author class references the field in the Book class that manages the relationship; namely the authors property.
That's a quick demo for a fairly complex topic. We'll dive further into the @JoinTable and @JoinColumn annotations in the next article.
EntityManager is the class that performs database interactions in JPA. It is initialized through a configuration file named persistence.xml. This file is found in the META-INF folder in your CLASSPATH, which is typically packaged in your JAR or WAR file. The persistence.xml file contains:
Let's look at an example.
First, we create an EntityManager using the EntityManagerFactory retrieved from the Persistence class:
In this case we've created an EntityManager that is connected to the "Books" persistence unit, which we've configured in the persistence.xml file.
The EntityManager class defines how our software will interact with the database through JPA entities. Here are some of the methods used by EntityManager:
Don't worry about integrating all of these methods at once. You'll get to know them by working directly with the EntityManager, which we'll do more in the next section.