Now Reading: What is JPA? Introduction to Java persistence

Loading
svg

What is JPA? Introduction to Java persistence

NewsOctober 8, 2025Artifice Prime
svg5

The Jakarta Persistence API (JPA) is a specification that defines a standardized framework for data persistence. Persistence is the fundamental mechanism for saving and retrieving data over time. In Java, the data we persist is held in objects. The JPA specification lets you define how your objects will be persisted while leaving the work itself to an underlying framework.

The mechanism of persistence in JPA is object-relational mapping (ORM). The concept of object-relational mapping was pioneered by Hibernate, an ORM framework. ORM was such a powerful concept that JPA was created to generalize it into a specification. Like most JVM (Java virtual machine) extensions, JPA provides a specification and lets the ecosystem provide implementations.

While JPA was originally defined for relational databases, some JPA implementations have been extended for use with NoSQL datastores. EclipseLink, the reference implementation for JPA 3, is a popular framework that supports JPA with NoSQL. We’ll configure a JPA setup using EclipseLink at the end of this article.

Another essential piece of the Java persistence puzzle, JDBC (the Java Database Connectivity API). is the standard mechanism for directly accessing a database in Java using SQL statements. The core difference between the JPA and JDBC specifications is that, for the most part, JPA lets you avoid the need to “think relationally”—meaning you never have to deal directly with SQL. JPA lets you define your persistence rules using Java code and objects, whereas JDBC requires you to manually translate from code to relational tables and back again.

The benefits of JPA’s abstraction and well-defined data layer are particularly noticeable in data-driven applications where data persistence is a primary requirement.

JPA and Hibernate

Because of their intertwined histories, Hibernate and JPA are frequently conflated. Developed by Gavin King and first released in early 2002, Hibernate is an ORM library for Java. As an alternative to entity beans, Hibernate was so popular—and so needed at the time—that its ideas were adopted and codified in the first JPA specification.

Today, Hibernate ORM is one of the most mature JPA implementations, and still a popular option for ORM in Java. The latest release as of this writing, Hibernate ORM 7.0, implements JPA 3.2. Additional Hibernate tools include Hibernate Search, Hibernate Validator, and Hibernate OGM (which supports domain-model persistence for NoSQL).

JPA and ORM

JPA is an ORM layer for your application and is implemented by a variety of providers. You don’t usually have to think about how the provider works, but to understand JPA and JPA-compatible tools, you do need a good grasp of object-relational mapping.

Object-relational mapping is a task—one that developers have good reason to want to avoid doing manually. A framework like Hibernate ORM or EclipseLink offloads that task into a library or framework, an ORM layer. As part of the application architecture, the ORM layer is responsible for managing the conversion of software objects to the tables and columns in a relational database.

In Java, the ORM layer maps classes and objects to tables and fields in a database. The simplest way to think about it is that a class translates to a table and the properties on an object instance translate into the fields on the table.

By default, the name of a class being persisted becomes the name of the table, and the field names become the column names. Once the table is created, each table row corresponds to an object in the application. The fields of the object are, in essence, the object itself, and very often an object will persist a distinctive ID field that uniquely identifies it. Object mapping is configurable, but defaults tend to work. By sticking with defaults, you avoid having to maintain configuration metadata.

In addition to relational databases, a variety of NoSQL databases are available to Java developers. JPA implementations that embrace NoSQL include Hibernate OGM and EclipseLink.

Configuring the Java ORM layer

When you set up a new project to use JPA, you first need to configure the datastore and JPA provider. You’ll configure a datastore connector to connect to your chosen database. You’ll also include and configure the JPA provider, which is a framework such as Hibernate or EclipseLink. While you can configure JPA manually, many developers use the out-of-the-box support provided by Spring. We’ll look at both manual and Spring-based configurations later in the article.

Data persistence in Java

From a programming perspective, the ORM layer is an adapter layer: It adapts the language of object graphs to the language of SQL and relational tables. The ORM layer allows object-oriented developers to build software that persists data without leaving the object-oriented paradigm.

When you use JPA, you create a mapping from the datastore to your application’s data model objects. Instead of describing imperatively how objects are saved and retrieved, you decorate your classes with metadata, which tells JPA how to move the objects to and from the database. You then invoke JPA persistence when necessary. Alternatively, if you are using a relational database, the connection between your application code and the database will be handled by JPA using JDBC. Either way, you never have to deal with JDBC and SQL.

JPA provides metadata annotations, which you use to define the mapping between objects and the database. The JPA spec provides the EntityManager, which is the main point of code contact with the JPA system. The EntityManager is your proxy for making JPA take action.

You can see this process in action in the code below, which is a simple data class for modeling a musician:


public class Musician {
  private Long id;
  private String name;
  private Instrument mainInstrument;
  private ArrayList performances = new ArrayList();
  public Musician( String name){ /* constructor setter... */ }
  public void setName(String name){
  	this.name = name;
  }
  public String getName(){
  	return this.name;
  }
  public void setMainInstrument(Instrument instr){
  	this.mainInstrument = instr;
  }
  public Instrument getMainInstrument(){
  	return this.mainInstrument;
  }
  // ...Other getters and setters...
}

The Musician class is used to hold data. Its properties are primitives like the name field (a string). It can also hold relationships to other classes such as mainInstrument and performances.

Musician’s purpose is to contain data. This type of class is sometimes known as a domain object. Domain objects are a common feature of software development, used to model the business “domain.” While domain objects hold many kinds of data, they do not (usually) contain business logic. Persisting domain objects is a ubiquitous need in software development.

In the realm of ORM and JPA, domain objects are often called entities. You can think of an entity as an object that the ORM layer persists.

Data persistence with JDBC

One way to save an instance of the Musician class to a relational database would be to use the JDBC library. JDBC is a layer of abstraction that lets an application issue SQL commands without thinking about the underlying database implementation.

Here is an example of how you could persist the Musician class using JDBC:


Musician georgeHarrison = new Musician(0, "George Harrison");

String myDriver = "org.gjt.mm.mysql.Driver";
String myUrl = "jdbc:mysql://localhost/test";
Class.forName(myDriver);
Connection conn = DriverManager.getConnection(myUrl, "root", "");
String query = " insert into users (id, name) values (?, ?)";
PreparedStatement preparedStmt = conn.prepareStatement(query);
preparedStmt.setLong (1, georgeHarrison.getId());
preparedStmt.setString (2, georgeHarrison.getName());
preparedStmt.execute();
conn.close();
// Error handling removed for brevity

This code is fairly self-documenting. The georgeHarrison object could come from anywhere (front-end submit, external service, etc.), and has its ID and name fields set. The fields on the object are then used to supply the values of an SQL insert statement. (The PreparedStatement class is part of JDBC, offering a way to safely apply values to an SQL query.)

While JDBC provides the fine-grained control that comes with manual configuration, it is cumbersome compared to JPA. To modify the database, you first need to create an SQL query that maps from your Java object to the tables in a relational database. You then have to write statements to take the object and populate the SQL. You also have to maintain the SQL whenever an object signature changes. With JDBC, maintaining the SQL becomes a task in itself.

Data persistence with JPA

Now consider the following code, where we persist the Musician class using JPA:


Musician georgeHarrison = new Musician("George Harrison");
musicianManager.save(georgeHarrison);

This code replaces the manual SQL from our previous sample with a single line, entityManager.save(), which instructs JPA to persist the object. It is quite a contrast! The JPA framework handles the SQL conversion based on annotations, so you can stay in the object-oriented paradigm. We’ll look at annotations shortly, but first a quick word about sessions.

Sessions

To obtain an entity manager, you can use:


EntityManager em = entityManagerFactory.createEntityManager();

This call effectively creates a session (a persistence context), which can be closed with entityManager.close(). These boundaries are most-often defined at the container level (by Spring, Tomcat, or similar) rather than explicitly by the developer. The container will inject the entityManager into the class, and automatically open/close the session.

Annotations

The magic in the above code is the result of metadata configuration, which is created using JPA’s annotations. Developers use annotations to inform JPA which objects should be persisted and how they should be persisted.

Here is the Musician class with a single JPA annotation:


@Entity
public class Musician {
  // ..class body
}

Persistent objects are called entities. Attaching the @Entity annotation to a class like Musician informs JPA that this class and its objects should be persisted.

Configuring JPA

Like most modern frameworks, JPA embraces coding by convention (also known as convention over configuration), in which the framework provides a default configuration based on industry best practices. So, for example, our Musician class will by default be mapped to a database table called Musician.

The conventional configuration is a timesaver, and in many cases, it works perfectly well. It is also possible to customize your JPA configuration. As an example, you could use JPA’s @Table annotation to specify the table where the Musician class should be stored:


@Entity
@Table(name="Musician")
public class Musician {
  // ..class body
}

This annotation tells JPA to persist the entity (the Musician class) to a table named Musician.

Primary key

The primary key is the field used to uniquely identify a row in the database. It is a fundamental part of schema design. The primary key allows other fields to reference the row (known as a foreign key).

When you store an object in a table, you almost always also specify its primary key. Sometimes, a business object will have a natural field that can be used as a primary key, but most commonly one is generated for it (a synthetic key).

Here, we tell JPA what field to use as Musician’s primary key:


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

In this case, we’ve used JPA’s @Id annotation to specify the id field as Musician’s primary key. By default, this configuration assumes the primary key will be set by the database—for instance, when the field is set to auto-increment on the table.

This is called the ID-generation strategy, which defaults to GenerationType.AUTO, which defers to the underlying database’s default approach (usually auto-increment).

Auto-increment is just one supported strategy for generating an object’s primary key. JPA also has annotations for changing individual field names. In general, JPA is flexible enough to adapt to any persistence mapping variations you might need.

CRUD operations

Once you’ve mapped a class to a database table and established its primary key, you have everything you need to create, retrieve, delete, and update that class in the database. Calling entityManager.save() will create or update the specified class, depending on whether the primary-key field is null or applies to an existing entity. Calling entityManager.remove() will delete the specified class.

Entity relationships

Simply persisting an object with a primitive field is only half the equation. JPA also lets you manage entities in relation to one another. Four kinds of entity relationships are possible in both tables and objects:

  1. One-to-many
  2. Many-to-one
  3. Many-to-many
  4. One-to-one

Each type of relationship describes how an entity relates to other entities. For example, the Musician entity could have a one-to-many relationship with Performance, an entity represented by a collection such as List or Set.

If the Musician included a Band field, the relationship between these entities could be many-to-one, implying a collection of Musicians on the single Band class. (Assuming each musician only performs in a single band.)

If Musician included a BandMates field, that could represent a many-to-many relationship with other Musician entities. (In this case the musician rows/objects are self-referencing, another common pattern.)

Finally, Musician might have a one-to-one relationship with a Quote entity, used to represent a famous quote: Quote famousQuote = new Quote().

Defining relationship types

JPA has annotations for each of its relationship mapping types. The following code shows how you might annotate the one-to-many relationship between Musician and Performances. In this case, each musician might have many performances, but there is only one musician for each performance:


// Performance.java
@Entity
public class Performance {
    @Id
    @GeneratedValue
    private Long id;
    private String title; // e.g., "Live at Abbey Road"

    // Many Performances belong to one Musician
    // @JoinColumn specifies the foreign key column in the 'Performance' table
    @ManyToOne
    @JoinColumn(name = "musician_id") // This will be the FK column in the 'performance' table
    private Musician musician;

   // constructor and members...
}

public class Musician {
  @OneToMany(mappedBy = "musician")
  private List performances = new ArrayList();
  //...
}

Notice that the @JoinColumn tells JPA what column on the Performance table will map to the Musician entity. Each Performance will be associated with a single Musician, which is tracked by this column. When JPA loads a Musician or Performance object into the database, it will use this information to reconstitute the object graph.

For Musician, the @OneToMany(mappedBy = 'musician') annotation tells JPA to use the Performance.musician field to populate the performances List on the Musician object. (That is, the Performance.musician field points from the Performance table to the Musician table.)

When JPA loads the foreign key from Performance, it will populate the actual Musician object found at that primary key in the Musician table, and the live List of performances hydrated by the performances holding those foreign keys. As a result, the performances are loaded holding a reference to the Musician objects, and these objects are loaded holding Lists of the performances.

There is more we can do to fine-tune how these relationships work. Right now, we’re just touching on the basics.

Also see: Java persistence with JPA and Hibernate: Entities and relationships.

JPA fetching strategies

In addition to knowing where to place related entities in the database, JPA needs to know how you want them loaded. Fetching strategies tell JPA how to load related entities. When loading and saving objects, a JPA framework must provide the ability to finetune how object graphs are handled. For instance, if the Musician class has a bandMate field, loading GeorgeHarrison could cause the entire Musician table to be loaded from the database!

You can use annotations to customize your fetching strategies, but JPA’s default configuration often works out of the box:

  1. One-to-many: Lazy
  2. Many-to-one: Eager
  3. Many-to-many: Lazy
  4. One-to-one: Eager

Transactions in JPA

While outside the scope of this introduction, transactions allow the developer to define boundaries for groups of operations to the database. We can define several operations together and then execute them together with entityManager.getTransaction().commit(). If any of the related operations fails, the whole transaction will rollback. This is another essential component of data design.

Transactions can be defined in a variety of ways, from explicit interactions via the API, to using annotations to define transactional boundaries, to using Spring AOP to define the boundaries.

JPA installation and setup

We’ll conclude with a quick look at installing and setting up JPA for your Java applications. For this demonstration we will use EclipseLink, the JPA reference implementation.

The common way to install JPA is to include a JPA provider into your project:



	org.eclipse.persistence
	eclipselink
	4.0.7


We also need to include a database driver:



	mysql
	mysql-connector-java
	8.0.33


Then, we need to tell the system about our database and provider, which we do in a persistence.xml file:


http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  
    
      
      
      
      
    
  


There are other ways to provide this information to the system, including programmatically. I recommend using the persistence.xml file because storing dependencies this way makes it very easy to update your application without modifying any code.

Spring configuration for JPA

Using Spring Data JPA will greatly ease the integration of JPA into your application. As an example, placing the @SpringBootApplication annotation in your application header instructs Spring to automatically scan for classes and inject the EntityManager as required, based on the configuration you’ve specified.

Include the following dependencies in your build if you want Spring’s JPA support for your application:



	org.springframework.boot
	spring-boot-starter-test
	3.5.3
	test


	org.springframework.boot
	spring-boot-starter-data-jpa
	3.5.3


When to use JPA

The question of whether to use JPA is a common source of analysis paralysis when designing a Java application. Especially when attempting to make up-front technology decisions, you don’t want to get data persistence—an essential and long-term factor—wrong.

To break this kind of paralysis, it’s useful to remember that applications can evolve into using JPA. You might build exploratory or prototype code using JDBC, then start adding in JPA. There’s no reason these solutions can’t coexist.

After being paralyzed by indecision, the next worst thing is to adopt JPA when the additional effort it implies will prevent a project from moving forward. JPA can be a win for overall system stability and maintainability, but sometimes simpler is better, especially at the beginning of a project. If your team doesn’t have the capacity to adopt JPA up front, consider putting it on your roadmap for the future.

Conclusion

Every application that interfaces with a database should define an application layer whose sole purpose is to isolate persistence code. As you’ve seen in this article, the Jakarta Persistence API (JPA) introduces a range of capabilities and support for Java object persistence. Simple applications may not require every JPA capability, and in some cases the overhead of configuring the framework may not be merited. As an application grows, however, JPA really earns its keep. Using JPA keeps your object code simple and provides a conventional framework for accessing data in Java applications.

Original Link:https://www.infoworld.com/article/2259807/what-is-jpa-introduction-to-the-java-persistence-api.html
Originally Posted: Wed, 08 Oct 2025 09:00:00 +0000

0 People voted this article. 0 Upvotes - 0 Downvotes.

Artifice Prime

Atifice Prime is an AI enthusiast with over 25 years of experience as a Linux Sys Admin. They have an interest in Artificial Intelligence, its use as a tool to further humankind, as well as its impact on society.

svg
svg

What do you think?

It is nice to know your opinion. Leave a comment.

Leave a reply

Loading
svg To Top
  • 1

    What is JPA? Introduction to Java persistence

Quick Navigation