Skip to content

Latest commit

 

History

History
324 lines (238 loc) · 8.5 KB

File metadata and controls

324 lines (238 loc) · 8.5 KB

Why we’ve used EntityManager but not EntityManagerFactory directly:


🧠 Spring manages EntityManagerFactory behind the scenes

  • EntityManagerFactory is 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 EntityManager instances for you — either:

    • Automatically, for repository operations
    • Or manually, when you inject EntityManager (e.g., @PersistenceContext)

🔁 Typical usage in a Spring Boot project

@PersistenceContext
private EntityManager entityManager;

or just use:

userRepository.save(user); // Spring handles it using EntityManager internally

You 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

✅ Summary

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:


✅ 1. How Spring Boot auto-configures EntityManagerFactory

Spring Boot auto-configures EntityManagerFactory using:

  • Your application.properties or application.yml
  • Spring Boot Starter Data JPA
  • LocalContainerEntityManagerFactoryBean under the hood

You just need:

# 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=true

Spring Boot will:

  • Create a DataSource
  • Create an EntityManagerFactory (bean name: entityManagerFactory)
  • Create a PlatformTransactionManager

You don’t need to define any of these manually.


🛠 2. How to manually define EntityManagerFactory

You’d do this if you’re:

  • Not using Spring Boot
  • Want full control

Example:

@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);
  }
}

🧩 3. Example of multiple database configuration using EntityManagerFactory

Scenario: You have two databases — db1 and db2


application.yml

spring:
  datasource:
    db1:
      url: jdbc:mysql://localhost:3306/db1
      username: root
      password: secret
    db2:
      url: jdbc:mysql://localhost:3306/db2
      username: root
      password: secret

Primary Config (DB1)

@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);
  }
}

Secondary Config (DB2)

@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);
  }
}

✅ 1. Single Database Transaction

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


🔄 2. Multiple Databases (Two DataSources)

When you work with two databases, say db1 and db2, and try to perform operations on both within a single transaction, things get tricky.

Scenario:

@Transactional  // Will only work with ONE PlatformTransactionManager
public void transfer() {
    userRepoDb1.save(...);     // on db1
    orderRepoDb2.save(...);    // on db2 ❌ problem!
}

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


✅ 3. Solutions to Manage Multi-DB Transactions

Option 1: Handle Transactions Manually Per DB

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.


Option 2: Use Distributed Transaction Manager (JTA / Atomikos / Narayana)

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 XADataSources

Then @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

⚠️ Summary: Multi-DB Transaction Behavior in Spring

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