[JavaWeb]關於DBUtils中QueryRunner的一些解讀.


前言:
[本文屬於原創分享文章, 轉載請注明出處, 謝謝.]
前面已經有文章說了DBUtils的一些特性, 這里再來詳細說下QueryRunner的一些內部實現, 寫的有錯誤的地方還懇請大家指出. 

QueryRunner

QueryRunner中提供對sql語句操作的API
它主要有三個方法
  query() 用於執行select
  update() 用於執行insert/update/delete
  batch() 批處理

1,Query語句
先來看下query的兩種形式, 我們這里主要講第一個方法, 因為我們用C3P0來統一管理connection.(QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()))
query(sql,ResultSetHandler,Object...params);
query(conn,sql,ResultSetHandler,Object...params);

第一種: 不需要params

//查詢所有圖書
public List<Book> selectAllBooks() throws SQLException {
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
    return qr.query("select * from books", new BeanListHandler<Book>(Book.class));
}

 

第二種: 需要一個參數查詢

//根據id查詢指定的書
public Book selectBookById(String id) throws SQLException {
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
    return qr.query("select * from books where id=?", new BeanHandler(Book.class),id);
}

三種:需要多個參數查詢

//多條件查詢圖書信息
public List<Book> findBookByManyCondition(String id, String category,
        String name, String minprice, String maxprice) throws SQLException {
    StringBuilder sql = new StringBuilder("select * from books where 1=1");
    List list = new ArrayList();
    if(!"".equals(id)){
        sql.append(" and id like ?");
        list.add("%"+id+"%");
    }
    if(!"".equals(category)){
        sql.append(" and category=?");
        list.add(category);
    }
    if(!"".equals(name)){
        sql.append(" and name like ?");
        list.add("%"+name+"%");
    }
    if(!"".equals(minprice)){
        sql.append(" and price > ?");
        list.add(minprice);
    }
    if(!"".equals(maxprice)){
        sql.append(" and price < ?");
        list.add(maxprice);
    }
    
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
    return qr.query(sql.toString(),new BeanListHandler<Book>(Book.class),list.toArray());
}


那么我們來看下源碼的實現: 
(1)QueryRunner.java

//第一種情況,無參數
public <T> T query(String sql, ResultSetHandler<T> rsh) throws SQLException {
    Connection conn = this.prepareConnection();

    return this.query(conn, true, sql, rsh, (Object[]) null);
}

//第二種和第三種使用同一方法: 需要參數
public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
    Connection conn = this.prepareConnection();

    return this.query(conn, true, sql, rsh, params);
}

解讀: 這里先是獲取connection, 利用this.preparaConnection() 獲取. 然后調用query()方法去執行查詢語句. 接下來看源碼是如何獲取到當前傳輸過來的connection以及query()方法的內部實現.

protected Connection prepareConnection() throws SQLException {
    if (this.getDataSource() == null) {
        throw new SQLException("QueryRunner requires a DataSource to be " +
            "invoked in this way, or a Connection should be passed in");
    }
    return this.getDataSource().getConnection();
}

這里很簡單, 因為我們用的C3P0數據庫連接池獲取的DataSource, 所以這里直就可以過去到當前的Connection.接下來就看下query()方法的內部實現. 

private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
            throws SQLException {
    if (conn == null) {
        throw new SQLException("Null connection");
    }

    if (sql == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null SQL statement");
    }

    if (rsh == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null ResultSetHandler");
    }

    PreparedStatement stmt = null;
    ResultSet rs = null;
    T result = null;

    try {
        stmt = this.prepareStatement(conn, sql);
        this.fillStatement(stmt, params);
        rs = this.wrap(stmt.executeQuery());
        result = rsh.handle(rs);

    } catch (SQLException e) {
        this.rethrow(e, sql, params);

    } finally {
        try {
            close(rs);
        } finally {
            close(stmt);
            if (closeConn) {
                close(conn);
            }
        }
    }

    return result;
}

解讀: 在這里可以看出, 無論是否有傳遞參數params, 都調用的是同一個query方法, 接着來看this.fillStatement(stmt, params);是如何將參數賦予preparedStatement中的. 

public void fillStatement(PreparedStatement stmt, Object... params) throws SQLException {

    // check the parameter count, if we can
    ParameterMetaData pmd = null;
    if (!pmdKnownBroken) {
        pmd = stmt.getParameterMetaData();
        int stmtCount = pmd.getParameterCount();
        int paramsCount = params == null ? 0 : params.length;

        if (stmtCount != paramsCount) {
            throw new SQLException("Wrong number of parameters: expected "
                    + stmtCount + ", was given " + paramsCount);
        }
    }

    // nothing to do here
    if (params == null) {
        return;
    }

    for (int i = 0; i < params.length; i++) {
        if (params[i] != null) {
            stmt.setObject(i + 1, params[i]);
        } else {
            // VARCHAR works with many drivers regardless
            // of the actual column type.  Oddly, NULL and
            // OTHER don't work with Oracle's drivers.
            int sqlType = Types.VARCHAR;
            if (!pmdKnownBroken) {
                try {
                    sqlType = pmd.getParameterType(i + 1);
                } catch (SQLException e) {
                    pmdKnownBroken = true;
                }
            }
            stmt.setNull(i + 1, sqlType);
        }
    }
}

這個方法就是核心所在.
第一種情況: 當params為null的時候, 直接return然后執行sql語句.
第二種第三種情況: 當params不為null時, 循環遍歷傳入的params, 然后將params賦值到preparedStatement中, 然后填充占位符進行sql查詢. 這里我們也來回顧下直接使用preparedStatement來進行查詢的方式:

@Test
public void update(){
    Connection conn = null;
    PreparedStatement st = null;
    ResultSet rs = null;
    try{
        conn = JdbcUtils.getConnection();
        String sql = "update users set name=?,email=? where id=?";
        st = conn.prepareStatement(sql);
        st.setString(1, "gacl");
        st.setString(2, "gacl@sina.com");
        st.setInt(3, 2);
        int num = st.executeUpdate();
        if(num>0){
            System.out.println("更新成功!!");
        }
    }catch (Exception e) {
        e.printStackTrace();
        
    }finally{
        JdbcUtils.release(conn, st, rs);
    }
}

@Test
public void find(){
    Connection conn = null;
    PreparedStatement st = null;
    ResultSet rs = null;
    try{
        conn = JdbcUtils.getConnection();
        String sql = "select * from users where id=?";
        st = conn.prepareStatement(sql);
        st.setInt(1, 1);
        rs = st.executeQuery();
        if(rs.next()){
            System.out.println(rs.getString("name"));
        }
    }catch (Exception e) {
        
    }finally{
        JdbcUtils.release(conn, st, rs);
    }
}


2, Update語句
查看update語句:

//修改圖書 
public void updateBook(Book book) throws SQLException {
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
    qr.update(
            "UPDATE books SET NAME=? ,price=?,bnum=?,category=?,description=? WHERE id=?",
            book.getName(), book.getPrice(), book.getBnum(),
            book.getCategory(), book.getDescription(), book.getId())
}

接着是QueryRunner.java中的update 方法:

public int update(String sql, Object... params) throws SQLException {
    Connection conn = this.prepareConnection();

    return this.update(conn, true, sql, params);
}

private int update(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException {
    if (conn == null) {
        throw new SQLException("Null connection");
    }

    if (sql == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null SQL statement");
    }

    PreparedStatement stmt = null;
    int rows = 0;

    try {
        stmt = this.prepareStatement(conn, sql);
        this.fillStatement(stmt, params);
        rows = stmt.executeUpdate();

    } catch (SQLException e) {
        this.rethrow(e, sql, params);

    } finally {
        close(stmt);
        if (closeConn) {
            close(conn);
        }
    }

    return rows;
}

到了參數賦值的時候又調用了上面的fillStatement方法, 這里就不再闡述了. 


3, Batch語句
這里直接看batch方法的實例, 然后結合源碼的實現.

//批量刪除
public void delBooks(String[] ids) throws SQLException {
    QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
    Object[][] params = new Object[ids.length][];//高維確定執行sql語句的次數,低維是給?賦值
    for (int i = 0; i < params.length; i++) {
        params[i] = new Object[]{ids[i]};//給“?”賦值
    }
    qr.batch("delete from books where id=?", params);
}

然后看QueryRunner中的batch()方法:

public int[] batch(String sql, Object[][] params) throws SQLException {
    Connection conn = this.prepareConnection();

    return this.batch(conn, true, sql, params);
}

private int[] batch(Connection conn, boolean closeConn, String sql, Object[][] params) throws SQLException {
    if (conn == null) {
        throw new SQLException("Null connection");
    }

    if (sql == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null SQL statement");
    }

    if (params == null) {
        if (closeConn) {
            close(conn);
        }
        throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
    }

    PreparedStatement stmt = null;
    int[] rows = null;
    try {
        stmt = this.prepareStatement(conn, sql);

        for (int i = 0; i < params.length; i++) {
            this.fillStatement(stmt, params[i]);
            stmt.addBatch();
        }
        rows = stmt.executeBatch();

    } catch (SQLException e) {
        this.rethrow(e, sql, (Object[])params);
    } finally {
        close(stmt);
        if (closeConn) {
            close(conn);
        }
    }

    return rows;
}

解讀: 因為params是一個二維數組, 所以往preparedStatement中賦值的時候使用了for循環, 然后通過preparedstatement.addBatch() 進行批量添加, 然后執行executeBatch()進行操作.

   /**
     * Adds a set of parameters to this <code>PreparedStatement</code>
     * object's batch of commands.
     *
     * @exception SQLException if a database access error occurs or
     * this method is called on a closed <code>PreparedStatement</code>
     * @see Statement#addBatch
     * @since 1.2
     */
    void addBatch() throws SQLException;


一看時間這么晚了, QueryRunner暫時就這么多了, 關於QueryRunner的用法自己挖掘的還不夠透徹, 寫在這里當做記錄和交流. 2016/05/24.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM