2015-11-14 12 views
9

Đây là Main.java:Có cách nào tốt hơn để sử dụng try-with-resource và PreparedStatement không?

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(); 
     } 
    } 
} 

Và đây là 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", ""); 
    } 
} 

Tôi đang sử dụng cơ sở dữ liệu H2 vì đơn giản vì nó là một tập tin dựa một trong đó là dễ dàng để tạo và thử nghiệm trên.

Vì vậy, mọi thứ hoạt động và tài nguyên được dọn sạch như mong đợi, tuy nhiên tôi chỉ cảm thấy có thể có cách sạch hơn để đặt tham số PreparedStatement từ bên trong khối try-with-resources (và tôi không muốn sử dụng lồng nhau) thử/nắm bắt các khối như những cái nhìn 'lúng túng'). Có lẽ đã tồn tại một lớp trợ giúp trong JDBC mà chỉ thực hiện điều này, nhưng tôi đã không thể tìm ra được nó.

Tốt hơn là với hàm lambda để khởi tạo PreparedStatement nhưng vẫn yêu cầu phân bổ đối tượng AutoCloseable để nó có thể nằm trong tài nguyên thử.

+1

Bản sao có thể có của [Làm cách nào tôi nên sử dụng try-with-resources với JDBC?] (Http://stackoverflow.com/questions/8066501/how-should-i-use-try-with-resources-with-jdbc) –

+0

Tôi hy vọng sẽ tìm cách để thực hiện init cho PreparedStatement bằng cách sử dụng lambda thay vì một cá thể lớp, gần giống như truyền các tham số vào PreparedStatement. – AlexC

Trả lời

7

tắt Thứ nhất, lớp PreparedStatementSetter của bạn là vụng về:

  • nó là một lớp đánh máy nhưng loại không được sử dụng.
  • hàm tạo đang gọi phương thức rõ ràng là which is a bad practice.

Thay vào đó hãy xem xét giao diện sau (lấy cảm hứng từ số Spring interface cùng tên).

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

Giao diện này định nghĩa một hợp đồng về những gì một PreparedStatementSetter là nghĩa vụ phải làm: thiết lập giá trị của một PreparedStatement, không có gì hơn.

Sau đó, sẽ tốt hơn nếu thực hiện tạo và khởi tạo PreparedStatement bên trong một phương thức. Xem xét bổ sung này bên trong lớp DatabaseManager của bạn:

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

Với phương pháp tĩnh này, sau đó bạn có thể viết:

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

Thông báo như thế nào PreparedStatementSetter được viết ở đây với một biểu thức lambda. Đó là một trong những lợi thế của việc sử dụng một giao diện thay vì một lớp trừu tượng: nó thực sự là một giao diện chức năng trong trường hợp này (vì có một phương thức trừu tượng đơn) và do đó có thể được viết dưới dạng lambda.

+0

Tôi tin rằng mã của bạn có cùng một lỗ hổng mà @Trejkaz đề cập ở đây: https://stackoverflow.com/questions/8066501/how-should-i-use-try-with-resources-with-jdbc#comment-23568725 - rằng một ngoại lệ được ném bởi setter.setValues ​​(ps) bỏ qua sự trở lại của PreparedStatement được xây dựng cục bộ, do đó không được đóng lại. – AjahnCharles

2

Mở rộng từ @ câu trả lời Tunaki, nó cũng có thể yếu tố trong các try-với-nguồnrs.executeQuery() như vậy mà DatabaseManager xử lý tất cả những điều này cho bạn và chỉ yêu cầu đối với SQL, một PreparedStatementSetter và một handler ResultSet .

Điều này sẽ tránh lặp lại điều này ở mọi nơi bạn thực hiện truy vấn. Tuy nhiên, API thực tế sẽ phụ thuộc vào việc sử dụng của bạn - ví dụ: bạn sẽ thực hiện một số truy vấn với cùng một kết nối?

Giả sử bạn sẽ, tôi đề xuất như sau:

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); 
     } 
    } 
} 

Nó kết thúc tốt đẹp các kết nối như một lĩnh vực thể hiện của DatabaseManager, mà sẽ xử lý vòng đời của kết nối, nhờ vào việc thực hiện của AutoCloseable.

Nó cũng định nghĩa 2 giao diện chức năng mới (bổ sung để @PreparedStatementSetter Tunaki của):

  • Work định nghĩa một số việc phải làm với một DatabaseManager thông qua phương thức executeWork tĩnh
  • ResultSetHandler định nghĩa như thế nào ResultSet phải được xử lý khi thực hiện truy vấn thông qua phương thức cá thể executeQuery mới.

Nó có thể được sử dụng như sau:

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(); 
    } 

Như bạn thấy, bạn không cần phải lo lắng về việc xử lý bất kỳ nhiều tài nguyên.

Tôi để lại SQLException xử lý bên ngoài api vì bạn có thể muốn cho phép tuyên truyền.

Giải pháp này được lấy cảm hứng từ Design Patterns in the Light of Lambda Expressions by Subramaniam.

0

tôi tìm thấy một cách khác để làm điều này mà có thể hữu ích với mọi người:

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(); 
     } 
    } 
} 

Trong khi điều này không xử lý trường hợp bạn có thể cần phải trả lại một ID từ chèn hoặc giao dịch, nó cung cấp một cách nhanh chóng để chạy một truy vấn, thiết lập các tham số và nhận được một ResultSet, mà trong trường hợp của tôi là số lượng lớn mã DB.

Các vấn đề liên quan