jeudi 18 février 2010

Mode curseur et transactions avec JDBC (PostgresQL)

Lorsque l'on travaille en mode curseur sous JDBC, le mode auto-commit du connecteur est nécessairement dévalidé:

connection.setAutoCommit(false);

La conséquence est que le postmaster garde en mémoire le log des requêtes.
Si l'on fait plusieurs requêtes avec le même connecteur JDBC, on reste connecté au même Postmaster et ce dernier explose en mémoire.

J'ai essayé d'insérer des commits entre les requêtes, mais cela a produit l'erreur suivante:

DECLARE CURSOR pourrait seulement être utilisé dans des blocs de transaction

J'avoue en pas très bien comprendre le sens de ce message et je me suis résolu à créer un nouveau connecteur à chaque requête en mode curseur:


public static ResultSet runLargeQuerySQL(String sql) throws FatalException {
try {
closeLargeQueryConnection();
jdbc_large_connection = DriverManager.getConnection(Database.getConnector().getJdbc_url(),Database.getConnector().getJdbc_reader(), Database.getConnector().getJdbc_reader_password());
Statement _stmts =jdbc_large_connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
jdbc_large_connection.setAutoCommit(false);
_stmts.setFetchSize(1000);
if( Messenger.debug_mode ) Messenger.printMsg(Messenger.DEBUG, "Select large query: " + sql);
/*
* Trailing semicolumns make the statement to run in non-cursor mode
*/
return _stmts.executeQuery(sql.replaceAll(";", ""));
} catch (Exception e) {
Messenger.printMsg(Messenger.ERROR, "Query: " + sql);
Messenger.printStackTrace(e);
FatalException.throwNewException(SaadaException.DB_ERROR, e);
}
return null;
}

/**
* @throws SQLException
*/
public static void closeLargeQueryConnection() throws SQLException {
if( jdbc_large_connection != null ) {
if( Messenger.debug_mode ) Messenger.printMsg(Messenger.DEBUG, "Close connection for large queries ");
jdbc_large_connection.close();
jdbc_large_connection = null;
}

}

mercredi 17 février 2010

Quand le mode curseur de JDBC ne fonctionne pas sur PostgresQL

Imaginer la requete suivante sur une grosse table (10.000.000 lignes):

SELECT oidprimary,oidsecondary FROM Counterparts

Comme vous ne voulez pas que votre application Java vous fasse un OutOfMemoryError, vous traitez votre requête en mode curseur (voir ici)):

connection().setAutoCommit(false);
Statement _stmts = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_READ_ONLY);
_stmts.setFetchSize(1000);
ResultSet rs = _stmts.executeQuery("SELECT oidprimary,oidsecondary FROM Counterparts");
....

Et ca marche, mais attention, si à la suite d'un couper/coller malicieux par exemple, votre requête se voit suivie d'un ";", vous risquez de vous retrouver avec cette erreur:

Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space

En effet, le pilote JDBC de PostgresQL considère alors que votre requête comporte une suite de requêtes séparées par des ";" et dans ce cas il quitte le mode curseur et tente de recopier tout les résultat en mémoire.