2015-11-14 32 views
9

這裏是Main.java是否有更簡潔的方式來使用try-with-resource和PreparedStatement?

package foo.sandbox.db; 

import java.sql.Connection; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 

public class Main { 
    public static void main(String[] args) { 
     final String SQL = "select * from NVPAIR where name=?"; 
     try (
       Connection connection = DatabaseManager.getConnection(); 
       PreparedStatement stmt = connection.prepareStatement(SQL); 
       DatabaseManager.PreparedStatementSetter<PreparedStatement> ignored = new DatabaseManager.PreparedStatementSetter<PreparedStatement>(stmt) { 
        @Override 
        public void init(PreparedStatement ps) throws SQLException { 
         ps.setString(1, "foo"); 
        } 
       }; 
       ResultSet rs = stmt.executeQuery() 
     ) { 
      while (rs.next()) { 
       System.out.println(rs.getString("name") + "=" + rs.getString("value")); 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

這裏是DatabaseManager.java

package foo.sandbox.db; 

import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.PreparedStatement; 
import java.sql.SQLException; 
import java.sql.Statement; 

/** 
* Initialize script 
* ----- 
* CREATE TABLE NVPAIR; 
* ALTER TABLE PUBLIC.NVPAIR ADD value VARCHAR2 NULL; 
* ALTER TABLE PUBLIC.NVPAIR ADD id int NOT NULL AUTO_INCREMENT; 
* CREATE UNIQUE INDEX NVPAIR_id_uindex ON PUBLIC.NVPAIR (id); 
* ALTER TABLE PUBLIC.NVPAIR ADD name VARCHAR2 NOT NULL; 
* ALTER TABLE PUBLIC.NVPAIR ADD CONSTRAINT NVPAIR_name_pk PRIMARY KEY (name); 
* 
* INSERT INTO NVPAIR(name, value) VALUES('foo', 'foo-value'); 
* INSERT INTO NVPAIR(name, value) VALUES('bar', 'bar-value'); 
*/ 
public class DatabaseManager { 
    /** 
    * Class to allow PreparedStatement to initialize parmaters inside try-with-resource 
    * @param <T> extends Statement 
    */ 
    public static abstract class PreparedStatementSetter<T extends Statement> implements AutoCloseable { 
     public PreparedStatementSetter(PreparedStatement pstmt) throws SQLException { 
      init(pstmt); 
     } 

     @Override 
     public void close() throws Exception { 
     } 

     public abstract void init(PreparedStatement pstmt) throws SQLException; 
    } 

    /* Use local file for database */ 
    private static final String JDBC_CONNECTION = "jdbc:h2:file:./db/sandbox_h2.db;MODE=PostgreSQL"; 

    static { 
     try { 
      Class.forName("org.h2.Driver"); // Init H2 DB driver 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    /** 
    * @return Database connection 
    * @throws SQLException 
    */ 
    public static Connection getConnection() throws SQLException { 
     return DriverManager.getConnection(JDBC_CONNECTION, "su", ""); 
    } 
} 

我使用H2數據庫簡單,因爲它是一個基於文件的一個,很容易創建和測試上。

所以一切工作和資源得到清理不如預期,但我只是覺得有可能是設定與嘗試,與資源塊內的PreparedStatement參數的更清潔的方式(我不想使用嵌套嘗試/抓住塊看起來'尷尬')。也許在JDBC中已經存在一個助手類來做到這一點,但我一直沒有找到。

最好用一個lambda函數來初始化PreparedStatement,但它仍然需要分配一個AutoCloseable對象,以便它可以在try-with-resources中。

+1

[我應該如何在JDBC中使用try-with-resources?](http://stackoverflow.com/questions/8066501/how-should-i-use-try-with-resources-with-jdbc ) –

+0

我希望找到一種方法來使用lambda代替類實例來完成PreparedStatement的init,就像將參數傳入PreparedStatement一樣。 – AlexC

回答

7

首先,你PreparedStatementSetter類是尷尬:

  • 它是一種類型化的類,但不使用的類型。
  • 該構造函數明確調用一個可覆蓋的方法which is a bad practice

請考慮改用以下接口(受相同名稱的Spring interface啓發)。

public interface PreparedStatementSetter { 
    void setValues(PreparedStatement ps) throws SQLException; 
} 

這個接口定義了一個什麼樣的PreparedStatementSetter是應該做合同:一PreparedStatement的設定值,僅此而已。

然後,最好在單個方法內創建和初始化PreparedStatement。考慮這個除了你DatabaseManager類中:

public static PreparedStatement prepareStatement(Connection connection, String sql, PreparedStatementSetter setter) throws SQLException { 
    PreparedStatement ps = connection.prepareStatement(sql); 
    setter.setValues(ps); 
    return ps; 
} 

有了這個靜態方法,你就可以寫:

try (
    Connection connection = DatabaseManager.getConnection(); 
    PreparedStatement stmt = DatabaseManager.prepareStatement(connection, SQL, ps -> ps.setString(1, "foo")); 
    ResultSet rs = stmt.executeQuery() 
) { 
    // rest of code 
} 

注意如何PreparedStatementSetter用lambda表達式寫在這裏。這是使用接口而不是抽象類的好處之一:在這種情況下,它實際上是一個功能接口(因爲有一個抽象方法),因此可以寫成lambda表達式。

+0

我相信你的代碼有和@Trejkaz在這裏提到的一樣的缺陷:https://stackoverflow.com/questions/8066501/how-should-i-use-try-with-resources-with-jdbc#comment-23568725 - setter.setValues(ps)拋出的異常繞過了本地構造的PreparedStatement的返回,因此PreparedStatement不會被關閉。 – AjahnCharles

2

從@ Tunaki的答案擴展,它也可以因子在嘗試 - 與資源rs.executeQuery()使得DatabaseManager處理這一切爲你,只要求提供SQL,一個PreparedStatementSetterResultSet處理器。

這樣可以避免在查詢的任何位置重複此操作。實際API將取決於您的使用情況 - 例如你會用同一個連接做幾個查詢嗎?

假如你願意,我提出以下建議:

public class DatabaseManager implements AutoCloseable { 

    /* Use local file for database */ 
    private static final String JDBC_CONNECTION = "jdbc:h2:file:./db/sandbox_h2.db;MODE=PostgreSQL"; 

    static { 
     try { 
      Class.forName("org.h2.Driver"); // Init H2 DB driver 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    private final Connection connection; 

    private DatabaseManager() throws SQLException { 
     this.connection = getConnection(); 
    } 

    @Override 
    public void close() throws SQLException { 
     connection.close(); 
    } 

    public interface PreparedStatementSetter { 
     void setValues(PreparedStatement ps) throws SQLException; 
    } 

    public interface Work { 
     void doWork(DatabaseManager manager) throws SQLException; 
    } 

    public interface ResultSetHandler { 
     void process(ResultSet resultSet) throws SQLException; 
    } 

    /** 
    * @return Database connection 
    * @throws SQLException 
    */ 
    private static Connection getConnection() throws SQLException { 
     return DriverManager.getConnection(JDBC_CONNECTION, "su", ""); 
    } 

    private PreparedStatement prepareStatement(String sql, PreparedStatementSetter setter) throws SQLException { 
     PreparedStatement ps = connection.prepareStatement(sql); 
     setter.setValues(ps); 
     return ps; 
    } 

    public static void executeWork(Work work) throws SQLException { 
     try (DatabaseManager dm = new DatabaseManager()) { 
      work.doWork(dm); 
     } 
    } 

    public void executeQuery(String sql, PreparedStatementSetter setter, ResultSetHandler handler) throws SQLException { 
     try (PreparedStatement ps = prepareStatement(sql, setter); 
      ResultSet rs = ps.executeQuery()) { 
      handler.process(rs); 
     } 
    } 
} 

它包裝連接作爲DatabaseManager實例字段,這將處理連接的生命週期,這要歸功於其實施的AutoCloseable

它還定義了2個新的功能接口(另外,以@ Tunaki的PreparedStatementSetter):

  • Work通過executeWork靜態方法
  • ResultSetHandler定義ResultSet必須如何處理定義了一些工作,做一個DatabaseManager當通過新的executeQuery實例方法執行查詢時。

可以使用如下:

final String SQL = "select * from NVPAIR where name=?"; 
    try { 
     DatabaseManager.executeWork(dm -> { 
      dm.executeQuery(SQL, ps -> ps.setString(1, "foo"), rs -> { 
       while (rs.next()) { 
        System.out.println(rs.getString("name") + "=" + rs.getString("value")); 
       } 
      }); 
      // other queries are possible here 
     }); 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 

正如你看到的,你不必擔心資源處理任何 更多。

我離開SQLException在api之外進行處理,因爲您可能想讓它傳播。

該解決方案受Design Patterns in the Light of Lambda Expressions by Subramaniam啓發。

0

我發現這樣做的另一種方式,也可能是有用的人:

PreparedStatementExecutor.java:

/** 
* Execute PreparedStatement to generate ResultSet 
*/ 
public interface PreparedStatementExecutor { 
    ResultSet execute(PreparedStatement pstmt) throws SQLException; 
} 

PreparedStatementSetter.java:

/** 
* Lambda interface to help initialize PreparedStatement 
*/ 
public interface PreparedStatementSetter { 
    void prepare(PreparedStatement pstmt) throws SQLException; 
} 

JdbcTriple.java:

/** 
* Contains DB objects that close when done 
*/ 
public class JdbcTriple implements AutoCloseable { 
    Connection connection; 
    PreparedStatement preparedStatement; 
    ResultSet resultSet; 

    /** 
    * Create Connection/PreparedStatement/ResultSet 
    * 
    * @param sql String SQL 
    * @param setter Setter for PreparedStatement 
    * @return JdbcTriple 
    * @throws SQLException 
    */ 
    public static JdbcTriple create(String sql, PreparedStatementSetter setter) throws SQLException { 
     JdbcTriple triple = new JdbcTriple(); 
     triple.connection = DatabaseManager.getConnection(); 
     triple.preparedStatement = DatabaseManager.prepareStatement(triple.connection, sql, setter); 
     triple.resultSet = triple.preparedStatement.executeQuery(); 
     return triple; 
    } 

    public Connection getConnection() { 
     return connection; 
    } 

    public PreparedStatement getPreparedStatement() { 
     return preparedStatement; 
    } 

    public ResultSet getResultSet() { 
     return resultSet; 
    } 

    @Override 
    public void close() throws Exception { 
     if (resultSet != null) 
      resultSet.close(); 
     if (preparedStatement != null) 
      preparedStatement.close(); 
     if (connection != null) 
      connection.close(); 
    } 
} 

DatabaseManager.java:

/** 
* Initialize script 
* ----- 
* CREATE TABLE NVPAIR; 
* ALTER TABLE PUBLIC.NVPAIR ADD value VARCHAR2 NULL; 
* ALTER TABLE PUBLIC.NVPAIR ADD id int NOT NULL AUTO_INCREMENT; 
* CREATE UNIQUE INDEX NVPAIR_id_uindex ON PUBLIC.NVPAIR (id); 
* ALTER TABLE PUBLIC.NVPAIR ADD name VARCHAR2 NOT NULL; 
* ALTER TABLE PUBLIC.NVPAIR ADD CONSTRAINT NVPAIR_name_pk PRIMARY KEY (name); 
* 
* INSERT INTO NVPAIR(name, value) VALUES('foo', 'foo-value'); 
* INSERT INTO NVPAIR(name, value) VALUES('bar', 'bar-value'); 
*/ 
public class DatabaseManager { 
    /* Use local file for database */ 
    private static final String JDBC_CONNECTION = "jdbc:h2:file:./db/sandbox_h2.db;MODE=PostgreSQL"; 

    static { 
     try { 
      Class.forName("org.h2.Driver"); // Init H2 DB driver 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 

    /** 
    * @return Database connection 
    * @throws SQLException 
    */ 
    public static Connection getConnection() throws SQLException { 
     return DriverManager.getConnection(JDBC_CONNECTION, "su", ""); 
    } 

    /** Prepare statement */ 
    public static PreparedStatement prepareStatement(Connection conn, String SQL, PreparedStatementSetter setter) throws SQLException { 
     PreparedStatement pstmt = conn.prepareStatement(SQL); 
     setter.prepare(pstmt); 
     return pstmt; 
    } 

    /** Execute statement */ 
    public static ResultSet executeStatement(PreparedStatement pstmt, PreparedStatementExecutor executor) throws SQLException { 
     return executor.execute(pstmt); 
    } 
} 

Main.java:

public class Main { 
    public static void main(String[] args) { 
     final String SQL = "select * from NVPAIR where name=?"; 
     try (
      JdbcTriple triple = JdbcTriple.create(SQL, pstmt -> { pstmt.setString(1, "foo"); }) 
     ){ 
      while (triple.getResultSet().next()) { 
       System.out.println(triple.getResultSet().getString("name") + "=" + triple.getResultSet().getString("value")); 
      } 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 

雖然這不處理,你可能需要從插入或交易返回一個ID的情況下,它確實提供了一個快速的方法運行一個查詢,設置參數並獲得一個ResultSet,在我的情況下這是大量的DB代碼。

相關問題