Spring Object/Relational Mapping integration for JPA and Hibernate with transaction management
npx @tessl/cli install tessl/maven-org-springframework--spring-orm@7.0.0Spring's Object/Relational Mapping integration for JPA (Jakarta Persistence API) and Hibernate. Provides EntityManagerFactory/SessionFactory creation, declarative transaction management, exception translation, and resource management.
<!-- 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'| Spring ORM | Spring Framework | JPA API | Hibernate | Jakarta Persistence | Java |
|---|---|---|---|---|---|
| 7.0.x | 6.2.x | 3.2.x | 7.0.x | 3.2.0 | 17+ |
| 6.1.x | 6.1.x | 3.1.x | 6.4.x | 3.1.0 | 17+ |
| 6.0.x | 6.0.x | 3.0.x | 6.1.x | 3.0.0 | 17+ |
| 5.3.x | 5.3.x | 2.2.x | 5.6.x | 2.2.3 (javax) | 8+ |
Migration Notes:
jakarta.* instead of javax.*)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)// === 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;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);
}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();
}
}@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();
}
}<!-- 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 | Database Enum | Dialect Class | JDBC URL Pattern | Driver Class |
|---|---|---|---|---|
| PostgreSQL | Database.POSTGRESQL | org.hibernate.dialect.PostgreSQLDialect | jdbc:postgresql://host:5432/db | org.postgresql.Driver |
| MySQL 8.x | Database.MYSQL | org.hibernate.dialect.MySQL8Dialect | jdbc:mysql://host:3306/db | com.mysql.cj.jdbc.Driver |
| Oracle 12c+ | Database.ORACLE | org.hibernate.dialect.Oracle12cDialect | jdbc:oracle:thin:@host:1521:db | oracle.jdbc.OracleDriver |
| SQL Server | Database.SQL_SERVER | org.hibernate.dialect.SQLServerDialect | jdbc:sqlserver://host:1433;databaseName=db | com.microsoft.sqlserver.jdbc.SQLServerDriver |
| H2 (embedded) | Database.H2 | org.hibernate.dialect.H2Dialect | jdbc:h2:mem:testdb | org.h2.Driver |
| H2 (file) | Database.H2 | org.hibernate.dialect.H2Dialect | jdbc:h2:file:./data/db | org.h2.Driver |
| MariaDB | Database.MYSQL | org.hibernate.dialect.MariaDBDialect | jdbc:mariadb://host:3306/db | org.mariadb.jdbc.Driver |
| Value | Production Safe? | Effect | When to Use |
|---|---|---|---|
none | ✅ YES | No schema management | Production (recommended) - Use Flyway/Liquibase |
validate | ✅ YES | Validate schema matches entities | Production - Ensures schema correctness |
update | ❌ NO | Update schema (adds only, never drops) | DANGEROUS - Development only, can corrupt schema |
create | ❌ NO | Drop and create schema on startup | DESTROYS DATA - Integration tests only |
create-drop | ❌ NO | Create on startup, drop on shutdown | Unit 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);| Property | Value | Type | Production Safe? | Effect |
|---|---|---|---|---|
hibernate.show_sql | false | String | ✅ YES | No SQL logging (recommended) |
hibernate.show_sql | true | String | ❌ NO | Logs all SQL (performance impact, security risk) |
hibernate.format_sql | false | String | ✅ YES | No formatting |
hibernate.format_sql | true | String | ⚠️ DEV ONLY | Pretty-print SQL (requires show_sql=true) |
hibernate.use_sql_comments | false | String | ✅ YES | No comments |
hibernate.use_sql_comments | true | String | ⚠️ DEV ONLY | Add JPQL as SQL comments |
Validation: Must be string "true" or "false" (not boolean)
| Value Range | Recommended? | Effect | Use Case |
|---|---|---|---|
| 0 | ❌ NO | Batching disabled | Default (poor performance) |
| 1-10 | ❌ NO | Too small, inefficient | Avoid |
| 20-50 | ✅ YES | Optimal for most cases | Recommended default |
| 100+ | ⚠️ CAUTION | Large batches, may cause memory issues | High-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 entitiesMap<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);| Combination | Valid? | Effect | Use Case |
|---|---|---|---|
@Service + @Transactional | ✅ YES | All public methods transactional | Write-heavy services |
@Service + @Transactional(readOnly=true) + per-method override | ✅ YES | Default read-only, override for writes | Read-heavy services |
@Repository + @Transactional | ✅ YES | DAO with transactions + exception translation | Data access layer |
@Controller + @Transactional | ⚠️ AVOID | Transactions in web layer | Use service layer instead |
@Transactional on interface | ⚠️ AVOID | Only works with interface-based proxies | Use on implementation |
@Transactional on private method | ❌ NO | Silently ignored (proxy limitation) | Must be public or protected |
@Transactional on final method | ❌ NO | Cannot be proxied | Remove final modifier |
@PersistenceContext + @Autowired EntityManager | ❌ NO | Conflicting injection | Use only @PersistenceContext |
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
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
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());
}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
}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
}
}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);
}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:
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));
}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=TRACEERROR: 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
}| Error Message | Root Cause | Solution Reference |
|---|---|---|
No EntityManager with actual transaction available | Missing @Transactional or self-invocation | Mistake #3 |
LazyInitializationException | Lazy collection accessed outside transaction | Mistake #8 |
OutOfMemoryError in batch | Not flushing/clearing in batch operations | Mistake #4 |
Schema-validation: missing table | Entity doesn't match database schema | Use @Table(name) and @Column(name) |
TransactionRequiredException | Operation requires transaction but none active | Add @Transactional |
NonUniqueResultException | Query expected single result but found multiple | Use .getResultList() instead or add LIMIT |
NoResultException | Query expected result but found none | Catch exception or use try...catch |
OptimisticLockException | Concurrent modification detected | Implement retry logic or refresh entity |
PessimisticLockException | Lock timeout or deadlock | Use shorter timeouts or change isolation level |
ConstraintViolationException | Database constraint violated | Validate before persist/merge |
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 Viewapplication.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=falseSpring Boot Auto-Configures:
EntityManagerFactory beanJpaTransactionManager bean@EnableTransactionManagementManual Configuration Still Required For:
Priority-ordered checklist for optimal performance:
flush() + clear() every 50 items)hibernate.jdbc.batch_size=20)readOnly = true for read operations (skips dirty checking)hibernate.show_sql=false)LEFT JOIN FETCH)setFirstResult() + setMaxResults())@Transactional(timeout = 30))hibernate.query.plan_cache_max_size=2048)getReference() for delete operations (avoids unnecessary SELECT)hibernate.default_batch_fetch_size=10)