2009-12-15 15 views
17

Sembra che lo ResultSet si chiuda automaticamente quando chiudo lo Connection. Ma voglio restituire il ResultSet e usarlo in un altro metodo, quindi non so dove chiudere Connection e PreparedStatement.Dove chiudere una connessione JDBC mentre voglio restituire il ResultSet

public ResultSet executeQuery(String sql, String[] getValue) 
{ 
    Connection conn = null; 
    PreparedStatement pstmt = null; 
    ResultSet rs = null; 
    try 
    { 
     conn = getConn(); 
     pstmt = conn.prepareStatement(sql); 
     if (getValue != null) 
     { 
      for (int i = 0; i < getValue.length; i++) 
      { 
       pstmt.setString(i + 1, getValue[i]); 
      } 
     } 
     rs = pstmt.executeQuery(); 
    } catch (Exception e) 
    { 
     e.printStackTrace(); 
     closeAll(conn, pstmt, rs); 
    } 
    return rs; 
} 

ho spostato closeAll(conn, pstmt, null); in blocco catch perché ho scoperto che se ho messo nel blocco finally sarò perso la mia rs subito poco prima che ritorni. Ora, quando voglio chiudere lo rs, non riesco a chiudere lo conn e lo pstmt. C'è qualche soluzione?

+0

Si sta tentando di trasmettere il resultSet, vale a dire evitare la semplice lettura di tutti i risultati in una sorta di raccolta e di ritorno che? –

+6

Fuori tema, perché tutti usano questo stile? !!! Questo è Java non C# – OscarRyz

+0

Grazie a tutti voi, cordiali saluti! È quasi mattina in Cina, ma sono troppo toccato da voi e dalle vostre brillanti risposte per dormire. Questa è la mia prima domanda su Stackoverflow.com. Apprezzo molto il vostro aiuto. Sarò un frequentatore qui! – Aloong

risposta

30

Usa CachedRowSet per lo svolgimento di informazioni dopo aver staccato

Connection con = ... 
ResultSet rs = ... 

CachedRowSet rowset = new CachedRowSetImpl(); 
rowset.populate(rs); 

con.close() 
+0

Dopo aver letto l'API di CachedRowSet, ho trovato questo sembra il modo più semplice. Penso che prenderò questa risposta. – Aloong

3

In questo modo, la connessione non si chiuderà mai e causerebbe problemi in seguito (se non immediatamente) per il programma e l'RDBMS. Sarebbe meglio creare una classe Java per contenere i campi dal ResultSet e restituirlo. Il ResultSet è collegato alla connessione, quindi non è possibile restituirlo e chiudere la connessione.

20

Un modo pulito di codificare questo è passare un oggetto che ha un metodo di callback che accetta un set di risultati.

L'altro metodo crea l'oggetto con il metodo di callback con il suo codice di gestione resultSet e lo passa al metodo che esegue l'SQL.

In questo modo, il codice SQL & SQL rimane al suo posto, la logica di gestione dei set di risultati è più vicina a dove si utilizzano i dati e il codice SQL ripulisce quando necessario.

interface ResultSetCallBack{ 
    void handleResultSet(ResultSet r); 
    } 

    void executeQuery(..., ResultSetCallBack cb){ 
    //get resultSet r ... 
    cb.handleResultSet(r); 
    //close connection 
    } 

    void printReport(){ 
    executeQuery(..., new ResultSetCallBack(){ 
     public void handleResultSet(ResultSet r) { 
     //do stuff with r here 
     } 
    }); 
    } 
1

davvero non dovrebbe maneggiare con JDBC al livello inferiore. Utilizzare invece un framework come spring, che gestirà tutte le operazioni necessarie per close().

+0

Questo è un ottimo punto. Utilizzare una struttura come la primavera ti impedisce di dover reinventare la ruota tutto il tempo e concentrarti sulla costruzione dell'auto. –

+2

Sfortunatamente, impostare Spring su un nuovo progetto senza alcuna esperienza precedente è praticamente destinato a fallire. – Bombe

+0

davvero. JDBC non è sempre male. – Bozho

3

Non è possibile utilizzare ResultSet dopo aver chiuso Connection e/o PreparedStatement. Quindi, è necessario passare un oggetto su cui effettuare una richiamata in questo metodo.

Tutte le operazioni di pulizia devono essere eseguite nei blocchi finally.

riscriverlo come questo

public ResultSet executeQuery(
    String sql, 
    String[] getValue, 
    CallbackObj cbObj 
) throws SQLException 
{ 
    final Connection conn = getConn(); 

    try 
    { 
    final PreparedStatement pstmt = conn.prepareStatement(sql); 

    try 
    { 
     if (getValue != null) 
     { 
     for (int i = 0; i < getValue.length; i++) 
     { 
      pstmt.setString(i + 1, getValue[i]); 
     } 
     } 

     final ResultSet rs = pstmt.executeQuery(); 

     try 
     { 
     cbObj.processResultSet(rs); 
     } 
     finally 
     { 
     // You may want to handle SQLException 
     // declared by close 
     rs.close(); 
     } 
    } 
    finally 
    { 
     // You may want to handle SQLException 
     // declared by close 
     pstmt.close(); 
    } 
    } 
    finally 
    { 
    // You may want to handle SQLException 
    // declared by close 
    conn.close(); 
    } 
} 
4

si dovrebbe mai passaggio ResultSet (o Statement o Connection) nel pubblico al di fuori del blocco di metodo in cui essi devono essere acquisite e chiusi per evitare perdite di risorse. Una pratica comune è solo per mappare il ResultSet su un List<Data> dove Data è solo un oggetto javabean che rappresenta i dati di interesse.

Ecco un esempio di base:

public class Data { 
    private Long id; 
    private String name; 
    private Integer value; 
    // Add/generate public getters + setters. 
} 

ed ecco un esempio di base di come gestire correttamente:

public List<Data> list() throws SQLException { 
    Connection connection = null; 
    PreparedStatement statement = null; 
    ResultSet resultSet = null; 
    List<Data> list = new ArrayList<Data>(); 

    try { 
     connection = database.getConnection(); 
     statement = connection.prepareStatement("SELECT id, name, value FROM data"); 
     resultSet = statement.executeQuery(); 
     while (resultSet.next()) { 
      Data data = new Data(); 
      data.setId(resultSet.getLong("id")); 
      data.setName(resultSet.getString("name")); 
      data.setValue(resultSet.getInt("value")); 
      list.add(data); 
     } 
    } finally { 
     if (resultSet != null) try { resultSet.close(); } catch (SQLException logOrIgnore) {} 
     if (statement != null) try { statement.close(); } catch (SQLException logOrIgnore) {} 
     if (connection != null) try { connection.close(); } catch (SQLException logOrIgnore) {} 
    } 

    return list; 
} 

è possibile utilizzarlo come segue:

List<Data> list = dataDAO.list(); 

Per per saperne di più sulle best practice con JDBC, potresti trovare utile anche this basic kickoff article.

+0

Ciò richiede di archiviare tutto in memoria. Quando hai molte righe, a meno che non intendi conservare i dati in memoria, è davvero interessante passare un set di righe non letti a qualsiasi cosa tu voglia fare, sia che lo scriva su un file, lo filtri o calcoli una somma, e consumarlo mentre lo elabori. –

+0

@FlorianF Basta usare LIMIT/OFFSET. – BalusC

+0

Questo introduce molta complessità, moltiplica gli accessi al database e rompe l'atomicità della richiesta. È possibile ottenere duplicati perché una riga è stata eliminata in cima all'elenco tra due richieste "slice". –

3

È possibile chiamare ResultSet.getStatement per recuperare Statement e Statement.getConnection per recuperare Connection.

Da questi è possibile scrivere un metodo di utilità closeResultSet che chiuderà tutti e 3 per voi, dato nient'altro che il ResultSet.

1

mi consiglia di fare qualcosa di più simile a questo:

public List<Map> executeQuery(Connection connection, String sql) throws SQLException 
{ 
    List<Map> rows = new ArrayList<Map>(); 

    PreparedStatement stmt = null; 
    ResultSet rs = null; 

    try 
    { 
     pstmt = conn.prepareStatement(sql); 
     rs = stmt.execute(); 
     int numColumns = rs.getMetaData().getColumnCount(); 

     while (rs.next()) 
     { 
      Map<String, Object> row = new LinkedHashMap<String, Object>(); 
      for (int i = 0; i < numColumns; ++i) 
      { 
       String column = rs.getColumnName(i+1); 
       Object value = rs.getObject(i+1); 
       row.put(column, value); 
      } 
      rows.add(row); 
     } 
    } 
    finally 
    { 
     close(rs); 
     close(stmt); 
    } 

    return rows; 
} 

public static void close(Statement s) 
{ 
    try 
    { 
     if (s != null) 
     { 
      s.close(); 
     } 
    } 
    catch (SQLException e) 
    { 
     e.printStackTrace(); 
    } 
} 

public static void close(ResultSet rs) 
{ 
    try 
    { 
     if (rs != null) 
     { 
      rs.close(); 
     } 
    } 
    catch (SQLException e) 
    { 
     e.printStackTrace(); 
    } 
} 
3

Dove chiudere una connessione JDBC, mentre io voglio tornare il ResultSet

In realtà, hai quasi risposto a questa domanda da solo. Come hai sperimentato, la chiusura dello Connection rilascerà le risorse JDBC ad esso associate (almeno, questo è il modo in cui le cose dovrebbero funzionare). Quindi, se vuoi restituire un ResultSet (tornerò su questo più tardi), devi chiudere la connessione "più tardi". Un modo per fare questo sarebbe ovviamente quello di passare una connessione con il metodo, qualcosa di simile:

public ResultSet executeQuery(Connection conn, String sql, String[] getValue); 

Il problema è che io non so davvero che cosa è il vostro obiettivo finale e perché avete bisogno di roba così basso livello quindi non sono sicuro che questo sia un buon consiglio. A meno che non si stia scrivendo un framework JDBC di basso livello (e, per favore, non dirmi che non lo stai facendo), in realtà non consiglio di restituire uno ResultSet. Ad esempio, se si desidera alimentare un po 'di business class, restituire alcuni oggetti indipendenti da JDBC o una loro raccolta come altri hanno consigliato invece di un ResultSet. Tieni anche presente che uno RowSetè a ResultSet quindi se non dovresti usare un ResultSet allora non dovresti usare un RowSet.

Personalmente, penso che dovresti usare qualche classe di supporto invece di reinventare la ruota. Mentre Spring potrebbe essere eccessivo e ha un po 'di curve di apprendimento (troppo se non lo sai affatto), Spring non è l'unica strada da percorrere e consiglio vivamente di dare un'occhiata a Commons DbUtils. Più in particolare, guarda QueryRunner e soprattutto questa query() metodo:

public <T> T query(String sql, 
        ResultSetHandler<T> rsh, 
        Object... params) 
     throws SQLException 

Come si può vedere, questo metodo consente di passare un ResultSetHandler che espone un metodo di callback per convertire ResultSets in altri oggetti, come descritto nella z5h's answer e DbUtils fornisce diversi implementazioni, prendi semplicemente quello che si adatta alle tue esigenze. Dai anche un'occhiata ai metodi di utilità della classe DbUtils, ad esempio i vari DbUnit.close() che potresti trovare utile per chiudere le risorse JDBC.

Davvero, a meno che non si abbiano ottime ragioni per farlo (e sarei curioso di conoscerle), non scrivere ancora un altro framework JDBC, utilizzare una soluzione esistente, vi farà risparmiare un po 'di dolore e, più importante, alcuni bug e trarrai vantaggio da un buon design comprovato. Anche per cose di basso livello, ci sono soluzioni esistenti (e semplici) come abbiamo visto. Almeno, dai un'occhiata.

3

Il modo più pulito è utilizzare CachedRowSetImpl. Ma su MySQL 5.x + ci sono alcuni bug nella selezione delle colonne per nome o etichetta.

Per l'uso con MySQL utilizzare questa versione: https://stackoverflow.com/a/17399059/1978096

Problemi correlati