Bonjour à tous,

Pour mon premier post j'aimerais vous soumettre un bug qui méritera surement un coup de règle sur les doigts tellement c'était simple.

But du code : Copier la totalité du contenu d'une table dans une base Oracle nommée 'bve' vers une table presque identique dans une base DB2 nommée 'xms'.
Pour ce faire je crée un PreparedStatement du type 'insert into foobar(a,b,c...) values (?,?,?...)', et je boucle sur le resultat du 'select *' de la table source.
A chaque tour je fais un setXxx sur chacun des '?', et quand mon insert est prèt à être exécuté je l'ajoute à un batch. Au bout de 100, je les execute, commite, et on est repartis pour un prochain lot de 100.

Problème : Là où ça se gâte, c'est que j'ai 131 000 records de 50 colonnes à copier. Je me ramasse une 'java.lang.OutOfMemoryError: Java heap space' au bout de 29 100 records.

Une solution est d'augmenter la mémoire de la JMV mais ce n'est que repousser le problème à plus tard. Ce n'est pas réellement une solution car au fur et à mesure les batch mettent plus de temps à s'executer.

Est ce que qqun voit une faute flagrante dans mon algo ? Connaissez vous une meilleure façon de traiter le besoin ?
J'ai essayé de setter à null insertStatement juste apres avoir commité puis d'appeler System.gc() pour tenter de nettoyer la memoire mais rien n'y fait.

Merci pour votre clairvoyance.

SuperFoieGras


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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
 
// Source est le nom de la table source, target celui de la table cible
// bve est la connection de la base source, xms celle de la base destination
    public void transfer(String source, String target) throws DataControllerException {
        String sqlRequest, insertRequest;
        // <bveColumnName, xmsColumnName>
        Map<String, String> mapping = new HashMap<String, String>();
        // <columnName, columnDataType>
        Map<String, Integer> xmsColumnTypes = new HashMap<String, Integer>();
        // <columnName, columnNumber>
        Map<String, Integer> xmsColumnOrder = new HashMap<String, Integer>();
 
        PreparedStatement ps = null;
        int statementsCount = 0;
        int totalCount = 0;
        int batchNumber = 1;
        ResultSet rs = null;
        try {
            // loading mapping
            mapping = getColumsMapping(xms, source, target);
            // loading xms columns types
            xmsColumnTypes = getColumnTypes(target, xms);
            // loading columns order
            xmsColumnOrder = getColumnOrder(mapping);
            // building select * from source on bve database
            sqlRequest = createSelectRequest(source, mapping);
            // building insert into target on xms database
            insertRequest = createInsertSql(target, mapping);
 
	    // insertStatement est un PreparedStatement chargé avec une requete 'insert into foobar(a,b,c...) values (?,?,?...)'
            insertStatement = xms.prepareStatement(insertRequest);
	    // ps un PreparedStatement
            ps = bve.prepareStatement(sqlRequest, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
	    // rs est un énorme ResultSet de 131 000 lignes de 50 colonnes resultant d'un 'select * from ...'
            rs = ps.executeQuery();
            rs.beforeFirst();
            long lastExec = System.currentTimeMillis();
            long now;
            while (rs.next()) {
                // Cette méthode prend les valeurs de la ligne courante pour binder les ? 
		// du PreparedStatement insertStatement avec les valeurs de rs. Ensuite j'ajoute au batch.
                addInsertToBatch(rs, xmsColumnTypes, mapping, xmsColumnOrder);
                statementsCount++;
                totalCount++;
		// tous les MAX_BATCH_SIZE (100), j'execute mon batch et je commite.
                if (statementsCount == MAX_BATCH_SIZE) {
                    insertStatement.executeBatch();
                    xms.commit();
                    insertStatement.clearBatch();
                    //insertStatement = null;
                    //System.gc();
                    //insertStatement = xms.prepareStatement(insertRequest);
                    now = System.currentTimeMillis();
                    log.finest("Executed and commited " + MAX_BATCH_SIZE + " records batch number " + batchNumber + " in " + (now - lastExec) + "ms");
                    lastExec = now;
                    batchNumber++;
                    statementsCount = 0;
                }
            }
            // some statements left for last batch ?
            if (statementsCount > 0) {
                insertStatement.executeBatch();
            }
            log.fine(totalCount + " rows transfered");
        } catch blabla...
    }
 
 
    public void addInsertToBatch(ResultSet rs, Map<String, Integer> columnTypes, Map<String, String> mapping, Map<String, Integer> columnOrder)
            throws DataControllerException {
	// peut etre une mauvaise idée de les déclarer ici 
	// alors que cette methode est appelée 130 000 fois ?
        String xmsColumn;
        String bveColumn;
        int index;
        int type;
        try {
	// Cette map sert a connaitre la correspondance entre nom de colonne du ResultSet et nom de colonne à insérer
            Iterator<Map.Entry<String, String>> it = mapping.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, String> pairs = (Map.Entry<String, String>) it.next();
                xmsColumn = pairs.getValue();
                bveColumn = pairs.getKey();
		// columnTypes sert a connaitre le type de la colonne à inserer
                type = columnTypes.get(xmsColumn);
		// columnOrder sert a connaitre l'index de la colonne dans le PreparedStatement (à quel '?' la colonne correspond)
                index = columnOrder.get(xmsColumn);
		// Si la donnee est nulle je set un null
                if (rs.getObject(bveColumn) == null) {
                    insertStatement.setNull(index, type);
                } else {
			// sinon selon le type je fais un setXxx
                    switch (type) {
                        case Types.BIGINT:
                        case Types.INTEGER:
                        case Types.SMALLINT:
                        case Types.TINYINT:
                            insertStatement.setInt(index, new Integer(rs.getInt(bveColumn)));
                            break;
                        case Types.BOOLEAN:
                            insertStatement.setBoolean(index, new Boolean(rs.getBoolean(bveColumn)));
                            break;
                        case Types.DECIMAL:
                            insertStatement.setBigDecimal(index, rs.getBigDecimal(bveColumn));
                            break;
                        case Types.DOUBLE:
                            insertStatement.setDouble(index, new Double(rs.getDouble(bveColumn)));
                            break;
                        case Types.FLOAT:
                            insertStatement.setFloat(index, new Float(rs.getFloat(bveColumn)));
                            break;
                        case Types.TIMESTAMP:
                            insertStatement.setTimestamp(index, rs.getTimestamp(bveColumn));
                            break;
                        case Types.TIME:
                        case Types.DATE:
                            insertStatement.setDate(index, rs.getDate(bveColumn));
                            break;
                        default:
                            insertStatement.setString(index, new String(rs.getString(bveColumn)));
                            break;
                    }
                }
            }
	// J'ajoute mon 'insert into ...' tout prêt au batch
            insertStatement.addBatch();
        } catch blabla...
    }