Have you ever wondered what JPA means? Or have you thought about how it relates to Spring Data JPA? Or Hibernate? Also, what Spring Data JPA is not? And what Hibernate can’t do for you?
In this post, we will discuss these with code examples.
Understanding JPA vs Hibernate vs Spring Data JPA
Before we dive in, it's worth understanding what these terminologies mean.
JPA
JPA until 2019 meant Java Persistence API. JPA currently stands for Jarkarta Persistence API.
So what does JPA do?
JPA’s essential purpose is persistence, a process in which a Java object (for example Customer
) outlives the process that created it. This is by storing this object in data storage, think SQL or no-SQL.
Worth noting that on its own JPA is not a framework. It specifies how objects (like Customer
) are mapped for persistence.
How does JPA do this?
JPA helps with persistence by specifying several annotations (metadata).
A few JPA annotations
Id
This specifies the primary for a table within the modeled entity class. For a field or variable to be annotated withId
, it has to be one of the following Java types:String
,java.util.Date
,java.sql.Date
,java.math.BigDecimal
,java.math.BigInteger
.GeneratedValue
This is used in conjunction withId
and specifies generation strategies for the values of primary keys.Column
This annotation defines the mapped column for a persistent field. The field or property name is used as the column's name in the table when theColumn
annotation is not provided.Entity
Specifies that the class is an entity. This annotation is applied to the entity class.Table
Specifies the primary table for the annotated entityOneToMany
Specifies a many-valued association with one-to-many multiplicity.ManyToOne
Specifies a single-valued association to another entity class that has many-to-one multiplicityJoinColumn
Specifies a column for joining an entity association or element collection.
@Entity
@Table(name="Customers", schema="dbo")
public class Customer { ... }
JPA & Hibernate
Object Relational Mapping is the automated (and transparent) persistence of objects in a Java application to the tables in an SQL database, using metadata that describes the mapping between the classes of the application and the schema of the SQL database.
From: Java Persistence with Hibernate. 2nd Edition.
Hibernate Object Relational Model (ORM), is an implementation of JPA thereby utilizing JPA standards. A few differences between Hibernate and JPA include
- Hibernate-related classes are included in
org.hibernate
package while JPA is injakarta.persistent
package; - JPA uses an SQL-like language called Java Persistence Query Language while Hibernate provides Hibernate Query Language
- JPA provides
EntityManager
to provide operations to and from the database while Hibernate providesSessionFactory
Spring Data JPA
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories (from Spring Data Docs)
Why Spring Data JPA?
- As we would see later, with Spring Data JPA, we do not need either the
persistence.xml
orhibernate.cfg.xml
configuration files - It understands the JPA mappings (remember
Column
,Entity
,Table
used in our model classes such asCustomer
above - It exposes interfaces such as
JpaRepository
,CrudRespository
,PagingAndSortingRepository
. These interfaces help us easily build JPA-specific Create-Read-Update-Delete pagination methods. - As we will see below, it removes the need to specifically have call
commit
methods. This is as we canextend
one of the above-mentioned repository interfaces and has methods such assaveAll
,save
,findById
,findAll
amongst other methods - Underneath the hood to uses hibernate
Next, we will look at how to use JPA, Hibernate, and Spring Data JPA with examples.
The How ⚙️
Before we proceed to how to use all of JPA, Hibernate, and Spring Data JPA, let's first look at a few vanilla examples. This will include
- Vanilla JPA to persist and retrieve data;
- Vanilla Hibernate to achieve the same persisting retrieval of data; and
- Spring Data JPA
Before we move into our code examples, it is worth setting up a database and a Java project, and for this post, we will be using PostgreSQL on Docker.
Setting up Postgres on Docker
Just JPA
Why start with a vanilla JPA example you might ask? This is because it is worth gaining a from-the-scratch understanding of how JPA fits in Hibernate and how both fit into Spring Data JPA.
For our vanilla JPA example, all we need to do include
- adding the below dependencies to our project
// To build.gradle
// Properties
def versions = [
'hibernateCore' : '6.2.4.Final',
'postgres' : '42.6.0',
]
// Postgres
implementation group: 'org.postgresql', name: 'postgresql', version: versions.postgres
// Hibernate
implementation group: 'org.hibernate.orm', name: 'hibernate-core', version: versions.hibernateCore
Whoops!! Did we add hibernate-core
dependency to our vanilla JPA project?
Yes, we did, this is because it brings in the JPA’s jakarta.persistence
dependency.
- Create a
Customer
class using JPA to specify how we want to map it to thecustomers
database table
@Entity(name = "customers")
@Table(name = "customers")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@Column(name = "username")
private String username;
@Column(name = "fullName")
private String fullName;
@Column(name = "joinedOn")
private LocalDate joinedOn;
}
- Next, we create a
persistence.xml
file inresources > META-INF
folder of your project.persistence.xml
, configures at least one persistent unit and each unit must have a unique name. Think of a persistence unit as a configuration for — database details to be used, classes to be mapped to said database tables, how and if you want database and/or tables to be recreated, and how to log SQL amongst others
<persistence-unit name="vanilla-jpa-unit">
<description>A demo persisting to a DB with vanilla JPA</description>
<class>dev.etimbuk.models.Customer</class>
<properties>
<!-- Database connection details -->
<property name="jakarta.persistence.jdbc.driver" value="org.postgresql.Driver" />
<property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/blogposts" />
<property name="jakarta.persistence.jdbc.user" value="yourusername" />
<property name="jakarta.persistence.jdbc.password" value="yourpassword" />
<!-- DON'T USE THIS IN PRODUCTION -->
<!-- automatically drop and create required database tables -->
<property name="jakarta.persistence.schema-generation.database.action" value="drop-and-create" />
</properties>
</persistence-unit>
- Now that we have our
hibernate-core
dependency, mapped class andpersistence.xml
in place, let’s test that we can save to ourcustomers
table and retrieve data from it.
// First we will create an EntityManager from jakarta.persistence package
final EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("vanilla-jpa-unit");
final EntityManager entityManager = entityManagerFactory.createEntityManager();
//Create a basic Customer instance
final var customer = new Customer("Test.User", "Test User", LocalDate.now().minusYears(5));
//Persist data into customers table
entityManager.getTransaction().begin();
entityManager.persist(customer);
entityManager.getTransaction().commit();
//SELECT query to verify above persist works as expected
List<Customer> savedCustomers = entityManager.createQuery("SELECT c FROM customers c", Customer.class).getResultList();
//assert we only have one customer in customers table
assertEquals(1, savedCustomers.size());
Complete code sample for Just JPA section available here
JPA + Hibernate
We can use JPA and Hibernate in two ways
- using the
hibernate.cfg.xml
to configure database details as we did above viapersistence.xml
- applying configuration through
StandardServiceRegistryBuilder
class.
Worth noting here that
- same
Customer
class as above is still used; EntityFactory
switched forSessionFactory
hibernate class;- same
hibernate-core
dependency is still used.
//Create SessionFactory instance
sessionFactory = metaDataSources.buildMetadata().buildSessionFactory();
//Create a basic Customer instance
final var customer = new Customer("Test.User", "Test User", LocalDate.now().minusYears(5));
//Create a Session. Think of this as a JDBC connection
try (final Session session = sessionFactory.openSession()){
session.beginTransaction();
session.persist(customer);
session.getTransaction().commit();
//SELECT query to verify above persist works as expected
List<Customer> savedCustomers = session.createQuery("SELECT c FROM customers c", Customer.class).getResultList();
//assert we only have one customer in customers table
assertEquals(1, savedCustomers.size());
}
Complete code sample for JPA + Hibernate section available here
JPA, Hibernate & Spring Data JPA
Now, we have looked at how we can use JPA on its own and JPA + Hibernate together, next, we will create a basic CRUD application to understand why it would be preferred to use Spring Data JPA.
Steps
- Let’s update our
build.gradle
file to include all needed dependencies
dependencies {
compileOnly group: 'org.projectlombok', name: 'lombok', version: versions.lombok
annotationProcessor group: 'org.projectlombok', name: 'lombok', version: versions.lombok
// This dependency is used by the application.
implementation group: 'com.google.guava', name: 'guava', version: versions.googleGuava
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
// Logging
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: versions.log4j
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: versions.log4j
// Postgres
runtimeOnly group: 'org.postgresql', name: 'postgresql', version: versions.postgres
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly group: 'org.projectlombok', name: 'lombok', version: versions.lombok
testAnnotationProcessor group: 'org.projectlombok', name: 'lombok', version: versions.lombok
}
- Next, we create an
application.properties
file.
# jdbc.config
spring.datasource.url=jdbc:postgresql://localhost:5432/blogposts
spring.datasource.username=yourusername
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=org.postgresql.Driver
#DON'T DO THIS IN PRODUCTION
spring.jpa.hibernate.ddl-auto=create-drop
- Given we have also included Spring Boot (yup!!! 😃), we also need to create our entry point to the application
@SpringBootApplication
public class JPAHibernateSpringDataJPAApplication {
public static void main(String[] args) {
SpringApplication.run(JPAHibernateSpringDataJPAApplication.class, args);
}
}
- Now, we
extend
JpaRepository
to create ourCustomerRepository
. Think of this as our data access class - Next, we create a service class that uses our
CustomerRepository
class with basic CRUD methods
public Optional<Customer> findById(final String customerId) {
return customerRepo.findById(customerId);
}
public Customer create(final Customer customer) {
return customerRepo.save(customer);
}
// ...
- Now, some tests
@BeforeEach
void init() {
expectedCustomerToSave = new Customer("firstname", "surname", LocalDate.now());
savedCustomer = service.create(expectedCustomerToSave);
}
@Test
void testExistingCustomerUpdated() {
final var newDate = LocalDate.now().plusDays(100);
final var existingCustomer = service.findById(savedCustomer.getId()).get();
existingCustomer.setJoinedOn(newDate);
final var updatedCustomer = service.update(existingCustomer);
assertEquals(newDate.toString(), updatedCustomer.get().getJoinedOn().toString());
}
Complete code sample for JPA, Hibernate & Spring Data JPA section available here
In Summary
Wheew!!!!
In this post, we have looked at what JPA, Hibernate, and Spring Data JPA are and illustrated with code examples how we can use them independently to map and persist data in a database.
As is the practice all the code examples in this post are available here