七、DAO及相关实现类

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

image.png

【BaseDAO.java】

DAO:data( base) access object,数据库访问对象

  1. package com.wu.web.jdbc.bookstore.dao;
  2. import java.lang.reflect.ParameterizedType;
  3. import java.lang.reflect.Type;
  4. import java.sql.Connection;
  5. import java.sql.SQLException;
  6. import java.util.List;
  7. import org.apache.commons.dbutils.QueryRunner;
  8. import org.apache.commons.dbutils.handlers.BeanHandler;
  9. import org.apache.commons.dbutils.handlers.BeanListHandler;
  10. import org.apache.commons.dbutils.handlers.ScalarHandler;
  11. /**
  12. * 定义一个用来被继承的对数据库进行基本操作的Dao
  13. */
  14. public abstract class BaseDAO<T>{
  15. private QueryRunner queryRunner = new QueryRunner();
  16. // 定义一个变量来接收泛型的类型
  17. private Class<T> type;
  18. // 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
  19. public BaseDAO() {
  20. // 获取子类的类型
  21. Class clazz = this.getClass();
  22. // 获取父类的类型
  23. // getGenericSuperclass()用来获取当前类的父类的类型
  24. // ParameterizedType表示的是带泛型的类型
  25. ParameterizedType parameterizedType = (ParameterizedType)
  26. clazz.getGenericSuperclass();
  27. // 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型
  28. // 这个方法会返回一个Type的数组
  29. Type[] types = parameterizedType.getActualTypeArguments();
  30. // 获取具体的泛型的类型·
  31. this.type = (Class<T>) types[0];
  32. }
  33. /**
  34. * 通用的增删改操作
  35. *
  36. * @param sql
  37. * @param params
  38. * @return
  39. */
  40. public int update(Connection conn,String sql, Object... params) {
  41. int count = 0;
  42. try {
  43. count = queryRunner.update(conn, sql, params);
  44. } catch (SQLException e) {
  45. e.printStackTrace();
  46. }
  47. return count;
  48. }
  49. /**
  50. * 获取一个对象
  51. *
  52. * @param sql
  53. * @param params
  54. * @return
  55. */
  56. public T getBean(Connection conn,String sql, Object... params) {
  57. T t = null;
  58. try {
  59. t = queryRunner.query(conn, sql, new BeanHandler<T>(type), params);
  60. } catch (SQLException e) {
  61. e.printStackTrace();
  62. }
  63. return t;
  64. }
  65. /**
  66. * 获取所有对象
  67. *
  68. * @param sql
  69. * @param params
  70. * @return
  71. */
  72. public List<T> getBeanList(Connection conn, String sql, Object... params) {
  73. List<T> list = null;
  74. try {
  75. list = queryRunner.query(conn, sql, new BeanListHandler<T>(type), params);
  76. } catch (SQLException e) {
  77. e.printStackTrace();
  78. }
  79. return list;
  80. }
  81. /**
  82. * 获取一个但一值得方法,专门用来执行像 select count(*)...这样的sql语句
  83. *
  84. * @param sql
  85. * @param params
  86. * @return
  87. */
  88. public Object getValue(Connection conn,String sql, Object... params) {
  89. Object count = null;
  90. try {
  91. // 调用queryRunner的query方法获取一个单一的值
  92. count = queryRunner.query(conn, sql, new ScalarHandler<>(), params);
  93. } catch (SQLException e) {
  94. e.printStackTrace();
  95. }
  96. return count;
  97. }
  98. }

【BookDAO.java】

package com.wu.web.jdbc.bookstore.dao;
import com.wu.web.jdbc.bookstore.dao.beans.Page;
import java.awt.print.Book;
import java.sql.Connection;
import java.util.List;

/**
 * 此接口用于规范针对于表的常用操作
 */
public interface BookDAO {
    /**
     * 从数据库中查询出所有的记录
     *
     * @return
     */
    List<Book> getBooks(Connection conn);
    /**
     * 向数据库中插入一条记录
     *
     * @param book
     */
    void saveBook(Connection conn,Book book);
    /**
     * 从数据库中根据图书的id删除一条记录
     *
     * @param bookId
     */
    void deleteBookById(Connection conn,String bookId);
    /**
     * 根据图书的id从数据库中查询出一条记录
     *
     * @param bookId
     * @return
     */
    Book getBookById(Connection conn, String bookId);
    /**
     * 根据图书的id从数据库中更新一条记录
     *
     * @param book
     */
    void updateBook(Connection conn,Book book);
    /**
     * 获取带分页的图书信息
     *
     * @param page:是只包含了用户输入的pageNo属性的page对象
     * @return 返回的Page对象是包含了所有属性的Page对象
     */
    Page<Book> getPageBooks(Connection conn, Page<Book> page);
    /**
     * 获取带分页和价格范围的图书信息
     *
     * @param page:是只包含了用户输入的pageNo属性的page对象
     * @return 返回的Page对象是包含了所有属性的Page对象
     */
    Page<Book> getPageBooksByPrice(Connection conn, Page<Book> page, double minPrice, double maxPrice);
}

【UserDAO.java】

package com.wu.web.jdbc.bookstore.dao;
import java.sql.Connection;

public interface UserDAO {
    /**
     * 根据User对象中的用户名和密码从数据库中获取一条记录
     *
     * @param user
     * @return User 数据库中有记录 null 数据库中无此记录
     */
    User getUser(Connection conn, User user);
    /**
     * 根据User对象中的用户名从数据库中获取一条记录
     *
     * @param user
     * @return true 数据库中有记录 false 数据库中无此记录
     */
    boolean checkUsername(Connection conn,User user);
    /**
     * 向数据库中插入User对象
     *
     * @param user
     */
    void saveUser(Connection conn,User user);

}

【BookDaoImpl.java】

package com.wu.web.jdbc.bookstore.dao;
import com.wu.web.jdbc.bookstore.dao.beans.Book;
import com.wu.web.jdbc.bookstore.dao.beans.Page;
import java.sql.Connection;
import java.util.List;

public class BookDAOImpl extends BaseDAO<Book> implements BookDAO{

    @Override
    public List<Book> getBooks(Connection conn) {
        // 调用BaseDao中得到一个List的方法
        List<Book> beanList = null;
        // 写sql语句
        String sql = "select id,title,author,price,sales,stock,img_path imgPath from books";
        beanList = getBeanList(conn,sql);
        return beanList;
    }

    @Override
    public void saveBook(Connection conn, Book book) {

        // 写sql语句
        String sql = "insert into books(title,author,price,sales,stock,img_path)values(?,?,?,?,?,?)";
        // 调用BaseDao中通用的增删改的方法
        update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath());

    }

    @Override
    public void deleteBookById(Connection conn, String bookId) {

        // 写sql语句
        String sql = "DELETE FROM books WHERE id = ?";
        // 调用BaseDao中通用增删改的方法
        update(conn,sql, bookId);
    }

    @Override
    public Book getBookById(Connection conn, String bookId) {
        // 调用BaseDao中获取一个对象的方法
        Book book = null;
        // 写sql语句
        String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?";
        book = getBean(conn,sql, bookId);
        return book;
    }

    @Override
    public void updateBook(Connection conn, Book book) {
        // 写sql语句
        String sql = "update books set title = ? , author = ? , price = ? , sales = ? ,stock = ? where id = ?";
        // 调用BaseDao中通用的增删改的方法
        update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId());

    }

    @Override
    public Page<Book> getPageBooks(Connection conn, Page<Book> page) {
        // 获取数据库中图书的总记录数
        String sql = "select count(*) from books";
        // 调用BaseDao中获取一个单一值的方法
        long totalRecord = (long) getValue(conn,sql);
        // 将总记录数设置都page对象中
        page.setTotalRecord((int) totalRecord);
        // 获取当前页中的记录存放的List
        String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books limit ?,?";
        // 调用BaseDao中获取一个集合的方法
        List<Book> beanList = getBeanList(conn,sql2, (page.getPageNo() - 1) *
                Page.PAGE_SIZE, Page.PAGE_SIZE);
        // 将这个List设置到page对象中
        page.setList(beanList);
        return page;
    }

    @Override
    public Page<Book> getPageBooksByPrice(Connection conn, Page<Book> page, double minPrice, double maxPrice) {
        // 获取数据库中图书的总记录数
        String sql = "select count(*) from books where price between ? and ?";
        // 调用BaseDao中获取一个单一值的方法
        long totalRecord = (long) getValue(conn,sql,minPrice,maxPrice);
        // 将总记录数设置都page对象中
        page.setTotalRecord((int) totalRecord);
        // 获取当前页中的记录存放的List
        String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books where price between ? and ? limit ?,?";
        // 调用BaseDao中获取一个集合的方法
        List<Book> beanList = getBeanList(conn,sql2, minPrice , maxPrice ,
                (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
        // 将这个List设置到page对象中
        page.setList(beanList);
        return page;
    }
}

【UserDaoImpl.java】

package com.wu.web.jdbc.bookstore.dao;

import java.sql.Connection;

public class UserDAOImpl extends BaseDAO<User> implements UserDAO {
    @Override
    public User getUser(Connection conn, User user) {
        // 调用BaseDao中获取一个对象的方法
        User bean = null;
        // 写sql语句
        String sql = "select id,username,password,email from users where username = ? and password = ?";
        bean = getBean(conn,sql, user.getUsername(), user.getPassword());
        return bean;
    }
    @Override
    public boolean checkUsername(Connection conn,User user) {
        // 调用BaseDao中获取一个对象的方法
        User bean = null;
        // 写sql语句
        String sql = "select id,username,password,email from users where username = ?";
        bean = getBean(conn,sql, user.getUsername());
        return bean != null;
    }
    @Override
    public void saveUser(Connection conn,User user) {
        //写sql语句
        String sql = "insert into users(username,password,email) values(?,?,?)";
        //调用BaseDao中通用的增删改的方法
        update(conn,sql, user.getUsername(),user.getPassword(),user.getEmail());
    }
}

【Book.java】

package com.wu.web.jdbc.bookstore.dao.beans;
/**
 * 图书类
 */
public class Book {
    private Integer id;
    private String title; // 书名
    private String author; // 作者
    private double price; // 价格
    private Integer sales; // 销量
    private Integer stock; // 库存
    private String imgPath = "static/img/default.jpg"; // 封面图片的路径
//构造器,get(),set(),toString()方法略
}

【Page.java】

package com.wu.web.jdbc.bookstore.dao.beans;

import java.util.List;

/**
 * 页码类
 */
public class Page<T> {

    private List<T> list; // 每页查到的记录存放的集合
    public static final int PAGE_SIZE = 4; // 每页显示的记录数
    private int pageNo; // 当前页
    // private int totalPageNo; // 总页数,通过计算得到
    private int totalRecord; // 总记录数,通过查询数据库得到
}

【User.java】

package com.wu.web.jdbc.bookstore.dao;

/**
 * 用户类
 */
public class User {
    private Integer id;
    private String username;
    private String password;
    private String email;

}

八、数据库连接池

8.1 JDBC数据库连接池的必要性

  • 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
    • 在主程序(如servlet、beans)中建立数据库连接
    • 进行sql操作
    • 断开数据库连接
  • 这种模式开发,存在的问题:
    • 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
  • 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
  • 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。

    8.2 数据库连接池技术

  • 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。

  • 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
  • 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
  • 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。

image.png

  • 工作原理

image.png

  • 数据库连接池技术的优点
  1. 资源重用
    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一
    方面也增加了系统运行环境的平稳性。
    2. 更快的系统反应速度
    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均
    已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,
    从而减少了系统的响应时间
    3. 新的资源分配手段
    对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库
    连接数的限制,避免某一应用独占所有的数据库资源
    4. 统一的连接管理,避免数据库连接泄漏
    在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据
    库连接操作中可能出现的资源泄露

    8.3 多种开源的数据库连接池

  • JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
    • DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
    • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
    • BoneCP 是一个开源组织提供的数据库连接池,速度快
    • Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
  • DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
  • DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
  • 特别注意:
    • 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
    • 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池

      Druid(德鲁伊)数据库连接池

      Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了
      日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的
      连接池之一。

配置文件:

url=jdbc:mysql://112.126.91.232:3306/book?rewriteBatchedStatements=true
username=root
password=root
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall

测试代码:

public class TestDruid {

    public static void main(String[] args) throws Exception {
        Properties pro = new Properties();
        pro.load(ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"));
        DataSource ds = DruidDataSourceFactory.createDataSource(pro);
        Connection conn = ds.getConnection();
        System.out.println(conn);
    }
}

使用Druid优化数据库连接工具类

package com.example.web.jdbc;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.*;
import java.util.Properties;

/**
 * @Date: 2021/6/28
 * @Time: 18:58
 * @BelongsProject book
 * @BelongsPackage com.wu.web.jdbc
 * @Description: 操作数据库的工具类
 */
public class JDBCUtils {

    /**
     * 使用Druid数据库连接池技术
     */
    private static DataSource source;
    static {
        Properties pro = new Properties();
        try {
            pro.load(ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"));
            source = DruidDataSourceFactory.createDataSource(pro);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    /**
     * 获取数据库连接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception{

        Connection conn = source.getConnection();
        return conn;
    }
    /**
     * 关闭资源
     * @param conn
     * @param ps
     * @throws SQLException
     */
    public static void closeResource(Connection conn,PreparedStatement ps)  {
        if (null != conn){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (null != ps){
            try {
                ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    /**
     * 关闭资源
     * @param conn
     * @param ps
     * @throws SQLException
     */
    public static void closeResource(Connection conn, PreparedStatement ps, ResultSet rs)  {
        if (null != conn){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (null != ps){
            try {
                ps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (null != rs){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

详细配置参数:
image.png
image.png
image.png

九、Apache-DBUtils实现CRUD操作

9.1 Apache-DBUtils简介

  • commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
  • API介绍:
  • org.apache.commons.dbutils.QueryRunner
  • org.apache.commons.dbutils.ResultSetHandler
  • 工具类:org.apache.commons.dbutils.DbUtils

API包说明:
image.png
image.png

9.2.1 DbUtils

  • DbUtils :提供如关闭连接、装载JDBC驱动程序等常规工作的工具类,里面的所有方法都是静态的。主要方法如下:

    • public static void close(…) throws java.sql.SQLException: DbUtils类提供了三个重载的关闭方法。这些方法检查所提供的参数是不是NULL,如果不是的话,它们就关闭Connection、Statement和ResultSet。
    • public static void closeQuietly(…): 这一类方法不仅能在Connection、Statement和ResultSet为NULL情况下避免关闭,还能隐藏一些在程序中抛出的SQLEeception。
    • public static void commitAndClose(Connection conn)throws SQLException: 用来提交连接的事务,然后关闭连接
    • public static void commitAndCloseQuietly(Connection conn): 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常。
    • public static void rollback(Connection conn)throws SQLException:允许conn为null,因为方法内部做了判断
    • public static void rollbackAndClose(Connection conn)throws SQLException
    • rollbackAndCloseQuietly(Connection)
    • public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,如果成功就返回true。使用该方法,你不需要捕捉这个异常ClassNotFoundException。

      9.2.2 QueryRunner类

  • 该类简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

  • QueryRunner类提供了两个构造器:
    • 默认的构造器
    • 需要一个 javax.sql.DataSource 来作参数的构造器
  • QueryRunner类的主要方法:

    • 更新
      • public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
    • 插入
      • public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throwsSQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object fromthe ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
    • 批处理
      • public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT,UPDATE, or DELETE语句
      • public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
    • 查询
      • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

        测试:

        /**
        * 测试添加
        */
        @Test
        public void testInsert(){
        QueryRunner runner = new QueryRunner();
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            String sql = "insert into customers(name,email,birth)values(?,?,?)";
            runner.update(conn, sql,"蔡徐坤","wer@rr.com","1999-02-03");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            JDBCUtils.closeResource(conn,null);
        }
        }
        
        /**
        * 测试删除
        */
        @Test
        public void testDelete() throws Exception {
        QueryRunner runner = new QueryRunner();
        Connection conn = JDBCUtils.getConnection();
        String sql = "delete from customers where id < ?";
        int count = runner.update(conn, sql,3);
        System.out.println("删除了" + count + "条记录");
        JDBCUtils.closeResource(conn, null);
        }
        

        9.2.3 ResultSetHandler接口及实现类

  • 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。

  • ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
  • 接口的主要实现类:

    • ArrayHandler:把结果集中的第一行数据转成对象数组。
    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    • ColumnListHandler:将结果集中某一列的数据存放到List中。
    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
    • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
    • ScalarHandler:查询单个值对象

      测试

      /**
      * 查询测试
      * BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。
      */
      @Test
      public void testQuery1(){
         Connection conn = null;
         try {
             QueryRunner runner = new QueryRunner();
             conn = JDBCUtils.getConnection();
             String sql = "select id,name email,birth from customers where id = ?";
             BeanHandler<Customer> handler = new BeanHandler<Customer>(Customer.class);
             Customer query = runner.query(conn, sql, handler, 21);
             System.out.println(query);
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             JDBCUtils.closeResource(conn, null);
         }
      }
      
      /**
      * 查询测试表中的多条记录。
      */
      @Test
      public void testQuery1(){
         Connection conn = null;
         try {
             QueryRunner runner = new QueryRunner();
             conn = JDBCUtils.getConnection();
             String sql = "select id,name email,birth from customers";
             BeanListHandler<Customer> handler = new BeanListHandler<Customer>(Customer.class);
             List<Customer> query = runner.query(conn, sql, handler);
             query.forEach(System.out::println);
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             JDBCUtils.closeResource(conn, null);
         }
      }
      
      /**
      * MapHander:是ResultSetHandler接口的实现类,对应表中的一条记录。
      * 将字段及相应字段的值作为map中的key和value
      */
      @Test
      public void testQuery1(){
         Connection conn = null;
         try {
             QueryRunner runner = new QueryRunner();
             conn = JDBCUtils.getConnection();
             String sql = "select id,name email,birth from customers where id = ?";
             MapHandler handler = new MapHandler();
             Map<String,Object> map = runner.query(conn, sql, handler,21);
             System.out.println(map);
      
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             JDBCUtils.closeResource(conn, null);
         }
      }
      
      /**
      * MapListHander:是ResultSetHandler接口的实现类,对应表中的一条记录。
      * 将字段及相应字段的值作为map中的key和value
      */
      @Test
      public void testQuery1(){
         Connection conn = null;
         try {
             QueryRunner runner = new QueryRunner();
             conn = JDBCUtils.getConnection();
             String sql = "select id,name email,birth from customers where id < ?";
             MapListHandler handler = new MapListHandler();
             List<Map<String,Object>> map = runner.query(conn, sql, handler,29);
             System.out.println(map);
      
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             JDBCUtils.closeResource(conn, null);
         }
      }
      
      /**
      * 查询特殊值
      */
      @Test
      public void testQuery1(){
         Connection conn = null;
         try {
             QueryRunner runner = new QueryRunner();
             conn = JDBCUtils.getConnection();
             String sql = "select count(*) from customers";
             ScalarHandler handler = new ScalarHandler();
             Long count = (Long) runner.query(conn, sql, handler);
             System.out.println(count);
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             JDBCUtils.closeResource(conn, null);
         }
      }
      
      /**
      * 查询特殊值
      */
      @Test
      public void testQuery1(){
         Connection conn = null;
         try {
             QueryRunner runner = new QueryRunner();
             conn = JDBCUtils.getConnection();
             String sql = "select max(birth) from customers";
             ScalarHandler handler = new ScalarHandler();
             Date date = (Date) runner.query(conn, sql, handler);
             System.out.println(date);
         } catch (Exception e) {
             e.printStackTrace();
         } finally {
             JDBCUtils.closeResource(conn, null);
         }
      }
      
      /**
      * 自定义ResultSetHandler的实现类
      */
      @Test
      public void testQueryInstance1() throws Exception{
         QueryRunner runner = new QueryRunner();
         Connection conn = JDBCUtils.getConnection();
         String sql = "select id,name,email,birth from customers where id = ?";
         ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
             @Override
             public Customer handle(ResultSet rs) throws SQLException {
                 System.out.println("handle");
                 // return new Customer(1,"Tom","tom@126.com",new Date(123323432L));
                 if(rs.next()){
                     int id = rs.getInt("id");
                     String name = rs.getString("name");
                     String email = rs.getString("email");
                     Date birth = rs.getDate("birth");
                     return new Customer(id, name, email, birth);
                 }
                 return null;
             }
         };
         Customer customer = runner.query(conn, sql, handler, 21);
         System.out.println(customer);
         JDBCUtils.closeResource(conn, null);
      }