连接池_DbUtils

连接池和DBUtils

第一章-自定义连接池

知识点-连接池概念

1.目标

  • 能够理解连接池解决现状问题的原理

2.讲解

2.1为什么要使用连接池

​ Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优化.

​ 程序初始化的时候,初始化多个连接,将多个连接放入到池(集合)中.每次获取的时候,都可以直接从连接池中进行获取.使用结束以后,将连接归还到池中.

2.2.生活里面的连接池例子
  • 老方式:

    ​ 下了地铁需要骑车, 跑去生产一个, 然后骑完之后,直接把车销毁了.

  • 连接池方式 摩拜单车:

    ​ 骑之前, 有一个公司生产了很多的自行车, 下了地铁需要骑车, 直接扫码使用就好了, 然后骑完之后, 还回去

2.3连接池原理【重点】

在这里插入图片描述

  1. 程序一开始就创建一定数量的连接,放在一个容器(集合)中,这个容器称为连接池。
  2. 使用的时候直接从连接池中取一个已经创建好的连接对象, 使用完成之后 归还到池子
  3. 如果池子里面的连接使用完了, 还有程序需要使用连接, 先等待一段时间(eg: 3s), 如果在这段时间之内有连接归还, 就拿去使用; 如果还没有连接归还, 新创建一个, 但是新创建的这一个不会归还了
  4. 集合选择LinkedList
    • 增删比较快
    • LinkedList里面的removeFirst()和addLast()方法和连接池的原理吻合

3.小结

  1. 使用连接池的目的: 可以让连接得到复用, 避免浪费

自定义连接池-初级版本

1.目标

​ 根据连接池的原理, 使用LinkedList自定义连接池

2.分析

  1. 创建一个类MyDataSource, 定义一个集合LinkedList
  2. 程序初始化的时候, 创建5个连接 存到LinkedList
  3. 定义getConnection() 从LinkedList取出Connection返回
  4. 定义addBack()方法归还Connection到LinkedList

3.实现

/**
 * 包名:com.itheima.customer.datasource
 *
 * @author Leevi
 * 日期2020-07-06  09:37
 * 自定义连接池的第一个版本
 * 1. 创建一个容器,存放连接
 * 2. 默认往容器中存放5个连接
 *    在构造函数中编写代码
 * 3. 提供一个方法,让调用者获取连接
 * 4. 提供一个方法,让调用者归还连接
 *
 * 当前第一个版本存在的问题:
 * 1. 新创建的连接(原本没有在连接池中的连接)也会归还回连接池
 * 2. 连接池使用的耦合性太高了,不便于以后项目切换连接池
 */
public class MyDataSource {
    private LinkedList<Connection> connectionPool = new LinkedList<>();

    public MyDataSource() {
        //初始化往connectionPool中存放5个连接
        for (int i = 0; i < 5; i++) {
            try {
                //创建一个连接
                Connection conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
                //将连接添加到connectionPool中
                connectionPool.add(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 获取连接的方法
     * @return
     */
    public Connection getConn() throws SQLException {
        //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
        Connection conn = null;
        if (connectionPool.size() > 0){
            //从容器中获取连接
            conn = connectionPool.removeFirst();
        }else {
            //则新创建连接
            conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
        }
        return conn;
    }

    public void addBack(Connection connection){
        //归还连接:其实就是将要归还的那个连接,添加到容器的尾部
        connectionPool.addLast(connection);
    }
}

4.小结

  1. 创建一个类MyDataSource, 定义一个集合LinkedList
  2. 程序初始化(静态代码块)里面 创建5个连接存到LinkedList
  3. 定义提供Connection的方法
  4. 定义归还Connection的方法

自定义连接池-进阶版本

1.目标

​ 实现datasource完成自定义连接池

2.分析

​ 在初级版本版本中, 我们定义的方法是getConnection(). 因为是自定义的.如果改用李四的自定义的连接池,李四定义的方法是getAbc(), 那么我们的源码就需要修改, 这样不方便维护. 所以sun公司定义了一个接口datasource,让自定义连接池有了规范

3.讲解

3.1datasource接口概述

​ Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商(用户)需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!

3.2代码实现
/**
 * 包名:com.itheima.customer.datasource
 *
 * @author Leevi
 * 日期2020-07-06  10:01
 * 自定义连接池的第二版:
 * 编写连接池,并且实现官方的DataSource接口
 *
 * 存在的问题:DataSource中并没有提供归还连接的方法
 */
public class MyDataSource2 implements DataSource{
    private LinkedList<Connection> connectionPool = new LinkedList<>();

    public MyDataSource2() {
        //初始化往connectionPool中存放5个连接
        for (int i = 0; i < 5; i++) {
            try {
                //创建一个连接
                Connection conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
                //将连接添加到connectionPool中
                connectionPool.add(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }
    @Override
    public Connection getConnection() throws SQLException {
        //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
        Connection conn = null;
        if (connectionPool.size() > 0){
            //从容器中获取连接
            conn = connectionPool.removeFirst();
        }else {
            //则新创建连接
            conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
        }
        return conn;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

4.小结

4.1编写连接池遇到的问题
  • 实现DataSource接口后,addBack()不能调用了.
  • 能不能不引入新的api,直接调用之前的connection.close(),但是这个close不是关闭,是归还
4.2解决办法
  • 继承

    • 条件:可以控制父类, 最起码知道父类的名字
  • 装饰者模式

    • 作用:改写已存在的类的某个方法或某些方法
    • 条件:
      • 增强类和被增强类实现的是同一个接口
      • 增强类里面要拿到被增强类的引用
  • 动态代理

自定义连接池-终极版本

1.目标

使用装饰者模式改写connection的close()方法, 让connection归还

2.讲解

2.1自定义连接池终极版本
2.1.1分析

​ 增强connection的close()方法, 其它的方法逻辑不改

在这里插入图片描述

  1. 创建WrapperConnection实现Connection
  2. 在WrapperConnection里面需要得到被增强的connection对象(通过构造方法传进去)
  3. 改写close()的逻辑, 变成归还
  4. 其它方法的逻辑, 还是调用被增强connection对象之前的逻辑
2.2.2实现
  • WrapperConnection
/**
 * 包名:com.itheima.customer.connection
 *
 * @author Leevi
 * 日期2020-07-06  10:16
 * 依赖倒置原则:
 * 尽量依赖抽象,不依赖具体
 */
public class WrapperConnection implements Connection{
    private Connection connection;

    private LinkedList<Connection> connectionPool;

    public WrapperConnection(Connection connection,LinkedList<Connection> connectionPool) {
        this.connection = connection;
        this.connectionPool = connectionPool;
    }

    @Override
    public void close() throws SQLException {
        //将当前这个连接归还回原来那个连接池容器
        connectionPool.addLast(this);
    }

    @Override
    public Statement createStatement() throws SQLException {
        return connection.createStatement();
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return connection.prepareStatement(sql);
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        connection.setAutoCommit(autoCommit);
    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }

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

    @Override
    public void rollback() throws SQLException {
        connection.rollback();
    }
    //....其它方法省略
}
  • MyDataSource03
/**
 * 包名:com.itheima.customer.datasource
 *
 * @author Leevi
 * 日期2020-07-06  10:01
 * 自定义连接池的第三版:
 * 1. 可以归还连接
 * 2. 如果是原本在连接池中的连接,就归还;如果是新创建的连接用完后就销毁
 *
 * 如果是原本在连接池中的连接,调用close()方法不销毁,而是将其归还回连接池
 * 如果是新创建的连接,调用close()方法,就销毁(执行原本的close)
 *
 * 要在不修改类的源码的基础之上,改变类的方法
 * 1. 继承(在这里没法用)
 * 2. 动态代理(代理模式)
 * 3. 装饰者模式
 *    1. 装饰者和被装饰者要实现相同的接口
 *    2. 将被装饰者的对象传入装饰者中
 *    3. 不需要修改的方法,直接调用被装饰者的方法;需要修改的方法,由装饰者重写
 */
public class MyDataSource3 implements DataSource{
    private LinkedList<Connection> connectionPool = new LinkedList<>();

    public MyDataSource3() {
        //初始化往connectionPool中存放5个连接
        for (int i = 0; i < 5; i++) {
            try {
                //创建一个装饰后的连接
                Connection conn = new WrapperConnection(DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8","root","123"),connectionPool);
                //将连接添加到connectionPool中
                connectionPool.add(conn);
            } catch (SQLException e) {
                e.printStackTrace();
            }

        }
    }
    @Override
    public Connection getConnection() throws SQLException {
        //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
        Connection conn = null;
        if (connectionPool.size() > 0){
            //从容器中获取连接
            conn = connectionPool.removeFirst();
        }else {
            //则新创建连接
            conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
        }
        return conn;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

3.小结

  1. 创建一个MyConnection实现Connection
  2. 在MyConnection得到被增强的connection对象
  3. 改写MyConnection里面的close()方法的逻辑为归还
  4. MyConnection里面的其它方法 调用被增强的connection对象之前的逻辑
  5. 在MyDataSource03的getConnection()方法里面 返回了myConnection

自定义连接池扩展版本-使用动态代理

使用动态代理创建Connection对象的代理对象,增强Connection的close方法

/**
 * 包名:com.itheima.customer.datasource
 *
 * @author Leevi
 * 日期2020-07-06  10:01
 * 自定义连接池的第四版:
 * 1. 可以归还连接
 * 2. 如果是原本在连接池中的连接,就归还;如果是新创建的连接用完后就销毁
 *
 * 使用动态代理技术,增强connection的close方法
 */
public class MyDataSource4 implements DataSource{
    private LinkedList<Connection> connectionPool = new LinkedList<>();

    public MyDataSource4() {
        //初始化往connectionPool中存放5个连接
        for (int i = 0; i < 5; i++) {
            try {
                Connection connection = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
                ClassLoader classLoader = connection.getClass().getClassLoader();
                //创建动态代理对象
                Connection connectionProxy = (Connection) Proxy.newProxyInstance(classLoader, new Class[]{Connection.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //判断执行方法是否是close方法,如果是,则增强,如果不是则执行被代理者原本的方法
                        if (method.getName().equals("close")) {
                            //增强close
                            //将当前这个连接对象,添加到容器中
                            connectionPool.addLast((Connection) proxy);
                            return null;
                        }
                        //不需要增强的方法,就执行原本的方法
                        return method.invoke(connection,args);
                    }
                });

                //将连接添加到connectionPool中
                connectionPool.add(connectionProxy);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    @Override
    public Connection getConnection() throws SQLException {
        //如果容器中有连接,则从容器中获取,如果容器中没有连接,就直接新创建连接
        Connection conn = null;
        if (connectionPool.size() > 0){
            //从容器中获取连接
            conn = connectionPool.removeFirst();
        }else {
            //则新创建连接
            conn = DriverManager.getConnection("jdbc:mysql:///day20?characterEncoding=utf8", "root", "123");
        }
        return conn;
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

第二章-第三方连接池

知识点-常用连接池

1.目标

  • 常用连接池

2.分析

​ 通过前面的学习,我们已经能够使用所学的基础知识构建自定义的连接池了。其目的是锻炼大家的基本功,帮助大家更好的理解连接池的原理, 但现实是残酷的,我们所定义的 连接池 和第三方的连接池相比,还是显得渺小. 工作里面都会用第三方连接池.

3.讲解

常见的第三方连接池如下:

  • C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。C3P0是异步操作的,所以一些操作时间过长的JDBC通过其它的辅助线程完成。目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能
  • 阿里巴巴-德鲁伊druid连接池:Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求。
  • DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。dbcp没有自动回收空闲连接的功能。

4.小结

我们工作里面用的比较多的是:

  • C3P0
  • druid
  • 光连接池

知识点-C3P0

1.目标

  • 掌握C3P0的使用

2.路径

  1. c3p0介绍
  2. c3p0的使用(硬编码)
  3. c3p0的使用(配置文件)
  4. 编写C3P0Util工具类

3.讲解

3.1 c3p0介绍
  • C3P0开源免费的连接池!目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时还需要添加配置文件c3p0-config.xml.
  • 使用C3P0需要添加c3p0-0.9.1.2.jar
3.2c3p0的使用
3.2.1通过硬编码来编写【了解】

步骤

  1. 拷贝jar
  2. 创建C3P0连接池对象
  3. 从C3P0连接池对象里面获得connection

实现:

ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/web10"); 
cpds.setUser("root");
cpds.setPassword("123456");
Connection connection = cpds.getConnection();
3.2.2 通过配置文件来编写【重点】

步骤:

  1. 拷贝jar
  2. 拷贝配置文件(c3p0-config.xml)到src目录【名字不要改】
  3. 创建C3P0连接池对象【自动的读取】
  4. 从池子里面获得连接

实现:

  • 编写配置文件c3p0-config.xml,放在src目录下(注:文件名一定不要改)
<c3p0-config>
	<default-config>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<property name="jdbcUrl">jdbc:mysql://localhost:3306/web11</property>
		<property name="user">root</property>
		<property name="password">123</property>
		<property name="initialPoolSize">5</property>
	</default-config>
</c3p0-config>
  • 编写Java代码 (会自动读取resources目录下的c3p0-config.xml,所以不需要我们解析配置文件)
DataSource ds = new ComboPooledDataSource(); 
3.3使用c3p0改写工具类【重点】

编写C3P0Util工具类,提供DataSource对象,保证整个项目只有一个DataSource对象

/**
 * 包名:com.itheima.utils
 *
 * @author Leevi
 * 日期2020-07-06  11:43
 * 这个工具类就负责,提供C3P0连接池对象
 */
public class C3P0Util {
    private static DataSource dataSource;
    static {
        dataSource = new ComboPooledDataSource();
    }

    /**
     * 获取连接池
     * @return
     */
    public static DataSource getDataSource(){
        return dataSource;
    }
}

4.小结

  1. C3P0 配置文件方式使用

    • 拷贝jar
    • 拷贝配置文件到resources【配置文件的名字不要改】
    • 创建C3P0连接池对象
  2. C3P0工具类

    • 保证DataSource连接池只有一个【static】

知识点-DRUID

1.目标

  • 掌握DRUID连接池的使用

2.路径

  1. DRUID的介绍
  2. DRUID的使用(硬编码方式)
  3. DRUID的使用(配置文件方式)
  4. DRUID抽取成工具类

3.讲解

3.1DRUID介绍

​ Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是国内目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过很久生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票

Druid的下载地址:https://github.com/alibaba/druid

DRUID连接池使用的jar包:druid-1.0.9.jar

3.2DRUID的使用
3.2.1通过硬编码方式【了解】

步骤:

  1. 导入DRUID jar 包
  2. 创建Druid连接池对象, 配置4个基本参数
  3. 从Druid连接池对象获得Connection

实现:

//1. 创建DataSource
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");//设置驱动
dataSource.setUrl("jdbc:mysql://localhost:3306/web17");//设置数据库路径
dataSource.setUsername("root");//设置用户名
dataSource.setPassword("123");//设置密码

dataSource.setInitialSize(5);//设置初始化连接的数量


//2. 从数据源里面获得Connection
Connection connection = dataSource.getConnection();
3.2.2 通过配置文件方式【重点】

步骤:

  1. 导入DRUID jar 包
  2. 拷贝配置文件到src目录
  3. 根据配置文件创建Druid连接池对象
  4. 从Druid连接池对象获得Connection

实现:

  • 创建druid.properties, 放在src目录下
url=jdbc:mysql:///day20
username=root
password=123
driverClassName=com.mysql.jdbc.Driver
  • 编写Java代码
//0 根据druid.properties创建配置文件对象
Properties properties = new Properties();
// 关联druid.properties文件
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
//1. 创建DataSource
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

//2. 从数据源(连接池)获得连接对象
Connection connection = dataSource.getConnection();
3.3 Druid工具类
/**
 * 包名:com.itheima.utils
 *
 * @author Leevi
 * 日期2020-07-06  11:45
 */
public class DruidUtil {
    private static DataSource dataSource;
    static {
        try {
            //1. 创建Properties对象
            Properties properties = new Properties();
            //2. 将配置文件转换成字节输入流
            InputStream is = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
            //3. 使用properties对象加载is
            properties.load(is);
            //druid底层是使用的工厂设计模式,去加载配置文件,创建DruidDataSource对象
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static DataSource getDataSource(){
        return dataSource;
    }
}

4.小结

  1. Druid配置文件使用
    • 拷贝jar
    • 拷贝配置文件到src
    • 读取配置文件成properties对象
    • 使用工厂根据properties创建DataSource
    • 从DataSource获得Connection

第三章-DBUtils

知识点-DBUtils的介绍

1.目标

  • 知道什么是DBUtils

2.路径

  1. DBUtils的概述
  2. DBUtils的常用API介绍

3.讲解

3.1 DBUtils的概述

​ DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能

3.2 DBUtils的常用API介绍
  1. 创建QueryRunner对象的API

    QueryRunner(DataSource ds) ,提供数据源(连接池),DBUtils底层自动维护连接connection

  2. 执行增删改的SQL语句的API

    int update(String sql, Object… params),执行增删改的SQL语句, params参数就是可变参数,参数个数取决于语句中问号的个数

  3. 执行查询的SQL语句的API

    query(String sql, ResultSetHandler rsh, Object… params),其中ResultSetHandler是一个接口,表示结果集处理者

4.小结

  1. DBUtils: Apache开发的一个数据库工具包, 用来简化JDBC操作数据库的步骤

知识点-JavaBean(POJO)

1.目标

  • 理解JavaBean的字段和属性

2.讲解

  1. JavaBean说白了就是一个类, 用来封装数据用的

  2. JavaBean要求

    • 私有字段
    • 提供公共的get/set方法
    • 无参构造
    • 实现Serializable
    1. 字段和属性
    • 字段: 全局/成员变量 eg: private String username
    • 属性: 去掉get或者set首字母变小写 eg: setUsername-去掉set->Username-首字母变小写->username

    一般情况下,我们通过IDEA直接生成的set/get 习惯把字段和属性搞成一样而言

3.小结

  1. JavaBean用来封装数据用的
  2. 字段和属性
    • 字段: 全局/成员变量 eg: private String username
    • 属性: 去掉get或者set首字母变小写 eg: setUsername-去掉set->Username-首字母变小写->username

知识点-使用DBUtils完成增删改

1.目标

  • 掌握使用DBUtils完成增删改

2.步骤

  1. 拷贝jar包
  2. 创建QueryRunner()对象,传入dataSource
  3. 调用update()方法

3.实现

/**
 * 包名:com.itheima
 *
 * @author Leevi
 * 日期2020-07-06  11:50
 * DBUtils是一个JDBC的简单的框架(它存在的价值,仅仅是简化这一阶段的JDBC的代码)
 * 1. 引入jar包:dbutils的jar、mysql驱动的jar
 * 2. 创建QueryRunner对象,并且往它的构造函数中传入一个DataSource对象
 * 3. 调用queryRunner对象的方法执行SQL语句
 *    1. update(sql)执行增删改的SQL语句
 *    2. query(sql)执行查询的SQL语句
 */
public class TestDBUtils {
    @Test
    public void test01() throws SQLException {
        //目标:执行添加数据的SQL语句
        String sql = "insert into user values (null,?,?,?)";

        //1. 创建QueryRunner对象
        QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());

        //2. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句
        queryRunner.update(sql,"aobama","666666","圣枪游侠");
    }

    @Test
    public void test02() throws SQLException {
        //目标:执行修改数据的SQL语句
        String sql = "update user set username=?,password=?,nickname=? where id=?";

        QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());

        queryRunner.update(sql,"threezhang","654321","小张",1);
    }
}

4.小结

  1. 创建QueryRuner()对象, 传入DataSource
  2. 调用update(String sql, Object…params)

知识点-使用DBUtils完成查询

1.目标

  • 掌握使用DBUtils完成查询

2.步骤

  1. 拷贝jar包
  2. 创建QueryRunner()对象 传入DataSource
  3. 调用query(sql, resultSetHandler,params)方法

3.实现

3.1ResultSetHandler结果集处理类介绍

在这里插入图片描述

3.2代码实现
3.2.1 查询一条数据封装到JavaBean对象中(使用BeanHandler)
@Test
public void test04() throws SQLException {
    //1. 查询一条数据,封装到POJO对象中
    String sql = "select * from user where id=?";
    QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());

    //执行SQL语句
    U user = queryRunner.query(sql, new BeanHandler<>(U.class), 3);
    System.out.println(user);
}
3.2.2 查询多条数据封装到List中(使用BeanListHandler)
@Test
public void test05() throws SQLException {
    //1. 查询多行数据,封装到List<POJO>对象中
    String sql = "select * from user where id>?";
    QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
    List<U> uList = queryRunner.query(sql, new BeanListHandler<>(U.class), 1);
    for (U u : uList) {
        System.out.println(u);
    }
}
3.2.3 查询一条数据,封装到Map对象中(使用MapHandler)
@Test
public void test06() throws SQLException {
    //1. 查询一条数据,封装到Map中: 结果集的字段名就是map的key,结果集的字段值就是map的value
    String sql = "select * from user where id=?";
    QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());

    Map<String, Object> map = queryRunner.query(sql, new MapHandler(), 3);

    System.out.println(map);
}
3.2.4 查询多条数据,封装到List<Map>对象中(使用MapListHandler)
@Test
public void test07() throws SQLException {
    //1. 查询多条数据,封装到List<Map>中
    String sql = "select * from user where id>?";
    QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());

    List<Map<String, Object>> mapList = queryRunner.query(sql, new MapListHandler(), 1);

    for (Map<String, Object> map : mapList) {
        System.out.println(map);
    }
}
3.2.5 查询单个数据(使用ScalarHandler())
@Test
public void test03() throws SQLException {
    //1. 查询单个数据:比如查询用户的总数
    String sql = "select count(*) from user";

    QueryRunner queryRunner = new QueryRunner(DruidUtil.getDataSource());
    //执行查询,调用query()方法
    long count = (long) queryRunner.query(sql,new ScalarHandler());
    System.out.println(count);
}

4. 小结

  1. 步骤

    • 创建QueryRunner() 对象传入DataSource
    • 调用query(sql,ResultSetHandler, params…)
  2. ResultSetHandler

    • BeanHandler() 查询一条记录封装到JavaBean对象
    • BeanListHandler() 查询多条记录封装到List list
    • MapHandler() 查询一条记录封装到Map对象
    • MapListHandler() 查询多条记录封装到List list
    • ScalarHandler() 封装单个记录的 eg:统计数量
  3. 注意实现

    封装到JavaBean条件, 查询出来的数据的列名必须和JavaBean属性一致

第四章-自定义DBUtils

知识点-元数据

1.目标

  • 能够说出什么是数据库元数据

2.分析

​ 我们要自定义DBUtils, 就需要知道列名, 参数个数等, 这些可以通过数据库的元数据库进行获得.元数据在建立框架和架构方面是特别重要的知识,我们可以使用数据库的元数据来创建自定义JDBC工具包, 模仿DBUtils.

3.讲解

3.1什么是元数据

​ 元数据(MetaData),即定义数据的数据。打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。

歌曲:凉凉

作词:刘畅

演唱: 杨宗纬 张碧晨

时长: 3分28秒

​ 简单来说: 数据库的元数据就是 数据库、表、列的定义信息。

​ ① 由PreparedStatement对象的getParameterMetaData ()方法获取的是ParameterMetaData对象。

② 由ResultSet对象的getMetaData()方法获取的是ResultSetMetaData对象。

3.2ParameterMetaData
3.2.1概述

​ ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,ParameterMetaData可用于获取有关PreparedStatement对象和其预编译sql语句 中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型

在这里插入图片描述

​ 获得ParameterMetaData:

`ParameterMetaData parameterMetaData =  preparedStatement.getParameterMetaData ()`
3.2.2ParameterMetaData相关的API
  • int getParameterCount(); 获得参数个数
  • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
3.2.3实例代码
//3. 获得参数的元数据
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
// 获得sql语句里面参数的个数
int parameterCount = parameterMetaData.getParameterCount();
System.out.println("parameterCount="+parameterCount);
3.3ResultSetMetaData
3.3.1概述

​ ResultSetMetaData是由ResultSet对象通过getMetaData方法获取而来,ResultSetMetaData可用于获取有关ResultSet对象中列的类型和属性的信息。

在这里插入图片描述

​ 获得ResultSetMetaData:

`ResultSetMetaData resultSetMetaData =  resultSet.getMetaData()`
3.3.2resultSetMetaData 相关的API
  • getColumnCount(); 获取结果集中列项目的个数
  • getColumnName(int column); 获得数据指定列的列名
  • getColumnTypeName();获取指定列的SQL类型
  • getColumnClassName();获取指定列SQL类型对应于Java的类型
3.2.3实例代码
@Test
public void test03() throws SQLException {
    DataSource dataSource = DruidUtil.getDataSource();
    Connection connection = dataSource.getConnection();

    String sql = "select * from user";

    PreparedStatement pstm = connection.prepareStatement(sql);

    //获取结果集元数据
    ResultSetMetaData resultSetMetaData = pstm.getMetaData();

    int columnCount = resultSetMetaData.getColumnCount();
    System.out.println(columnCount);

    for (int i = 1; i <= columnCount; i++) {
        //获取每列的列名
        String columnName = resultSetMetaData.getColumnName(i);
        System.out.println(columnName);
    }
}
4.小结
  1. 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型…
  2. mysql元数据:
    • ParameterMetaData
    • ResultSetMetaData

案例-自定义DBUtils增删改

1.需求

  • 模仿DBUtils, 完成增删改的功能

2.分析

  1. 创建MyQueryRunner类, 定义dataSource, 提供有参构造方法
  2. 定义int update(String sql, Object…params)方法
//1.从dataSource里面获得connection
//2.根据sql语句创建预编译sql语句对象
//3.获得参数元数据对象, 获得参数的个数
//4.遍历, 从params取出值, 依次给参数? 赋值
//5.执行
//6.释放资源

3.实现

public class MyQueryRunner {
    private DataSource dataSource;

    public MyQueryRunner(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 执行增删改的SQL语句的方法
     * @param sql
     * @param params
     * @return
     * @throws SQLException
     */
    public int update(String sql,Object... params) throws SQLException {
        //1. 获取连接对象
        Connection connection = dataSource.getConnection();
        //2. 预编译SQL语句
        PreparedStatement pstm = connection.prepareStatement(sql);
        //3. 设置参数
        //3.1 获取参数的个数
        ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
        int parameterCount = parameterMetaData.getParameterCount();
        for (int i = 1; i <= parameterCount; i++) {
            pstm.setObject(i,params[i-1]);
        }

        //4. 执行SQL语句
        int num = pstm.executeUpdate();
        //5. 关闭资源
        pstm.close();
        connection.close();

        return num;
    }
}

4.小结

  1. 先模拟DBUtils写出架子
  2. update()
    • 封装了PrepareStatement
    • 用到了参数元数据

案例-自定义DBUtils查询多条数据封装到List<POJO>

/**
 * 包名:com.itheima.framework
 *
 * @author Leevi
 * 日期2020-07-06  15:03
 */
public class MyQueryRunner {
    private DataSource dataSource;

    public MyQueryRunner(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 执行增删改的SQL语句的方法
     * @param sql
     * @param params
     * @return
     * @throws SQLException
     */
    public int update(String sql,Object... params) throws SQLException {
        //1. 获取连接对象
        Connection connection = dataSource.getConnection();
        //2. 预编译SQL语句
        PreparedStatement pstm = connection.prepareStatement(sql);
        //3. 设置参数
        //3.1 获取参数的个数
        ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
        int parameterCount = parameterMetaData.getParameterCount();
        for (int i = 1; i <= parameterCount; i++) {
            pstm.setObject(i,params[i-1]);
        }

        //4. 执行SQL语句
        int num = pstm.executeUpdate();
        //5. 关闭资源
        pstm.close();
        connection.close();

        return num;
    }

    /**
     * 查询到多条数据封装到List<T>
     * @param sql
     * @param clazz
     * @param params
     * @param <T>
     * @return
     * @throws Exception
     */
    public <T> List<T> queryList(String sql, Class<T> clazz, Object... params) throws Exception {
        //1. 获取连接对象
        Connection connection = dataSource.getConnection();
        //2. 预编译SQL语句
        PreparedStatement pstm = connection.prepareStatement(sql);
        //3. 设置参数
        //3.1 获取参数的个数
        ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
        int parameterCount = parameterMetaData.getParameterCount();
        for (int i = 1; i <= parameterCount; i++) {
            pstm.setObject(i,params[i-1]);
        }

        //4. 执行SQL语句
        ResultSet rst = pstm.executeQuery();
        //获取结果集元数据
        ResultSetMetaData rstMetaData = rst.getMetaData();
        //获取结果集的列数
        int columnCount = rstMetaData.getColumnCount();

        //1. 获取POJO中的所有方法
        Method[] methods = clazz.getMethods();

        //声明一个集合存储t
        List<T> tList = new ArrayList<>();
        while (rst.next()) {
            //创建出封装数据的POJO对象
            T t = clazz.newInstance();

            for (int i = 1; i <= columnCount; i++) {
                //获取每列的名字(字段名)
                String columnName = rstMetaData.getColumnName(i);
                //1. 获取每一行数据的各个字段的数据
                Object columnValue = rst.getObject(columnName);

                //2. 调用对象的对应的set方法,往对象中设置数据
                //遍历出POJO的每一个方法
                for (Method method : methods) {
                    //获取方法名
                    String methodName = method.getName();
                    //匹配方法名
                    if (methodName.equalsIgnoreCase("set"+columnName)) {
                        //说明匹配到了对应的set方法,那么就调用这个set方法,设置columnValue
                        method.invoke(t,columnValue);
                    }
                }

            }

            tList.add(t);
        }

        //关闭资源
        rst.close();
        pstm.close();
        connection.close();
        return tList;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值