or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

exception-translation.mdhibernate-configuration.mdhibernate-transaction-management.mdindex.mdjpa-configuration.mdjpa-transaction-management.mdjpa-vendor-adapters.mdpersistence-unit-management.mdshared-resources.mdutility-classes.mdweb-integration.md
tile.json

index.mddocs/

Spring ORM

Spring's Object/Relational Mapping integration for JPA (Jakarta Persistence API) and Hibernate. Provides EntityManagerFactory/SessionFactory creation, declarative transaction management, exception translation, and resource management.

Package Information

<!-- Maven -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>7.0.1</version>
</dependency>
// Gradle
implementation 'org.springframework:spring-orm:7.0.1'

Version Compatibility Matrix

Spring ORMSpring FrameworkJPA APIHibernateJakarta PersistenceJava
7.0.x6.2.x3.2.x7.0.x3.2.017+
6.1.x6.1.x3.1.x6.4.x3.1.017+
6.0.x6.0.x3.0.x6.1.x3.0.017+
5.3.x5.3.x2.2.x5.6.x2.2.3 (javax)8+

Migration Notes:

  • Spring 6.x requires Jakarta EE 9+ (package jakarta.* instead of javax.*)
  • Hibernate 6.x is a major rewrite with breaking API changes
  • Use Spring Boot BOM for automatic version management

Decision Tree: Choose Your Approach

START: Setting up persistence layer
  │
  ├─ Using Spring Boot?
  │    YES → Use application.yml auto-configuration
  │          [→ Spring Boot Auto-Configuration section]
  │          Skip manual config unless custom requirements
  │    NO  → Continue
  │
  ├─ Need JPA standard API OR new project?
  │    YES → JPA with Hibernate provider [→ JPA Configuration]
  │          ✅ Portable across JPA providers
  │          ✅ Standard API (jakarta.persistence.*)
  │          ✅ Easier to test and mock
  │          ✅ Better for microservices
  │          Use unless you need Hibernate-specific features
  │    NO  → Continue
  │
  ├─ Need Hibernate-specific features?
  │    Examples: Criteria API, StatelessSession, interceptors,
  │    custom types, multi-tenancy, second-level cache advanced config
  │    YES → Hibernate native SessionFactory [→ Hibernate Configuration]
  │          ⚠️  Vendor lock-in
  │          ✅ Full Hibernate feature access
  │    NO  → Use JPA (recommended for portability)
  │
  └─ Multiple databases in same application?
       YES → Multiple persistence units [→ Multi-Database Setup]
             Requires careful transaction manager configuration
       NO  → Single persistence unit (simpler)

Core Imports by Purpose

// === JPA CONFIGURATION (required for JPA setup) ===
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.Database;

// === TRANSACTION MANAGEMENT (required for @Transactional) ===
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Isolation;

// === JPA API (required for entity operations) ===
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.PersistenceUnit;
import jakarta.persistence.TypedQuery;

// === HIBERNATE NATIVE (only if using SessionFactory) ===
import org.hibernate.SessionFactory;
import org.hibernate.Session;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.hibernate5.HibernateTransactionManager;

// === SPRING CONFIGURATION ===
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

// === SERVICE LAYER ===
import org.springframework.stereotype.Service;
import org.springframework.stereotype.Repository;

Template: Minimal JPA Setup (Most Common)

COPY-PASTE READY - Complete working JPA configuration for 90% of use cases:

package com.example.config;

import jakarta.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement  // CRITICAL: Required for @Transactional
public class JpaConfig {

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);
        em.setPackagesToScan("com.example.domain");  // REQUIRED: Scans recursively for @Entity

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.POSTGRESQL);  // POSTGRESQL, MYSQL, ORACLE, SQL_SERVER, H2
        vendorAdapter.setGenerateDdl(false);  // ALWAYS false in production
        vendorAdapter.setShowSql(false);      // ALWAYS false in production
        em.setJpaVendorAdapter(vendorAdapter);

        return em;
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);  // Parameter MUST be EntityManagerFactory
    }
}

DataSource Configuration (add this if not using Spring Boot):

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Bean
public DataSource dataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
    config.setUsername("user");
    config.setPassword("pass");
    config.setDriverClassName("org.postgresql.Driver");

    // HikariCP optimal settings
    config.setMaximumPoolSize(10);
    config.setMinimumIdle(5);
    config.setConnectionTimeout(30000);
    config.setIdleTimeout(600000);
    config.setMaxLifetime(1800000);

    return new HikariDataSource(config);
}

CRITICAL MISTAKE TO AVOID:

// WRONG - parameter type LocalContainerEntityManagerFactoryBean
@Bean
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean em) {
    return new JpaTransactionManager(em.getObject());  // Can return null during init!
}

// CORRECT - parameter type EntityManagerFactory
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
    return new JpaTransactionManager(emf);
}

Template: JPA Service (Standard CRUD)

COPY-PASTE READY - Standard service with CRUD operations:

package com.example.service;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;

@Service
@Transactional  // REQUIRED: Enables transactions for all public methods
public class UserService {

    @PersistenceContext  // REQUIRED: Injects thread-safe EntityManager proxy
    private EntityManager entityManager;

    // CREATE
    public User create(String name, String email) {
        User user = new User();
        user.setName(name);
        user.setEmail(email);
        entityManager.persist(user);  // Changes auto-flushed on commit
        return user;
    }

    // READ (optimized with readOnly=true)
    @Transactional(readOnly = true)  // Skips dirty checking, better performance
    public User findById(Long id) {
        return entityManager.find(User.class, id);
    }

    // READ with associations - prevents LazyInitializationException
    @Transactional(readOnly = true)
    public User findByIdWithOrders(Long id) {
        return entityManager.createQuery(
            "SELECT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id",
            User.class)
            .setParameter("id", id)
            .getSingleResult();
    }

    // READ multiple
    @Transactional(readOnly = true)
    public List<User> findAll() {
        return entityManager.createQuery("SELECT u FROM User u", User.class)
            .getResultList();
    }

    // READ with pagination
    @Transactional(readOnly = true)
    public List<User> findPage(int page, int size) {
        return entityManager.createQuery("SELECT u FROM User u ORDER BY u.id", User.class)
            .setFirstResult(page * size)
            .setMaxResults(size)
            .getResultList();
    }

    // UPDATE (automatic dirty checking)
    public void updateName(Long id, String name) {
        User user = entityManager.find(User.class, id);
        if (user != null) {
            user.setName(name);  // NO merge() needed - changes auto-detected
        }
    }

    // UPDATE detached entity
    public User updateDetached(User user) {
        return entityManager.merge(user);  // merge() for detached entities
    }

    // DELETE
    public void delete(Long id) {
        User user = entityManager.getReference(User.class, id);  // Lazy proxy, no SELECT
        entityManager.remove(user);
    }

    // DELETE with verification
    public boolean deleteIfExists(Long id) {
        User user = entityManager.find(User.class, id);
        if (user != null) {
            entityManager.remove(user);
            return true;
        }
        return false;
    }

    // BULK DELETE
    @Transactional
    public int deleteByStatus(String status) {
        return entityManager.createQuery(
            "DELETE FROM User u WHERE u.status = :status")
            .setParameter("status", status)
            .executeUpdate();
    }
}

Template: Batch Processing (Memory-Safe)

@Service
@Transactional
public class BatchService {

    @PersistenceContext
    private EntityManager entityManager;

    public void importUsers(List<UserDTO> userDTOs) {
        int batchSize = 50;  // Adjust based on entity size

        for (int i = 0; i < userDTOs.size(); i++) {
            User user = new User();
            user.setName(userDTOs.get(i).getName());
            user.setEmail(userDTOs.get(i).getEmail());
            entityManager.persist(user);

            if (i > 0 && i % batchSize == 0) {
                entityManager.flush();  // REQUIRED: Write to DB
                entityManager.clear();  // REQUIRED: Free memory
            }
        }
        // Flush remaining entities
        entityManager.flush();
        entityManager.clear();
    }
}

Required Dependencies (Complete Dependency Tree)

<!-- JPA with Hibernate Provider -->
<dependencies>
    <!-- Spring ORM -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>7.0.1</version>
    </dependency>

    <!-- Jakarta Persistence API -->
    <dependency>
        <groupId>jakarta.persistence</groupId>
        <artifactId>jakarta.persistence-api</artifactId>
        <version>3.2.0</version>
    </dependency>

    <!-- Hibernate Core (JPA Provider) -->
    <dependency>
        <groupId>org.hibernate.orm</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>7.0.0.Final</version>
    </dependency>

    <!-- Spring Transaction Support -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>6.2.0</version>
    </dependency>

    <!-- Spring Context (for DI and configuration) -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.2.0</version>
    </dependency>

    <!-- DataSource: HikariCP (recommended) -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.1.0</version>
    </dependency>

    <!-- JDBC Driver (choose one based on database) -->
    <!-- PostgreSQL -->
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.7.0</version>
    </dependency>

    <!-- OR MySQL -->
    <!--
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <version>8.2.0</version>
    </dependency>
    -->

    <!-- OR Oracle -->
    <!--
    <dependency>
        <groupId>com.oracle.database.jdbc</groupId>
        <artifactId>ojdbc11</artifactId>
        <version>23.3.0.23.09</version>
    </dependency>
    -->

    <!-- OR SQL Server -->
    <!--
    <dependency>
        <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <version>12.4.2.jre11</version>
    </dependency>
    -->

    <!-- OR H2 (for testing) -->
    <!--
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>2.2.224</version>
        <scope>test</scope>
    </dependency>
    -->
</dependencies>

Gradle equivalent:

dependencies {
    implementation 'org.springframework:spring-orm:7.0.1'
    implementation 'jakarta.persistence:jakarta.persistence-api:3.2.0'
    implementation 'org.hibernate.orm:hibernate-core:7.0.0.Final'
    implementation 'org.springframework:spring-tx:6.2.0'
    implementation 'org.springframework:spring-context:6.2.0'
    implementation 'com.zaxxer:HikariCP:5.1.0'

    // Choose database driver
    implementation 'org.postgresql:postgresql:42.7.0'
    // OR implementation 'com.mysql:mysql-connector-j:8.2.0'
    // OR implementation 'com.h2database:h2:2.2.224'
}

Database Configuration Quick Reference

DatabaseDatabase EnumDialect ClassJDBC URL PatternDriver Class
PostgreSQLDatabase.POSTGRESQLorg.hibernate.dialect.PostgreSQLDialectjdbc:postgresql://host:5432/dborg.postgresql.Driver
MySQL 8.xDatabase.MYSQLorg.hibernate.dialect.MySQL8Dialectjdbc:mysql://host:3306/dbcom.mysql.cj.jdbc.Driver
Oracle 12c+Database.ORACLEorg.hibernate.dialect.Oracle12cDialectjdbc:oracle:thin:@host:1521:dboracle.jdbc.OracleDriver
SQL ServerDatabase.SQL_SERVERorg.hibernate.dialect.SQLServerDialectjdbc:sqlserver://host:1433;databaseName=dbcom.microsoft.sqlserver.jdbc.SQLServerDriver
H2 (embedded)Database.H2org.hibernate.dialect.H2Dialectjdbc:h2:mem:testdborg.h2.Driver
H2 (file)Database.H2org.hibernate.dialect.H2Dialectjdbc:h2:file:./data/dborg.h2.Driver
MariaDBDatabase.MYSQLorg.hibernate.dialect.MariaDBDialectjdbc:mariadb://host:3306/dborg.mariadb.jdbc.Driver

Configuration Property Validation

hibernate.hbm2ddl.auto

ValueProduction Safe?EffectWhen to Use
none✅ YESNo schema managementProduction (recommended) - Use Flyway/Liquibase
validate✅ YESValidate schema matches entitiesProduction - Ensures schema correctness
update❌ NOUpdate schema (adds only, never drops)DANGEROUS - Development only, can corrupt schema
create❌ NODrop and create schema on startupDESTROYS DATA - Integration tests only
create-drop❌ NOCreate on startup, drop on shutdownUnit tests only

Validation Regex: ^(none|validate|update|create|create-drop)$

Configuration Example:

Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "validate");  // or "none" in production
em.setJpaPropertyMap(properties);

hibernate.show_sql and hibernate.format_sql

PropertyValueTypeProduction Safe?Effect
hibernate.show_sqlfalseString✅ YESNo SQL logging (recommended)
hibernate.show_sqltrueString❌ NOLogs all SQL (performance impact, security risk)
hibernate.format_sqlfalseString✅ YESNo formatting
hibernate.format_sqltrueString⚠️ DEV ONLYPretty-print SQL (requires show_sql=true)
hibernate.use_sql_commentsfalseString✅ YESNo comments
hibernate.use_sql_commentstrueString⚠️ DEV ONLYAdd JPQL as SQL comments

Validation: Must be string "true" or "false" (not boolean)

hibernate.jdbc.batch_size (Critical for Performance)

Value RangeRecommended?EffectUse Case
0❌ NOBatching disabledDefault (poor performance)
1-10❌ NOToo small, inefficientAvoid
20-50✅ YESOptimal for most casesRecommended default
100+⚠️ CAUTIONLarge batches, may cause memory issuesHigh-throughput bulk inserts only

Validation: Positive integer ^[0-9]+$

Required companion properties:

properties.put("hibernate.jdbc.batch_size", "20");
properties.put("hibernate.order_inserts", "true");  // REQUIRED for batching
properties.put("hibernate.order_updates", "true");  // REQUIRED for batching
properties.put("hibernate.jdbc.batch_versioned_data", "true");  // For @Version entities

Critical Production Settings (Copy-Paste Ready)

Map<String, Object> properties = new HashMap<>();

// === SCHEMA GENERATION - NEVER use "update" or "create" in production ===
properties.put("hibernate.hbm2ddl.auto", "validate");  // or "none"

// === SQL LOGGING - ALWAYS false in production (performance + security) ===
properties.put("hibernate.show_sql", "false");
properties.put("hibernate.format_sql", "false");
properties.put("hibernate.use_sql_comments", "false");

// === JDBC BATCH PROCESSING - ALWAYS enable for write-heavy apps ===
properties.put("hibernate.jdbc.batch_size", "20");
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");
properties.put("hibernate.jdbc.batch_versioned_data", "true");

// === CONNECTION MANAGEMENT ===
properties.put("hibernate.connection.autocommit", "false");  // Let Spring manage

// === PERFORMANCE - Query plan cache ===
properties.put("hibernate.query.plan_cache_max_size", "2048");
properties.put("hibernate.query.plan_parameter_metadata_max_size", "128");

// === STATISTICS - Enable in dev/staging for N+1 detection ===
properties.put("hibernate.generate_statistics", "false");  // true in dev only

em.setJpaPropertyMap(properties);

Annotation Combination Rules

CombinationValid?EffectUse Case
@Service + @Transactional✅ YESAll public methods transactionalWrite-heavy services
@Service + @Transactional(readOnly=true) + per-method override✅ YESDefault read-only, override for writesRead-heavy services
@Repository + @Transactional✅ YESDAO with transactions + exception translationData access layer
@Controller + @Transactional⚠️ AVOIDTransactions in web layerUse service layer instead
@Transactional on interface⚠️ AVOIDOnly works with interface-based proxiesUse on implementation
@Transactional on private method❌ NOSilently ignored (proxy limitation)Must be public or protected
@Transactional on final method❌ NOCannot be proxiedRemove final modifier
@PersistenceContext + @Autowired EntityManager❌ NOConflicting injectionUse only @PersistenceContext

Top 10 Critical Mistakes and Solutions

1. Missing @EnableTransactionManagement

ERROR PATTERN: Transactions silently don't work, changes not persisted, NO exception thrown

// WRONG - transactions silently don't work
@Configuration
public class JpaConfig {
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(...) { ... }
}

// CORRECT
@Configuration
@EnableTransactionManagement  // REQUIRED!
public class JpaConfig { ... }

DETECTION: Enable debug logging: logging.level.org.springframework.transaction.interceptor=TRACE

2. @Transactional on Private Methods

ERROR PATTERN: Silently ignored, no transaction created

// WRONG - silently ignored due to proxy mechanism
@Transactional
private void saveUser(User user) {
    entityManager.persist(user);
}

// CORRECT - must be public or protected
@Transactional
public void saveUser(User user) {
    entityManager.persist(user);
}

DETECTION: Enable debug logging: logging.level.org.springframework.transaction=DEBUG

3. Self-Invocation Bypasses Transaction Proxy

ERROR MESSAGE: javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available

// WRONG - internal calls bypass transaction proxy
@Service
public class UserService {
    public void doSomething() {
        this.saveUser(new User());  // NO transaction!
    }

    @Transactional
    public void saveUser(User user) { ... }
}

// SOLUTION 1: Move logic into transactional method
@Transactional
public void doSomething() {
    User user = new User();
    entityManager.persist(user);
}

// SOLUTION 2: Inject self to use proxy
@Service
public class UserService {
    @Autowired
    private UserService self;  // Injects proxy

    public void doSomething() {
        self.saveUser(new User());  // Transaction works!
    }

    @Transactional
    public void saveUser(User user) { ... }
}

// SOLUTION 3: Use AopContext (requires aspectj-weaver)
import org.springframework.aop.framework.AopContext;

public void doSomething() {
    ((UserService) AopContext.currentProxy()).saveUser(new User());
}

4. Not Flushing in Batch Operations

ERROR: java.lang.OutOfMemoryError: Java heap space

CONDITION: Processing large batches (1000+ entities)

// WRONG - causes OutOfMemoryError for large batches
@Transactional
public void importUsers(List<User> users) {
    for (User user : users) {
        entityManager.persist(user);  // All kept in memory!
    }
}

// CORRECT - flush and clear periodically
@Transactional
public void importUsers(List<User> users) {
    int batchSize = 50;
    for (int i = 0; i < users.size(); i++) {
        entityManager.persist(users.get(i));
        if (i > 0 && i % batchSize == 0) {
            entityManager.flush();  // Write to DB
            entityManager.clear();  // Clear memory
        }
    }
    entityManager.flush();  // Flush remaining
}

5. Catching Exceptions Without Rethrowing

ERROR PATTERN: Transaction commits despite error

// WRONG - transaction still commits!
@Transactional
public void updateUser(Long id, String name) {
    try {
        User user = entityManager.find(User.class, id);
        user.setName(name);
    } catch (Exception e) {
        log.error("Error", e);  // Swallowed - transaction commits anyway!
    }
}

// CORRECT - rethrow to trigger rollback
@Transactional
public void updateUser(Long id, String name) {
    try {
        User user = entityManager.find(User.class, id);
        user.setName(name);
    } catch (Exception e) {
        log.error("Error", e);
        throw e;  // Triggers rollback
    }
}

// ALTERNATIVE - manual rollback
import org.springframework.transaction.interceptor.TransactionAspectSupport;

@Transactional
public void updateUser(Long id, String name) {
    try {
        User user = entityManager.find(User.class, id);
        user.setName(name);
    } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        log.error("Error", e);
        // Handle gracefully
    }
}

6. Wrong TransactionManager Parameter Type

ERROR: java.lang.NullPointerException at JpaTransactionManager.<init>

// WRONG - receives factory bean, can return null during init
@Bean
public PlatformTransactionManager transactionManager(
        LocalContainerEntityManagerFactoryBean em) {
    return new JpaTransactionManager(em.getObject());  // NPE risk!
}

// CORRECT - receives actual EntityManagerFactory
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
    return new JpaTransactionManager(emf);
}

7. Not Using readOnly=true for Read Operations

ISSUE: Suboptimal performance due to unnecessary dirty checking

// SUBOPTIMAL - dirty checking overhead for read-only operation
@Transactional
public List<User> findAll() {
    return entityManager.createQuery("SELECT u FROM User u", User.class)
        .getResultList();
}

// OPTIMAL - skip dirty checking
@Transactional(readOnly = true)
public List<User> findAll() {
    return entityManager.createQuery("SELECT u FROM User u", User.class)
        .getResultList();
}

Benefits of readOnly=true:

  • Hibernate skips dirty checking (10-30% faster for reads)
  • Flush mode set to MANUAL
  • Database can apply read-only optimizations
  • JDBC connection marked read-only

8. LazyInitializationException - Accessing Lazy Associations Outside Transaction

ERROR: org.hibernate.LazyInitializationException: failed to lazily initialize a collection

// WRONG - accessing lazy association outside transaction
@Service
public class OrderService {
    @Transactional(readOnly = true)
    public Order findById(Long id) {
        return entityManager.find(Order.class, id);
    }  // Transaction ends here
}

@Controller
public class OrderController {
    public String showOrder(Long id) {
        Order order = orderService.findById(id);
        // LazyInitializationException when accessing items
        order.getItems().size();
    }
}

// SOLUTION 1: Use JOIN FETCH
@Transactional(readOnly = true)
public Order findById(Long id) {
    return entityManager.createQuery(
        "SELECT o FROM Order o LEFT JOIN FETCH o.items WHERE o.id = :id",
        Order.class)
        .setParameter("id", id)
        .getSingleResult();
}

// SOLUTION 2: Initialize in transaction
@Transactional(readOnly = true)
public Order findById(Long id) {
    Order order = entityManager.find(Order.class, id);
    order.getItems().size();  // Force initialization
    return order;
}

// SOLUTION 3: Use @EntityGraph (JPA 2.1+)
@Transactional(readOnly = true)
public Order findById(Long id) {
    EntityGraph<Order> graph = entityManager.createEntityGraph(Order.class);
    graph.addAttributeNodes("items");

    return entityManager.find(Order.class, id,
        Map.of("jakarta.persistence.fetchgraph", graph));
}

9. N+1 Query Problem

ISSUE: Loading collection items one by one (1 query for parent + N queries for children)

// WRONG - N+1 queries
@Transactional(readOnly = true)
public List<Order> findAllOrders() {
    List<Order> orders = entityManager.createQuery(
        "SELECT o FROM Order o", Order.class)
        .getResultList();  // 1 query

    for (Order order : orders) {
        order.getItems().size();  // N queries (1 per order)
    }
    return orders;
}

// CORRECT - Single query with JOIN FETCH
@Transactional(readOnly = true)
public List<Order> findAllOrders() {
    return entityManager.createQuery(
        "SELECT DISTINCT o FROM Order o LEFT JOIN FETCH o.items",
        Order.class)
        .getResultList();  // Single query
}

// ALTERNATIVE - Use batch fetching in properties
properties.put("hibernate.default_batch_fetch_size", "10");

DETECTION: Enable SQL logging temporarily:

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

10. Using update on Detached Entity Instead of merge

ERROR: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist

// WRONG - persist() only works for new entities
@Transactional
public void updateUser(User user) {
    entityManager.persist(user);  // ERROR if user is detached
}

// CORRECT - use merge() for detached entities
@Transactional
public User updateUser(User user) {
    return entityManager.merge(user);  // Returns managed entity
}

// ALTERNATIVE - find and update (for managed entities)
@Transactional
public void updateUser(Long id, String name) {
    User user = entityManager.find(User.class, id);  // Managed
    user.setName(name);  // Auto-detected, no merge needed
}

Common Error Messages → Solutions Quick Reference

Error MessageRoot CauseSolution Reference
No EntityManager with actual transaction availableMissing @Transactional or self-invocationMistake #3
LazyInitializationExceptionLazy collection accessed outside transactionMistake #8
OutOfMemoryError in batchNot flushing/clearing in batch operationsMistake #4
Schema-validation: missing tableEntity doesn't match database schemaUse @Table(name) and @Column(name)
TransactionRequiredExceptionOperation requires transaction but none activeAdd @Transactional
NonUniqueResultExceptionQuery expected single result but found multipleUse .getResultList() instead or add LIMIT
NoResultExceptionQuery expected result but found noneCatch exception or use try...catch
OptimisticLockExceptionConcurrent modification detectedImplement retry logic or refresh entity
PessimisticLockExceptionLock timeout or deadlockUse shorter timeouts or change isolation level
ConstraintViolationExceptionDatabase constraint violatedValidate before persist/merge

Spring Boot Auto-Configuration

When using Spring Boot, most configuration is automatic:

application.yml:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/mydb
    username: user
    password: pass
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: 10
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

  jpa:
    hibernate:
      ddl-auto: validate  # none, validate, update, create, create-drop
    show-sql: false
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
        use_sql_comments: true
        jdbc:
          batch_size: 20
        order_inserts: true
        order_updates: true
        jdbc.batch_versioned_data: true
        # Query plan cache
        query.plan_cache_max_size: 2048
        query.plan_parameter_metadata_max_size: 128
        # Statistics (dev/staging only)
        generate_statistics: false
    open-in-view: false  # Recommended: disable Open EntityManager in View

application.properties equivalent:

# DataSource
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb
spring.datasource.username=user
spring.datasource.password=pass
spring.datasource.driver-class-name=org.postgresql.Driver

# HikariCP
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000

# JPA
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.jdbc.batch_size=20
spring.jpa.properties.hibernate.order_inserts=true
spring.jpa.properties.hibernate.order_updates=true
spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true
spring.jpa.open-in-view=false

Spring Boot Auto-Configures:

  • EntityManagerFactory bean
  • JpaTransactionManager bean
  • @EnableTransactionManagement
  • ✅ Exception translation
  • ✅ @PersistenceContext support
  • ✅ DataSource pooling (HikariCP)

Manual Configuration Still Required For:

  • Multiple databases
  • Advanced Hibernate features (interceptors, custom types)
  • Custom transaction manager configuration
  • Non-standard entity scanning

Performance Best Practices Checklist

Priority-ordered checklist for optimal performance:

Critical (Must Have)

  • Flush and clear in batch operations (flush() + clear() every 50 items)
  • Enable JDBC batching (hibernate.jdbc.batch_size=20)
  • Use readOnly = true for read operations (skips dirty checking)
  • Disable SQL logging in production (hibernate.show_sql=false)
  • Use connection pooling (HikariCP with proper sizing)

High Priority

  • Use fetch joins for known associations (LEFT JOIN FETCH)
  • Use pagination for large result sets (setFirstResult() + setMaxResults())
  • Set appropriate transaction timeouts (@Transactional(timeout = 30))
  • Configure query plan cache (hibernate.query.plan_cache_max_size=2048)
  • Use appropriate isolation levels (default READ_COMMITTED for most cases)

Medium Priority

  • Use projections when full entity not needed (DTO projections via constructor)
  • Configure second-level cache selectively (only for read-heavy, rarely-modified entities)
  • Use getReference() for delete operations (avoids unnecessary SELECT)
  • Enable batch fetch size (hibernate.default_batch_fetch_size=10)
  • Optimize entity graph loading (use @EntityGraph or fetch profiles)

Low Priority (Optimize After Profiling)

  • Use query hints (for database-specific optimizations)
  • Consider native queries (for complex queries)
  • Use stateless sessions (for bulk operations in Hibernate)
  • Implement caching strategy (L2 cache, query cache)

Documentation Topics

Core Configuration

  • JPA Configuration - EntityManagerFactory setup, persistence units, properties, multiple databases
  • JPA Transaction Management - Declarative transactions, propagation, isolation, performance
  • JPA Vendor Adapters - Hibernate and EclipseLink adapter configuration

Hibernate Native API

Advanced Topics