When are connections returned to the connection pool with Spring JPA (Hibernate) Entity Manager?

48,722

It's not complicated at all.

  1. First, you need to understand that the Spring transaction manager is only a transaction management abstraction. In your case, the actual transactions happen at the JDBC Connection level.

  2. All @Transactional service method calls are intercepted by the TransactionInterceptor Aspect.

  3. The TransactionIntreceptor delegates transaction management to the current configured AbstractPlatformTransactionManager implementation (JpaTransactionManager in your case).

  4. JpaTransactionManager will bind the current running Spring transaction to an EntityManager, so all DAOs participating in the current transaction share the same Persistence Context.

  5. JpaTransactionManager simply uses the EntityManager Transaction API for controlling transactions:

     EntityTransaction tx = txObject.getEntityManagerHolder().getEntityManager().getTransaction();
     tx.commit();
    

The JPA Transaction API simply delegates the call to the underlying JDBC Connection commit/rollback methods.

  1. When the transaction is done (commit/rollback), the org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction calls:

     transactionCoordinator().getTransactionContext().managedClose();
    

which triggers a Hibernate Session (Entity Manager) close.

  1. The underlying JDBC connection is therefore triggered to be closed as well:

     jdbcCoordinator.close();
    
  2. Hibernate has a logical JDBC connection handle:

     @Override
     public Connection close() {
         LOG.tracev( "Closing JDBC container [{0}]", this );
         if ( currentBatch != null ) {
         LOG.closingUnreleasedBatch();
             currentBatch.release();
         }
         cleanup();
         return logicalConnection.close();
     }
    
  3. The logical connection delegates the close call to the currently configured connection provider (DataSourceConnectionProvider in your case), which simply calls the close method on the JDBC connection:

     @Override
     public void closeConnection(Connection connection) throws SQLException {
          connection.close();
     }
    
  4. Like any other connection pooling DataSource, the JDBC connection close simply returns the connection to the pool and doesn't close the physical database connection. That's because the connection pooling DataSource returns a JDBC Connection proxy that intercepts all calls and delegates the closing to the connection pool handling logic.

Note that for RESOURCE_LOCAL transactions, you should also set the hibernate.connection.provider_disables_autocommit property if the autocommit check was disabled by the connection pool. This way, the database connections are going to be acquired lazily prior to executing a SQL query or flushing the Persistence Context.

Share:
48,722

Related videos on Youtube

forhas
Author by

forhas

Updated on July 09, 2022

Comments

  • forhas
    forhas almost 2 years

    In my java process I'm connecting to MySql using the following spring configuration:

    @Configuration
    @EnableTransactionManagement
    @PropertySources({ @PropertySource("classpath:/myProperties1.properties"), @PropertySource("classpath:/myProperties2.properties") })
    public class MyConfiguration {
    
        @Autowired
        protected Environment env;
    
        /**
         * @return EntityManagerFactory for use with Hibernate JPA provider
         */
        @Bean(destroyMethod = "destroy")
        public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setPersistenceUnitManager(persistenceUnitManager());
    
        return em;
        }
    
        /**
         * 
         * @return jpaVendorAdapter that works in conjunction with the
         *         persistence.xml
         */
        @Bean
        public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.valueOf(env.getProperty("jpa.database")));
        vendorAdapter.setDatabasePlatform(env.getProperty("jpa.dialect"));
        vendorAdapter.setGenerateDdl(env.getProperty("jpa.generateDdl", Boolean.class, false));
        vendorAdapter.setShowSql(env.getProperty("jpa.showSql", Boolean.class, false));
    
        return vendorAdapter;
        }
    
        @Bean
        public PersistenceUnitManager persistenceUnitManager() {
        DefaultPersistenceUnitManager pum = new DefaultPersistenceUnitManager();
        pum.setPackagesToScan("com.app.dal");
        pum.setDefaultPersistenceUnitName("my-pu");
        pum.setPersistenceXmlLocations("classpath:/META-INF/persistence.xml");
        pum.setDefaultDataSource(dataSource());
    
        return pum;
        }
    
        @Bean(destroyMethod = "close")
        public DataSource dataSource() {
        Properties dsProps = new Properties();
        dsProps.put("driverClassName", env.getProperty("hikari.driverClassName"));
        dsProps.put("username", env.getProperty("hikari.username"));
        dsProps.put("password", env.getProperty("hikari.password"));
        dsProps.put("jdbcUrl", env.getProperty("hikari.source.data.jdbcUrl"));
        dsProps.put("connectionTimeout", env.getProperty("hikari.connectionTimeout", Integer.class));
        dsProps.put("idleTimeout", env.getProperty("hikari.idleTimeout", Integer.class));
        dsProps.put("maxLifetime", env.getProperty("hikari.maxLifetime", Integer.class));
        dsProps.put("maximumPoolSize", env.getProperty("hikari.maximumPoolSize.rtb.source", Integer.class));
        dsProps.put("leakDetectionThreshold", env.getProperty("hikari.leakDetectionThreshold", Integer.class));
        dsProps.put("jdbc4ConnectionTest", env.getProperty("hikari.jdbc4ConnectionTest", Boolean.class));
    
        HikariConfig config = new HikariConfig(dsProps);
        HikariDataSource ds = new HikariDataSource(config);
    
        return ds;
        }
    
        @Bean(name = "sourceTxMgr")
        public PlatformTransactionManager sourceDatatransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setPersistenceUnitName("my-pu");
        transactionManager.setDataSource(dataSource());
    
        return transactionManager;
        }
    
        @Bean
        public PersistencyManager persistencyManager() {
        return new JpaPersistencyManager();
        }
    
        @Bean
        public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
        }
    
    }
    

    The Entity-Manager is injected to the data access layer by the container:

    @PersistenceContext(type = PersistenceContextType.TRANSACTION, unitName = "my-pu")
    private EntityManager myEntityManager;
    

    And my public business logic methods are annotated with the @Transactional annotation.

    As far as I understand the container is responsible for ensuring that the entity-manager returns connections to the pool (in my case HikariCP) once a transaction is done but I did not find any official documentation that describes how the connections are managed. Can anyone explain it to me or provide a good reference that can explain when exactly connections are returned to the pool when using such a configuration?

    UPDATE:

    The best related info I could come up with so far (taken from here):

    The persistence context proxy that implements EntityManager is not the only component needed for making declarative transaction management work. Actually three separate components are needed:

    The EntityManager Proxy itself The Transactional Aspect The Transaction Manager Let's go over each one and see how they interact.

    The Transactional Aspect

    The Transactional Aspect is an 'around' aspect that gets called both before and after the annotated business method. The concrete class for implementing the aspect is TransactionInterceptor.

    The Transactional Aspect has two main responsibilities:

    At the 'before' moment, the aspect provides a hook point for determining if the business method about to be called should run in the scope of an ongoing database transaction, or if a new separate transaction should be started.

    At the 'after' moment, the aspect needs to decide if the transaction should be committed, rolled back or left running.

    At the 'before' moment the Transactional Aspect itself does not contain any decision logic, the decision to start a new transaction if needed is delegated to the Transaction Manager.

    The Transaction Manager

    The transaction manager needs to provide an answer to two questions:

    should a new Entity Manager be created? should a new database transaction be started? This needs to be decided at the moment the Transactional Aspect 'before' logic is called. The transaction manager will decide based on:

    the fact that one transaction is already ongoing or not the propagation attribute of the transactional method (for example REQUIRES_NEW always starts a new transaction) If the transaction manager decides to create a new transaction, then it will:

    create a new entity manager bind the entity manager to the current thread grab a connection from the DB connection pool bind the connection to the current thread The entity manager and the connection are both bound to the current thread using ThreadLocal variables.

    They are stored in the thread while the transaction is running, and it's up to the Transaction Manager to clean them up when no longer needed.

    Any parts of the program that need the current entity manager or connection can retrieve them from the thread. One program component that does exactly that is the EntityManager proxy.

    • Andy Dufresne
      Andy Dufresne over 9 years
      I doubt the container is responsible for returning connections. It's Spring which is responsible since it is managing the transactions through the JPATransactionManager. A good way to confirm would be to enable spring and the HikariCP logs and verify it.
    • forhas
      forhas over 9 years
      When I say "The container" I spring container, or to be more specific the Entity-Manager which is managed by spring container. docs.spring.io/spring/docs/current/spring-framework-referenc‌​e/…
    • Pant
      Pant over 5 years
      The website you shared is down. Can you update the link.
  • forhas
    forhas over 9 years
    Nice, but where did you get this info from? How can I reach an official documentation explaining this process, or at least declaring the responsibilities of each component in the chain?
  • Vlad Mihalcea
    Vlad Mihalcea over 9 years
    I just browsed the source code. I think it's the most updated documentation.
  • forhas
    forhas over 9 years
    I'm not completely satisfied since I was looking for some official documentation but it appears this is the best answer I can get. Thank you
  • Vlad Mihalcea
    Vlad Mihalcea over 9 years
    I will write a blog post and then link it to this question. It is a very interesting question.
  • Hector
    Hector about 7 years
    Hi, I have a question. If my app use Spring and Hibernate JPA, with RESOURCE_LOCAL transaction type. My datasource is defined with JNDI with a pool defined in the WAS server. Who is the responsable for take/create/release connections defined in this pool ?. I access to EntityManager through the annotation @PersistenceContext and my method only call an store procedure in sql server. My problem is that the max connections defined at poll is consumed quickly when the stored procedure is called sequentially in multiple times.
  • Vlad Mihalcea
    Vlad Mihalcea about 7 years
    That's a very unusual setup. Is the WAS DS really non-JTA? Usually, the Java EE AS use JTA by default.
  • Hector
    Hector about 7 years
    I have never used JTA with Spring for do JEE apps, If I change the attribute of transaction type in my persistence.xml, Should I change something in my code or maybe at WAS setup ?
  • Hector
    Hector about 7 years
    I read follow in a post: "Typically you need an application server’s JTA capability only if your application needs to handle transactions across multiple resources, which is not a requirement for many applications."
  • Vlad Mihalcea
    Vlad Mihalcea about 7 years
    You are the only one who can tell if the WAS DataSource is JTA or not. You have to check the App server config to know for sure.
  • Artem Novikov
    Artem Novikov over 5 years
    It's very disappointing Spring documentation on that side is still vague nowadays 4 years later even and people have to frantically search for blog posts and whatnot. Why would I waste my time and browse the code - as a device owner, you don't disassemble it unless it's broken or the docs are poor. For serious applications developers should 200% understand when, how many, etc transactions start and close. Spring doesn't want to tell it.
  • KJEjava48
    KJEjava48 almost 5 years
    @VladMihalcea Can u please link the blog post that u said.
  • Vlad Mihalcea
    Vlad Mihalcea almost 5 years
    Sure, I updated the answer with a link to a blog post that provides more details.
  • morgwai
    morgwai over 2 years
    "It's not complicated at all.", then proceeds to explain 10 layers of abstraction... ;-)
  • Vlad Mihalcea
    Vlad Mihalcea over 2 years
    @morgwai Those are 10 item lists. There are only 3 layers of abstractions here: Spring Logical Transaction, Hibernate logical transaction, and the Hibernate connection provider mechanism.