Bonjour,

J'ai un gros volume de grappe de données que je dois sauvegarder en base. Pour autant, si la sauvegarde d'une grappe plante, le but est de ne pas faire planter la sauvegarde des autres grappes.

Mon idée de départ était d'utiliser le traitement par lot d'hibernate avec des savepoints. Mais après lecture de la doc et des specs, Hibernate ne supporte pas cette fonctionnalité de JDBC.

J'ai alors 3 solutions :

solution 1 : j'effectue un commit à chaque sauvegarde de grappe.
-> ce n'est plus du traitement par lot !
-> si une grappe plante cela ne m'empêche pas de sauvegarder les autres...

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
	// connection JDBC depuis un POOL
        Connection conn = DbManager.getConnection();
 
        // session hibernate à partir de la connection JDBC
        Session session = HibernateUtil.getSession(conn);
 
        for ( Grappe grappe : grappes ) {
 
            try {
                // début de la transaction
                tx = session.beginTransaction();
 
                // sauvegarde de ma grappe d'objets dans la session
                save(session, grappe);
 
                // fin de la transaction
                tx.commit();
            }
            catch (Throwable t) {
                if ( tx != null )
                    tx.rollback();
            }
            session.clear();
        }
        session.close();
solution 2 : j'effectue le commit à la fin
-> traitement par lot ok.
-> si une grappe plante tout plante...

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
 
	// connection JDBC depuis un POOL
        Connection conn = DbManager.getConnection();
 
        // session hibernate à partir de la connection JDBC
        Session session = HibernateUtil.getSession(conn);
 
        Transaction tx = null;
 
        try {
            // début de la transaction
            tx = session.beginTransaction();
 
            int savepointCount = 0;
            for ( Grappe grappe : grappes ) {
 
                // sauvegarde de ma grappe d'objets dans la session
                save(session, grappe);
 
                // envoie des inserts vers la base
                session.flush();
 
                // nettoyage de la session
                // -> évite un OutOfMemoryException dans le cas d'un gros volume de données
                session.clear();
            }
            // fin de la transaction
            tx.commit();
        }
        catch (Throwable t) {
            if ( tx != null )
                tx.rollback();
        }
        finally {
            session.close();
        }
solution 3 : mélange hibernate - JDBC
L'idée est de construire ma session Hibernate avec une connection JDBC déjà établie.
-> j'effectue mon traitement par lot avec un commit à la fin.
-> à l'intérieur de la boucle, j'effectue un savepoint sur la connection JDBC, si l'envoie de mes requêtes sur une grapppe plante, rollback de la connection JDBC vers le dernier savepoint.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 
        // connection JDBC depuis un POOL
        Connection conn = DbManager.getConnection();
 
        // session hibernate à partir de la connection JDBC
        Session session = HibernateUtil.getSession(conn);
 
        Transaction tx = null;
 
        try {
	    // début de la transaction
            tx = session.beginTransaction();
 
            int savepointCount = 0;
 
            for ( Grappe grappe : grappes ) {
 
                // savepoint sur la connection JDBC directement avant chaque sauvegarde de clients
                String savepointName = "save" + savepointCount;
                Savepoint currentSavepoint = conn.setSavepoint(savepointName);
 
                try {
 
                    // sauvegarde de ma grappe d'objets dans la session
                    save(session, grappe);
 
                    // envoie des inserts vers la base
                    session.flush();
                }
                catch (HibernateException ex) {
                    // en cas d'erreur rollback vers le dernier flush réussi
                    conn.rollback(currentSavepoint);
                    System.out.println(ex.getCause().getMessage());
 
                }
                savepointCount++;
 
                // nettoyage de la session
                // -> evite un OutOfMemoryError dans le cas d'un gros volume de données.
                // -> en cas de rollback évite aussi des problèmes d'incohérences entre la session et la base.
                session.clear();
            }
            // fin de la transaction
            tx.commit();
        }
        catch (Throwable t) {
            if ( tx != null )
                tx.rollback();
        }
        finally {
            session.close();
        }

Si au niveau perf c'est bon, je pense partir sur la première solution.

Je suis quand même attiré par la troisième (que j'ai testée et qui fonctionne).
Le seul risque que j'ai relevé est d'avoir une incohérence entre ma session hibernate et la base.
Ex :
J'ai une grappe qui plante à un certain moment. Le rollback se fait sur l'ensemble de la grappe donc pas de soucis d'incohérence au niveau base où je pourrais avoir qu'une partie de la grappe sauvée. Le problème est que je peux avoir des objets qui restent en mémoire au niveau de ma session hibernate...
--> je pense minimiser ce risque en nettoyant ma session à chaque itération (session.clear()).

Mes questions :
- Est-ce politiquement correct d'attaquer la couche JDBC et Hibernate en même temps ?
- En plus de celui déjà relevé, avez-vous identifié d'autres risques dans la troisième solution ?

Merci pour votre lecture et vos réponses.