Transactions dans Spring

Transactions SQL dans un projet Spring

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com

Certaines opérations sur la base de données peuvent être très complexes. Les objets y participant peuvent dépendre les uns les autres. Pour la cohérence des informations, il est donc important de respecter au maximum l'atomicité des données. Il faut alors privilégier le principe "all or nothing", où soit on effectue toutes les opérations, soit aucune. Transactions sont une technique idéale pour son implémentation.

A travers cet article on verra deux façons de gérer les transactions dans Spring. La première méthode sera basée sur l'injection d'un gestionnaire des transactions à la couche service. L'autre méthode, peu utilisée dans notre exemple d'application Spring, se reposera sur l'annotation @Transactional.

Gestion des transactions dans Spring

Les transactions peuvent être gérées dans un fichier XML, via l'annotation @Transactional ou explicitement dans le code avec l'implémentation de l'interface org.springframework.transaction.PlatformTransactionManager défini dans la liste des beans. Dans notre cas il s'agit du JpaTransactionManager.

@Service("penaltyService")
public class PenaltyServiceImpl implements PenaltyService {

    @Autowired
    private PlatformTransactionManager transactionManager;
    // other attributes are ommitted

    public List<Penalty> saveNotValid(PenaltyForm penaltyForm) throws Exception {
        List<Penalty> penalties = new ArrayList<Penalty>();
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            PaymentMethod paymentMethod = paymentMethodRepository.findOne(penaltyForm.getModeChecked());
            for (Penalty penalty : penaltyForm.getPenaltiesChecked()) {
                penalty.setState(Penalty.STATE_PENDING);
                penalty.setPaymentMethod(paymentMethod);
                penalty = penaltyRepository.save(penalty);
                penalties.add(penalty);
            }
            transactionManager.commit(status);
        } catch(Exception e) {
            logger.error("An exception occured on saving PenaltyRepository", e);
            penalties = null;
            transactionManager.rollback(status);
            throw new Exception(e);
        }
        return penalties;
    }
}

Au tout début on récupère l'instance du bean transactionManager avec l'annotation @Autowired. D'abord, on crée l'instance de la classe DefaultTransactionDefinition. Il s'agit de la transaction et de toutes ses caractéristiques (readOnly, PROPAGATION_REQUIRED, ISOLATION_DEFAULT ou TIMEOUT_DEFAULT). Grâce à cette définition on crée l'instance de la classe TransactionStatus. Ce statut sera utilisé plus loin pour commiter (transactionManager.commit()) ou annuler toutes les requêtes (transactionManager.rollback).

TODO : exemple et explication d'un @Transactional(readOnly = true)

Bartosz KONIECZNY Couche des données

Une question ? Une remarque ?

*

*

Un conseil MySQL

Vous rencontrez un problème lors de la suppression d'un élément ou d'une table liée à des contraintes des clés étrangères. L'une des solution peut être l'annulation de la vérification de ces contraintes.


-- Au début on indique à MySQL de ne pas vérifier les contraintes des clés étrangères
SET FOREIGN_KEY_CHECKS=0;
-- Ensuite on passe à l'opération de suppression d'une table
DROP TABLE ma_table;
-- A la fin (ou au moment voulu) on restaure la vérification des contraintes
SET FOREIGN_KEY_CHECKS=1;

Vous pouvez en savoir plus sur FOREIGN_KEY_CHEKS dans la documentation du MySQL.