Spring JPA: Dual Datasource - Could not open JPA EntityManager for transaction

Viewed 531

I'm using Spring JPA, dual data source in my application. Both Datasources works just fine. But intermittently (usually when application is idle / when difference between access time for 1st data source is 1 minute), it throw below exception when a new request comes in

For example, I made a write on master db -> Access slave db for some read operation for more than 1 minute -> Access master to write again, here below error is thrown

Could not open JPA EntityManager for transaction; nested exception is org.hibernate.TransactionException: JDBC begin transaction failed

Attaching master and slave database configuration as below.

Master Database Config

@Configuration
@EnableJpaRepositories(basePackages = "com.myproject.master.dao",
    entityManagerFactoryRef = "masterEntityManager",
    transactionManagerRef = "masterTransactionManager")
public class MasterDatabaseConfig {

  @Autowired
  private Environment env;

  @Bean
  @Primary
  public LocalContainerEntityManagerFactoryBean masterEntityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(masterDataSource());
    em.setPackagesToScan(new String[] { "com.myproject.master.dao" });

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<String, Object> properties = new HashMap<>();
    properties.put("spring.jpa.properties.hibernate.dialect",
        env.getProperty("spring.jpa.properties.hibernate.dialect"));

    properties.put("spring.jpa.hibernate.ddl-auto",
        env.getProperty("spring.jpa.hibernate.ddl-auto"));

    properties.put("spring.jmx.default-domain", env.getProperty("spring.jmx.default-domain"));

    properties.put("spring.datasource.tomcat.initial-size",
        env.getProperty("spring.datasource.tomcat.initial-size"));

    properties.put("spring.datasource.tomcat.max-wait",
        env.getProperty("spring.datasource.tomcat.max-wait"));

    properties.put("spring.datasource.tomcat.max-active",
        env.getProperty("spring.datasource.tomcat.max-active"));

    properties.put("spring.datasource.tomcat.max-idle",
        env.getProperty("spring.datasource.tomcat.max-idle"));

    properties.put("spring.datasource.tomcat.min-idle",
        env.getProperty("spring.datasource.tomcat.min-idle"));

    properties.put("spring.datasource.tomcat.default-auto-commit",
        env.getProperty("spring.datasource.tomcat.default-auto-commit"));

    properties.put("spring.jpa.open-in-view", env.getProperty("spring.jpa.open-in-view"));
    properties.put("spring.jpa.show-sql", env.getProperty("spring.jpa.show-sql"));

    properties.put("hibernate.physical_naming_strategy",
        SpringPhysicalNamingStrategy.class.getName());
    properties.put("hibernate.implicit_naming_strategy",
        SpringImplicitNamingStrategy.class.getName());

    em.setJpaPropertyMap(properties);

    return em;
  }

  @Bean
  @Primary
  public DataSource masterDataSource() {

    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
    dataSource.setUrl(env.getProperty("spring.datasource.url"));
    dataSource.setUsername(env.getProperty("spring.datasource.username"));
    dataSource.setPassword(env.getProperty("spring.datasource.password"));

    return dataSource;
  }

  @Bean
  @Primary
  public PlatformTransactionManager masterTransactionManager() {

    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(masterEntityManager().getObject());
    return transactionManager;
  }

}

Slave Database Config

@Configuration
@EnableJpaRepositories(basePackages = "com.myproject.salve.services",
    entityManagerFactoryRef = "slaveEntityManager",
    transactionManagerRef = "slaveTransactionManager")
public class SlaveDatabaseConfig {

  @Autowired
  private Environment env;

  @Bean
  public LocalContainerEntityManagerFactoryBean slaveEntityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(slaveDataSource());
    em.setPackagesToScan(new String[] { "com.myproject.salve.services" });

    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<String, Object> properties = new HashMap<>();
    properties.put("spring.jpa.properties.hibernate.dialect",
        env.getProperty("spring.jpa.properties.hibernate.dialect"));

    properties.put("spring.jpa.hibernate.ddl-auto",
        env.getProperty("spring.jpa.hibernate.ddl-auto"));

    properties.put("spring.jmx.default-domain", env.getProperty("spring.jmx.default-domain"));

    properties.put("spring.datasource.tomcat.initial-size",
        env.getProperty("spring.datasource.tomcat.initial-size"));

    properties.put("spring.datasource.tomcat.max-wait",
        env.getProperty("spring.datasource.tomcat.max-wait"));

    properties.put("spring.datasource.tomcat.max-active",
        env.getProperty("spring.datasource.tomcat.max-active"));

    properties.put("spring.datasource.tomcat.max-idle",
        env.getProperty("spring.datasource.tomcat.max-idle"));

    properties.put("spring.datasource.tomcat.min-idle",
        env.getProperty("spring.datasource.tomcat.min-idle"));

    properties.put("spring.datasource.tomcat.default-auto-commit",
        env.getProperty("spring.datasource.tomcat.default-auto-commit"));

    properties.put("spring.jpa.open-in-view", env.getProperty("spring.jpa.open-in-view"));
    properties.put("spring.jpa.show-sql", env.getProperty("spring.jpa.show-sql"));

    em.setJpaPropertyMap(properties);

    return em;
  }

  @Bean
  public DataSource slaveDataSource() {

    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
    dataSource.setUrl(env.getProperty("spring.replica.ds.url"));
    dataSource.setUsername(env.getProperty("spring.replica.ds.username"));
    dataSource.setPassword(env.getProperty("spring.replica.ds.password"));

    return dataSource;
  }

  @Bean
  public PlatformTransactionManager slaveTransactionManager() {

    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(slaveEntityManager().getObject());
    return transactionManager;
  }
}
2 Answers

The default timeout for the database is 8 hours. It may restart after 8 hours of inactivity.

You can configure tomcat connection pool as follows :

spring.datasource.tomcat.validation-query=select 1
spring.datasource.tomcat.test-while-idle=true
spring.datasource.tomcat.log-abandoned=true

# Maximum number of active connections that can be allocated from this pool at the same time.
spring.datasource.tomcat.max-active=100
spring.datasource.tomcat.max-idle=30
spring.datasource.tomcat.max-age=18000000

#Validate the connection before borrowing it from the pool.
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.num-tests-per-eviction-run=3
spring.datasource.tomcat.initial-size=5
spring.datasource.tomcat.remove-abandoned=true
spring.datasource.tomcat.remove-abandoned-timeout=82800
spring.datasource.tomcat.validation-query-timeout=10000
spring.datasource.tomcat.time-between-eviction-runs-millis=600000
Related