Java Persistence (JPA) Tutorial with WildFly

Jakarta Persistence API (JPA) is a Java specification for accessing, persisting, and managing data between Java objects/classes and a relational database. It is a part of the Jakarta EE platform, and it provides a standard approach for ORM (Object-Relational Mapping). In this JPA tutorial, we will learn how to use JPA to create, read, update, and delete (CRUD) data in a database.

Setting up the JPA Project

This tutorial will begin by creating a project from a Maven archetype. The following archetype is a good start for any Jakarta EE project:

mvn archetype:generate -DarchetypeGroupId=org.eclipse.starter -DarchetypeArtifactId=jakartaee10-minimal -DarchetypeVersion=1.1.0 -DgroupId=com.mastertheboss -DartifactId=jpa-basic -Dprofile=api -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false

The basic archetype just includes a sample REST Endpoint. We will add the Java Classes we need to build a JPA Application.

Firstly, let’s define our basic Model which includes a single Table:

jpa tutorial

Then, we will map the Customer Table with the following Entity:

@Entity
public class Customer implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String email;
    private String address;

    @Column(name = "phone_number")
    private String phoneNumber;
    
    // Getter/Setter omitted for brevity

}

As you can see, our Entity Class includes several annotations in it:

The JPA @Entity annotation marks a Java class as an Entity class. An entity class is a Java class that represents a database table, and the fields of the class correspond to the columns of the table.

The JPA @Column annotation specifies the mapping of a persistent field or property of an entity class to a column in a database table. It is not mandatory to use this annotation. You can use it to customize the mapping of the field or property to the database column, such as specifying the column name/length, and whether the column is nullable or unique.

Finally, the JPA @Id annotation is used in Java Persistence API (JPA) to mark a field or property of an entity class as the primary key of the entity.

Along with the @Id there’s the @GeneratedValue annotation. You can use this annotation in combination with the @Id annotation to specify that the primary key value should be generated automatically.

To learn more about JPA generation strategies check this article: Choosing the Strategy for Primary keys in JPA

How to connect the Entity to a DataSource?

You can configure the Connection to the Database using JPA configuration file, which is persistence.xml. In a Maven project, you can place this file under the resources/META-INF folder of your application.

Within the persistence.xml, you can either specify directly the JDBC Connection Settings or the Datasource JNDI Address. For example, here we will be using WildFly default H2 Datasource:

<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_1.xsd"
             version="3.1">
    <persistence-unit name="primary">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>

        <properties>

            <property name="jakarta.persistence.schema-generation.database.action"
                      value="drop-and-create"/>
            <property name="jakarta.persistence.schema-generation.create-source"
                      value="metadata"/>
            <property name="jakarta.persistence.schema-generation.drop-source"
                      value="metadata"/>

        </properties>
    </persistence-unit>
</persistence>

Finally, as you can see from the jakarta.persistence.schema-generation properties, we are using an automatic creation of Database Tables from the JPA Annotation.

Writing the JPA Service Class

Next, we will add a CDI Bean that will manage the Create/Read/Update/Delete Operations on the Entity Bean. Therefore, we will add the CustomerService Class to our project:

@Model
public class CustomerService {
    private static final Logger LOGGER = Logger.getLogger(CustomerService.class.getName());

    @PersistenceContext
    EntityManager em;

    @Transactional
    public void createCustomer(Customer customer) {
        em.persist(customer);
        LOGGER.info("Created Customer "+customer);

    }


    @Transactional
    public void updateCustomer( Customer customer ) {
        Customer customerToUpdate = findCustomerById(customer.getId());
        customerToUpdate.setName(customer.getName());
        customerToUpdate.setEmail(customer.getEmail());
        customerToUpdate.setAddress(customer.getAddress());
        customerToUpdate.setPhoneNumber(customer.getPhoneNumber());
        LOGGER.info("Updated customer" + customer);
    }


    @Transactional
    public void deleteCustomer(Long customerId) {
        Customer c = findCustomerById(customerId);
        em.remove(c);
        LOGGER.info("Deleted Customer with id" + customerId);
    }


    public Customer findCustomerById(Long id) {
        Customer customer = em.find(Customer.class, id);
        if (customer == null) {
            throw new WebApplicationException("Customer with id of " + id + " does not exist.", 404);
        }
        return customer;
    }


    public List<Customer> findAllCustomers() {
        Query query = em.createQuery("SELECT c FROM Customer c");
        List<Customer> customerList = query.getResultList();
        return customerList;
    }



    public Customer findCustomerByName(String name) {
        Query query = em
                .createQuery("SELECT c FROM Customer c WHERE c.name = :name");
        query.setParameter("name", name);
        Customer customer = (Customer) query.getSingleResult();
        return customer;
    }

}

As you can see from the above code, our Service class includes Read/Write Operations (bearing the @Transactional annotation) and Read only operations. Let’s see them in detail.

Read/Write operations

The createCustomer method uses the JPA persist method to store an entity in the database. You can call this method on an instance of the EntityManager interface, which manages the persistence of entities.

The deleteCustomer method uses the JPA remove method to delete an entity in the database. You can call this method on an instance of the EntityManager interface, which manages the persistence of entities.

The updateCustomer method uses the JPA persist method to update an entity in the database. This is an example of transparent update in JPA. As a matter of fact, there are two ways to update an Entity object in JPA:

  • Transparent update refers to the automatic updating of the state of an entity in the database whenever you change the state of the Entity in memory. When the transaction is committed, the EntityManager will automatically detect the changes to the entity and update the state of the entity in the database to match. You can only use it for Entities that are being managed by the EntityManager and are associated with an active transaction
  • Explicit update, on the other hand, refers to the explicit updating of the state of an entity in the database using the persist method of the EntityManager. This method takes an entity instance as an argument and updates the state of the entity in the database to match the state of the entity in memory. You can use explicit updates also to update detached entities.

Read Only Operations

The findCustomerById method uses the JPA find method to retrieve an Entity object by using its Primary Key. This is the most efficient way to fetch a single Entity object.

The findAllCustomers method uses a JPA Query object to return a List of Entities. In most cases, you would try to fetch only a subset of Entity Objects. Check this article to learn How to paginate your Entity data

The findCustomerByName uses a Query Parameter as placeholder for the Customer name. You can use Query parameters to pass values to a Query to filter the results of the query based on specific criteria.

Adding an Endpoint with CRUD methods

Finally, we will add a REST Endpoint to be able to access our Service from a remote REST Client.

@Path("/customer")
@Produces("application/json")
@Consumes("application/json")

public class CustomerEndpoint {

    @Inject
    CustomerService customerService;

    @POST
    public Response create(Customer customer) {
        customerService.createCustomer(customer);
        return Response.status(201).build();
    }

    @GET
    public List<Customer> findAllCustomers() {
        return customerService.findAllCustomers();
    }

    @GET
    @Path("/{id}")
    public Customer findCustomerById(@PathParam("id") Long id) {
        return customerService.findCustomerById(id);
    }

    @PUT
    public Response updateCustomer(Customer customer) {
        customerService.updateCustomer(customer);
        return Response.status(204).build();
    }
    @DELETE
    @Path("/{id}")
    public Response delete(@PathParam("id") Long id) {
        customerService.deleteCustomer(id);
        return Response.status(204).build();
    }

}

Discussing the details of JAX-RS is out of the scope of this JPA tutorial. However, if you want to learn more about REST Service check this article: Getting started with RESTEasy and WildFly

Building the JPA application

The following tree structure represents the project we will now deploy on WildFly:

src
└── main
├── java
│   └── com
│   └── mastertheboss
│   ├── ejb
│   │   └── CustomerService.java
│   ├── model
│   │   └── Customer.java
│   └── rest
│   ├── CustomerEndpoint.java
│   └── RestActivator.java
├── resources
│   └── META-INF
│   └── persistence.xml
└── webapp
├── index.html
└── WEB-INF
└── beans.xml

To be able to build the project, include the jakarta.jakartaee-api dependency within your pom.xml. For example, for Jakarta EE 10 applications:

<dependency>
   <groupId>jakarta.platform</groupId>
   <artifactId>jakarta.jakartaee-api</artifactId>
   <version>10.0.0</version>
   <scope>provided</scope>
</dependency>

Testing the application

Firstly, deploy the application on WildFly using the WildFly Maven plugin:

$ mvn install wildfly:deploy

Then, when the application is available, we will create our first Customer using any HTTP tool. For example, with curl:

curl -X POST http://localhost:8080/jpa-basic/rest/customer -H 'Content-Type: application/json' -d '{"name":"JohnSmith","email":"[email protected]","address":"3170 Broadway","phoneNumber":"33312345678"}'

Next, we will check the list of Customers with the plain GET Endpoint:

curl -s http://localhost:8080/jpa-basic/rest/customer | jq
[
  {
    "address": "3170 Broadway",
    "email": "[email protected]",
    "id": 1,
    "name": "JohnSmith",
    "phoneNumber": "33312345678"
  }
]

Then, let’s verify the GET Endpoint which does a JPA findById:

curl -s http://localhost:8080/jpa-basic/rest/customer/1 | jq
[
  {
    "address": "3170 Broadway",
    "email": "[email protected]",
    "id": 1,
    "name": "JohnSmith",
    "phoneNumber": "33312345678"
  }
]

Next, let’s test the update of our Customer using HTTP PUT:

curl -X PUT http://localhost:8080/jpa-basic/rest/customer -H 'Content-Type: application/json' -d '{"id":"1","name":"JohnDoe","email":"[email protected]","address":"1105 Rogers St","phoneNumber":"12345678"}'

Finally, we will delete the Customer using HTTP DELETE:

curl -X DELETE http://localhost:8080/jpa-basic/rest/customer/1

Conclusion
After this JPA tutorial you should have a good understanding of how to use JPA to persist data in a Java application. You should be able to create entity classes and use JPA annotations to map these classes to database tables, write queries to retrieve and manipulate data, and use the EntityManager to manage the persistence of your entities.

The Source code for this article is available here: https://github.com/fmarchioni/mastertheboss/tree/master/javaee/jpa-basic

Found the article helpful? if so please follow us on Socials