© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
L. Jungmann et al.Pro Jakarta Persistence in Jakarta EE 10https://doi.org/10.1007/978-1-4842-7443-9_2

2. Getting Started

Lukas Jungmann1  , Mike Keith2, Merrick Schincariol3 and Massimo Nardone4
(1)
Prague, Czech Republic
(2)
Ottawa, ON, Canada
(3)
Almonte, ON, Canada
(4)
HELSINKI, Finland
 

One of the main goals of Jakarta Persistence was that it should be simple to use and easy to understand. Although its problem domain cannot be trivialized or watered down, the technology that enables developers to deal with it can be straightforward and intuitive. This chapter shows how effortless it can be to develop and use entities.

We start by describing the basic characteristics of entities and review the requirements that an entity class must follow. We define what an entity is and how to create, read, update, and delete one. We also introduce entity managers and how they are obtained and used. Then, we take a quick look at queries and cover how to specify and execute a query using the EntityManager and Query objects. The chapter concludes by showing a simple working application that runs in a standard Java SE environment and demonstrates all of the example code in action.

Entity Overview

An entity is not a new thing in data management. In fact, entities have been around longer than many programming languages and certainly longer than Java.

In general, an entity is a Java representation of the database table that has characteristics like persistability, identity, transactionality, and granularity.

They were introduced by Peter Chen in his seminal paper on entity-relationship modeling.1 He described entities as things that have attributes and relationships. The expectation was that the attributes and relationships would be persisted in a relational database.

Even now, the definition holds true. An entity is essentially a noun, or a grouping of states associated together as a single unit. It may participate in relationships with any number of other entities in a number of standard ways. In the object-oriented paradigm, we add behavior to it and call it an object. In Jakarta Persistence, any application-defined object can be an entity, so the important question might be this: what are the characteristics of an object that has been turned into an entity?

Persistability

The first and most basic characteristic of entities is that they are persistable. This generally just means that they can be made persistent. More specifically, it means that their state can be represented in a data store and can be accessed at a later time, perhaps well after the end of the process that created it.

You could call them persistent objects, and many people do, but it is not technically correct. Strictly speaking, a persistent object becomes persistent the moment it is instantiated in memory. If a persistent object exists, then by definition it is already persistent.

An entity is persistable because it can be saved in a persistent store. The difference is that it is not automatically persisted, and that in order for it to have a durable representation, the application must actively invoke an API method to initiate the process. This is an important distinction because it leaves control over persistence firmly in the hands of the application. The application has the flexibility to manipulate data and perform business logic on the entity, making it persistent only when the application decides it is the right time. The lesson is that entities may be manipulated without necessarily being persisted, and it is the application that decides whether they are.

Identity

Like any other Java object, an entity has an object identity, but when it exists in the database, it also has a persistent identity. Object identity is simply the differentiation between objects that occupy memory. Persistent identity, or an identifier, is the key that uniquely identifies an entity instance and distinguishes it from all the other instances of the same entity type. An entity has a persistent identity when there exists a representation of it in the data store, that is, a row in a database table. If it is not in the database, then even though the in-memory entity may have its identity set in a field, it does not have a persistent identity. The entity identifier, then, is equivalent to the primary key in the database table that stores the entity state.

Transactionality

Entities might be called quasi-transactional . Although they can be created, updated, and deleted in any context, these operations are normally done within the context of a transaction2 because a transaction is required for the changes to be committed in the database. Changes made to the database either succeed or fail atomically, so the persistent view of an entity should indeed be transactional.

In memory, it is a slightly different story in the sense that entities may be changed without the changes ever being persisted. Even when enlisted in a transaction, they may be left in an undefined or inconsistent state in the event of a rollback or transaction failure. The in-memory entities are simple Java objects that obey all of the rules and constraints that are applied by the Java Virtual Machine (JVM) to other Java objects.

Granularity

Finally , a good way to show what entities are is to describe what they are not. They are not primitives, primitive wrappers, or built-in objects with single-dimensional state. These are no more than scalars and do not have any inherent semantic meaning to an application. A string, for example, is too fine-grained an object to be an entity because it does not have any domain-specific connotation. Rather, a string is well-suited and very often used as a type for an entity attribute and given meaning according to the entity attribute that it is typing.

Entities are meant to be fine-grained objects that have a set of aggregated states normally stored in a single place, such as a row in a table, and typically have relationships to other entities. In the most general sense, they are business domain objects that have specific meaning to the application that accesses them.

While it is certainly true that entities may be defined in exaggerated ways to be as fine-grained as storing a single string or coarse-grained enough to contain 500 columns’ worth of data, Jakarta Persistence entities were definitely intended to be on the smaller end of the granularity spectrum. Ideally, entities should be designed and defined as fairly lightweight objects of a size comparable to that of the average Java object.

Entity Metadata

In addition to its persistent state, every Jakarta Persistence entity has some associated metadata (even if a very small amount) that describes it. This metadata may exist as part of the saved class file, or it may be stored external to the class, but it is not persisted in the database. It enables the persistence layer to recognize, interpret, and properly manage the entity from the time it is loaded through to its runtime invocation.

The metadata that is actually required for each entity is minimal, rendering entities easy to define and use. However, like any sophisticated technology with its share of switches, levers, and buttons, there is also the possibility to specify much, much more metadata than is required. It may be extensive amounts, depending on the application requirements, and may be used to customize every detail of the entity configuration or state mappings.

Entity metadata may be specified in two ways: annotations and XML. Each is equally valid, but the one that you use will depend on your development preferences or process.

Annotations

Annotation metadata is a language feature introduced in Java SE 5 that allows structured and typed metadata to be attached to the source code. Although annotations are not required by Jakarta Persistence, they are a convenient way to learn and use the API. Because annotations co-locate the metadata with the program artifacts, it is not necessary to escape to an additional file and a special language (XML) just to specify the metadata.

Annotations are used throughout the examples and the accompanying explanations in this book. All the Jakarta Persistence annotations that are shown and described (except in Chapter 3, which talks about Jakarta EE annotations) are defined in the jakarta.persistence package. Example code snippets can be assumed to have an implicit import of the form import jakarta.persistence.*;.

As we said in Chapter 1, one of the Jakarta Persistence features is making an annotation @Repeatable. This feature allows developers to use the same annotation multiple times for a certain class or attribute without using a container annotation. It helps by making your Java code much easier to read.

Practically, you are able to annotate any of your entity classes with multiple @NamedQuery annotations without needing to wrap them in a @NamedQueries annotation.

Here are the annotations that are repeatable when using Jakarta Persistence:
  • AssociationOverride

  • AttributeOverride

  • Convert

  • JoinColumn

  • MapKeyJoinColumn

  • NamedEntityGraph

  • NamedNativeQuery

  • NamedQuery

  • NamedStoredProcedureQuery

  • PersistenceContext

  • PersistenceUnit

  • PrimaryKeyJoinColumn

  • SecondaryTable

  • SqlResultSetMapping

Let’s take one from the list, for instance the AssociationOverride annotation, which is used to override a mapping for an entity relationship .

Here is how the AssociationOverride annotation works. Notice the @Repeatable(AssociationOverrides.class):
@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
@Repeatable(AssociationOverrides.class)
public @interface AssociationOverride {
    String name();
    JoinColumn[] joinColumns() default {};
    ForeignKey foreignKey() default @ForeignKey(PROVIDER_DEFAULT);
    JoinTable joinTable() default @JoinTable;
}

XML

For those who prefer to use traditional XML, this option is still available. It should be fairly straightforward to switch to using XML descriptors after having learned and understood the annotations, because the XML has mostly been patterned after the annotations. Chapter 12 describes how to use XML to specify or override entity mapping metadata.

Configuration by Exception

The notion of configuration by exception means that the persistence engine defines defaults that apply to the majority of applications and that users need to supply values only when they want to override the default value. In other words, having to supply a configuration value is an exception to the rule, not a requirement.

Configuration by exception is ingrained in Jakarta Persistence and contributes strongly to its usability. Most configuration values have defaults, rendering the metadata that does have to be specified more relevant and concise.

The extensive use of defaults and the ease of use that it brings to configuration come at a price, however. When defaults are embedded into the API and do not have to be specified, then they are not visible or obvious to users. This can make it possible for users to be unaware of the complexity of developing persistence applications, making it harder to debug or to change the behavior when it becomes necessary.

Defaults are not meant to shield users from the often complex issues surrounding persistence. They are meant to allow a developer to get started easily and quickly with something that will work and then iteratively improve and implement additional functionality as the complexity of their application increases. Even though the defaults may be what you want to have happen most of the time, it is still important for developers to be familiar with the default values that are being applied. For example, if a table name default is being assumed, it is important to know what table the runtime is expecting, or if schema generation is used, what table will be generated.

For each of the annotations, we also discuss the default value so that it is clear what will be applied if the annotation is not specified. We recommend that you remember these defaults as you learn them. After all, a default value is still part of the configuration of the application; it is just really easy to configure!

Creating an Entity

Regular Java classes are easily transformed into entities simply by annotating them. In fact, by adding a couple of annotations, almost any class with a no-arg constructor can become an entity.

The primary programming artifact of an entity is the entity class.

Here are the requirements that an entity class must follow:
  • Must be annotated with the jakarta.persistence.Entity annotation.

  • Must have a public or protected no-argument constructor (it may have other constructors).

  • Must not be declared final (therefore, no methods or persistent instance variables can be declared final).

  • Must implement the Serializable interface (in case the entity instance is passed by value as a detached object via a session bean’s remote business interface).

  • May extend both entity and non-entity classes, and non-entity classes may extend entity classes.

  • Persistent instance variables must be declared private, protected, or package-private (they can be accessed directly only by the entity class methods).

Let’s start by creating a regular Java class for an employee. Listing 2-1 shows a simple Employee class.
public class Employee {
    private int id;
    private String name;
    private long salary;
    public Employee() {}
    public Employee(int id) { this.id = id; }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public long getSalary() { return salary; }
    public void setSalary (long salary) { this.salary = salary; }
}
Listing 2-1

Employee Class

You may notice that this class resembles a JavaBean-style class with three properties: id, name, and salary. Each of these properties is represented by a pair of accessor methods to get and set the property and is backed by a member field. Properties or member fields are the units of state within the entity that can be persisted.

To turn Employee into an entity, we first annotate the class with @Entity. This is primarily just a marker annotation to indicate to the persistence engine that the class is an entity.

The second annotation that we need to add is @Id. This annotates the particular field or property that holds the persistent identity of the entity (the primary key) and is needed so the provider knows which field or property to use as the unique identifying key in the table.

Adding these two annotations to the Employee class, we end up with pretty much the same class that we had before, except that now it is an entity. Listing 2-2 shows the entity class.
@Entity
public class Employee {
    @Id private int id;
    private String name;
    private long salary;
    public Employee() {}
    public Employee(int id) { this.id = id; }
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public long getSalary() { return salary; }
    public void setSalary (long salary) { this.salary = salary; }
}
Listing 2-2

Employee Entity

When we say that the @Id annotation is placed on the field or property, we mean that the user can choose to annotate either the declared field or the getter method3 of a JavaBean-style property. Either field or property strategy is allowed, depending on the needs and tastes of the entity developer. We have chosen in this example to annotate the field because it is simpler; in general, this will be the easiest and most direct approach. We discuss the details of annotating persistent state using field or property access in subsequent chapters.

The fields in the entity are automatically made persistable by virtue of their existence in the entity. Default mapping and loading configuration values apply to these fields and enable them to be persisted when the object is persisted. Given the questions that were brought up in the last chapter, one might be led to ask: “How did the fields get mapped, and where do they get persisted to?”

To find the answer, we must first take a quick detour to dig inside the @Entity annotation and look at an element called name that uniquely identifies the entity. The entity name may be explicitly specified for any entity by using this name element in the annotation, as in @Entity(name="Emp"). In practice, this is seldom specified because it gets defaulted to be the unqualified name of the entity class. This is almost always both reasonable and adequate.

Now we can get back to the question about where the data gets stored. It turns out that the default name of the table used to store any given entity of a particular entity type is the name of the entity. If we have specified the name of the entity, it will be the default table name; if not, the default value of the entity name will be used. We just stated that the default entity name was the unqualified name of the entity class, so that is effectively the answer to the question of which table gets used. In the Employee example, the entity name will be defaulted to "Employee", and all entities of type Employee will get stored in a table called EMPLOYEE.

Each of the fields or properties has an individual state in it and needs to be directed to a particular column in the table. We know to go to the EMPLOYEE table, but which column should be used for a given field or property? When no columns are explicitly specified, the default column is used for a field or property, which is just the name of the field or property itself. So the employee ID will get stored in the ID column, the name in the NAME column, and the salary in the SALARY column of the EMPLOYEE table.

Of course, these values can all be overridden to match an existing schema. We discuss how to override them when we get to Chapter 4 and discuss mapping in more detail.

Entity Manager

In the “Entity Overview” section, it was stated that a specific API call needs to be invoked before an entity actually gets persisted to the database. In fact, separate API calls are needed to perform many of the operations on entities. This API is implemented by the entity manager and encapsulated almost entirely within a single interface called jakarta.persistence.EntityManager. When all is said and done, it is to an entity manager that the real work of persistence is delegated. Until an entity manager is used to actually create, read, or write an entity, the entity is nothing more than a regular (nonpersistent) Java object.

When an entity manager obtains a reference to an entity, either by having it explicitly passed in as an argument to a method call or because it was read from the database, that object is said to be managed by the entity manager. The set of managed entity instances within an entity manager at any given time is called its persistence context. Only one Java instance with the same persistent identity may exist in a persistence context at any time. For example, if an Employee with a persistent identity (or ID) of 158 exists in the persistence context, then no other Employee object with its ID set to 158 may exist within that same persistence context.

Entity managers are configured to be able to persist or manage specific types of objects, read and write to a given database, and be implemented by a particular persistence provider (or provider for short). It is the provider that supplies the backing implementation engine for the entire Jakarta Persistence API, from the EntityManager through to implementation of the query classes and SQL generation.

All entity managers come from factories of type jakarta.persistence.EntityManagerFactory. The configuration for an entity manager is templated from the entity manager factory that created it, but it is defined separately as a persistence unit. A persistence unit dictates either implicitly or explicitly the settings and entity classes used by all entity managers obtained from the unique EntityManagerFactory instance bound to that persistence unit. There is, therefore, a one-to-one correspondence between a persistence unit and its concrete EntityManagerFactory instance .

Persistence units are named to allow differentiation of one entity manager factory from another. This gives the application control over which configuration or persistence unit is to be used for operating on a particular entity.

Figure 2-1 shows that for each persistence unit, there is an entity manager factory and that many entity managers can be created from a single entity manager factory. The part that may come as a surprise is that many entity managers can point to the same persistence context. We have talked only about an entity manager and its persistence context, but later on you will see that there may in fact be multiple references to different entity managers, all pointing to the same group of managed entities. This will enable the control flow to traverse container components but continue to access the same persistence context.
Figure 2-1

Relationships between Jakarta Persistence concepts

Table 2-1 summarizes the concepts and API objects previously mentioned or discussed. Note that while some are actual API objects, others are only abstract concepts that help explain how the API works.
Table 2-1

Summary of API Objects and Concepts

Object

API Object

Description

Persistence

Persistence

Bootstrap class used to obtain an entity manager factory

Entity Manager Factory

EntityManagerFactory

Configured factory object used to obtain entity managers

Persistence Unit

--

Named configuration declaring the entity classes and data store info

Entity Manager

EntityManager

Main API object used to perform operations and queries on entities

Persistence Context

--

Set of all entity instances managed by a specific entity manager

Obtaining an Entity Manager

An entity manager is always obtained from an EntityManagerFactory. The factory from which it was obtained determines the configuration parameters that govern its operation. While there are shortcuts that veil the factory from the user view when running in a Jakarta EE application server environment, in the Java SE environment, we can use a simple bootstrap class called Persistence. The static createEntityManagerFactory() method in the Persistence class returns the EntityManagerFactory for the specified persistence unit name. The following example demonstrates creating an EntityManagerFactory for the persistence unit named EmployeeService:
EntityManagerFactory emf =
    Persistence.createEntityManagerFactory("EmployeeService");

The name of the specified persistence unit EmployeeService passed into the createEntityManagerFactory() method identifies the given persistence unit configuration that determines such things as the connection parameters that entity managers generated from this factory will use when connecting to the database.

Now that we have a factory, we can easily obtain an entity manager from it. The following example demonstrates creating an entity manager from the factory acquired in the previous example:
EntityManager em = emf.createEntityManager();

With this entity manager, we are in a position to start working with persistent entities.

Persisting an Entity

Persisting an entity is the operation of taking a transient entity, or one that does not yet have any persistent representation in the database, and storing its state so that it can be retrieved later. This is really the basis of persistence—creating a state that may outlive the process that created it. We start by using the entity manager to persist an instance of Employee. Here is a code example that does just that:
Employee emp = new Employee(158);
em.persist(emp);

The first line in this code segment is simply creating an Employee instance that we want to persist. If we ignore the sad fact of employing a nameless individual and paying him nothing (we are setting only the ID, not the name or salary), the instantiated Employee is just a regular Java object.

The next line uses the entity manager to persist the entity. Calling persist() is all that is required to initiate it being persisted in the database. If the entity manager encounters a problem doing this, it will throw an unchecked PersistenceException. When the persist() call completes, emp will have become a managed entity within the entity manager’s persistence context.

Listing 2-3 shows how to incorporate this into a simple method that creates a new employee and persists it to the database.
public Employee createEmployee(int id, String name, long salary) {
    Employee emp = new Employee(id);
    emp.setName(name);
    emp.setSalary(salary);
    em.persist(emp);
    return emp;
}
Listing 2-3

Method for Creating an Employee

This method assumes the existence of an entity manager in the em field of the instance and uses it to persist the Employee. Note that we do not need to worry about the failure case in this example. It will result in a runtime PersistenceException being thrown, which will get propagated up to the caller.

Finding an Entity

Once an entity is in the database, the next thing one typically wants to do is find it again. In this section, you discover how an entity can be found using the entity manager. There is really only one line that’s important:
Employee emp = em.find(Employee.class, 158);

We are passing in the class of the entity that is being sought (in this example, we are looking for an instance of Employee) and the ID or primary key that identifies the particular entity (in this case we want to find the entity that we just created). This is all the information needed by the entity manager to find the instance in the database, and when the call completes, the employee that gets returned will be a managed entity, meaning that it will exist in the current persistence context associated with the entity manager. Passing in the class as a parameter also allows the find method to be parameterized and to return an object of same type that was passed in, saving the caller an extra cast.

What happens if the object has been deleted or if you supply the wrong ID by accident? In the event that the object was not found, then the find() call simply returns null. You would need to ensure that a null check is performed before the next time the emp variable is used.

The code for a method that looks up and returns the Employee with a given ID is now trivial and is shown in Listing 2-4.
public Employee findEmployee(int id) {
    return em.find(Employee.class, id);
}
Listing 2-4

Method for Finding an Employee

In the case where no employee exist for the ID that is passed in, the method will return null because that is what find() will return.

Removing an Entity

Removal of an entity from the database is not as common as you might think. Many applications never delete objects, or if they do they just flag the data as being out of date or no longer valid and then just keep it out of sight of clients. We are not talking about that kind of application-level logical removal where the data is not even removed from the database. We are talking about something that results in a DELETE statement being made across one or more tables.

In order to remove an entity, the entity itself must be managed, meaning that it is present in the persistence context. This means that the calling application should have already loaded or accessed the entity and is now issuing a command to remove it. This is not normally a problem given that most often the application will have caused it to become managed as part of the process of determining that this was the object that it wanted to remove.

A simple example of removing an employee is the following:
Employee emp = em.find(Employee.class, 158);
em.remove(emp);

In this example, we are first finding the entity using the find() call, which returns a managed instance of Employee, and then removing the entity using the remove() call on the entity manager. Of course, you learned in the previous section that if the entity was not found, then the find() method will return null. You would get a java.lang.IllegalArgumentException if it turned out that you passed null into the remove() call because you forgot to include a null check before calling remove().

In the application method for removing an employee, the problem can be fixed by checking for the existence of the employee before issuing the remove() call, as shown in Listing 2-5.
public void removeEmployee(int id) {
    Employee emp = em.find(Employee.class, id);
    if (emp != null) {
        em.remove(emp);
    }
}
Listing 2-5

Method for Removing an Employee

This method will ensure that the employee with the given ID, provided the ID is not null, is removed from the database. It will return successfully whether the employee exists or not.

Updating an Entity

There are a few different ways of updating an entity, but for now we will illustrate the simplest and most common case. This is where we have a managed entity and want to make changes to it. If we do not have a reference to the managed entity, then we must first get one using find() and then perform our modifying operations on the managed entity. The following code adds $1,000 to the salary of the employee with an ID of 158:
Employee emp = em.find(Employee.class, 158);
emp.setSalary(emp.getSalary() + 1000);

Note the difference between this operation and the others. In this case, we are not calling into the entity manager to modify the object, but directly calling the object itself. For this reason, it is important that the entity be a managed instance; otherwise, the persistence provider will have no means of detecting the change, and no changes will be made to the persistent representation of the employee.

The method to raise the salary of a given employee will take the ID and amount of the raise, find the employee, and change the salary to the adjusted one. Listing 2-6 demonstrates this approach.
public Employee raiseEmployeeSalary(int id, long raise) {
    Employee emp = em.find(Employee.class, id);
    if (emp != null) {
        emp.setSalary(emp.getSalary() + raise);
    }
    return emp;
}
Listing 2-6

Method for Updating an Employee

If we couldn’t find the employee, we return null so the caller will know that no change could be made. We indicate success by returning the updated employee.

Transactions

You may feel that the code so far seems inconsistent with what we said earlier about transactionality when working with entities. There were no transactions in any of the preceding examples, even though we said that changes to entities must be made persistent using a transaction.

In all the examples except the one that called only find(), we assume that a transaction enclosed each method. The find() call is not a mutating operation, so it may be called any time, with or without a transaction.

Once again, the key is the environment in which the code is being executed. The typical situation when running inside the Jakarta EE container environment is that the standard Jakarta Transactions API is used. The transaction model when running in the container is to assume the application will ensure that a transactional context is present when one is required. If a transaction is not present, then either the modifying operation will throw an exception or the change will simply never be persisted to the data store. We come back to discussing transactions in the Jakarta EE environment in more detail in Chapter 3.

In the example in this chapter, though, we are not running in Jakarta EE. It was in a Java SE environment, and the transaction service that should be used in Java SE is the jakarta.persistence.EntityTransaction service. When executing in Java SE, we either need to begin and to commit the transaction in the operational methods, or we need to begin and to commit the transaction before and after calling an operational method. In either case, a transaction is started by calling getTransaction() on the entity manager to get the EntityTransaction and then invoking begin() on it. Likewise, to commit the transaction, the commit() call is invoked on the EntityTransaction object obtained from the entity manager. For example, starting and committing before and after the method would produce code that creates an employee the way it is done in Listing 2-7.
em.getTransaction().begin();
createEmployee(158, "John Doe", 45000);
em.getTransaction().commit();
Listing 2-7

Beginning and Committing an EntityTransaction

Further details about resource-level transactions and the EntityTransaction API are contained in Chapter 6.

Queries

In general, given that most developers have used a relational database at some point or another in their lives, most of us pretty much know what a database query is. In Jakarta Persistence, a query is similar to a database query, except that instead of using Structured Query Language (SQL) to specify the query criteria, we are querying over entities and using a language called Jakarta Persistence Query Language (Jakarta Persistence QL).

A query is implemented in code as a Query or TypedQuery<X> object. It is constructed using the EntityManager as a factory. The EntityManager interface includes a variety of API calls that return a new Query or TypedQuery<X> object. As a first-class object, a query can in turn be customized according to the needs of the application.

A query can be defined either statically or dynamically. A static query is typically defined in either annotation or XML metadata, and it must include the query criteria as well as a user-assigned name. This kind of query is also called a named query, and it is later looked up by its name at the time it is executed.

A dynamic query can be issued at runtime by supplying the Jakarta Persistence QL query criteria or a criteria object. They may be a little more expensive to execute because the persistence provider cannot do any query preparation beforehand, but Jakarta Persistence QL queries are nevertheless very simple to use and can be issued in response to program logic or even user logic.

The following example shows how to create a dynamic query and then execute it to obtain all the employees in the database. Of course, this may not be a very good query to execute if the database is large and contains hundreds of thousands of employees, but it is nevertheless a legitimate example. The simple query is as follows:

Example using getResultList:
TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e",
                                            Employee.class);
List<Employee> emps = query.getResultList();
Example using getResultStream:
TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e",                                                                    Employee.class);
Stream<Employee> employee = query.getResultStream();

We create a TypedQuery<Employee> object by issuing the createQuery() call on the EntityManager and passing in the Jakarta Persistence QL string that specifies the query criteria, as well as the class that the query should be parameterized on. The Jakarta Persistence QL string refers not to an EMPLOYEE database table but to the Employee entity, so this query is selecting all Employee objects without filtering them any further. You will be diving into queries in Chapter 7, Jakarta Persistence QL in Chapters 7 and 8, and Criteria queries in Chapter 9. You will see that you can be far more discretionary about which objects you want to be returned.

To execute the query, we simply invoke either getResultList() method or getResultStream() method on it. The getResultList() method returns a List<Employee> containing the Employee objects that matched the query criteria. Notice that the List is parameterized by Employee since the parameterized type is propagated from the initial class argument passed into the createQuery() method. We can easily create a method that returns all of the employees, as shown in Listing 2-8. The getResultStream() method returns a stream of the query result so, in this case, it returns the stream of the Employee query result. By default, it delegates to getResultList().stream(). The getResultStream() method provides a better way to move through the query result set as itfor large data setsallows avoiding reading the entire “result set” into memory before it could be used in the application.
public List<Employee> findAllEmployees() {
    TypedQuery<Employee> query = em.createQuery("SELECT e FROM Employee e",
                                                Employee.class);
    return query.getResultList();
}
Listing 2-8

Method for Issuing a Query

This example shows how simple it is to create, execute, and process queries, but it does not show how powerful queries are. In Chapter 7, you see many other extremely useful and interesting ways of defining and using queries in an application.

Putting It All Together

We can now take all the methods that we created and combine them into a class. The class acts like a service class, which we call EmployeeService, and allows us to perform operations on employees. The code should be pretty familiar by now. Listing 2-9 shows the complete implementation.
import jakarta.persistence.*;
import java.util.List;
public class EmployeeService {
    protected EntityManager em;
    public EmployeeService(EntityManager em) {
        this.em = em;
    }
    public Employee createEmployee(int id, String name, long salary) {
        Employee emp = new Employee(id);
        emp.setName(name);
        emp.setSalary(salary);
        em.persist(emp);
        return emp;
    }
    public void removeEmployee(int id) {
        Employee emp = findEmployee(id);
        if (emp != null) {
            em.remove(emp);
        }
    }
    public Employee raiseEmployeeSalary(int id, long raise) {
        Employee emp = em.find(Employee.class, id);
        if (emp != null) {
            emp.setSalary(emp.getSalary() + raise);
        }
        return emp;
    }
    public Employee findEmployee(int id) {
        return em.find(Employee.class, id);
    }
    public List<Employee> findAllEmployees() {
        TypedQuery<Employee> query = em.createQuery(
                  "SELECT e FROM Employee e", Employee.class);
        return query.getResultList();
    }
}
Listing 2-9

Service Class for Operating on Employee Entities

This is a simple yet fully functional class that can be used to issue the typical create, read, update, and delete (CRUD) operations on Employee entities. This class requires that an entity manager be created and passed into it by the caller and also that any required transactions are begun and committed by the caller. It may seem strange at first, but decoupling the transaction logic from the operation logic makes this class more portable to the Jakarta EE environment. We revisit this example in the next chapter, which focuses on Jakarta EE applications.

A simple main program that uses this service and performs all the required entity manager creation and transaction management is shown in Listing 2-10.
import jakarta.persistence.*;
import java.util.List;
public class EmployeeTest {
    public static void main(String[] args) {
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("EmployeeService");
        EntityManager em = emf.createEntityManager();
        EmployeeService service = new EmployeeService(em);
        //  create and persist an employee
        em.getTransaction().begin();
        Employee emp = service.createEmployee(158, "John Doe", 45000);
        em.getTransaction().commit();
        System.out.println("Persisted " + emp);
        // find a specific employee
        emp = service.findEmployee(158);
        System.out.println("Found " + emp);
        // find all employees
        List<Employee> emps = service.findAllEmployees();
        for (Employee e : emps)
            System.out.println("Found employee: " + e);
        // update the employee
        em.getTransaction().begin();
        emp = service.raiseEmployeeSalary(158, 1000);
        em.getTransaction().commit();
        System.out.println("Updated " + emp);
        // remove an employee
        em.getTransaction().begin();
        service.removeEmployee(158);
        em.getTransaction().commit();
        System.out.println("Removed Employee 158");
        // close the EM and EMF when done
        em.close();
        emf.close();
    }
}
Listing 2-10

Using EmployeeService

Note that at the end of the program, we use the close() methods to clean up the entity manager and the factory that we used to create it. This ensures that all the resources they might have allocated are properly released.

Packaging It Up

Now that you know the basic building blocks of Jakarta Persistence, you are ready to organize the pieces into an application that runs in Java SE. The only thing left to discuss is how to put it together so that it runs.

Persistence Unit

The configuration that describes the persistence unit is defined in an XML file called persistence.xml. Each persistence unit is named, so when a referencing application wants to specify the configuration for an entity, it needs only to reference the name of the persistence unit that defines that configuration. A single persistence.xml file can contain one or more named persistence unit configurations, but each persistence unit is separate and distinct from the others, and they can be logically thought of as being in separate persistence.xml files.

Many of the persistence unit elements in the persistence.xml file apply to persistence units that are deployed within the Jakarta EE container. The only ones that we need to specify for this example are name, transaction-type, class, and properties. There are a number of other elements that can be specified in the persistence unit configuration in the persistence.xml file, but they are discussed in more detail in Chapter 13. Listing 2-11 shows the relevant parts of the persistence.xml file for this example.
<persistence>
    <persistence-unit name="EmployeeService"
                      transaction-type="RESOURCE_LOCAL">
        <class>examples.model.Employee</class>
        <properties>
            <property name="jakarta.persistence.jdbc.driver"
                      value="org.apache.derby.jdbc.ClientDriver"/>
            <property name="jakarta.persistence.jdbc.url"
                      value="jdbc:derby://localhost:1527/EmpServDB;create=true"/>
            <property name="jakarta.persistence.jdbc.user" value="APP"/>
            <property name="jakarta.persistence.jdbc.password" value="APP"/>
        </properties>
    </persistence-unit>
</persistence>
Listing 2-11

Elements in the persistence.xml File

The name attribute of the persistence-unit element indicates the name of the persistence unit and is the string that we specify when we create the EntityManagerFactory. We used EmployeeService as the name. The transaction-type attribute indicates that the persistence unit uses resource-level EntityTransaction instead of Jakarta Transactions transactions. The class element lists the entity that is part of the persistence unit. Multiple class elements can be specified when there is more than one entity. They would not normally be needed when deploying in a Jakarta EE container because the container will automatically scan for entity classes annotated with @Entity as part of the deployment process, but they are needed for portable execution when running in Java SE. We have only a single Employee entity.

The last section is just a list of properties that can be standard or vendor-specific. The JDBC database login parameters must be specified when running in a Java SE environment to tell the provider what resource to connect to. Other provider properties, such as logging options, are vendor-specific and might also be useful.

Persistence Archive

The persistence artifacts are packaged in what we will loosely call a persistence archive. This is really just a JAR-formatted file that contains the persistence.xml file in the METAINF directory and normally the entity class files.

Because the application is running as a simple Java SE application, all we have to do is put the persistence archive, the application classes that use the entities, and the persistence provider JARs on the classpath when the program is executed.

Summary

This chapter discussed just enough of the basics of the Jakarta Persistence API to develop and run a simple application in a Java SE runtime.

We started out discussing the entity, how to define one, and how to turn an existing Java class into one. We discussed entity managers and how they are obtained and constructed in the Java SE environment.

The next step was to instantiate an entity instance and use the entity manager to persist it in the database. After we inserted a new entity, we could retrieve it again and then remove it. We also made some updates and ensured that the changes were written back to the database.

We talked about the resource-local transaction API and how to use it. We then went over some of the different types of queries and how to define and execute them. Finally, we aggregated all these techniques and combined them into a simple application that we can execute in isolation from an enterprise environment.

In the next chapter, we look at the impact of the Jakarta EE environment when developing enterprise applications using the Jakarta Persistence API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.122.162