Hibernate in One Shot - Ultimate Java ORM Cheatsheet & Guide

    Hibernate in One Shot - Ultimate Java ORM Cheatsheet & Guide

    Hibernate in One Shot is a concise cheatsheet that covers everything from ORM basics and entity mapping to CRUD operations, caching, transactions, and performance optimization—perfect for quick learning and revision

    default profile

    Shreyash Gurav

    March 20, 2026

    14 min read

    Hibernate in One Shot: Ultimate Java ORM Cheatsheet & Guide


    Hibernate is an open-source Object Relational Mapping (ORM) framework for Java that simplifies database interactions by allowing developers to work with Java objects instead of writing raw SQL queries. It handles mapping between object-oriented code and relational databases, manages connections, caching, and transactions, and is widely used in modern backend applications.

    1. Hibernate Fundamentals#

    What is Hibernate#

    Hibernate is an open-source ORM (Object Relational Mapping) framework for Java. It simplifies database interaction by letting you work with Java objects instead of raw SQL. It implements the JPA specification and sits between your application and the database.

    • Developed by Gavin King, now maintained by Red Hat
    • Implements JPA (Jakarta Persistence API)
    • Handles SQL generation, connection pooling, caching, and transactions

    ORM - Object Relational Mapping#

    ORM bridges the gap between object-oriented Java code and relational databases. Instead of writing INSERT or SELECT statements, you work with plain Java objects (POJOs).

    Object-Oriented WorldRelational World
    ClassTable
    ObjectRow
    Field / PropertyColumn
    ReferenceForeign Key
    CollectionJoin Table
    Hibernate ORM Mapping

    Why Hibernate over JDBC#

    JDBC works, but it is verbose, error-prone, and forces you to manage everything manually. Hibernate abstracts all of that away.

    FeatureJDBCHibernate
    SQL writingManualAuto-generated
    Object mappingManualAutomatic
    CachingNone built-inFirst and second level
    Transaction managementManualDeclarative
    Boilerplate codeHighMinimal
    PortabilityDB-specific SQLDialect-based

    Hibernate Architecture Overview#

    Application | v SessionFactory <--- Configuration (hibernate.cfg.xml or annotations) | v Session <--- Wraps JDBC Connection | v Transaction | v Database (via JDBC Driver)

    Key components:

    • Configuration - loads Hibernate settings
    • SessionFactory - heavyweight, one per database, thread-safe
    • Session - lightweight, one per request/transaction, not thread-safe
    • Transaction - wraps DB operations
    • Query / Criteria - for executing HQL or JPQL queries

    2. Core Concepts#

    Entity Class and POJO#

    A POJO (Plain Old Java Object) becomes a Hibernate entity when you annotate it with @Entity. It must have a no-arg constructor and a primary key field.

    @Entity @Table(name = "employees") public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String department; // no-arg constructor required public Employee() {} // getters and setters }

    Rules for an entity class:

    • Must not be final
    • Must have a public or protected no-arg constructor
    • Must have at least one @Id field
    • Fields should be private with getters/setters

    Session and SessionFactory#

    SessionFactory is created once when the application starts. It is expensive to build but cheap to use. Think of it as a factory that produces Session objects.

    Session is the primary interface to interact with the database. It represents a single unit of work and wraps a JDBC connection.

    // Build SessionFactory (do this once, on startup) SessionFactory sessionFactory = new Configuration() .configure("hibernate.cfg.xml") .buildSessionFactory(); // Open a session for each unit of work Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); // ... do work ... tx.commit(); session.close();

    Persistence Context#

    The persistence context is an in-memory cache of entities that Hibernate manages during a session. Every entity loaded or saved goes into this context.

    • Tracks changes automatically (dirty checking)
    • Ensures identity - same DB row returns the same Java object
    • Synchronizes state to DB at flush time

    Three entity states:

    • Transient - object created but not associated with any session
    • Persistent - associated with an active session, tracked by Hibernate
    • Detached - was persistent, session is now closed
    Employee emp = new Employee(); // Transient session.save(emp); // Persistent - Hibernate tracks this session.close(); // emp becomes Detached

    First-Level Cache#

    First-level cache is scoped to the Session. It is enabled by default and cannot be disabled. When you load the same entity twice in the same session, the second call hits cache, not the database.

    Employee e1 = session.get(Employee.class, 1L); // hits DB Employee e2 = session.get(Employee.class, 1L); // hits L1 cache, no DB call System.out.println(e1 == e2); // true - same object reference

    3. Configuration#

    hibernate.cfg.xml#

    This is the traditional XML-based configuration file placed in src/main/resources.

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "<http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd>"> <hibernate-configuration> <session-factory> <!-- Database connection --> <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mydb</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">password</property> <!-- Dialect --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</property> <!-- Show SQL in console --> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <!-- Schema generation --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- Entity class mapping --> <mapping class="com.example.Employee"/> </session-factory> </hibernate-configuration>

    Annotation-Based Configuration#

    In modern Spring Boot projects, application.properties handles Hibernate config:

    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 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.MySQL8Dialect

    Key Properties Explained#

    PropertyValuesDescription
    hibernate.dialectMySQL8Dialect, PostgreSQLDialect, etc.Tells Hibernate which SQL dialect to use
    show_sqltrue / falsePrints generated SQL to console
    format_sqltrue / falsePretty prints SQL output
    hbm2ddl.autocreate, create-drop, update, validate, noneControls schema generation

    hbm2ddl.auto values:

    • create - drops and recreates schema on startup
    • create-drop - drops schema on shutdown (good for tests)
    • update - updates schema without dropping data
    • validate - validates schema, throws error if mismatch
    • none - does nothing (recommended for production)

    4. Mapping Annotations#

    Hibernate Mapping Annotations Overview

    Basic Annotations#

    @Entity // marks this class as a Hibernate entity @Table(name = "employees") // maps to a specific table name public class Employee { @Id // primary key @GeneratedValue(strategy = GenerationType.IDENTITY) // auto-increment @Column(name = "emp_id") // maps to specific column private Long id; @Column(name = "full_name", nullable = false, length = 100) private String name; @Column(unique = true) private String email; @Transient // this field will NOT be persisted private String tempToken; }

    @GeneratedValue strategies:

    StrategyDescription
    IDENTITYDB auto-increment (MySQL, PostgreSQL)
    SEQUENCEUses DB sequence object (PostgreSQL, Oracle)
    TABLEUses a separate table to track keys
    AUTOHibernate picks the best strategy

    Relationship Annotations#

    @OneToOne#

    One entity maps to exactly one instance of another.

    @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "address_id", referencedColumnName = "id") private Address address; }

    @OneToMany and @ManyToOne#

    One department has many employees. Each employee belongs to one department.

    // Department side (One) @Entity public class Department { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List<Employee> employees = new ArrayList<>(); } // Employee side (Many) @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "department_id") private Department department; }
    • mappedBy tells Hibernate that the other side owns the relationship
    • The owning side (the one with @JoinColumn) controls the foreign key

    @ManyToMany#

    Students can enroll in many courses. Each course has many students.

    @Entity public class Student { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToMany @JoinTable( name = "student_course", joinColumns = @JoinColumn(name = "student_id"), inverseJoinColumns = @JoinColumn(name = "course_id") ) private List<Course> courses = new ArrayList<>(); } @Entity public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToMany(mappedBy = "courses") private List<Student> students = new ArrayList<>(); }

    CascadeType Quick Reference#

    CascadeTypeEffect
    PERSISTSaves child when parent is saved
    MERGEUpdates child when parent is merged
    REMOVEDeletes child when parent is deleted
    REFRESHRefreshes child when parent is refreshed
    DETACHDetaches child when parent is detached
    ALLAll of the above

    5. CRUD Operations#

    Hibernate CRUD operations

    Save and Persist#

    Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Employee emp = new Employee(); emp.setName("Alice"); emp.setDepartment("Engineering"); session.save(emp); // Hibernate-specific, returns generated ID // OR session.persist(emp); // JPA standard, returns void tx.commit(); session.close();
    • save() returns the generated identifier immediately
    • persist() is JPA-standard and does not guarantee immediate INSERT

    Get and Load#

    // get() - hits DB immediately, returns null if not found Employee emp = session.get(Employee.class, 1L); // load() - returns a proxy, hits DB only when you access fields // throws ObjectNotFoundException if not found (not null) Employee emp = session.load(Employee.class, 1L);

    Use get() when you are unsure whether the record exists. Use load() only when you are certain the record is there.

    Update#

    // For detached objects session.update(emp); // merge() - safer, works for both detached and transient session.merge(emp); // Automatic dirty checking - no explicit update needed for persistent objects Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Employee emp = session.get(Employee.class, 1L); // persistent emp.setName("Bob"); // just change the field tx.commit(); // Hibernate detects the change and fires UPDATE automatically session.close();

    Delete#

    Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); Employee emp = session.get(Employee.class, 1L); session.delete(emp); // or session.remove(emp) in JPA tx.commit(); session.close();

    6. HQL and JPQL#

    Basics of HQL#

    HQL (Hibernate Query Language) is object-oriented SQL. You write queries against entity class names and field names, not table names and column names. Hibernate translates them to native SQL.

    // Basic SELECT List<Employee> employees = session .createQuery("FROM Employee", Employee.class) .getResultList(); // WHERE clause List<Employee> result = session .createQuery("FROM Employee WHERE department = :dept", Employee.class) .setParameter("dept", "Engineering") .getResultList(); // SELECT specific fields List<String> names = session .createQuery("SELECT e.name FROM Employee e", String.class) .getResultList(); // JOIN List<Employee> result = session .createQuery("FROM Employee e JOIN FETCH e.department WHERE e.department.name = :deptName", Employee.class) .setParameter("deptName", "Engineering") .getResultList();

    Named Queries#

    Named queries are defined once (usually on the entity) and reused by name. They are parsed and validated at startup, which catches errors early.

    @Entity @NamedQuery( name = "Employee.findByDepartment", query = "FROM Employee WHERE department.name = :deptName" ) public class Employee { // ... } // Usage List<Employee> employees = session .createNamedQuery("Employee.findByDepartment", Employee.class) .setParameter("deptName", "Engineering") .getResultList();

    Pagination#

    List<Employee> page = session .createQuery("FROM Employee ORDER BY name", Employee.class) .setFirstResult(0) // offset - starts from 0 .setMaxResults(10) // page size .getResultList();

    7. Fetching Strategies#

    Lazy vs Eager Loading#

    Fetching strategy decides when related entities are loaded from the database.

    StrategyWhen data is loadedDefault for
    LAZYOnly when you access the field@OneToMany, @ManyToMany
    EAGERImmediately with the parent@ManyToOne, @OneToOne
    // Lazy - employees not loaded until you call dept.getEmployees() @OneToMany(mappedBy = "department", fetch = FetchType.LAZY) private List<Employee> employees; // Eager - employees always loaded with department @OneToMany(mappedBy = "department", fetch = FetchType.EAGER) private List<Employee> employees;

    General rule: prefer LAZY loading. Use EAGER only when you always need the related data.

    N+1 Problem#

    The N+1 problem happens when loading N parent entities triggers N additional queries to load their children, resulting in N+1 total queries to the database.

    // BAD - triggers N+1 List<Department> departments = session .createQuery("FROM Department", Department.class) .getResultList(); for (Department dept : departments) { dept.getEmployees(); // fires a separate query for EACH department } // Result: 1 query for departments + N queries for employees = N+1 queries

    Fix: use JOIN FETCH

    // GOOD - loads everything in one query List<Department> departments = session .createQuery("FROM Department d JOIN FETCH d.employees", Department.class) .getResultList();
    N+1 Query Problem vs JOIN FETCH

    8. Caching#

    First-Level Cache#

    • Enabled by default, always active
    • Scoped to the Session - lives and dies with the session
    • Cannot be turned off
    • Automatically used when you call session.get() multiple times for the same entity

    Second-Level Cache#

    • Shared across sessions within the same SessionFactory
    • Disabled by default - you must configure it
    • Useful for read-heavy, rarely-changing data (like reference tables)
    • Common providers: Ehcache, Infinispan, Hazelcast
    <!-- hibernate.cfg.xml --> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.EhCacheRegionFactory </property>
    @Entity @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // enable L2 cache for this entity public class Country { @Id private Long id; private String name; }

    Cache Concurrency Strategies:

    StrategyUse when
    READ_ONLYData never changes (best performance)
    READ_WRITEData changes occasionally
    NONSTRICT_READ_WRITEOccasional updates, stale data acceptable
    TRANSACTIONALFull transactional integrity needed

    Query Cache - caches query result sets (not just entities). Must be explicitly enabled per query.

    session.createQuery("FROM Country", Country.class) .setCacheable(true) .getResultList();

    9. Transactions#

    Transaction Lifecycle#

    Every database write operation must happen inside a transaction. Hibernate does not auto-commit by default.

    Session session = sessionFactory.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); // your database operations here session.save(employee); tx.commit(); // writes changes to DB } catch (Exception e) { if (tx != null) tx.rollback(); // undo everything on error e.printStackTrace(); } finally { session.close(); // always close the session }

    With Spring's @Transactional:

    @Service public class EmployeeService { @Transactional public void saveEmployee(Employee emp) { employeeRepository.save(emp); // Spring manages begin, commit, and rollback automatically } }

    ACID Properties#

    PropertyMeaning
    AtomicityAll operations succeed or none do
    ConsistencyData is always in a valid state before and after
    IsolationConcurrent transactions do not interfere with each other
    DurabilityCommitted data survives system failure

    Isolation Levels:

    LevelDirty ReadNon-Repeatable ReadPhantom Read
    READ_UNCOMMITTEDpossiblepossiblepossible
    READ_COMMITTEDpreventedpossiblepossible
    REPEATABLE_READpreventedpreventedpossible
    SERIALIZABLEpreventedpreventedprevented

    10. Schema Design and Best Practices#

    Hibernate Entity Relationship 

    Normalization vs Denormalization#

    • Normalization - reduce redundancy, split data into related tables. Better for write-heavy systems. Easier to maintain.
    • Denormalization - combine related data into fewer tables. Better for read-heavy systems. Improves query performance at the cost of redundancy.

    Hibernate works better with normalized schemas but can handle denormalized designs too.

    Efficient Mapping Strategies#

    • Use @ManyToOne instead of @OneToMany as the owning side where possible
    • Avoid bidirectional relationships unless you genuinely need to navigate both ways
    • Use mappedBy correctly - only one side should own the relationship
    • Prefer List over Set for ordered collections, but Set for unordered to avoid duplicates
    // Good practice - explicit column definitions @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; // Good practice - use @Index for frequently queried columns @Table(name = "employees", indexes = { @Index(name = "idx_email", columnList = "email"), @Index(name = "idx_department", columnList = "department_id") })

    Performance Considerations#

    • Always close sessions - use try-with-resources or finally blocks
    • Use pagination for large result sets - never load all rows
    • Avoid FetchType.EAGER on collections
    • Use JOIN FETCH only when you need related data in the same query
    • Enable second-level cache for reference/static data
    • Use batch inserts for bulk operations
    // Batch inserts - configure batch size in properties // hibernate.jdbc.batch_size=50 for (int i = 0; i < 1000; i++) { session.save(new Employee("emp_" + i)); if (i % 50 == 0) { session.flush(); // write batch to DB session.clear(); // clear L1 cache to avoid memory issues } }

    11. Common Pitfalls#

    LazyInitializationException#

    This is one of the most common Hibernate errors. It happens when you try to access a lazily loaded collection or association after the session is already closed.

    // BAD - session closes after this method returns public Department findDepartment(Long id) { Session session = sessionFactory.openSession(); Department dept = session.get(Department.class, id); session.close(); // session closed here return dept; } // Later... dept.getEmployees().size(); // LazyInitializationException - session is gone

    Fixes:

    • Use JOIN FETCH to load required associations eagerly within the session
    • Use @Transactional to keep session open until the method returns
    • Use DTOs to avoid returning entities outside the session boundary
    • Use Hibernate.initialize(dept.getEmployees()) explicitly before closing session

    N+1 Query Issue#

    Already covered in Section 7. Quick summary:

    • Root cause: lazy loading inside a loop
    • Detection: enable show_sql and count your queries
    • Fix: use JOIN FETCH or @EntityGraph
    // @EntityGraph approach (JPA standard) @EntityGraph(attributePaths = {"employees"}) List<Department> findAll(); // Spring Data JPA method

    Improper Session Handling#

    • Never share a Session across threads - it is not thread-safe
    • Never leave sessions open - causes connection pool exhaustion
    • Always use transactions for writes - reads may work without them but writes are unreliable without explicit transaction boundaries
    // BAD - storing session as a field public class EmployeeService { private Session session = sessionFactory.openSession(); // wrong } // GOOD - open and close per operation public Employee findById(Long id) { try (Session session = sessionFactory.openSession()) { return session.get(Employee.class, id); } }

    12. Hibernate vs JPA#

    The Difference#

    JPA (Jakarta Persistence API) is a specification - it defines a standard set of interfaces and annotations for ORM in Java. Hibernate is the most popular implementation of that specification.

    Think of JPA as the interface and Hibernate as the class that implements it.

    AspectJPAHibernate
    TypeSpecification (API)Implementation
    Packagejakarta.persistence.*org.hibernate.*
    PortabilityHigh - works with any JPA providerTied to Hibernate
    FeaturesStandard features onlyJPA + Hibernate-specific extras
    Query languageJPQLHQL (superset of JPQL)
    EntityManagerEntityManagerSession (also implements EntityManager)

    Code Comparison#

    // JPA style (portable, works with EclipseLink too) EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-pu"); EntityManager em = emf.createEntityManager(); em.getTransaction().begin(); em.persist(employee); em.getTransaction().commit(); em.close(); // Hibernate native style SessionFactory sf = new Configuration().configure().buildSessionFactory(); Session session = sf.openSession(); session.beginTransaction(); session.save(employee); session.getTransaction().commit(); session.close();

    When to Use What#

    • Use JPA when you want portability and do not need Hibernate-specific features. Standard choice for enterprise apps.
    • Use Hibernate-specific APIs when you need features like Hibernate filters, second-level cache fine-tuning, batch processing helpers, or advanced fetch profiles.
    • In practice: use JPA annotations and EntityManager for all standard operations, and reach for Hibernate-specific APIs only when JPA falls short.
    JPA & Hibernate Architecture

    Quick Reference Card#

    Session Methods#

    MethodDescription
    session.save(obj)Persists new entity, returns ID
    session.persist(obj)JPA-standard persist
    session.get(Class, id)Loads by ID, returns null if not found
    session.load(Class, id)Returns proxy, throws if not found
    session.update(obj)Updates detached entity
    session.merge(obj)Merges detached or transient entity
    session.delete(obj)Deletes entity
    session.flush()Syncs session state to DB
    session.clear()Clears first-level cache
    session.evict(obj)Removes specific entity from cache

    Common Annotations Summary#

    AnnotationPurpose
    @EntityMarks class as persistent entity
    @Table(name="...")Maps to specific table
    @IdPrimary key field
    @GeneratedValueAuto-generate primary key
    @ColumnMaps field to column with constraints
    @TransientField not persisted
    @OneToOneOne-to-one relationship
    @OneToManyOne-to-many relationship
    @ManyToOneMany-to-one relationship
    @ManyToManyMany-to-many relationship
    @JoinColumnSpecifies foreign key column
    @JoinTableSpecifies join table for many-to-many
    @CacheEnables second-level caching
    @NamedQueryDefines reusable named HQL query

    hbm2ddl.auto Quick Reference#

    ValueCreatesDropsUse for
    createYesOn startupDevelopment only
    create-dropYesOn shutdownUnit testing
    updateAlters if neededNeverDev / staging
    validateNeverNeverProduction (safe)
    noneNeverNeverProduction (explicit SQL)

    Conclusion#

    Hibernate simplifies database interaction by abstracting complex SQL into clean, object-oriented code, allowing developers to focus on business logic instead of low-level data handling. With features like automatic mapping, caching, and transaction management, and its seamless use with frameworks like Spring Boot, Hibernate remains an essential tool for building scalable and maintainable Java backend applications. If you found this helpful, consider sharing it with your friends and peers.

    Want to Master Spring Boot and Land Your Dream Job?

    Struggling with coding interviews? Learn Data Structures & Algorithms (DSA) with our expert-led course. Build strong problem-solving skills, write optimized code, and crack top tech interviews with ease

    Learn more
    Hibernate
    JPA
    ORM
    Was it helpful?

    Subscribe to our newsletter

    Read articles from Coding Shuttle directly inside your inbox. Subscribe to the newsletter, and don't miss out.

    More articles