Comment gérer les transactions en Java?

Jusqu’à présent, tous les changements que nous avons effectués dans la base de données prenaient effet immédiatement. Un commit est comme sauvegarder un fichier. Cependant, vous pouvez modifier ce comportement pour contrôler les commits vous-même. Une transaction est un regroupement d’une ou plusieurs instructions dont les résultats finaux sont soit validés (commit) soit annulés (rollback). Un rollback est comme fermer un fichier sans sauvegarder. Tous les changements depuis le début de la transaction sont abandonnés. D’abord, nous examinons comment écrire du code pour valider et annuler. Ensuite, nous verrons comment contrôler vos points de rollback.

Validation et Annulation

Notre zoo est en rénovation et a décidé de donner plus d’espace aux éléphants. Cependant, nous n’avons qu’une quantité limitée d’espace, donc l’enclos des zèbres devra être réduit. Puisque nous ne pouvons pas inventer de l’espace, nous voulons nous assurer que la surface totale reste la même. Si l’ajout d’espace pour les éléphants ou la réduction d’espace pour les zèbres échoue, nous voulons que notre transaction soit annulée. Par souci de simplicité, nous supposons que la table de la base de données est dans un état valide avant d’exécuter ce code. Maintenant, examinons le code pour ce scénario :

public static void main(String[] args) throws SQLException {
    try (Connection conn =
         DriverManager.getConnection("jdbc:hsqldb:file:zoo")) {
            
        conn.setAutoCommit(false);
            
        var elephantRowsUpdated = updateRow(conn, 5, "Éléphant d'Afrique");
        var zebraRowsUpdated = updateRow(conn, -5, "Zèbre");
            
        if (! elephantRowsUpdated || ! zebraRowsUpdated)
            conn.rollback();
        else {
            String selectSql = """
                SELECT COUNT(*)
                FROM exhibits
                WHERE num_acres <= 0""";
            try (PreparedStatement ps = conn.prepareStatement(selectSql);
                 ResultSet rs = ps.executeQuery()) {
                    
                rs.next();
                int count = rs.getInt(1);
                if (count == 0)
                    conn.commit();
                else
                    conn.rollback();
            }
        }
    }
}
    
private static boolean updateRow(Connection conn,
                               int numToAdd, String name)
                               
    throws SQLException {
        
    String updateSql = """
        UPDATE exhibits
        SET num_acres = num_acres + ?
        WHERE name = ?""";
        
    try (PreparedStatement ps = conn.prepareStatement(updateSql)) {
        ps.setInt(1, numToAdd);
        ps.setString(2, name);
        return ps.executeUpdate() > 0;
    }
}

La première chose intéressante dans cet exemple est à la ligne 9, où nous désactivons le mode autocommit et déclarons que nous gérerons les transactions nous-mêmes. La plupart des bases de données prennent en charge la désactivation du mode autocommit. Si une base de données ne le fait pas, elle lèvera une SQLException à la ligne 9. Nous essayons ensuite de mettre à jour le nombre d’acres allouées à chaque animal. Si nous échouons et qu’aucune ligne n’est mise à jour, nous annulons la transaction à la ligne 15, ce qui fait que l’état de la base de données reste inchangé.

En supposant qu’au moins une ligne soit mise à jour, nous vérifions les exhibits et nous assurons qu’aucune des lignes ne contient une valeur num_acres invalide. Si c’était une vraie application, nous aurions plus de logique pour nous assurer que la quantité d’espace a du sens. Aux lignes 26-30, nous décidons de valider la transaction dans la base de données ou d’annuler toutes les mises à jour effectuées à la table exhibits.

Cas Particuliers d’Autocommit

Vous devez connaître deux cas particuliers. Premièrement, appeler setAutoCommit(true) déclenchera automatiquement un commit lorsque vous n’êtes pas déjà en mode autocommit. Après cela, le mode autocommit prend effet, et chaque instruction est automatiquement validée.

L’autre cas particulier est ce qui se passe si vous avez défini autocommit sur false et fermez votre connexion sans annuler ou valider vos modifications. La réponse est que le comportement est indéfini. Il peut valider ou annuler, selon uniquement le pilote. Ne comptez pas sur ce comportement ; n’oubliez pas de valider ou d’annuler à la fin d’une transaction !

Création de Points de Sauvegarde avec Savepoints

Jusqu’à présent, nous avons annulé jusqu’au point où l’autocommit a été désactivé. Vous pouvez utiliser des savepoints pour avoir plus de contrôle sur le point d’annulation. Considérez l’exemple suivant :

conn.setAutoCommit(false);
Savepoint sp1 = conn.setSavepoint();
// code de base de données
Savepoint sp2 = conn.setSavepoint("second savepoint");
// code de base de données
conn.rollback(sp2);
// code de base de données
conn.rollback(sp1);

La ligne 20 est importante. Vous ne pouvez utiliser des savepoints que lorsque vous contrôlez la transaction. Les lignes 21 et 23 montrent comment créer un Savepoint. Le nom est facultatif et est généralement inclus dans le toString() si vous imprimez la référence du savepoint.

La ligne 25 montre le premier rollback. Cela élimine tous les changements effectués depuis la création de ce savepoint : dans ce cas, le code à la ligne 24. Ensuite, la ligne 27 montre le second rollback, éliminant le code à la ligne 22.

L’ordre est important. Si nous inversions les lignes 25 et 27, le code lèverait une exception. Revenir à sp1 élimine tous les changements effectués après, ce qui inclut le second savepoint ! De même, appeler conn.rollback() à la ligne 25 annulerait les deux savepoints, et la ligne 27 lèverait à nouveau une exception.

Revue des API de Transaction

Il n’y a pas beaucoup de méthodes pour travailler avec les transactions, mais vous devez connaître toutes celles du Tableau 15.9.

MéthodeDescription
setAutoCommit(boolean b)Définit le mode pour déterminer s’il faut valider immédiatement
commit()Sauvegarde les données dans la base de données
rollback()Élimine les instructions déjà effectuées
rollback(Savepoint sp)Revient à l’état au Savepoint
setSavepoint()Crée un signet
setSavepoint(String name)Crée un signet avec un nom