-
EntityManagerFactoryis a heavyweight object that Spring configures only once at application startup. -
Spring creates it internally using your application properties (like DB URL, username, dialect, etc.).
-
Then, it uses this factory to create
EntityManagerinstances for you — either:- Automatically, for repository operations
- Or manually, when you inject
EntityManager(e.g.,@PersistenceContext)
@PersistenceContext
private EntityManager entityManager;or just use:
userRepository.save(user); // Spring handles it using EntityManager internallyYou don’t need to worry about the EntityManagerFactory unless:
- You are manually configuring JPA (e.g., no Spring Boot)
- You have multiple databases (so you define multiple factories)
- Or you're doing something very low-level
| Concept | You use it? | Why? |
|---|---|---|
EntityManagerFactory |
❌ Rarely | Managed by Spring behind the scenes |
EntityManager |
✅ Often | Used for persistence operations, either directly or via Spring Data |
Great! Let’s break this down into three parts:
Spring Boot auto-configures EntityManagerFactory using:
- Your
application.propertiesorapplication.yml - Spring Boot Starter Data JPA
LocalContainerEntityManagerFactoryBeanunder the hood
# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=trueSpring Boot will:
- Create a
DataSource - Create an
EntityManagerFactory(bean name:entityManagerFactory) - Create a
PlatformTransactionManager
You don’t need to define any of these manually.
You’d do this if you’re:
- Not using Spring Boot
- Want full control
@Configuration
@EnableJpaRepositories(basePackages = "com.example.repo")
public class JpaConfig {
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/mydb");
ds.setUsername("root");
ds.setPassword("secret");
return ds;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
emf.setPackagesToScan("com.example.entity");
emf.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return emf;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}spring:
datasource:
db1:
url: jdbc:mysql://localhost:3306/db1
username: root
password: secret
db2:
url: jdbc:mysql://localhost:3306/db2
username: root
password: secret@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repo.db1",
entityManagerFactoryRef = "db1EntityManagerFactory",
transactionManagerRef = "db1TransactionManager"
)
public class Db1Config {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.db1")
public DataSource db1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean db1EntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(db1DataSource())
.packages("com.example.entity.db1")
.persistenceUnit("db1")
.build();
}
@Bean
@Primary
public PlatformTransactionManager db1TransactionManager(
@Qualifier("db1EntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repo.db2",
entityManagerFactoryRef = "db2EntityManagerFactory",
transactionManagerRef = "db2TransactionManager"
)
public class Db2Config {
@Bean
@ConfigurationProperties("spring.datasource.db2")
public DataSource db2DataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean db2EntityManagerFactory(
EntityManagerFactoryBuilder builder) {
return builder
.dataSource(db2DataSource())
.packages("com.example.entity.db2")
.persistenceUnit("db2")
.build();
}
@Bean
public PlatformTransactionManager db2TransactionManager(
@Qualifier("db2EntityManagerFactory") EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}In most Spring Boot apps, a transaction is managed using @Transactional, and it works with one DataSource and one EntityManagerFactory.
@Transactional
public void createUser(User user) {
userRepository.save(user); // Everything committed or rolled back as one unit
}This uses a single database and a local transaction (e.g., via JDBC or JPA).
When you work with two databases, say db1 and db2, and try to perform operations on both within a single transaction, things get tricky.
@Transactional // Will only work with ONE PlatformTransactionManager
public void transfer() {
userRepoDb1.save(...); // on db1
orderRepoDb2.save(...); // on db2 ❌ problem!
}Spring can only apply the transaction to one PlatformTransactionManager (either for db1 or db2), not both. So, if one succeeds and the other fails — you get data inconsistency.
public void transfer() {
TransactionStatus tx1 = txManagerDb1.getTransaction(...);
TransactionStatus tx2 = txManagerDb2.getTransaction(...);
try {
userRepoDb1.save(...);
orderRepoDb2.save(...);
txManagerDb1.commit(tx1);
txManagerDb2.commit(tx2);
} catch (Exception e) {
txManagerDb1.rollback(tx1);
txManagerDb2.rollback(tx2);
}
}✅ Gives you full control, but it's manual and error-prone.
To handle transactions across multiple databases in a single unit (ACID), use a JTA-compliant transaction manager:
Spring Boot supports JTA by:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>@AtomikosDataSourceBean(...) // Configure both DataSources as XADataSourcesThen @Transactional can span both databases.
✅ Pros:
- ACID guaranteed across DBs
❌ Cons:
- More configuration
- Some performance overhead
- Not supported by all RDBMS combos out of the box
| Scenario | Behavior |
|---|---|
Single DB with @Transactional |
Works perfectly — rollback on failure |
Multiple DBs with one @Transactional |
Only works for one DB — others not rolled back |
| Manual transaction management | Gives control but is tedious and risky |
| JTA (e.g. Atomikos) | Proper distributed transactions — but more config and heavier setup |