web-dev-qa-db-fra.com

Quand les connexions sont-elles renvoyées au pool de connexions avec Spring JPA (Hibernate) Entity Manager?

Dans mon processus Java, je me connecte à MySql en utilisant la configuration de ressort suivante:

@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();
    }

}

L'Entity-Manager est injecté dans la couche d'accès aux données par le conteneur:

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

Et mes méthodes de logique d'entreprise publique sont annotées avec le @Transactional annotation.

Pour autant que je sache, le conteneur est responsable de s'assurer que le gestionnaire d'entités renvoie les connexions au pool (dans mon cas HikariCP ) une fois la transaction effectuée, mais je n'ai trouvé aucune documentation officielle décrivant comment les connexions sont gérées. Quelqu'un peut-il me l'expliquer ou fournir une bonne référence qui peut expliquer quand exactement les connexions sont retournées au pool lors de l'utilisation d'une telle configuration?

MISE À JOUR:

La meilleure information connexe que j'ai pu trouver jusqu'à présent ( prise d'ici ):

Le proxy de contexte de persistance qui implémente EntityManager n'est pas le seul composant nécessaire pour que la gestion déclarative des transactions fonctionne. En fait, trois composants distincts sont nécessaires:

Le proxy EntityManager lui-même L'aspect transactionnel Le gestionnaire de transactions Passons en revue chacun d'eux et voyons comment ils interagissent.

L'aspect transactionnel

L'aspect transactionnel est un aspect "autour" qui est appelé avant et après la méthode commerciale annotée. La classe concrète pour implémenter l'aspect est TransactionInterceptor.

L'aspect transactionnel a deux responsabilités principales:

Au moment "avant", l'aspect fournit un point de raccordement pour déterminer si la méthode métier sur le point d'être appelée doit s'exécuter dans le cadre d'une transaction de base de données en cours, ou si une nouvelle transaction distincte doit être démarrée.

Au moment "après", l'aspect doit décider si la transaction doit être validée, annulée ou laissée en cours d'exécution.

Au moment "avant", l'aspect transactionnel lui-même ne contient aucune logique de décision, la décision de démarrer une nouvelle transaction si nécessaire est déléguée au gestionnaire de transactions.

Le gestionnaire de transactions

Le gestionnaire de transactions doit répondre à deux questions:

faut-il créer un nouveau gestionnaire d'entités? faut-il démarrer une nouvelle transaction de base de données? Cela doit être décidé au moment où la logique "avant" de l'aspect transactionnel est appelée. Le gestionnaire de transactions décidera en fonction:

le fait qu'une transaction est déjà en cours ou non l'attribut de propagation de la méthode transactionnelle (par exemple REQUIRES_NEW démarre toujours une nouvelle transaction) Si le gestionnaire de transactions décide de créer une nouvelle transaction, alors il:

créer un nouveau gestionnaire d'entités lier le gestionnaire d'entités au thread actuel saisir une connexion du pool de connexions DB lier la connexion au thread actuel Le gestionnaire d'entités et la connexion sont tous deux liés au thread actuel à l'aide de variables ThreadLocal.

Ils sont stockés dans le thread pendant l'exécution de la transaction et il appartient au gestionnaire de transactions de les nettoyer lorsqu'ils ne sont plus nécessaires.

Toutes les parties du programme qui nécessitent le gestionnaire d'entités ou la connexion en cours peuvent les récupérer à partir du thread. Un composant de programme qui fait exactement cela est le proxy EntityManager.

23
forhas

Ce n'est pas compliqué du tout.

  1. Tout d'abord, vous devez comprendre que le gestionnaire de transactions Spring n'est qu'un abstraction de gestion des transactions . Dans votre cas, les transactions réelles se produisent au niveau de la connexion JDBC.

  2. Tout @Transactional Les appels de méthode de service sont interceptés par l'aspect TransactionInterceptor.

  3. TransactionIntreceptor délègue la gestion des transactions à l'implémentation AbstractPlatformTransactionManager actuellement configurée (JpaTransactionManager dans votre cas).

  4. JpaTransactionManager liera la transaction Spring en cours d'exécution à un EntityManager, de sorte que tous les DAO participant à la transaction actuelle partagent le même contexte de persistance.

  5. JpaTransactionManager utilise simplement l'API de transaction EntityManager pour contrôler les transactions:

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

    L'API de transaction JPA délègue simplement l'appel aux méthodes de validation/restauration JDBC Connection sous-jacentes.

  6. Une fois la transaction terminée (commit/rollback), le org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction appels:

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

    qui déclenche la fermeture d'une session de mise en veille prolongée (Entity Manager).

  7. La connexion JDBC sous-jacente est donc également déclenchée:

    jdbcCoordinator.close();
    
  8. Hibernate possède un descripteur de connexion JDBC logique:

    @Override
    public Connection close() {
        LOG.tracev( "Closing JDBC container [{0}]", this );
        if ( currentBatch != null ) {
        LOG.closingUnreleasedBatch();
            currentBatch.release();
        }
        cleanup();
        return logicalConnection.close();
    }
    
  9. La connexion logique délègue l'appel de fermeture au fournisseur de connexion actuellement configuré (DataSourceConnectionProvider dans votre cas), qui appelle simplement la méthode close sur la connexion JDBC:

    @Override
    public void closeConnection(Connection connection) throws SQLException {
         connection.close();
    }
    
  10. Comme tout autre connection pooling DataSource , la connexion JDBC close renvoie simplement la connexion au pool et ne ferme pas la connexion à la base de données physique. En effet, le pool de connexions DataSource renvoie un proxy de connexion JDBC qui intercepte tous les appels et délègue la fermeture à la logique de gestion du pool de connexions.

Vous pouvez également trouver plus de détails sur ce sujet et pourquoi vous devez définir le hibernate.connection.provider_disables_autocommit propriété avec Hibernate dans cet article .

30
Vlad Mihalcea