Appearance
Spring Boot JPA Part 2 | Setup, JPA Architecture, Entity Lifecycle
Introduction: The Translator Between Java and SQL
Imagine you are a tourist in Japan who only speaks English. You want to order food, ask for directions, and pay a bill. You could spend months learning Japanese, or you could hire a translator who bridges the two worlds seamlessly. That translator speaks both languages fluently and handles all the awkward details of the conversation for you.
JPA with Hibernate is that translator for your Java application and your relational database. Your Java code works with objects. The database stores rows and columns. JPA translates between the two worlds automatically, so you write Java and the framework produces SQL.
What Is ORM (Object Relational Mapping)?
In the previous chapter you saw raw JDBC and JdbcTemplate, where you wrote SQL directly. ORM takes a different approach. Instead of writing SQL, you annotate Java classes to describe how they map to database tables. The ORM framework then generates and executes the SQL on your behalf.
Without ORM (JdbcTemplate approach):
Application → writes SQL → JDBC → DatabaseWith ORM (JPA/Hibernate approach):
Application → works with Java objects → ORM framework → generates SQL → JDBC → DatabaseThe major benefits of ORM:
- No handwritten SQL for basic CRUD operations.
- Changes to your Java class automatically affect the schema (with
ddl-auto=update). - built in caching, lazy loading, and relationship management.
- Database portability: switch from MySQL to PostgreSQL by changing a driver and configuration.
JPA vs Hibernate: Specification vs Implementation
A common source of confusion is the relationship between JPA and Hibernate.
JPA (Jakarta Persistence API) is a specification. It defines a set of interfaces and annotations (@Entity, @Table, @Id, EntityManager, etc.) but contains no executable code. Think of it as a contract.
Hibernate is an implementation of that contract. It provides the actual code that generates SQL, manages the persistence context, and talks to the database. Other implementations exist (EclipseLink, OpenJPA) but Hibernate is by far the most widely used.
When you write @Entity or use EntityManager, you are writing to the JPA specification. Hibernate fulfills that specification at runtime.
The JPA Architecture Stack
Your Application (Service, Repository)
|
v
JPA Specification (EntityManager, @Entity, etc.)
|
v
Hibernate ORM (the implementation)
|
v
JDBC (Hibernate generates SQL and executes via JDBC)
|
v
JDBC Driver (MySQL, PostgreSQL, H2, etc.)
|
v
DatabaseKey Components of the JPA Architecture
EntityManagerFactory
The EntityManagerFactory is a heavyweight, thread safe object that is created once per application per persistence unit. Creating it is expensive because it reads configuration, validates the schema, and initializes the ORM mappings. You create it once at startup and reuse it throughout the application's lifetime.
In a plain Java application you create it explicitly:
java
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPersistenceUnit");Spring Boot creates and manages this automatically when you add spring-boot-starter-data-jpa.
EntityManager
The EntityManager is a lightweight, non-thread safe object created from the factory. Each database session (or HTTP request) gets its own EntityManager. It manages the Persistence Context for that session.
In Spring, a new EntityManager is created for each HTTP request automatically (via the DispatcherServlet lifecycle). You rarely interact with it directly when using Spring Data JPA repositories.
Persistence Context
The Persistence Context is the in memory cache managed by the EntityManager. Think of it as a first level cache that tracks all entities you have read or written during a session. It knows which entities are new, which have been modified, and which should be deleted.
Any entity currently tracked by the persistence context is called a managed entity. When the transaction commits, the persistence context flushes all pending changes to the database automatically.
persistence.xml (Non-Spring Setup)
In a plain JPA application (without Spring Boot), you define a persistence unit in src/main/resources/META-INF/persistence.xml:
xml
<persistence xmlns="https://jakarta.ee/xml/ns/persistence" version="3.0">
<persistence-unit name="myPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<class>com.example.UserDetail</class>
<properties>
<property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="jakarta.persistence.jdbc.user" value="root"/>
<property name="jakarta.persistence.jdbc.password" value="password"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>Transaction types:
RESOURCE_LOCAL: One database, one transaction. The typical case.JTA: Distributed transactions spanning multiple databases. More complex.
Spring Boot eliminates persistence.xml entirely. Configuration lives in application.properties.
Spring Boot JPA Setup
Maven Dependencies
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>application.properties
properties
# Database connection
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA / Hibernate settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8DialectThe Entity: Your Java Class Mapped to a Table
An entity is a plain Java class annotated with @Entity. Hibernate reads these annotations and knows exactly which table and columns to use.
java
import jakarta.persistence.*;
@Entity
@Table(name = "user_details")
public class UserDetail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String phone;
// Default constructor required by JPA
public UserDetail() {}
public UserDetail(String name, String phone) {
this.name = name;
this.phone = phone;
}
// Getters and setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
}The default no argument constructor is mandatory. JPA uses reflection to instantiate entities when loading from the database, and reflection requires a no argument constructor.
The Repository: Spring Data JPA
Spring Data JPA provides the JpaRepository interface that pre-implements all basic CRUD operations for you:
java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<UserDetail, Integer> {
// findById, findAll, save, delete — all already implemented
}JpaRepository<T, ID> takes the entity type and the type of the primary key. You get dozens of methods for free: save(), findById(), findAll(), deleteById(), count(), and many more.
Entity Lifecycle: The Four States
This is one of the most important concepts in JPA. Every entity object can be in one of four states, and Hibernate tracks transitions between them.
1. Transient
An object is transient when it has just been created with new and is not yet known to any EntityManager. Changes to it are completely invisible to JPA. No database row corresponds to it.
java
UserDetail user = new UserDetail("Alice", "9876543210");
// user is TRANSIENT — no DB row, not tracked2. Persistent (Managed)
An object becomes persistent when it is associated with an active persistence context, either by calling entityManager.persist(entity) or by loading it from the database with find() or a query.
When an entity is persistent, Hibernate tracks every change to its fields. At flush time (before the transaction commits) Hibernate generates and executes the necessary SQL automatically.
java
entityManager.persist(user);
// user is now PERSISTENT — Hibernate tracks it
user.setPhone("1111111111");
// Hibernate will generate UPDATE automatically at flush3. Detached
An entity becomes detached when the persistence context closes (entity manager is closed) or when you explicitly call entityManager.detach(entity). The entity still holds its data and its primary key, but Hibernate no longer tracks changes.
java
entityManager.close();
// user is now DETACHED — changes are not tracked
user.setName("Bob"); // no SQL generatedIf you want Hibernate to track a detached entity again, you use entityManager.merge(entity), which attaches it (or a copy of it) back to the active persistence context.
4. Removed
An entity transitions to the removed state when you call entityManager.remove(entity). It is still in the persistence context but is scheduled for deletion. The DELETE SQL is issued at flush time.
java
entityManager.remove(user);
// user is REMOVED — DELETE will execute at flush/commitIf you change your mind before the transaction commits, you can call entityManager.persist(user) again to move it back to persistent state.
Entity Lifecycle Diagram
new UserDetail()
|
v
[TRANSIENT] ← no EntityManager awareness
|
| persist()
v
[PERSISTENT] ← tracked, changes auto-flushed
| |
| | remove()
| v
| [REMOVED] ← DELETE at flush
| |
| | persist() again (undo)
| v
| [PERSISTENT]
|
| detach() or close EntityManager
v
[DETACHED] ← data retained, changes not tracked
|
| merge()
v
[PERSISTENT]Flush vs Commit
Two important operations often confused:
- Flush: Hibernate writes pending changes from the persistence context to the database (SQL is sent). The transaction is still open; changes are visible to queries within the same transaction.
- Commit: The database transaction is committed. Changes become permanent and visible to other transactions.
By default, Spring triggers a flush just before a transaction commits, but you can also trigger it manually with entityManager.flush().
Spring Data JPA Service Example
java
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public UserDetail createUser(UserDetail user) {
// save() calls persist for new entities and merge for detached ones
return userRepository.save(user);
}
public Optional<UserDetail> getUserById(int id) {
// Returns empty Optional if not found — no exception
return userRepository.findById(id);
}
public List<UserDetail> getAllUsers() {
return userRepository.findAll();
}
public UserDetail updateUser(UserDetail user) {
// save() on an entity with an ID performs merge (update)
return userRepository.save(user);
}
public void deleteUser(int id) {
userRepository.deleteById(id);
}
}Summary
- ORM maps Java objects to database tables, eliminating handwritten SQL for common operations.
- JPA is the specification; Hibernate is the most popular implementation.
- The architecture stack: Application → JPA → Hibernate → JDBC → JDBC Driver → Database.
EntityManagerFactoryis created once;EntityManageris created per session.- The Persistence Context is the in memory first level cache managed by the
EntityManager. - Entities have four lifecycle states: Transient, Persistent, Detached, and Removed.
- Spring Boot auto configures everything; you only need
spring-boot-starter-data-jpaand datasource properties.
Interview Questions
Q1: What is the difference between JPA and Hibernate?
JPA is a specification (a set of interfaces and annotations defined in the Jakarta EE standard). Hibernate is an implementation of that specification. When you write @Entity or use EntityManager, you program against the JPA API. Hibernate fulfills those contracts at runtime by generating SQL and managing the persistence context.
Q2: What is the Persistence Context?
The Persistence Context is an in memory cache managed by the EntityManager. It tracks all entities that have been loaded or saved within the current session. Hibernate uses it to detect changes (dirty checking) and to avoid redundant database queries within the same session. It is also called the first level cache.
Q3: What are the four states of a JPA entity?
Transient (just created with new, not tracked), Persistent (tracked by the EntityManager, changes are auto-flushed), Detached (was persistent but the EntityManager was closed or detach() was called), and Removed (scheduled for deletion, DELETE runs at flush).
Q4: What is the difference between persist() and merge()?
persist() transitions a Transient entity to Persistent state. It requires the entity to have no ID yet. merge() takes a Detached entity (or a new entity with an ID), copies its state into the Persistence Context, and returns a Persistent copy. You should use the returned object from merge().
Q5: What is EntityManagerFactory and why is it heavyweight?
EntityManagerFactory is created once per application and per persistence unit. It is expensive to create because it reads entity mappings, validates the schema, and initializes Hibernate's internal data structures. It is thread safe and long lived. EntityManager instances are cheap, non-thread safe, and short-lived (one per request/transaction).
Q6: What does spring.jpa.hibernate.ddl-auto=update do?
It tells Hibernate to compare the current entity mappings against the existing database schema and apply only the differences. It will create new tables and add new columns but will not drop existing columns or tables. Common values are: create (drop and recreate on startup), create-drop (drop on shutdown), update (migrate forward), validate (only verify — throw if mismatch), none (do nothing).
Q7: Why does JPA require a no argument constructor on entity classes?
JPA uses Java reflection to instantiate entity objects when loading records from the database. Java's Constructor.newInstance() requires a no argument constructor. Without it, Hibernate cannot create instances during queries or find operations and will throw an exception at startup.
Q8: What is the difference between flush and commit?
Flush sends the pending SQL statements from the Persistence Context to the database but keeps the transaction open. Other transactions cannot yet see the changes. Commit makes the transaction permanent. All changes become visible to other transactions and cannot be rolled back. By default, Spring flushes automatically just before a transaction commits.