JavaWeb关于DBUtils中QueryRunner的一些解读.

原文:[JavaWeb]关于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.prepareConnection() 获取. 然后调用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;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容