Java 何时使用 Spring JPA (Hibernate) Entity Manager 将连接返回到连接池?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27486104/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 04:27:02  来源:igfitidea点击:

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

javamysqlspringhibernatejpa

提问by forhas

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

在我的 java 进程中,我使用以下 spring 配置连接到 MySql:

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

Entity-Manager 由容器注入数据访问层:

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

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

并且我的公共业务逻辑方法是用@Transactional注解来注解的。

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?

据我了解,一旦事务完成,容器负责确保实体管理器将连接返回到池(在我的情况下为HikariCP),但我没有找到任何描述连接管理方式的官方文档。任何人都可以向我解释它或提供一个很好的参考,可以解释使用这种配置时连接何时返回到池中?

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:

实现 EntityManager 的持久化上下文代理并不是使声明式事务管理工作所需的唯一组件。实际上需要三个独立的组件:

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

EntityManager 代理本身 事务方面 事务管理器 让我们仔细看看它们是如何交互的。

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.

事务方面是一个“周围”方面,在注释的业务方法之前和之后都会被调用。实现方面的具体类是 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:

一个事务是否已经在进行中的事实 事务方法的传播属性(例如 REQUIRES_NEW 总是启动一个新事务) 如果事务管理器决定创建一个新事务,那么它将:

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.

创建一个新的实体管理器将实体管理器绑定到当前线程从数据库连接池中获取一个连接将连接绑定到当前线程实体管理器和连接都使用 ThreadLocal 变量绑定到当前线程。

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.

需要当前实体管理器或连接的程序的任何部分都可以从线程中检索它们。一个完全执行此操作的程序组件是 EntityManager 代理。

采纳答案by Vlad Mihalcea

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 @Transactionalservice method calls are intercepted by the TransactionInterceptorAspect.

  3. The TransactionIntreceptordelegates transaction management to the current configured AbstractPlatformTransactionManagerimplementation (JpaTransactionManagerin your case).

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

  5. JpaTransactionManagersimply uses the EntityManagerTransaction 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.

  6. When the transaction is done (commit/rollback), the org.hibernate.engine.transaction.internal.jdbc.JdbcTransactioncalls:

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

    which triggers a Hibernate Session (Entity Manager) close.

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

    jdbcCoordinator.close();
    
  8. 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();
    }
    
  9. The logical connection delegates the close call to the currently configured connection provider (DataSourceConnectionProviderin your case), which simply calls the close method on the JDBC connection:

    @Override
    public void closeConnection(Connection connection) throws SQLException {
         connection.close();
    }
    
  10. 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.

  1. 首先,您需要了解 Spring 事务管理器只是一个事务管理抽象。在您的情况下,实际事务发生在 JDBC 连接级别。

  2. 所有@Transactional服务方法调用都被TransactionInterceptorAspect拦截。

  3. TransactionIntreceptor委托事务管理当前的配置 AbstractPlatformTransactionManager实现(JpaTransactionManager你的情况)。

  4. JpaTransactionManager将当前运行的 Spring 事务绑定到一个 EntityManager,因此所有参与当前事务的 DAO 共享相同的持久化上下文。

  5. JpaTransactionManager只需使用EntityManagerTransaction API 来控制事务:

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

    JPA 事务 API 只是将调用委托给底层 JDBC 连接提交/回滚方法。

  6. 当事务完成(提交/回滚)时,org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction调用:

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

    这会触发休眠会话(实体管理器)关闭。

  7. 因此,底层 JDBC 连接也被触发关闭:

    jdbcCoordinator.close();
    
  8. Hibernate 有一个逻辑 JDBC 连接句柄:

    @Override
    public Connection close() {
        LOG.tracev( "Closing JDBC container [{0}]", this );
        if ( currentBatch != null ) {
        LOG.closingUnreleasedBatch();
            currentBatch.release();
        }
        cleanup();
        return logicalConnection.close();
    }
    
  9. 逻辑连接将 close 调用委托给当前配置的连接提供程序(DataSourceConnectionProvider在您的情况下),它只是调用 JDBC 连接上的 close 方法:

    @Override
    public void closeConnection(Connection connection) throws SQLException {
         connection.close();
    }
    
  10. 与任何其他连接池 DataSource 一样,JDBC 连接关闭只是将连接返回到池,而不会关闭物理数据库连接。这是因为连接池 DataSource 返回一个 JDBC Connection 代理,该代理拦截所有调用并将关闭委托给连接池处理逻辑。

You can also find more details about this topic and why you need to set the hibernate.connection.provider_disables_autocommitproperty with Hibernate in this article.

您还可以找到关于此主题的更多细节,以及为什么需要设置hibernate.connection.provider_disables_autocommit与Hibernate属性在这篇文章