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

Posted

技术标签:

【中文标题】何时使用 Spring JPA (Hibernate) Entity Manager 将连接返回到连接池?【英文标题】:When are connections returned to the connection pool with Spring JPA (Hibernate) Entity Manager? 【发布时间】:2015-02-13 16:05:38 【问题描述】:

在我的 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();
    


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

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

并且我的公共业务逻辑方法使用@Transactional 注释进行注释。

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

更新:

到目前为止我能想到的最好的相关信息 (taken from here):

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

EntityManager 代理本身 交易方面 事务管理器 让我们逐一分析,看看它们是如何相互作用的。

事务方面

事务方面是一个“环绕”方面,在带注释的业务方法之前和之后都被调用。实现方面的具体类是 TransactionInterceptor。

事务方面有两个主要职责:

在“之前”时刻,方面提供了一个挂钩点,用于确定将要调用的业务方法是否应该在正在进行的数据库事务的范围内运行,或者是否应该启动一个新的单独事务。

在“之后”时刻,切面需要决定事务是应该提交、回滚还是继续运行。

在“之前”时刻,事务方面本身不包含任何决策逻辑,如果需要,启动新事务的决策被委托给事务管理器。

事务管理器

事务管理器需要回答两个问题:

是否应该创建一个新的实体管理器? 是否应该启动一个新的数据库事务? 这需要在调用事务方面“之前”逻辑时决定。事务管理器将根据:

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

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

它们在事务运行时存储在线程中,由事务管理器在不再需要时清理它们。

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

【问题讨论】:

我怀疑容器负责返回连接。由 Spring 负责,因为它通过 JPATransactionManager 管理事务。确认的一个好方法是启用 spring 和 HikariCP 日志并进行验证。 当我说“容器”时,我是 spring 容器,或者更具体地说是由 spring 容器管理的 Entity-Manager。 docs.spring.io/spring/docs/current/spring-framework-reference/… 您共享的网站已关闭。你能更新链接吗? 【参考方案1】:

一点也不复杂。

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

    所有@Transactional服务方法调用都被TransactionInterceptor切面拦截。

    TransactionIntreceptor 将事务管理委托给当前配置的 AbstractPlatformTransactionManager 实现(在您的情况下为JpaTransactionManager)。

    JpaTransactionManager 会将当前正在运行的 Spring 事务绑定到一个 EntityManager,因此参与当前事务的所有 DAO 共享相同的 Persistence Context。

    JpaTransactionManager 只是使用EntityManager Transaction API 来控制事务:

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

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

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

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

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

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

     jdbcCoordinator.close();
    

    Hibernate 有一个逻辑 JDBC 连接句柄:

     @Override
     public Connection close() 
         LOG.tracev( "Closing JDBC container [0]", this );
         if ( currentBatch != null ) 
         LOG.closingUnreleasedBatch();
             currentBatch.release();
         
         cleanup();
         return logicalConnection.close();
     
    

    逻辑连接将关闭调用委托给当前配置的连接提供程序(在您的情况下为DataSourceConnectionProvider),它只是调用 JDBC 连接上的 close 方法:

     @Override
     public void closeConnection(Connection connection) throws SQLException 
          connection.close();
     
    

    与任何其他连接池数据源一样,JDBC 连接关闭只是将连接返回到池中,而不关闭物理数据库连接。这是因为连接池 DataSource 返回一个 JDBC Connection 代理,该代理拦截所有调用并将关闭委托给连接池处理逻辑。

请注意,对于 RESOURCE_LOCAL 事务,如果连接池禁用了 autocommit 检查,您还应该设置 hibernate.connection.provider_disables_autocommit 属性。这样,数据库连接将在执行 SQL 查询或刷新 Persistence Context 之前延迟获取。

【讨论】:

很好,但是您从哪里获得此信息?我如何才能获得解释此过程的官方文档,或者至少声明链中每个组件的职责? 我刚刚浏览了源代码。我认为这是最新的文档。 我并不完全满意,因为我一直在寻找一些官方文档,但看来这是我能得到的最佳答案。谢谢 我会写一篇博文,然后将其链接到这个问题。这是一个非常有趣的问题。 当然,我更新了答案,并提供了一个博客文章的链接,该文章提供了更多详细信息。

以上是关于何时使用 Spring JPA (Hibernate) Entity Manager 将连接返回到连接池?的主要内容,如果未能解决你的问题,请参考以下文章

SSH框架的搭建+JPA接口

Spring Data Jpa如何实现审计和乐观锁功能

Spring Data Jpa如何实现审计和乐观锁功能

grails 3(spring-boot) - 如何配置hibernate二级缓存

Spring Boot with JOOQ 和 Spring Data JPA 之间的技术差异

6.Spring+Struts+Hibernat注解方式整合