web-dev-qa-db-fra.com

Où fermer Java PreparedStatements et ResultSets?

Considérez le code:

PreparedStatement ps = null;
ResultSet rs = null;
try {
  ps = conn.createStatement(myQueryString);
  rs = ps.executeQuery();
  // process the results...
} catch (Java.sql.SQLException e) {
  log.error("an error!", e);
  throw new MyAppException("I'm sorry. Your query did not work.");
} finally {
  ps.close();
  rs.close();
}

Ce qui précède ne se compile pas, car PreparedStatement.close() et ResultSet.close() lancent un Java.sql.SQLException. Alors est-ce que j'ajoute un bloc try/catch à la clause finally? Ou déplacer les déclarations fermées dans la clause try? Ou tout simplement pas pris la peine d'appeler près?

35
MCS

Pour les E/S de fichiers, j'ajoute généralement un try/catch au bloc finally. Cependant, vous devez faire attention à ne pas lever d'exceptions du bloc finally, car elles entraîneront la perte de l'exception d'origine (le cas échéant).

Voir cet article pour un exemple plus spécifique de fermeture de connexion à la base de données.

9
Michael Myers

Dans Java 7, vous ne devez pas les fermer explicitement, mais utilisez gestion automatique des ressources pour vous assurer que ressources sont fermées et que les exceptions sont gérées de manière appropriée La gestion des exceptions fonctionne comme ceci:

 Exception lors d'un essai | Exception en fin | Résultat 
 ----------------- + -------------------- + ----- ----------------------------------- 
 Non | Non | Continuez normalement 
 Non | Oui | Lancez l'exception close () 
 Oui | Non | Jetez l'exception du bloc try 
 Oui | Oui | Ajouter l'exception close () à l'exception principale 
 | | comme "supprimé", lève l'exception principale 

J'espère que cela a du sens. En permet un joli code, comme ceci:

private void doEverythingInOneSillyMethod(String key)
  throws MyAppException
{
  try (Connection db = ds.getConnection()) {
    db.setReadOnly(true);
    ...
    try (PreparedStatement ps = db.prepareStatement(...)) {
      ps.setString(1, key);
      ...
      try (ResultSet rs = ps.executeQuery()) {
        ...
      }
    }
  } catch (SQLException ex) {
    throw new MyAppException("Query failed.", ex);
  }
}

Avant Java 7, il est préférable d'utiliser des blocs nested enfin, plutôt que de tester les références pour null.

L'exemple que je vais montrer peut sembler laid avec l'imbrication profonde, mais dans la pratique, un code bien conçu ne va probablement pas créer une connexion, une instruction et des résultats dans la même méthode; souvent, chaque niveau d'imbrication implique le passage d'une ressource à une autre méthode, qui l'utilise comme fabrique pour une autre ressource. Avec cette approche, les exceptions d'une close() masqueront une exception de l'intérieur du bloc try. Cela peut être surmonté, mais il en résulte un code encore plus compliqué et nécessite une classe d'exceptions personnalisée qui fournit le chaînage d'exceptions "supprimé" présent dans Java 7.

Connection db = ds.getConnection();
try {
  PreparedStatement ps = ...;
  try {
    ResultSet rs = ...
    try {
      ...
    }
    finally {
      rs.close();
    }
  } 
  finally {
    ps.close();
  }
} 
finally {
  db.close();
}
45
erickson

Si vous faites vraiment rouler votre propre jdbc à la main, cela devient vraiment désordonné. Le close () dans le enfin doit être enveloppé de son propre catch catch, qui, à tout le moins, est moche. Vous ne pouvez pas ignorer la fermeture, bien que les ressources soient effacées lorsque la connexion est fermée (ce qui pourrait ne pas être le cas immédiatement, si vous utilisez un pool). En fait, l'un des principaux arguments de vente d'utiliser un framework (par exemple hibernate) pour gérer votre accès db est de gérer la connexion et la gestion de l'ensemble de résultats afin de ne pas oublier de fermer.

Vous pouvez faire quelque chose de simple comme ça, qui cache au moins le gâchis et garantit que vous n'oublierez pas quelque chose.

public static void close(ResultSet rs, Statement ps, Connection conn)
{
    if (rs!=null)
    {
        try
        {
            rs.close();

        }
        catch(SQLException e)
        {
            logger.error("The result set cannot be closed.", e);
        }
    }
    if (ps != null)
    {
        try
        {
            ps.close();
        } catch (SQLException e)
        {
            logger.error("The statement cannot be closed.", e);
        }
    }
    if (conn != null)
    {
        try
        {
            conn.close();
        } catch (SQLException e)
        {
            logger.error("The data source connection cannot be closed.", e);
        }
    }

}

puis,

finally {
    close(rs, ps, null); 
}
25
Steve B.

Ne perdez pas votre temps à coder la gestion des exceptions de bas niveau, utilisez une API de niveau supérieur comme Spring-JDBC, ou un wrapper personnalisé autour des objets connection/statement/rs, pour masquer le code monté par try-catch désordonné.

7
BraveSirFoobar

J'ai généralement une méthode utilitaire qui peut fermer des choses comme celle-ci, notamment en prenant soin de ne rien essayer avec une référence nulle.

Habituellement, si close() lève une exception, je ne m'en soucie pas, alors je journalise l'exception et l'avale - mais une autre alternative serait de la convertir en RuntimeException. Quoi qu'il en soit, je recommande de le faire dans une méthode utilitaire facile à appeler, car vous devrez peut-être le faire à de nombreux endroits.

Notez que votre solution actuelle ne fermera pas le ResultSet si la fermeture du PreparedStatement échoue - il est préférable d'utiliser des blocs nested enfin.

5
Jon Skeet

Si vous utilisez Java 7, vous pouvez utiliser les améliorations des mécanismes de gestion des exceptions dans les classes qui implémentent AutoCloseable (c'est-à-dire PreparedStatement, Resultset)

Vous pouvez également trouver cette question intéressante: Closing ResultSet in Java 7

2
Guido

Je sais que c'est une vieille question, mais juste au cas où quelqu'un chercherait la réponse, Java a maintenant la solution try-with-resouce.

static String readFirstLineFromFile(String path) throws IOException {
      try (BufferedReader br =
                   new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}
1
Xin

Probablement une ancienne (mais simple) façon de faire les choses, mais cela fonctionne toujours:

public class DatabaseTest {

    private Connection conn;    
    private Statement st;   
    private ResultSet rs;
    private PreparedStatement ps;

    public DatabaseTest() {
        // if needed
    }

    public String getSomethingFromDatabase(...) {
        String something = null;

        // code here

        try {
            // code here

        } catch(SQLException se) {
            se.printStackTrace();

        } finally { // will always execute even after a return statement
            closeDatabaseResources();
        }

        return something;
    }

    private void closeDatabaseResources() {
        try {
            if(conn != null) {
                System.out.println("conn closed");
                conn.close();
            }

            if(st != null) {
                System.out.println("st closed");
                st.close();
            }

            if(rs != null) {
                System.out.println("rs closed");
                rs.close();
            }

            if(ps != null) {
                System.out.println("ps closed");
                ps.close();
            }

        } catch(SQLException se) {
            se.printStackTrace();
        }               
    }
}
0
silver

En s'appuyant sur la réponse de @ erickson, pourquoi ne pas simplement le faire dans un bloc try comme celui-ci?

private void doEverythingInOneSillyMethod(String key) throws MyAppException
{
  try (Connection db = ds.getConnection();
       PreparedStatement ps = db.prepareStatement(...)) {

    db.setReadOnly(true);
    ps.setString(1, key);
    ResultSet rs = ps.executeQuery()
    ...
  } catch (SQLException ex) {
    throw new MyAppException("Query failed.", ex);
  }
}

Notez que vous n'avez pas besoin de créer l'objet ResultSet à l'intérieur du bloc try car les ResultSet sont automatiquement fermés lorsque l'objet PreparedStatement est fermé.

Un objet ResultSet est automatiquement fermé lorsque l'objet Statement qui l'a généré est fermé, réexécuté ou utilisé pour récupérer le résultat suivant à partir d'une séquence de plusieurs résultats.

Référence: https://docs.Oracle.com/javase/7/docs/api/Java/sql/ResultSet.html

0
Catfish

N'oubliez pas d'appeler close. Cela peut provoquer des problèmes.

Je préfère ajouter le bloc try/catch au enfin.

0
asalamon74