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.
Ce n'est pas compliqué du tout.
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.
Tout @Transactional
Les appels de méthode de service sont interceptés par l'aspect TransactionInterceptor
.
TransactionIntreceptor
délègue la gestion des transactions à l'implémentation AbstractPlatformTransactionManager
actuellement configurée (JpaTransactionManager
dans votre cas).
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.
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.
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).
La connexion JDBC sous-jacente est donc également déclenchée:
jdbcCoordinator.close();
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();
}
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();
}
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 .