Spring JPA(Hibernate)实体管理器何时将连接返回到连接池?

48

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

}

实体管理器由容器注入到数据访问层:

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

我的公共业务逻辑方法使用@Transactional注解进行了标注。

就我所了解的,容器负责确保实体管理器在事务完成后将连接返回到池中(在我的情况下为HikariCP),但我没有找到任何官方文档来描述连接是如何被管理的。有人可以为我解释一下,或者提供一个好的参考资料来解释在使用这种配置时何时会将连接返回到池中吗?

更新:

我能够找到的最佳相关信息(摘自此处):

实现EntityManager的持久性上下文代理不是使声明式事务管理生效所需的唯一组件。其实需要三个单独的组件:

EntityManager代理本身
事务性方面
事务管理器
让我们逐个细看,并看看它们如何互动。

事务性方面

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

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

在'before'时刻,该方面提供了一个钩子点,用于确定即将调用的业务方法是否应在正在进行的数据库事务范围内运行,或者是否应启动新的独立事务。

在'after'时刻,方面需要决定事务是应该提交、回滚还是保持运行。

在'before'时刻,事务性方面本身不包含任何决策逻辑,必要时启动新事务的决定委托给事务管理器。

事务管理器

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

应该创建一个新的EntityManager吗?
应该启动一个新的数据库事务吗?

这需要在调用事务性方面之前做出决定。事务管理器将根据以下因素做出决策:

已经存在一个事务还是不存在
事务性方法的传播属性(例如,REQUIRES_NEW始终会启动一个新事务)
如果事务管理器决定创建一个新事务,那么它将:

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

它们在事务运行时存储在线程中,当不再需要时,由事务管理器清理。程序中需要当前实体管理器或连接的任何部分都可以从线程中检索它们。一个恰好如此的程序组件就是EntityManager代理。


1
我怀疑容器不负责返回连接。这是Spring的责任,因为它通过JPATransactionManager管理事务。确认的好方法是启用Spring和HikariCP日志并进行验证。 - Andy Dufresne
2
当我说“容器”时,我指的是Spring容器,更具体地说是由Spring容器管理的实体管理器。http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html - forhas
你分享的网站已经无法访问了。能否更新链接? - Mahadeva
1个回答

84

这其实并不复杂。

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

  2. 所有带有 @Transactional 注解的服务方法调用都会被 TransactionInterceptor 切面拦截。

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

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

  5. JpaTransactionManager 简单地使用 EntityManager 事务API 来控制事务:

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

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

  1. 当事务完成(commit/rollback)时,org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction会调用:

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

触发 Hibernate Session(实体管理器)关闭。

  1. 因此,底层的 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();
     }
    
  • 就像其他连接池DataSource一样,JDBC连接关闭仅将连接返回给连接池,并不关闭实际的数据库连接。这是因为连接池DataSource返回一个拦截所有调用并将关闭委托给连接池处理逻辑的JDBC连接代理。

  • 请注意,对于RESOURCE_LOCAL事务,如果连接池禁用了autocommit检查,您还应该设置hibernate.connection.provider_disables_autocommit属性。这样,数据库连接将在执行SQL查询或刷新持久化上下文之前进行惰性获取。


    3
    好的,您想知道这些信息的来源在哪里?我如何获得一份官方文件来解释这个过程,或者至少声明链中每个组件的责任? - forhas
    13
    我浏览了源代码,我认为这是最新的文档。 - Vlad Mihalcea
    4
    我会写一篇博客文章,并将其链接到这个问题上。这是一个非常有趣的问题。 - Vlad Mihalcea
    1
    “这不难的。”,然后继续解释10层抽象……;-) - morgwai
    1
    @morgwai 这些是包含10个项目的列表。在这里只有3层抽象:Spring逻辑事务、Hibernate逻辑事务和Hibernate连接提供程序机制。 - Vlad Mihalcea
    显示剩余9条评论

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接