Configuration for creating JPA EntityManagerFactory instances with persistence unit scanning, DataSource injection, and vendor-specific configuration.
Need JPA EntityManagerFactory?
│
├─ YES (99% of cases)
│ → LocalContainerEntityManagerFactoryBean
│ ✅ Full container bootstrap with classpath scanning
│ ✅ DataSource injection and management
│ ✅ No persistence.xml required
│ ✅ Spring Boot compatible
│ ✅ Supports multiple databases
│
└─ Legacy migration only
→ LocalEntityManagerFactoryBean
⚠️ Requires persistence.xml
⚠️ Minimal Spring integration
❌ Use only for legacy appsCOPY-PASTE READY for most projects:
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.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class JpaConfig {
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain"); // Scans recursively for @Entity
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false); // ALWAYS false in production
vendorAdapter.setShowSql(false); // ALWAYS false in production
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
}COPY-PASTE READY with externalized configuration:
package com.example.config;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.orm.jpa.vendor.Database;
@Configuration
@EnableTransactionManagement
public class JpaConfig {
@Value("${spring.jpa.show-sql:false}")
private boolean showSql;
@Value("${spring.jpa.hibernate.ddl-auto:validate}")
private String ddlAuto;
@Value("${spring.jpa.properties.hibernate.jdbc.batch_size:20}")
private int batchSize;
@Value("${spring.jpa.database:POSTGRESQL}")
private Database database;
@Value("${spring.jpa.properties.hibernate.dialect:}")
private String dialect;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(database);
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setShowSql(showSql);
em.setJpaVendorAdapter(vendorAdapter);
// Hibernate properties
Map<String, Object> properties = buildHibernateProperties();
em.setJpaPropertyMap(properties);
return em;
}
private Map<String, Object> buildHibernateProperties() {
Map<String, Object> properties = new HashMap<>();
// Schema management
properties.put("hibernate.hbm2ddl.auto", ddlAuto);
// Dialect (if specified)
if (dialect != null && !dialect.isEmpty()) {
properties.put("hibernate.dialect", dialect);
}
// SQL logging
properties.put("hibernate.show_sql", showSql);
properties.put("hibernate.format_sql", showSql);
properties.put("hibernate.use_sql_comments", showSql);
// JDBC batch processing - critical for performance
properties.put("hibernate.jdbc.batch_size", String.valueOf(batchSize));
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");
// Query plan cache
properties.put("hibernate.query.plan_cache_max_size", "2048");
properties.put("hibernate.query.plan_parameter_metadata_max_size", "128");
// Default batch fetch size (helps with N+1)
properties.put("hibernate.default_batch_fetch_size", "10");
return properties;
}
}application.properties:
# Development
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.jdbc.batch_size=20
spring.jpa.database=POSTGRESQL
# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Production - override with:
# spring.jpa.show-sql=false
# spring.jpa.hibernate.ddl-auto=noneCOPY-PASTE READY - complete multi-database setup:
package com.example.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.orm.jpa.JpaTransactionManager;
@Configuration
@EnableTransactionManagement
public class MultiDbConfig {
// ========== PRIMARY DATABASE ==========
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
@Qualifier("primaryDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.primary.domain");
em.setPersistenceUnitName("primaryPU"); // REQUIRED - must be unique
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "validate");
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
properties.put("hibernate.jdbc.batch_size", "20");
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");
em.setJpaPropertyMap(properties);
return em;
}
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
// ========== SECONDARY DATABASE ==========
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
@Qualifier("secondaryDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.secondary.domain");
em.setPersistenceUnitName("secondaryPU"); // REQUIRED - must be unique
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "validate");
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
properties.put("hibernate.jdbc.batch_size", "20");
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");
em.setJpaPropertyMap(properties);
return em;
}
@Bean
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryEntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}application.properties:
# Primary Database (PostgreSQL)
spring.datasource.primary.jdbc-url=jdbc:postgresql://localhost:5432/primary_db
spring.datasource.primary.username=user1
spring.datasource.primary.password=pass1
spring.datasource.primary.driver-class-name=org.postgresql.Driver
spring.datasource.primary.hikari.maximum-pool-size=10
# Secondary Database (MySQL)
spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/secondary_db
spring.datasource.secondary.username=user2
spring.datasource.secondary.password=pass2
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.secondary.hikari.maximum-pool-size=10IMPORTANT: Use jdbc-url instead of url when using @ConfigurationProperties
Usage with multiple databases:
package com.example.service;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MultiDbService {
@PersistenceContext(unitName = "primaryPU")
private EntityManager primaryEM;
@PersistenceContext(unitName = "secondaryPU")
private EntityManager secondaryEM;
// Uses primary transaction manager (default due to @Primary)
@Transactional
public void saveToPrimary(PrimaryEntity entity) {
primaryEM.persist(entity);
}
// Explicitly specify secondary transaction manager
@Transactional("secondaryTransactionManager")
public void saveToSecondary(SecondaryEntity entity) {
secondaryEM.persist(entity);
}
// CRITICAL ERROR: Cannot mix transaction managers in same method!
// This will NOT work as expected:
@Transactional("primaryTransactionManager")
public void saveToBoth() {
primaryEM.persist(new PrimaryEntity()); // OK - in primary transaction
secondaryEM.persist(new SecondaryEntity()); // WRONG - not in secondary transaction!
}
// CORRECT: Use separate methods or ChainedTransactionManager (not recommended)
@Transactional("primaryTransactionManager")
public void saveToPrimaryOnly(PrimaryEntity entity) {
primaryEM.persist(entity);
}
@Transactional("secondaryTransactionManager")
public void saveToSecondaryOnly(SecondaryEntity entity) {
secondaryEM.persist(entity);
}
// Call both methods from a service without @Transactional
public void saveBoth(PrimaryEntity primary, SecondaryEntity secondary) {
saveToPrimaryOnly(primary);
saveToSecondaryOnly(secondary);
// Note: Not atomic across both databases!
}
}import jakarta.persistence.SharedCacheMode;
import org.hibernate.cache.jcache.ConfigSettings;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
// Cache configuration
em.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
// Options: ALL, NONE, ENABLE_SELECTIVE (use @Cacheable), DISABLE_SELECTIVE
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.cache.use_second_level_cache", "true");
properties.put("hibernate.cache.use_query_cache", "true");
properties.put("hibernate.cache.region.factory_class",
"org.hibernate.cache.jcache.JCacheRegionFactory");
properties.put("hibernate.javax.cache.provider",
"org.ehcache.jsr107.EhcacheCachingProvider");
// Cache configuration file
properties.put("hibernate.javax.cache.uri",
"classpath:ehcache.xml");
em.setJpaPropertyMap(properties);
return em;
}Entity with cache:
import jakarta.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Cacheable // Required when using ENABLE_SELECTIVE
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@OneToMany(mappedBy = "product")
private List<Review> reviews;
}ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://www.ehcache.org/v3">
<cache alias="com.example.domain.Product">
<expiry>
<ttl unit="minutes">10</ttl>
</expiry>
<resources>
<heap unit="entries">1000</heap>
<offheap unit="MB">10</offheap>
</resources>
</cache>
<cache alias="com.example.domain.Product.reviews">
<expiry>
<ttl unit="minutes">5</ttl>
</expiry>
<resources>
<heap unit="entries">5000</heap>
</resources>
</cache>
<!-- Default cache for query results -->
<cache alias="default-query-results-region">
<expiry>
<ttl unit="minutes">5</ttl>
</expiry>
<resources>
<heap unit="entries">1000</heap>
</resources>
</cache>
</config>import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Bean
public AsyncTaskExecutor jpaBootstrapExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(1);
executor.setThreadNamePrefix("jpa-bootstrap-");
executor.setWaitForTasksToCompleteOnShutdown(false);
executor.initialize();
return executor;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,
AsyncTaskExecutor jpaBootstrapExecutor) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
em.setBootstrapExecutor(jpaBootstrapExecutor); // Async initialization
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
return em;
}package com.example.config;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Configuration
@EnableTransactionManagement
@Profile("test")
public class TestJpaConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("testdb")
.build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.H2);
vendorAdapter.setGenerateDdl(true); // OK for tests
vendorAdapter.setShowSql(true); // OK for tests
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "create-drop"); // OK for tests
properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
em.setJpaPropertyMap(properties);
return em;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}package com.example.service;
import com.example.domain.User;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@ActiveProfiles("test")
@Transactional // Rollback after each test
class UserServiceTest {
@Autowired
private UserService userService;
@PersistenceContext
private EntityManager entityManager;
@Test
void testCreateUser() {
User user = userService.create("John", "john@example.com");
// Verify ID was generated
assertThat(user.getId()).isNotNull();
// Force write to DB and clear cache
entityManager.flush();
entityManager.clear();
// Verify user was persisted
User found = userService.findById(user.getId());
assertThat(found.getName()).isEqualTo("John");
assertThat(found.getEmail()).isEqualTo("john@example.com");
}
@Test
void testUpdateUser() {
User user = userService.create("Jane", "jane@example.com");
entityManager.flush();
entityManager.clear();
userService.updateName(user.getId(), "Jane Doe");
entityManager.flush();
entityManager.clear();
User updated = userService.findById(user.getId());
assertThat(updated.getName()).isEqualTo("Jane Doe");
}
@Test
void testDeleteUser() {
User user = userService.create("Bob", "bob@example.com");
Long userId = user.getId();
entityManager.flush();
entityManager.clear();
userService.delete(userId);
entityManager.flush();
User found = entityManager.find(User.class, userId);
assertThat(found).isNull();
}
}package com.example.config;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.PostgreSQLContainer;
@TestConfiguration
public class TestcontainersConfig {
@Bean
@ServiceConnection // Spring Boot 3.1+
public PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
}
}| Value | Behavior | Production Safe? | Use Case | Data Loss Risk |
|---|---|---|---|---|
none | Do nothing | ✅ YES | Production (recommended) - use Flyway/Liquibase | ✅ No |
validate | Validate schema against entities | ✅ YES | Production fallback - ensures schema matches | ✅ No |
update | Update schema (adds, doesn't drop) | ❌ NO | Development only - risky, can cause issues | ⚠️ Possible |
create | Drop and create schema on startup | ❌ NO | Testing only - loses all data | ❌ YES |
create-drop | Drop on startup and shutdown | ❌ NO | Unit tests only - clean slate each run | ❌ YES |
Validation Regex: ^(none|validate|update|create|create-drop)$
Configuration:
properties.put("hibernate.hbm2ddl.auto", "validate");| Database | Dialect Class | Database Enum | Min Version |
|---|---|---|---|
| PostgreSQL | org.hibernate.dialect.PostgreSQLDialect | Database.POSTGRESQL | 9.5+ |
| MySQL 8.x | org.hibernate.dialect.MySQL8Dialect | Database.MYSQL | 8.0+ |
| MySQL 5.7 | org.hibernate.dialect.MySQL57Dialect | Database.MYSQL | 5.7+ |
| Oracle 12c+ | org.hibernate.dialect.Oracle12cDialect | Database.ORACLE | 12c+ |
| Oracle 21c+ | org.hibernate.dialect.Oracle21cDialect | Database.ORACLE | 21c+ |
| SQL Server | org.hibernate.dialect.SQLServerDialect | Database.SQL_SERVER | 2012+ |
| H2 | org.hibernate.dialect.H2Dialect | Database.H2 | 2.x |
| MariaDB | org.hibernate.dialect.MariaDBDialect | Database.MYSQL | 10.3+ |
Set dialect explicitly:
// Option 1: Via vendor adapter (recommended)
vendorAdapter.setDatabase(Database.POSTGRESQL);
// Option 2: Via properties (more specific)
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");Map<String, Object> properties = new HashMap<>();
// === JDBC Batch Processing - CRITICAL for write performance ===
properties.put("hibernate.jdbc.batch_size", "20"); // Recommended: 20-50
properties.put("hibernate.order_inserts", "true"); // REQUIRED to enable batching
properties.put("hibernate.order_updates", "true"); // REQUIRED to enable batching
properties.put("hibernate.jdbc.batch_versioned_data", "true"); // Batch @Version entities
// === Default Fetch Size - reduces round trips ===
properties.put("hibernate.default_batch_fetch_size", "10");
properties.put("hibernate.jdbc.fetch_size", "50"); // JDBC ResultSet fetch size
// === Query Plan Cache - improves query performance ===
properties.put("hibernate.query.plan_cache_max_size", "2048");
properties.put("hibernate.query.plan_parameter_metadata_max_size", "128");
// === Statistics - enable in development to detect N+1 ===
properties.put("hibernate.generate_statistics", "false"); // true in dev only
properties.put("hibernate.session.events.log", "false"); // true for detailed logging
// === SQL Logging - ALWAYS false in production ===
properties.put("hibernate.show_sql", "false");
properties.put("hibernate.format_sql", "false");
properties.put("hibernate.use_sql_comments", "false");
properties.put("hibernate.highlight_sql", "false"); // ANSI color highlighting in logs
// === Connection Provider ===
properties.put("hibernate.connection.provider_disables_autocommit", "true"); // HikariCP optimizationERROR: 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);
}ERROR:
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'jakarta.persistence.EntityManager' available:
expected single matching bean but found 2// WRONG - ambiguous which persistence unit
@PersistenceContext
private EntityManager entityManager; // Which one?
// CORRECT - specify unit name
@PersistenceContext(unitName = "primaryPU")
private EntityManager primaryEntityManager;
@PersistenceContext(unitName = "secondaryPU")
private EntityManager secondaryEntityManager;// WRONG - using Hibernate TXN manager with JPA factory
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(...) { ... }
@Bean
public HibernateTransactionManager transactionManager(SessionFactory sf) {
return new HibernateTransactionManager(sf); // Type mismatch!
}
// CORRECT - use matching transaction manager
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}ISSUE: Hibernate may guess wrong dialect, causing SQL errors
// WRONG - Hibernate guesses dialect (unreliable)
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
// No dialect set - may use wrong SQL syntax
// CORRECT - explicit dialect (recommended)
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
// OR via properties (more specific)
properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");RISK: Can corrupt schema, drop columns, lose data
// WRONG - dangerous in production, can corrupt data
properties.put("hibernate.hbm2ddl.auto", "update");
// CORRECT - use schema migration tool
properties.put("hibernate.hbm2ddl.auto", "validate");
// Use Flyway or Liquibase for schema changesExample Flyway migration:
-- V1__create_user_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);ERROR: No entities found for package com.example.domain
// WRONG - typo or wrong package
em.setPackagesToScan("com.exmaple.domain"); // Typo!
// WRONG - not recursive
em.setPackagesToScan("com.example"); // Misses com.example.domain.user.User
// CORRECT - exact package (scans recursively)
em.setPackagesToScan("com.example.domain"); // Finds all @Entity in subpackages
// CORRECT - multiple packages
em.setPackagesToScan("com.example.domain", "com.example.entities");ISSUE: Poor performance, connection exhaustion
// WRONG - using default connection pool settings
// (Usually fine with Spring Boot, but not standalone Spring)
// CORRECT - configure HikariCP properly
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/mydb");
config.setUsername("user");
config.setPassword("pass");
// Pool sizing
config.setMaximumPoolSize(10); // Based on available connections
config.setMinimumIdle(5); // Minimum idle connections
// Timeouts
config.setConnectionTimeout(30000); // 30 seconds
config.setIdleTimeout(600000); // 10 minutes
config.setMaxLifetime(1800000); // 30 minutes
// Health check
config.setConnectionTestQuery("SELECT 1");
return new HikariDataSource(config);
}When using Spring Boot, configuration is simplified:
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
show-sql: false
open-in-view: false # Recommended: disable
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
use_sql_comments: true
jdbc:
batch_size: 20
fetch_size: 50
order_inserts: true
order_updates: true
jdbc.batch_versioned_data: true
default_batch_fetch_size: 10
query.plan_cache_max_size: 2048
query.plan_parameter_metadata_max_size: 128Spring Boot Auto-Configures:
EntityManagerFactory beanJpaTransactionManager bean@EnableTransactionManagementCustom Configuration Still Needed For:
class LocalContainerEntityManagerFactoryBean
extends AbstractEntityManagerFactoryBean
implements ResourceLoaderAware, LoadTimeWeaverAware {
// ========== REQUIRED CONFIGURATION ==========
// DataSource (REQUIRED)
void setDataSource(DataSource dataSource);
DataSource getDataSource();
void setJtaDataSource(DataSource jtaDataSource); // For JTA transactions
// Entity Scanning (choose ONE approach)
void setPackagesToScan(String... packagesToScan); // Scan for @Entity (RECOMMENDED)
void setManagedTypes(PersistenceManagedTypes managedTypes); // Pre-scanned (AOT/GraalVM)
void setManagedClassNameFilter(ManagedClassNameFilter filter); // Filter scanned classes
// ========== PERSISTENCE UNIT ==========
// Unit name (REQUIRED for multiple databases)
void setPersistenceUnitName(String name);
String getPersistenceUnitName();
// Optional persistence.xml location
void setPersistenceXmlLocation(String location); // Default: META-INF/persistence.xml
// ========== VENDOR ADAPTER ==========
// Vendor adapter (HibernateJpaVendorAdapter, EclipseLinkJpaVendorAdapter)
void setJpaVendorAdapter(JpaVendorAdapter adapter);
JpaVendorAdapter getJpaVendorAdapter();
// Vendor properties
void setJpaProperties(Properties properties);
void setJpaPropertyMap(Map<String, ?> properties); // PREFERRED over Properties
Map<String, Object> getJpaPropertyMap();
// ========== CACHE AND VALIDATION ==========
// Cache mode
void setSharedCacheMode(SharedCacheMode mode);
// Options: ENABLE_SELECTIVE, DISABLE_SELECTIVE, ALL, NONE
// Validation mode
void setValidationMode(ValidationMode mode);
// Options: AUTO, CALLBACK, NONE
// ========== ADVANCED FEATURES ==========
// Load-time weaving for entity enhancement
void setLoadTimeWeaver(LoadTimeWeaver weaver);
// Async initialization (faster startup)
void setBootstrapExecutor(AsyncTaskExecutor bootstrapExecutor);
// Post-processing
void setPersistenceUnitPostProcessors(PersistenceUnitPostProcessor... processors);
// Persistence unit manager
void setPersistenceUnitManager(PersistenceUnitManager pum);
// ========== ACCESS ==========
// Get created factory (returns null until afterPropertiesSet() completes)
EntityManagerFactory getObject();
Class<? extends EntityManagerFactory> getObjectType();
boolean isSingleton(); // Returns true
}ERROR: Error creating bean with name 'entityManagerFactory'
COMMON CAUSES → SOLUTIONS:
| Cause | Solution | Verification |
|---|---|---|
| Missing DataSource bean | Create DataSource bean | Check bean exists in context |
| Invalid database URL/credentials | Check connection string | Test with SQL client |
| Missing JDBC driver dependency | Add driver to pom.xml/build.gradle | Check Maven/Gradle dependencies |
| Wrong dialect for database | Set correct Database enum or dialect class | Match database version |
| Missing @EnableTransactionManagement | Add annotation to @Configuration | Check configuration class |
| Entity scanning path incorrect | Verify package names | Check @Entity classes exist |
Verification:
@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");
// Test connection on startup
config.setConnectionTestQuery("SELECT 1");
config.setConnectionTimeout(5000);
return new HikariDataSource(config);
}ERROR: Schema-validation: missing table [user_table]
CAUSE: Entity doesn't match database schema
SOLUTIONS:
// Option 1: Fix entity to match database
@Entity
@Table(name = "user_table") // Match exact table name (case-sensitive!)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id") // Match exact column name
private Long id;
@Column(name = "user_name") // Match exact column name
private String name;
}
// Option 2: Disable validation temporarily (development only)
properties.put("hibernate.hbm2ddl.auto", "update"); // OR "none"
// Option 3: Use naming strategy for automatic mapping
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.domain");
// Spring naming strategy: camelCase -> snake_case
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.physical_naming_strategy",
SpringPhysicalNamingStrategy.class.getName());
em.setJpaPropertyMap(properties);
return em;
}CAUSE: Entity scanning of large classpath
SOLUTIONS:
// Solution 1: Narrow package scanning
em.setPackagesToScan(
"com.example.domain", // Only scan specific packages
"com.example.entities"
);
// Solution 2: Use async initialization
em.setBootstrapExecutor(jpaBootstrapExecutor);
// Solution 3: Explicitly list entity classes (fastest)
em.setManagedTypes(PersistenceManagedTypes.of(
User.class,
Order.class,
Product.class
));ERROR: No qualifying bean of type 'EntityManagerFactory' available: expected single matching bean but found 2
CAUSE: Multiple EntityManagerFactory beans without @Primary
SOLUTION:
// Mark one as @Primary
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(...) { ... }
// Use @Qualifier for injection
@Autowired
@Qualifier("primaryEntityManagerFactory")
private EntityManagerFactory primaryEMF;
@Autowired
@Qualifier("secondaryEntityManagerFactory")
private EntityManagerFactory secondaryEMF;