JPA is the specification — the contract for how Java code maps to relational tables. Hibernate is the implementation almost everyone uses to satisfy that contract. Together they have powered enterprise Java for two decades and shaped how a generation of engineers thinks about persistence.
← Back to Database Side@Entity that maps to a table.The Jakarta EE standard. Defines annotations (@Entity, @OneToMany) and the EntityManager API. Vendor-neutral on paper.
The reference implementation. Adds features beyond JPA — filters, envers, multi-tenancy, criteria extensions.
The friendly face. Repository interfaces, derived queries (findByEmailAndStatus), pagination — all on top of JPA.
The other JPA implementation. Reference for newer spec versions; rare in the wild compared to Hibernate.
Schema migrations. Hibernate's hbm2ddl auto-update is for demos; real systems use these.
Not JPA — a typed SQL DSL. The escape hatch when JPA's abstraction stops paying its rent.
An Order with line items, a customer, and a shipping address — load the order and walk the graph. Hibernate fetches what you need, when you ask, and tracks every change. Without an ORM, that's hundreds of lines of ResultSet mapping per aggregate.
Dialects translate the same JPQL to PostgreSQL, MySQL, Oracle, SQL Server. The bigger win in practice is the test suite — H2 in-memory for unit tests, real Postgres in CI, with no app code changing.
Inside a transaction, you mutate entities and commit. Hibernate decides the SQL order, batches inserts, defers updates, flushes once. The application code reads like business logic, not like database choreography.
L1 (per-session) is automatic. L2 (shared, across sessions, with Ehcache/Infinispan/Hazelcast) caches read-mostly entities. The query cache keys SQL → result IDs. Used carefully, all three cut load by an order of magnitude.
Load 50 orders, iterate, touch order.getCustomer() on each — that's 1 + 50 SQL statements. The fix is JOIN FETCH, @EntityGraph, or batch-size hints. The diagnosis is reading SQL logs, every time. This single problem accounts for most "Hibernate is slow" complaints.
You return an entity from a service, the transaction closes, the controller serializes it, and a lazy collection blows up because the session is gone. The fixes — open-session-in-view, DTO projections, fetch joins — each come with their own tradeoffs. Many teams ban entities from crossing service boundaries entirely.
CascadeType.ALL on a relationship means deleting a parent deletes the children — and sometimes the children's children. orphanRemoval = true deletes anything detached from the collection. Both are useful; both have ended careers when applied to the wrong association.
Should the API return entities directly, or always project to DTOs? Entities are convenient but couple the wire format to the table schema and drag the persistence context along. Most mature codebases land on DTOs at the boundary, entities only inside transactions.
hibernate.hbm2ddl.auto=update looks magical and is a production landmine — non-deterministic, can't review, can't roll back. Use Flyway or Liquibase. Hibernate generates the schema once for inspiration; humans own the migrations from there.
JpaRepository<Order, Long> with derived query methods.@Query(nativeQuery=true) or jOOQ) for the last 5% — reporting, window functions, vendor-specific tricks.