ORM & Hibernate Concepts
ORM & Hibernate Concepts
Before writing a single Hibernate annotation you need to understand why it exists. This lesson maps out the mismatch between the object-oriented world and the relational world, explains what an ORM framework does about it, and shows where Hibernate fits in the modern Spring Boot 3 stack.
The Object-Relational Mismatch
Java models the world with objects: an Order has a collection of OrderLine objects, each pointing to a Product. Relational databases model the world with tables and foreign keys. These two representations are fundamentally different in five ways that every developer eventually hits:
- Granularity: A single Java object (e.g.
Address) may map to either its own table or a few extra columns inside another table. There is no one-to-one correspondence. - Identity: Java distinguishes reference equality (
==) from value equality (equals). A database has a single notion of identity: the primary key. Two Java objects with the same PK represent the same row — but Java will let you hold two separate instances without complaining. - Associations: Java associations are directional pointers stored in memory. Relational associations are foreign keys in columns — navigating them requires a
JOIN. A bidirectional relationship in Java requires coordination; in a database it is just two queries pointing at the same FK. - Inheritance: Java has class hierarchies with polymorphism. Relational tables have no native inheritance concept. Mapping one to the other requires a strategy choice (a single table with nullable columns, one table per class, joined tables) and every strategy has trade-offs.
- The n+1 problem: Loading a list of 100
Orderobjects and then accessingorder.getLines()on each naively fires 100 extra SQL queries. Plain JDBC lets you control this precisely; without care an ORM hides it from you until you see it in production metrics.
What an ORM Does
An Object-Relational Mapper (ORM) is a library that automates the translation between your object model and the database schema. It takes over four mechanical tasks:
- SQL generation: You call
entityManager.persist(order); the ORM emits the correctINSERTstatement including all mapped columns. - Result mapping: The ORM executes a
SELECTand constructs fully populated Java objects from theResultSet, following the mapping metadata you declared. - Change detection (dirty checking): When you modify a managed entity inside a transaction, the ORM detects the change and issues an
UPDATEautomatically at flush time — without you ever calling a save method. - Association loading: You configure whether related objects load eagerly (in the same query) or lazily (on first access), and the ORM handles the joins or extra queries accordingly.
What an ORM does not do: design your schema, guarantee performance, or write efficient queries for you. You still need to understand SQL, indexes, and query plans. The ORM just removes the boilerplate; the thinking is still yours.
JPA vs. Hibernate
These two names appear together constantly, and the distinction matters:
- JPA (Jakarta Persistence API) is a specification — a set of interfaces, annotations, and contracts defined in the
jakarta.persistencepackage. It tells you what the API looks like:@Entity,EntityManager,@OneToMany. It ships no executable code. - Hibernate ORM is the dominant implementation of that specification. It provides the actual bytecode that persists objects. When you add
spring-boot-starter-data-jpato your project, Spring Boot pulls in Hibernate 6 as the JPA provider.
EntityManager, TypedQuery, standard annotations). Reserve Hibernate-specific APIs (Session, @Formula, @BatchSize) for the rare cases where JPA cannot express what you need. This keeps the code portable and easier to test.
There are other JPA providers (EclipseLink, OpenJPA), but Hibernate has been the default in Spring for over fifteen years and is the reference implementation you will encounter in every real project.
Hibernate in the Spring Boot 3 Stack
Spring Boot 3 uses Hibernate 6 and requires Java 17+. The key package change from older versions is that all JPA annotations moved from javax.persistence to jakarta.persistence — a breaking change you will hit when migrating legacy code. Every import in this tutorial uses the modern namespace.
The typical dependency in a Spring Boot 3 project:
That single starter transitively brings in: hibernate-core, jakarta.persistence-api, Spring Data JPA, Spring ORM, and HikariCP. You get the full persistence stack from one line.
The application.properties entries you will configure most often:
ddl-auto=update or create in production. These settings let Hibernate modify your live schema, which can silently drop columns or alter constraints. Use validate in production (it checks the schema matches your mappings but changes nothing) and manage migrations with Flyway or Liquibase.
How Hibernate Works at Runtime
Understanding the runtime model prevents surprises. When your application starts, Hibernate:
- Reads all classes annotated with
@Entity(discovered through Spring component scanning). - Builds a SessionFactory (or
EntityManagerFactoryin JPA terms) — a heavy, thread-safe, application-scoped object created once. - For each request or unit of work, opens a lightweight Session (
EntityManagerin JPA terms) scoped to a single transaction. - The
EntityManagermaintains the first-level cache (persistence context): a map of loaded entities keyed by their primary key. This ensures that within one transaction you never load the same row twice and changes are automatically tracked.
A minimal Spring Boot entity and repository to make it concrete:
When you call productRepository.save(product), Spring Data calls EntityManager.persist(), which queues an INSERT. When the surrounding @Transactional method commits, Hibernate flushes the pending SQL to the database. The developer writes zero SQL for basic CRUD — but every step of that path has a SQL statement behind it, and understanding those statements is what separates good Hibernate code from slow Hibernate code.
Summary
The object-relational mismatch — covering granularity, identity, associations, inheritance, and the n+1 problem — is the root reason ORM frameworks exist. Hibernate implements the JPA specification, acting as the translation layer between your Java object graph and the relational schema. Spring Boot 3 wires everything together automatically, but the developer must still understand what SQL is generated, when, and why. The remaining lessons in this tutorial explore each mapping annotation and Hibernate behaviour in depth.