- 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.
- 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:
@Entitypublic 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();}