J'ai une méthode de service gérée par Spring pour gérer les insertions de base de données. Il contient plusieurs instructions d'insertion.
@Transactional
public void insertObservation(ObservationWithData ob) throws SQLException
{
observationDao.insertObservation(ob.getObservation());
// aop pointcut inserted here in unit test
dataDao.insertData(ob.getData());
}
J'ai deux tests unitaires qui lèvent une exception avant d'appeler le deuxième insert. Si l'exception est une exception RuntimeException, la transaction est annulée. Si l'exception est une exception SQLException, la première insertion est persistante.
Je suis dérouté. Quelqu'un peut-il me dire pourquoi la transaction n'annule pas une exception SQLException? Quelqu'un peut-il suggérer comment gérer cela? Je pouvais attraper l’exception SQLException et lancer une exception RuntimeException, mais cela me semble étrange.
C'est un comportement défini. De la docs :
Toute
RuntimeException
déclenche une annulation et aucune exception vérifiée ne le fait.
Ce comportement est commun à toutes les API de transaction Spring. Par défaut, si une RuntimeException
est extraite du code de transaction, la transaction sera annulée. Si une exception vérifiée (c'est-à-dire pas une RuntimeException
) est levée, la transaction ne sera pas annulée.
La raison en est que les classes RuntimeException
sont généralement utilisées par Spring pour indiquer des conditions d'erreur irrécupérables.
Ce comportement peut être modifié par défaut si vous souhaitez le faire, mais la procédure à suivre dépend de la manière dont vous utilisez l'API Spring et de la manière dont vous avez configuré votre gestionnaire de transactions.
Spring utilise beaucoup les exceptions RuntimeExceptions (y compris l’utilisation de DataAccessExceptions pour encapsuler les exceptions SQLExceptions ou les exceptions d’ORM) dans les cas où l’exception ne récupère pas. Cela suppose que vous souhaitiez utiliser des exceptions vérifiées dans les cas où une couche située au-dessus du service doit être notifiée, mais vous ne souhaitez pas que la transaction soit perturbée.
Si vous utilisez Spring, vous pouvez également utiliser ses bibliothèques jdbc-wrapping et son service de traduction DataAccessException, ce qui réduira la quantité de code que vous devez gérer et fournira des exceptions plus significatives. De plus, avoir une couche de service générant des exceptions spécifiques à une implémentation est une odeur désagréable. La méthode pré-Spring consistait à créer des exceptions vérifiées indépendantes de la mise en œuvre, englobant des exceptions spécifiques à la mise en œuvre, ce qui entraînait une charge de travail importante et une base de code saturée. La voie du printemps évite ces problèmes.
Si vous voulez savoir pourquoi Spring a choisi de faire fonctionner les choses de cette manière, c'est probablement parce qu'ils utilisent AOP pour ajouter le traitement des transactions. Ils ne peuvent pas changer la signature de la méthode qu'ils encapsulent, les exceptions vérifiées ne sont donc pas une option.
Pour @Transactional
, par défaut, l'annulation a lieu uniquement pour les exceptions non vérifiées au moment de l'exécution. Ainsi, votre exception vérifiée SQLException
ne déclenche pas une restauration de la transaction; le comportement peut être configuré avec les paramètres d'annotation rollbackFor
et noRollbackFor
.
@Transactional(rollbackFor = SQLException.class)
public void insertObservation(ObservationWithData ob) throws SQLException
{
observationDao.insertObservation(ob.getObservation());
// aop pointcut inserted here in unit test
dataDao.insertData(ob.getData());
}