Mastering 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 extend to other Java persistence frameworks.

Object relations in JPA

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.

Object-relations impedance mismatch

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:

  • If an object contains another object, we define this through encapsulation–a has-a relationship.
  • If an object is a specialization of another object, we define this through inheritance–an is-a relationship.

Association, aggregation, composition, abstraction, generalization, realization, and dependencies are all object-oriented programming concepts that can be challenging to map to a relational model.

ORM: Object-relational mapping

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.

Getting started with JPA

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:

  • Define entities, fields, and primary keys in the database.
  • Create relationships between entities in the database.
  • Work with the EntityManager and its methods.

Defining entities

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:


@Entity
public class Book 
   ...

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:


@Entity
@Table(name="BOOKS")
public class Book 
   ...

If the BOOKS table was in the PUBLISHING schema, you could add the schema to the @Table annotation:


@Table(name="BOOKS", schema="PUBLISHING")

Mapping fields to columns

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:


@Entity
@Table(name="BOOKS")
public class Book 
   private String name;
   @Column(name="ISBN_NUMBER")
   private String isbn;
   ...

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 a 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.

Specifying the primary key

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:


@Entity
@Table(name="BOOKS")
public class Book 
   @Id
   private Integer id;
   private String name;
   @Column(name="ISBN_NUMBER")
   private String isbn;
   ...

It is also possible to combine the @Id annotation with the @Column annotation to overwrite the primary key’s column-name mapping.

Relationships between entities

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:

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • @ManyToMany

One-to-one relationships

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.


@Entity
public class User 
   @Id
   private Integer id;
   private String email;
   private String name;
   private String password;
   @OneToOne(mappedBy="user")
   private UserProfile profile;
   ...


@Entity
public class UserProfile 
   @Id
   private Integer id;
   private int age;
   private String gender;
   private String favoriteColor;
   @OneToOne
   private User user;
   ...

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.

One-to-many and many-to-one relationships

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.


@Entity
public class Book 
    @Id
    private Integer id;
    private String name;
    @ManyToOne
    @JoinColumn(name="AUTHOR_ID")
    private Author author;
    ...


@Entity
public class Author 
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    @OneToMany(mappedBy = "author")
    private List<Book> books = new ArrayList<>();
    ...

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.

Many-to-many relationships

Finally, the @ManyToMany annotation facilitates a many-to-many relationship between entities. Here’s a case where a Book entity has multiple Authors:


@Entity
public class Book 
    @Id
    private Integer id;
    private String name;
    @ManyToMany
    @JoinTable(name="BOOK_AUTHORS",
    		   joinColumns=@JoinColumn(name="BOOK_ID"),
    		   inverseJoinColumns=@JoinColumn(name="AUTHOR_ID"))
    private Set<Author> authors = new HashSet<>();
    ...


@Entity
public class Author 
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    @ManyToMany(mappedBy = "author")
    private Set<Book> books = new HashSet<>();
    ...

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.

Working with the EntityManager

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:

  • The named “persistence unit,” which specifies the persistence framework you’re using, such as Hibernate or EclipseLink.
  • A collection of properties specifying how to connect to your database, as well as any customizations in the persistence framework.
  • A list of entity classes in your project.

Let’s look at an example.

Configuring the EntityManager

First, we create an EntityManager using the EntityManagerFactory retrieved from the Persistence class:


EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books");
EntityManager entityManager = entityManagerFactory.createEntityManager();

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:

  • find retrieves an entity by its primary key.
  • createQuery creates a Query instance that can be used to retrieve entities from the database.
  • createNamedQuery loads a Query that has been defined in a @NamedQuery annotation inside one of the persistence entities. Named queries provide a clean mechanism for centralizing JPA queries in the definition of the persistence class on which the query will execute.
  • getTransaction defines an EntityTransaction to use in your database interactions. Just like database transactions, you will typically begin the transaction, perform your operations, and then either commit or rollback your transaction. The getTransaction() method lets you access this behavior at the level of the EntityManager, rather than the database.
  • merge() adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using merge(), objects are not managed.
  • persist adds an entity to the persistence context, so that when the transaction is committed, the entity will be persisted to the database. When using persist(), objects are managed.
  • refresh refreshes the state of the current entity from the database.
  • flush synchronizes the state of the persistence context with the database.

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.

Previous

1

2

3

4



Page 2

Next

JPA with Hibernate

In this section we move past concepts and start writing code that persists data to and from a relational database using JPA with Hibernate. We’ll start by configuring an example application to use Hibernate as the JPA provider, then we’ll quickly configure the EntityManager and write two classes that we want to persist to the database: Book and Author. Finally, we’ll write a simple application that pulls together all of the application components and successfully persists our two entities to the database.

In this section you’ll learn how to:

  • Configure a Java application to use Hibernate as your JPA provider.
  • Configure JPA’s EntityManager in a persistence.xml file.
  • Create a simple JPA domain model representing two classes with a one-to-many relationship.
  • Use repositories to cleanly separate persistence logic from your application code.
  • Write, build, and run the example application using JPA to connect with a relational database.

You’ll also get started with JPA Query Language (JPQL) and use it to execute a few simple database operations on the example application.

download

Download source code for the example application in this tutorial. Created for JavaWorld by Steven Haines.

Configuring Hibernate

To keep things simple, we’re going to use the embedded H2 database for both development and runtime examples. You can change the JDBC URL in the persistence.xml file to point to any database you wish.

Start by reviewing the Maven POM file for the project, shown in Listing 1.

Listing 1. pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.geekcap.javaworld</groupId>
    <artifactId>jpa-example</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>jpa-example</name>
    <url>http://maven.apache.org</url>
    <properties>
        <java.version>1.8</java.version>
        <hibernate.version>5.3.6.Final</hibernate.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.0.2</version>
                <configuration>
                    <source>$java.version</source>
                    <target>$java.version</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                            <mainClass>com.geekcap.javaworld.jpa.JpaExample</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy</id>
                        <phase>install</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>$project.build.directory/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>$hibernate.version</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>$hibernate.version</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.1-api</artifactId>
            <version>1.0.2.Final</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.197</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

We’ll build this project using Java 8 and Hibernate version 5.3.6. Final, which is the latest version as of this writing. Plug-ins in the build node will set the Java compilation version, make the resultant JAR file executable, and ensure that all dependencies are copied to a lib folder, so that the executable JAR can run. We include four dependencies:

  • hibernate-core: Hibernate’s core functionality.
  • hibernate-entitymanager: Hibernate’s support for an EntityManager.
  • hibernate-jpa-2.1-api: The JPA API.
  • h2: The embedded H2 database. Note that its scope is set to runtime so that we can use it when we run our code.

Configuring the EntityManager

Recall that JPA’s EntityManager is driven by the persistence.xml file. Listing 2 shows the contents of this file.

Listing 2. EntityManager config in persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
    <persistence-unit name="Books" transaction-type="RESOURCE_LOCAL">
        <!-- Persistence provider -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <!-- Entity classes -->
        <class>com.geekcap.javaworld.jpa.model.Book</class>
        <class>com.geekcap.javaworld.jpa.model.Author</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
            <property name="javax.persistence.jdbc.url"    value="jdbc:h2:mem:bookstore" />
            <property name="javax.persistence.jdbc.user" value="sa" />
            <property name="javax.persistence.jdbc.password" value="" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="show_sql" value="true"/>
            <property name="hibernate.temp.use_jdbc_metadata_defaults" value="false"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

The persistence.xml file begins with a persistence node that can contain one or more persistence-units. A persistence-unit has a name, which we’ll use later when we create the EntityManager, and it defines the attributes of that unit. In this case, we configure properties in this unit to do the following:

  • Specify HibernatePersistenceProvider, so the application knows we’re using Hibernate as our JPA provider.
  • Define two entities: a Book class and an Author class.
  • Define the database configuration via JDBC. In this case, we’re using an in-memory H2 instance.
  • Configure Hibernate, including setting the Hibernate dialect to H2Dialect, so that Hibernate knows how to communicate with the H2 database.

The domain model

For this application, we’re modeling a Book class and an Author class. These entities have a one-to-many relationship, meaning that a book can only be written by a single author, but an author can write many books. This domain model is shown in Figure 1.

osjp jpahibernate p1 fig1 Steven Haines

Figure 1. Domain model for a JPA/Hibernate application with two entities

Note: When we talk about database tables we typically speak about a “data model,” but when we talk about Java entities and their relationships we typically refer to it as a “domain model.”

Modeling the Book class

Let’s begin with our entities. Listing 3 shows the source code for the Book class.

Listing 3. Book.java

package com.geekcap.javaworld.jpa.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@Table(name = "BOOK")
@NamedQueries(
        @NamedQuery(name = "Book.findByName",
                query = "SELECT b FROM Book b WHERE b.name = :name"),
        @NamedQuery(name = "Book.findAll",
                query = "SELECT b FROM Book b")
)
public class Book 
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    @ManyToOne
    @JoinColumn(name="AUTHOR_ID")
    private Author author;
    public Book() 
    
    public Book(Integer id, String name) 
        this.id = id;
        this.name = name;
    
    public Book(String name) 
        this.name = name;
    
    public Integer getId() 
        return id;
    
    public void setId(Integer id) 
        this.id = id;
    
    public String getName() 
        return name;
    
    public void setName(String name) 
        this.name = name;
    
    public Author getAuthor() 
        return author;
    
    public void setAuthor(Author author) 
        this.author = author;
    
    @Override
    public String toString() 
        return "Book" +
                "id=" + id +
                ", name='" + name + ''' +
                ", author=" + author.getName() +
                '';
    

The Book class is a simple POJO (plain old Java object) that manages three properties:

  • id: The primary key, or identifier, of the book.
  • name: The name, or title, of the book.
  • author: The author who wrote the book.

The class itself is annotated with three annotations:

  • @Entity: Identifies the Book as a JPA entity.
  • @Table: Overrides the name of the table to which this entity will be persisted. In this case we define the table name as BOOK.
  • @NamedQueries: Allows you to define JPA Query Language queries that can later be retrieved and executed by the EntityManager.

The Book‘s id attribute is annotated with both the @Id and @GeneratedValue. The @Id annotation identifies the id as the primary key of the Book, which will resolve to the primary key of the underlying database. The @GeneratedValue annotation tells JPA that the database should generate the primary key when the entity is persisted to the database. Because we have not specified a @Column annotation, the id will be mapped to the same column name, “id.”

The Book‘s name attribute will be mapped to the “name” column in the BOOK table.

Previous

1

2

3

4



Page 3

Next

Finally, the author field is annotated with the @ManyToOne and @JoinColumn annotations. Recall that the @ManyToOne is one side of a one-to-many relationship. This annotation tells the JPA provider that there can be many books to one author.

Modeling the Author class

Listing 4 shows the source code for the Author class.

Listing 4. Author.java

package com.geekcap.javaworld.jpa.model;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name="AUTHOR")
@NamedQueries(
        @NamedQuery(name = "Author.findByName",
                query = "SELECT a FROM Author a WHERE a.name = :name")
)
public class Author 
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Book> books = new ArrayList<>();
    public Author() 
    
    public Author(String name) 
        this.name = name;
    
    public Author(Integer id, String name) 
        this.id = id;
        this.name = name;
    
    public Integer getId() 
        return id;
    
    public void setId(Integer id) 
        this.id = id;
    
    public String getName() 
        return name;
    
    public void setName(String name) 
        this.name = name;
    
    public List<Book> getBooks() 
        return books;
    
    public void addBook(Book book) 
        books.add(book);
        book.setAuthor(this);
    
    @Override
    public String toString() 
        return "Author" +
                "id=" + id +
                ", name='" + name + ''' +
                ", books=" + books +
                '';
    

The Author class isn’t much different from the Book class:

  • The @Entity annotation identifies Author as a JPA entity.
  • The @Table annotation tells Hibernate that this entity should be stored in the AUTHOR table.
  • The @Table annotation also defines an Author.findByName named query.

The Author class maintains a list of books written by the given author, which is annotated with the @OneToMany annotation. Author‘s @OneToMany annotation matches the @ManyToOne annotation on the Book class. The mappedBy field tells Hibernate that this field is stored in the Book‘s author property.

CascadeType

You might note the CascadeType in the @OneToMany annotation. CascadeType is an enumerated type that defines cascading operations to be applied in a given relationship. In this case, CascadeType defines operations performed on the author, that should be propagated to the book. CascadeTypes include the following:

  • DETACH: When an entity is detached from the EntityManager, detach the entities on the other side of the operation, as well.
  • MERGE: When an entity is merged into the EntityManager, merge the entities on the other side of the operation, as well.
  • PERSIST: When an entity is persisted to the EntityManager, persist the entities on the other side of the operation, as well.
  • REFRESH: When an entity is refreshed from the EntityManager, also refresh the entities on the other side of the operation.
  • FLUSH: When an entity is flushed to the EntityManager, flush its corresponding entities.
  • ALL: Includes all of the aforementioned operation types.

When any operation is performed on an author, its books should be updated. This makes sense because a book cannot exist without its author.

Repositories in JPA

We could create an EntityManager and do everything inside the sample application class, but using external repository classes will make the code cleaner. As defined by the Repository pattern, creating a BookRepository and AuthorRepository isolates the persistence logic for each entity. Listing 5 shows source code for the BookRepository.

Listing 5. BookRepository.java

package com.geekcap.javaworld.jpa.repository;
import com.geekcap.javaworld.jpa.model.Book;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class BookRepository 
    private EntityManager entityManager;
    public BookRepository(EntityManager entityManager) 
        this.entityManager = entityManager;
    
    public Optional<Book> findById(Integer id) 
        Book book = entityManager.find(Book.class, id);
        return book != null ? Optional.of(book) : Optional.empty();
    
    public List<Book> findAll() 
        return entityManager.createQuery("from Book").getResultList();
    
    public Optional<Book> findByName(String name) 
        Book book = entityManager.createQuery("SELECT b FROM Book b WHERE b.name = :name", Book.class)
                .setParameter("name", name)
                .getSingleResult();
        return book != null ? Optional.of(book) : Optional.empty();
    
    public Optional<Book> findByNameNamedQuery(String name) 
        Book book = entityManager.createNamedQuery("Book.findByName", Book.class)
                .setParameter("name", name)
                .getSingleResult();
        return book != null ? Optional.of(book) : Optional.empty();
    
    public Optional<Book> save(Book book) 
        try 
            entityManager.getTransaction().begin();
            entityManager.persist(book);
            entityManager.getTransaction().commit();
            return Optional.of(book);
         catch (Exception e) 
            e.printStackTrace();
        
        return Optional.empty();
    

The BookRepository is initialized with an EntityManager, which we’ll create in our sample application. The first method, findById(), invokes EntityManager‘s find() method, which retrieves an entity of a given class with its given primary key. If, for example, we add a new book and its primary key is generated as “1,” then entityManager.find(Book.class, 1) will return the Book with an ID of 1. If a Book with the requested primary key is not found in the database, then the find() method returns null. Because we want our code to be more resilient and not pass nulls around, it checks the value for null and returns either a valid book, wrapped in an Optional, or Optional.empty().

A closer look at EntityManager’s methods

Looking at the code in Listing 5, the find() method is probably the easiest to understand. The slightly more complex findAll() method creates a new query, using JPQL, to retrieve all books. As shown earlier, we could have written this as SELECT b FROM Book b, but from Book is a shorthand way of doing it. The createQuery() method creates a Query instance, which supports a host of setter methods–such as setParameter(), which we’ll see next–to make building a query look a little more elegant. It has two methods that execute the query of interest to us:

  • getResultList executes the JPQL SELECT statement and returns the results as a List; if no results are found then it returns an empty list.
  • getSingleResult executes the JPQL SELECT statement and returns a single result; if no results are found then it throws a NoResultException.

In the findAll() method, we execute the Query‘s getResultList() method and return the list of Books back to the caller.

The findByName() and findByNameNamedQuery() methods both find a Book by its name, but the first method executes a JPQL query and the second retrieves the named query defined in the Book class. Because these queries define a named parameter, “:name“, they call the Query::setParameter method to bind the method’s name argument to the query before executing.

We expect a single Book to be returned, so we execute the Query::getSingleResult method, which either returns a Book or null. We check the response. If it is not null, we return the Book wrapped in an Optional; otherwise we return Optional.empty().

Finally, the save() method saves a Book to the database. Both the persist and merge operations, which update the database, need to run in a transaction. We retrieve the resource-level EntityTransaction by invoking the EntityManager::getTransaction method and wrap the persist call in begin() and commit() calls. We opt to persist() the book to the database so that the book will be “managed” and saved to the database.

This way, the book we return will have the generated primary key. If we used merge() instead, then our book would be copied into the entity context. When the transaction was committed we would not see the auto-generated primary key.

The Author repository

Listing 6 shows the source code for the AuthorRepository.

Listing 6. AuthorRepository.java

package com.geekcap.javaworld.jpa.repository;
import com.geekcap.javaworld.jpa.model.Author;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class AuthorRepository 
    private EntityManager entityManager;
    public AuthorRepository(EntityManager entityManager) 
        this.entityManager = entityManager;
    
    public Optional<Author> findById(Integer id) 
        Author author = entityManager.find(Author.class, id);
        return author != null ? Optional.of(author) : Optional.empty();
    
    public List<Author> findAll() 
        return entityManager.createQuery("from Author").getResultList();
    
    public Optional<Author> findByName(String name) 
        Author author = entityManager.createNamedQuery("Author.findByName", Author.class)
                .setParameter("name", name)
                .getSingleResult();
        return author != null ? Optional.of(author) : Optional.empty();
    
    public Optional<Author> save(Author author) 
        try 
            entityManager.getTransaction().begin();
            entityManager.persist(author);
            entityManager.getTransaction().commit();
            return Optional.of(author);
         catch (Exception e) 
            e.printStackTrace();
        
        return Optional.empty();
    

The AuthorRepository is identical to the BookRepository, only it persists and queries for Authors instead of Books.

Example application for Hibernate JPA

Listing 7 presents a sample application that creates an EntityManager, creates our repositories, and then executes some operations to demonstrate how to use the repositories.

Listing 7. JpaExample.java

package com.geekcap.javaworld.jpa;
import com.geekcap.javaworld.jpa.model.Author;
import com.geekcap.javaworld.jpa.model.Book;
import com.geekcap.javaworld.jpa.repository.AuthorRepository;
import com.geekcap.javaworld.jpa.repository.BookRepository;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.List;
import java.util.Optional;
public class JpaExample 
    public static void main(String[] args) 
        // Create our entity manager
        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        // Create our repositories
        BookRepository bookRepository = new BookRepository(entityManager);
        AuthorRepository authorRepository = new AuthorRepository(entityManager);
        // Create an author and add 3 books to his list of books
        Author author = new Author("Author 1");
        author.addBook(new Book("Book 1"));
        author.addBook(new Book("Book 2"));
        author.addBook(new Book("Book 3"));
        Optional<Author> savedAuthor = authorRepository.save(author);
        System.out.println("Saved author: " + savedAuthor.get());
        // Find all authors
        List<Author> authors = authorRepository.findAll();
        System.out.println("Authors:");
        authors.forEach(System.out::println);
        // Find author by name
        Optional<Author> authorByName = authorRepository.findByName("Author 1");
        System.out.println("Searching for an author by name: ");
        authorByName.ifPresent(System.out::println);
        // Search for a book by ID
        Optional<Book> foundBook = bookRepository.findById(2);
        foundBook.ifPresent(System.out::println);
        // Search for a book with an invalid ID
        Optional<Book> notFoundBook = bookRepository.findById(99);
        notFoundBook.ifPresent(System.out::println);
        // List all books
        List<Book> books = bookRepository.findAll();
        System.out.println("Books in database:");
        books.forEach(System.out::println);
        // Find a book by name
        Optional<Book> queryBook1 = bookRepository.findByName("Book 2");
        System.out.println("Query for book 2:");
        queryBook1.ifPresent(System.out::println);
        // Find a book by name using a named query
        Optional<Book> queryBook2 = bookRepository.findByNameNamedQuery("Book 3");
        System.out.println("Query for book 3:");
        queryBook2.ifPresent(System.out::println);
        // Add a book to author 1
        Optional<Author> author1 = authorRepository.findById(1);
        author1.ifPresent(a -> 
            a.addBook(new Book("Book 4"));
            System.out.println("Saved author: " + authorRepository.save(a));
        );
        // Close the entity manager and associated factory
        entityManager.close();
        entityManagerFactory.close();
    

The first thing our sample application does is create an EntityManagerFactory:


EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("Books");

Previous

1

2

3

4



Page 4

The Persistence class has a createEntityManagerFactory method to which we pass the name of a persistence unit. Recall that in our persistence.xml file we defined a single persistence-unit with the name “Books.” The call to createEntityManagerFactory creates an EntityManagerFactory that will to connect this database with its configuration, including the Book and Author entity classes. We then use the EntityManagerFactory to create an EntityManager:


EntityManager entityManager = entityManagerFactory.createEntityManager();

Next, we create our repositories, passing the EntityManager to their constructors.

Now we’re ready we exercise the repositories.

CRUD operations on the example application

First, we create an author. We add three books to the author, then save it by invoking the AuthorRepository::save method. Here’s the resulting output:


Saved author: Authorid=1, name='Author 1', books=[Bookid=2, name='Book 1', author=Author 1, Bookid=3, name='Book 2', author=Author 1, Bookid=4, name='Book 3', author=Author 1]

Recall that we set the Author‘s @OneToMany annotation to use a CascadeType of ALL. Because of this, when we save the author all of its books will also be saved. As the output shows, the author is saved first. It gets an auto-generated primary key value of 1, then the author’s three books are saved.

Next, we retrieve all authors by executing the AuthorRepository::findAll method. We have just one author, so this code yields the following output:


Authors:
Authorid=1, name='Author 1', books=[Bookid=2, name='Book 1', author=Author 1, Bookid=3, name='Book 2', author=Author 1, Bookid=4, name='Book 3', author=Author 1]

We search for an author by name by executing the AuthorRepository::findByName method, passing it the name “Author 1”. This yields the following output:


Searching for an author by name:
Authorid=1, name='Author 1', books=[Bookid=2, name='Book 1', author=Author 1, Bookid=3, name='Book 2', author=Author 1, Bookid=4, name='Book 3', author=Author 1]

We execute two book queries by ID, one that should be found, namely the book with ID 2, and one that should not be found, namely one with ID 99. As expected we see only one record printed out:


Bookid=2, name='Book 1', author=Author 1

We query for all books by executing the BookRepository::findAll method, which successfully shows all books:


Books in database:
Bookid=2, name='Book 1', author=Author 1
Bookid=3, name='Book 2', author=Author 1
Bookid=4, name='Book 3', author=Author 1

We exercise our queries to find books by name, one using the raw JPQL query, BookRepository::findByName, and one using the named query, BookRepository::findByNameNamedQuery:


Query for book 2:
Bookid=3, name='Book 2', author=Author 1
Query for book 3:
Bookid=4, name='Book 3', author=Author 1

Finally, we retrieve the author with ID 1, add a new book to the author’s list of books, save the author, and output the saved author, expecting to now see four books in the list:


Saved author: Optional[Authorid=1, name='Author 1', books=[Bookid=2, name='Book 1', author=Author 1, Bookid=3, name='Book 2', author=Author 1, Bookid=4, name='Book 3', author=Author 1, Bookid=5, name='Book 4', author=Author 1]]

When we’re finished with all of our queries, we need to close both the EntityManager and the EntityManagerFactory; otherwise their threads will live on and our application will never complete:


entityManager.close();
entityManagerFactory.close();

Run the example application

Running the application is simple.

Step 1: Build with Maven:


mvn clean install

Step 2: Change directories into the target directory and execute the JAR file:


cd target
java -jar jpa-example-1.0-SNAPSHOT.jar

Conclusion

This tutorial has been a general introduction to JPA with Hibernate. We’ve reviewed JPA as a standard for ORM in Java and looked at how entities, relationships, and the EntityManager work together in your Java applications.

In the second half of this tutorial we’ll build a new example application that explores one of JPA’s more complicated relationship types: the bidirectional, many-to-many relationship. The example will allow you to practice and deepen what you already know about entity relationships and the EntityManager. You’ll also learn about lazy and eager fetching strategies, working with join tables, and how to choose the right CascadeType.

This story, “Mastering Java persistence with JPA and Hibernate, Part 1: Entities and relationships” was originally published by

JavaWorld.

Share this post if you enjoyed! 🙂



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *