Hibernate & Entity Mapping

ORM & Hibernate Concepts

18 min Lesson 1 of 13

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 Order objects and then accessing order.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.
The mismatch is not a bug — it is a fundamental difference. Relational theory was designed to answer ad-hoc queries over large, shared datasets. Object-oriented design was built for encapsulation and reuse inside a running process. An ORM bridges the two; it cannot make them identical.

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:

  1. SQL generation: You call entityManager.persist(order); the ORM emits the correct INSERT statement including all mapped columns.
  2. Result mapping: The ORM executes a SELECT and constructs fully populated Java objects from the ResultSet, following the mapping metadata you declared.
  3. Change detection (dirty checking): When you modify a managed entity inside a transaction, the ORM detects the change and issues an UPDATE automatically at flush time — without you ever calling a save method.
  4. 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.persistence package. 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-jpa to your project, Spring Boot pulls in Hibernate 6 as the JPA provider.
Always write your application code against the JPA interfaces (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:

<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>

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:

# application.properties # DDL strategy: validate | update | create | create-drop | none spring.jpa.hibernate.ddl-auto=validate # Log the SQL Hibernate generates (useful during development) spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true # Dialect is auto-detected in Hibernate 6; set explicitly only if needed # spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
Never use 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:

  1. Reads all classes annotated with @Entity (discovered through Spring component scanning).
  2. Builds a SessionFactory (or EntityManagerFactory in JPA terms) — a heavy, thread-safe, application-scoped object created once.
  3. For each request or unit of work, opens a lightweight Session (EntityManager in JPA terms) scoped to a single transaction.
  4. The EntityManager maintains 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:

import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private int stockQuantity; // Hibernate requires a no-arg constructor (can be package-private) protected Product() {} public Product(String name, int stockQuantity) { this.name = name; this.stockQuantity = stockQuantity; } // getters and setters omitted for brevity }
import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Long> { // Spring Data generates the implementation at startup }

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.