何时使用 Spring JPA(休眠)实体管理器将连接返回到连接池?

2022-09-01 00:02:47

在我的java过程中,我使用以下弹簧配置连接到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 代理本身 事务方面 事务管理器 让我们来看看它们是如何交互的。

事务方面

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

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

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

在“之后”时刻,该方面需要决定是否应提交,回滚或保持运行事务。

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

事务管理器

事务管理器需要提供两个问题的答案:

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

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

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

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

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


答案 1

它一点也不复杂。

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

  2. 所有服务方法调用都会被 Aspect 截获。@TransactionalTransactionInterceptor

  3. 将事务管理委托给当前配置的 AbstractPlatformTransactionManager 实现(在你的例子中)。TransactionIntreceptorJpaTransactionManager

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

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

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

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

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

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

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

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

     jdbcCoordinator.close();
    
  2. 休眠有一个逻辑 JDBC 连接句柄:

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

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

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


答案 2

推荐