Skip to content
thesarfo

Concept

Persistence: ORM, JPA & Hibernate

What ORM and JPA actually are, how Hibernate implements the spec, and two ways to connect to a database — persistence.xml and the programmatic PersistenceUnitInfo approach.

views 0
  1. ORM is the automated persistence of objects in an app to the tables in an RDBMS, using metadata that describes the mapping between the classes of the application and the schema of the SQL database.
  2. JPA is a specification defining an API that manages the persistence of objects and object/relational mappings.

Hibernate is the most popular implementation of the JPA specification — JPA specifies what must be done to persist objects, while Hibernate determines how to do it.

Spring Data Commons provides the core Spring framework concepts that support all Spring Data modules.

Spring Data JPA is an additional layer on top of JPA implementations such as Hibernate. Not only can it use all the capabilities of JPA, it adds its own — like generating database queries from method names.

If we’re using JDBC and make 2 different requests to insert or update the DB, they’re executed as 2 separate operations (insert and update). With an ORM this isn’t the case — with a JPA implementation, they aren’t even considered insert and update. The insert is a merge, and persist doesn’t even exist as a direct DB operation in Hibernate — it’s done automatically.

The ORM doesn’t execute these operations on the DB directly — they happen in the context. The context contains our database entities. Our requests do something to the context (which contains the entities), and the operations are then performed on the DB as SQL queries. Sometimes multiple requests to the context end up sending just one SQL query to the database.

Connecting to your DB with JPA/Hibernate

1. Using a persistence.xml file

One way to connect your app to your DB through Hibernate is a persistence.xml file, found in the META-INF folder under resources. Inside it you provide the name of your persistence-unit and provider, and inside <properties> you provide your driver, URL, username, and password.

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.2"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define persistence unit -->
<persistence-unit name="my-persistence-unit" transaction-type="RESOURCE_LOCAL">
<description>description</description>
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<!-- database connection -->
<property name="jakarta.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost/demo" />
<property name="jakarta.persistence.jdbc.user" value="root" />
<property name="jakarta.persistence.jdbc.password" value="" />
</properties>
</persistence-unit>
</persistence>

To connect with persistence.xml, we need an EntityManager, which lets us control transactions — beginning and committing them. The EntityManager represents the context, and only through it can you operate on the context.

But to get an EntityManager, we need an EntityManagerFactory — a factory for creating EntityManagers whenever needed. There are two ways to get one.

If you’re using a persistence.xml file, use the Persistence class and pass the name of your persistence-unit to createEntityManagerFactory():

try {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
EntityManager em = emf.createEntityManager(); // represents the context
em.getTransaction().begin();
em.getTransaction().commit();
} finally {
em.close();
}

Nothing is expected to happen when running the code above, since there’s nothing between begin() and commit() — it just created an empty context.

Next we can describe our entity:

@Entity
public class Product {
@Id
private Long id;
private String name;
}

Then create instances of it inside a transaction:

try {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Product p = new Product();
p.setId(1L);
p.setName("Beer");
em.persist(p); // add this to the context -> NOT AN INSERT QUERY
em.getTransaction().commit();
} finally {
em.close();
}

persist isn’t an insert operation because an application might have business logic that acts on the entity in the context — the persist mirrors that logic, and sometimes doesn’t end up as an insert in the DB at all. On the other hand, it usually does end up as an insert query on the database — it really depends.

2. Programmatic approach

You can also connect to the database from your application code directly. Replace persistence.xml with a single class implementing PersistenceUnitInfo — it contains everything needed to replace the XML file. The interface has a lot of methods, but we only need a few:

public class CustomPersistenceUnitInfo implements PersistenceUnitInfo {
@Override
public String getPersistenceUnitName() {
return "my-persistence-unit";
}
@Override
public String getPersistenceProviderClassName() {
return "org.hibernate.jpa.HibernatePersistenceProvider";
}
@Override
public PersistenceUnitTransactionType getTransactionType() {
return PersistenceUnitTransactionType.RESOURCE_LOCAL;
}
@Override
public DataSource getJtaDataSource() {
// you need the Hikari dependency to establish the datasource
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost/demo");
dataSource.setUsername("root");
dataSource.setPassword("");
return dataSource;
}
@Override
public DataSource getNonJtaDataSource() {
return null;
}
@Override
public List<String> getMappingFileNames() {
return null;
}
@Override
public List<URL> getJarFileUrls() {
return null;
}
@Override
public URL getPersistenceUnitRootUrl() {
return null;
}
@Override
public List<String> getManagedClassNames() {
// makes our app able to interact with our entities via reflection
return List.of("org.example.entities.Product");
}
@Override
public boolean excludeUnlistedClasses() {
return false;
}
@Override
public SharedCacheMode getSharedCacheMode() {
return null;
}
@Override
public ValidationMode getValidationMode() {
return null;
}
@Override
public Properties getProperties() {
return null;
}
@Override
public String getPersistenceXMLSchemaVersion() {
return null;
}
@Override
public ClassLoader getClassLoader() {
return null;
}
@Override
public void addTransformer(ClassTransformer classTransformer) {
}
@Override
public ClassLoader getNewTempClassLoader() {
return null;
}
}

And now we can drop the XML file from our EntityManager setup entirely:

try {
EntityManagerFactory emf = new HibernatePersistenceProvider()
.createContainerEntityManagerFactory(new CustomPersistenceUnitInfo(), new HashMap<>());
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Product p = new Product();
p.setId(1L);
p.setName("Beer");
em.persist(p); // add this to the context -> NOT AN INSERT QUERY
em.getTransaction().commit();
} finally {
em.close();
}