JPA Explained Simply (ELI5)
Before looking at code, understand the core concepts using simple analogies.
JPA vs Hibernate
JPA is just a rulebook (a set of Java interfaces). Hibernate is the actual worker that reads the rulebook and does the heavy lifting with the database.
EntityManager
Think of it as your personal assistant. You tell it to save, find, or delete things, and it handles the database connection and SQL generation for you.
Persistence Context
The assistant's short-term memory (Cache). It remembers which objects you've loaded or changed so it doesn't have to bother the database every single second.
Dirty Checking
The assistant watches your objects. If you change a salary from 50k to 75k, the assistant notices
the object is "dirty" and automatically writes an UPDATE SQL when
you commit. You don't need an update() method!
javax.persistence (JPA 2.2 / Java
EE).
If you are using Spring Boot 3+ or Jakarta EE 9+, the package has changed to
jakarta.persistence. Mentioning this transition in an interview shows you are up-to-date!
Layered Architecture Flow
Entity Lifecycle States
Interviewers love asking about entity states. Memorize this diagram.
| State | Meaning | Interview Key Point |
|---|---|---|
| Transient | New object (e.g., new Employee()) |
Has no database representation yet. |
| Managed | Tracked by the current Persistence Context | Any changes to fields are auto-detected (Dirty Checking). |
| Detached | Disconnected from the Persistence Context | Must use merge() to bring it back to Managed state. |
| Removed | Scheduled for deletion | Deleted from DB on transaction commit. |
CRUD & The Magic of Dirty Checking
CREATE (persist)
em.getTransaction().begin();
em.persist(newEmployee);
em.getTransaction().commit();
Turns a Transient object into a Managed object. SQL INSERT happens at commit.
READ (find)
Employee e = em.find(Employee.class, 101);
Queries the DB by Primary Key. The returned object is now Managed.
UPDATE (Dirty Checking)
Employee e = em.find(Employee.class, 101);
e.setSalary(90000); // No update() method needed!
// Just commit!
Hibernate compares the object's current state to its snapshot. If different ("dirty"), it
auto-generates an UPDATE SQL.
DELETE (remove)
Employee e = em.find(Employee.class, 101);
em.remove(e);
Trap: You can only call remove() on a Managed
entity. That's why we find() it first!
Interview Cheat Sheet
Difference between persist() and merge()?
persist() is for brand new objects (Transient → Managed). It throws an error if the
entity already has an ID that exists in the DB.
merge() is for updating existing objects or bringing detached objects back. It
copies the state into a new Managed instance.
Difference between find() and getReference()?
find() hits the database immediately and returns the actual entity (or null).
getReference() returns a Proxy object without hitting the DB. It
only queries the DB when you actually call a getter method. Use this for performance when setting up
relationships.
EntityManagerFactory vs EntityManager?
EMF is heavyweight, thread-safe, and created ONCE per application.
EM is lightweight, NOT thread-safe, and created per request/transaction.
Top 10 Scenario-Based Interview Questions
Don't just memorize definitions. Interviewers will give you a scenario. Here is exactly how to answer them.
Scenario 1: You call persist() but the database remains empty.
Why?
"In resource-local JPA, write operations require an active transaction. The changes are sitting
in the Persistence Context (Hibernate's memory) but haven't been flushed to the database. I need
to ensure I call em.getTransaction().begin() before persisting, and
commit() afterward."
Scenario 2: I changed an entity's salary, but didn't call
update(). Will it save?
"Yes, as long as the entity is in the Managed state. Hibernate uses Dirty Checking. When the transaction commits, Hibernate compares the current object to its original snapshot, detects the change, and automatically generates the UPDATE SQL."
Scenario 3: I call merge(employee), then change the salary on
employee, but it doesn't save.
"This is a common trap. merge() does not make the original object
managed. It copies the state into a new managed instance and returns it. I must capture
the return value: Employee managed = em.merge(detached); and modify
managed instead."
Scenario 4: remove() throws an
IllegalArgumentException. Why?
"The remove() method only works on Managed entities associated with
the current Persistence Context. If I pass a Transient or Detached object, it fails. The fix is
to find() the entity first to attach it, then call remove()."
Scenario 5: What is the "N+1 Select Problem" and how do you fix it?
"If I fetch 100 Employees, that's 1 query. If I then loop through them and access their
lazily-loaded Departments, Hibernate fires 100 additional queries. This is the N+1 problem and
it kills performance. I fix it by using JOIN FETCH in my JPQL query or
@EntityGraph to load the related data in a single SQL query."
Scenario 6: I get a LazyInitializationException in my UI layer.
Why?
"This happens when I try to access a lazily-loaded relationship (like
employee.getDepartment()) after the EntityManager has been closed. The
Persistence Context is gone, so Hibernate can't fetch the data. I need to fetch the data while
the transaction is still open, or use JOIN FETCH."
Scenario 7: Should I use one EntityManager for the whole
application?
"No. EntityManager is lightweight, not thread-safe, and holds a cache (Persistence
Context). Keeping one open forever causes memory leaks and stale data. We should use one
EntityManagerFactory for the app, but create and close a new
EntityManager for each request/transaction (EntityManager-per-request pattern)."
Scenario 8: Difference between flush() and commit()?
"flush() synchronizes the Persistence Context with the database (sends the SQL), but
doesn't finalize the transaction. The database might roll it back if it's in a transaction.
commit() flushes the changes AND finalizes the database transaction, making the
changes permanent."
Scenario 9: How do you map a Java Date to a SQL Date column?
"I use the @Temporal annotation. For example,
@Temporal(TemporalType.DATE) maps it to a SQL DATE (no time), whereas
TIMESTAMP includes both date and time."
Scenario 10: JPQL vs Native SQL?
"Native SQL queries database tables and columns directly. JPQL queries Java Entities and their fields. JPQL is database-agnostic; Hibernate translates it into the correct SQL dialect for MySQL, Postgres, etc. automatically."
Complete Project Code
Click below to expand the complete runnable Maven project (persistence.xml, Entity, DAO, Service, Tester).
Show / Hide Complete Codebase
1. persistence.xml
<persistence-unit name="employeePU">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>com.example.entity.EmployeeEntity</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/employee_db"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="root"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>
2. EmployeeEntity.java
@Entity
@Table(name = "employee")
public class EmployeeEntity {
@Id
@Column(name = "employee_id")
private int employeeId;
@Column(name = "employee_name")
private String employeeName;
private double salary;
@Temporal(TemporalType.DATE)
@Column(name = "joining_date")
private Date joiningDate;
// Getters and Setters...
}
3. EmployeeDAOImpl.java (The Core Logic)
public class EmployeeDAOImpl implements EmployeeDAO {
// CREATE
public void save(EmployeeEntity employee) {
EntityManager em = JPAUtility.getEntityManager();
try {
em.getTransaction().begin();
em.persist(employee); // Transient -> Managed
em.getTransaction().commit(); // Flushes to DB
} catch (RuntimeException e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
throw e;
} finally { em.close(); }
}
// UPDATE (Using Dirty Checking)
public void updateSalary(int id, double newSalary) {
EntityManager em = JPAUtility.getEntityManager();
try {
em.getTransaction().begin();
EmployeeEntity e = em.find(EmployeeEntity.class, id);
if (e != null) {
e.setSalary(newSalary);
// NO em.update() needed! Hibernate tracks the change.
}
em.getTransaction().commit();
} catch (RuntimeException e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
throw e;
} finally { em.close(); }
}
// DELETE
public void delete(int id) {
EntityManager em = JPAUtility.getEntityManager();
try {
em.getTransaction().begin();
EmployeeEntity e = em.find(EmployeeEntity.class, id); // Must find first!
if (e != null) {
em.remove(e); // Must be Managed to remove
}
em.getTransaction().commit();
} catch (RuntimeException e) {
if (em.getTransaction().isActive()) em.getTransaction().rollback();
throw e;
} finally { em.close(); }
}
}