连接池(C3P0 、DRUID)和DBUtils(简化jdbc的操作)

连接池和DBUtils

为什么要使用连接池

Connection对象在JDBC使用的时候就会去创建一个对象,使用结束以后就会将这个对象给销毁了(close).每次创建和销毁对象都是耗时操作.需要使用连接池对其进行优化. 数据库的连接建立,开销比较大,所以在一开始就用一个池子来装若干个连接对象。

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

连接池原理【重点】

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VjXd6fjH-1607964653408)(img/tu_1.png)]

  1. 开始先使用集合存储若干个连接对象到集合中

  2. 如果有程序需要使用连接,就从连接池中获取

  3. 使用完毕后,要记得归还而不是关闭连接

  4. 如果连接池中的连接对象已经使用完毕,再来一个新的程序想要连接,那么可以创建一个新的连接给它。使用完毕之后,可以归还到连接池里面,也可以选择不归还。

  5. 连接池最好使用LinkedList来实现

    因为它增删比较快。

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

在这里插入图片描述

自定义连接池-初级版本

package com.itheima.pool_01;

import com.itheima.util.JdbcUtil02;

import java.sql.Connection;
import java.util.LinkedList;

/*
 *  @创建时间:  2020/7/23 9:40
 *  @描述:    自定义连接池(初级版本)
 *  @ 思路
 *      1. 一开始要准备好5个连接放到集合当中 (只会发生一次) :: 静态代码块
 *
 *      2. 暴露一个方法,供他人获取连接
 *
 *      3. 暴露一个方法,供他人归还连接
 */
public class MyDataSource01 {

    //连接池
    static LinkedList<Connection> pool;

    static{

        try {
            //构建连接池
            pool = new LinkedList<>();

            //初始化5个连接对象,放到连接池中
            for (int i = 0; i < 5; i++) {
                Connection conn = JdbcUtil02.getConnection();
                pool.addLast(conn);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    //获取连接
    public  Connection  getConn(){
       return  pool.removeFirst() ;
    }


    //归还连接
    public void addBack(Connection conn){
        pool.addLast(conn);
    }


    public int size(){
        return pool.size();
    }
}

自定义连接池-进阶版本

datasource接口

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-swevnkJc-1607964653411)(img/tu_4.png)]

package com.itheima.pool_01;

import com.itheima.util.JdbcUtil02;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;

/*
 *  @创建时间:  2020/7/23 9:56
 *
 *  @描述:    自定义连接池(升级版) :  实现了Java的数据源连接池的规范
 *
 *  @思路
 *      1. 一开始也要准备连接对象,放到连接池里面去。(和初级版本一样)
 *
 *      2. 提供一个方法,供他人获取连接。
 *
 *      3. 提供一个方法,供他人归还连接
 */
public class MyDataSource02 implements DataSource {

    static LinkedList<Connection> pool;
    static{

        try {
            pool = new LinkedList<>();
            for (int i = 0; i < 5; i++) {
                Connection conn = JdbcUtil02.getConnection();
                pool.addLast(conn);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    @Override
    public Connection getConnection() throws SQLException {
        return pool.removeFirst();
    }


    public void addBack(Connection conn){
        pool.addLast(conn);
    }

    public int size(){
        return pool.size();
    }



    //=========================下面的方法都不需要理会======================
    @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;
    }
}

  • 测试代码
package com.itheima.pool_01;

import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
 *  @创建时间:  2020/7/23 9:46
 *  @描述:    测试升级版本的连接池
 */
public class TestMyDataSource02 {


    @Test
    public void testDataSource() throws SQLException {

        //1. 拿连接
        MyDataSource02 ds = new MyDataSource02();

        System.out.println("连接数量1:" + ds.size());

        Connection conn = ds.getConnection();
        System.out.println("连接数量2:" + ds.size());

        String sql = "insert into user values (null , ? , ? , ?)";
        PreparedStatement ps = conn.prepareStatement(sql);

        ps.setString(1, "bb");
        ps.setString(2, "bb");
        ps.setString(3, "bb");

        ps.executeUpdate();

        //执行完毕之后,
        ps.close();

        //得归还连接
        ds.addBack(conn);
        System.out.println("连接数量3:" + ds.size());

    }

}
1. 定义一个类,实现接口DataSource

2. 定义一个linkedList作为连接池,在静态代码块里面,初始化5个连接
3. 使用实现的getConnection方法对外提供连接
4. 自己编写归还的方法  :: addBack
升级版本的优势和弱势

优势: 规范了,实现了DataSource

缺点:有很多的方法,不用,但是也得写上。

自定义连接池-终极版本

装饰者模式

装饰者模式,是 23种常用的面向对象软件的设计模式之一. 动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更加有弹性的替代方案。

​ 装饰者的作用:改写已存在的类的某个方法或某些方法, 增强方法的逻辑

  • 使用装饰者模式需要满足的条件

    • 增强类和被增强类实现的是同一个接口
    • 增强类里面要拿到被增强类的引用 句柄 | 对象
装饰者模式的使用【重点】

实现步骤:

  1. 增强类(WrapperCar)和被增强类(Qq)需要实现同一个接口(Car)
  2. 增强类(WrapperCar)里面需要得到被增强类(Qq)的引用, eg:把车开进去
  3. 对于不需要改写的方法(stop()),调用被增强类原有的方法。
  4. 对于需要改写的方法(run()),写自己的代码

例子: 张三追妹子, 需要买车.

​ 张三钱不够, 只能买Qq车(只能跑60迈, 可以刹车)

​ 张三开了一段时间, 发现追不到妹子

​ 把车开到改装店进行改装(把速度改成5s破百, 刹车不改)

interface Car{
    void run();
    void stop();
}

class Qq implements Car{}  //QQ
public class QQWrap implements Car{
  • 接口: Car.java
package com.itheima.mode_02;

/*
 *  @创建时间:  2020/7/23 10:33
 *  @描述:    TODO
 */
public interface Car {

    void run();

    void stop();
}

  • 被增强的类: Qq.java
package com.itheima.mode_02;

/*
 *  @创建时间:  2020/7/23 10:34
 *  @描述:    被装饰类
 */
public class Qq  implements  Car{
    @Override
    public void run() {
        System.out.println("10s 破百");
    }

    @Override
    public void stop() {
        System.out.println("停车");
    }
}


  • 增强的类: WrapperCar.java
package com.itheima.mode_02;

/*
 *  @创建时间:  2020/7/23 10:36
 *  @描述:    装饰类,装饰QQ车
 *
 *  @ 思路:
 *      1. 实现Car接口
 *      2. 要持有被装饰类的对象,为什么要持有,因为有些功能不想增强,那还是得用原来的功能
 */
public class QQWrap implements Car{

    //被装饰的对象
    Car c;

    public QQWrap(Car c) {
        this.c = c;
    }

    //只想改造这个run方法
    @Override
    public void run() {

        System.out.println("3s 破百");
    }

    //如果不想改造某些功能,那么还是使用原来的功能
    @Override
    public void stop() {
        c.stop();
    }
}

  • 测试代码
package com.itheima.mode_02;

/*
 *  @创建时间:  2020/7/23 10:35
 *  @描述:    TODO
 */
public class Test {


    public static void main(String[] args) {

        //原厂的车
        Car c = new Qq();
       // c.run();
       // c.stop();

        //把原厂的车丢给装饰类
        Car c2= new QQWrap(c);


        //最终用的是装饰的对象
        c2.run();
        c2.stop();
    }
}


自定义连接池终极版本
  • 目标就是装饰conn对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5S5VWZ5U-1607964653417)(img/image-20191203101709735.png)]

  1. 创建MyConnection实现Connection
  2. 在MyConnection里面需要得到被增强的connection对象(通过构造方法传进去)
  3. 改写close()的逻辑, 变成归还
  4. 其它方法的逻辑, 还是调用被增强connection对象之前的逻辑
package com.itheima.pool_01;

import java.sql.*;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;

/*
 *  @创建时间:  2020/7/23 10:49
 *  @描述:    装饰类,装饰原生的Connection对象
 *
 *  @思路:
 *      1. 装饰类和被装饰类要实现同一个接口
 *
 *      2. 方法这么多,要装饰哪个方法呢??
 *          至少目前知道了要装饰close方法
 *
 *      3. 原来的连接对象,调用close执行的是关闭的操作,现在我们想执行归还的动作。
 *
 *      4. 归还连接,需要用到两个对象,
 *          a. 连接池,
 *          b. 原生对象.
 */
public class ConnectionWrap implements Connection {


    LinkedList<Connection> pool;
    Connection srcConnection;

    public ConnectionWrap(LinkedList<Connection> pool, Connection srcConnection) {
        this.pool = pool;
        this.srcConnection = srcConnection;
    }

    //装饰的方法
    @Override
    public void close() throws SQLException {
        //连接池对象.addLast(连接对象);
        pool.addLast(srcConnection);
    }

    //这两个方法我们不想装饰,但是也不能直接写null ,因为外面的程序需要使用他们。

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

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {

        System.out.println("执行这个函数了~~");
        return srcConnection.prepareStatement(sql);
    }

	//无需理会的方法
	....
}

package com.itheima.pool_01;

import com.itheima.util.JdbcUtil02;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.logging.Logger;

/*
 *  @创建时间:  2020/7/23 9:56
 *
 *  @描述:    自定义连接池(终极版本)
 *
 *  @思路
 *      使用装饰者模式对连接对象进行装饰
 *
 *      1. 返回连接的对象的时候,不能直接返回原生的conn,需要对原生的conn装饰一番
 *
 *      2. 在getConnection 返回装饰的对象
 *
 *      3. 定义一个装饰类,装饰连接对象。
 */
public class MyDataSource03 implements DataSource {

    static LinkedList<Connection> pool;
    static{

        try {
            pool = new LinkedList<>();
            for (int i = 0; i < 5; i++) {
                Connection conn = JdbcUtil02.getConnection();
                pool.addLast(conn);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }


    @Override
    public Connection getConnection() throws SQLException {

        //这是原生的连接对象
        Connection conn = pool.removeFirst();


        //装饰一下原生对象
        Connection c = new ConnectionWrap(pool , conn);


        //把装饰的对象抛出去。
        return c;
    }


    public void addBack(Connection conn){
        pool.addLast(conn);
    }

    public int size(){
        return pool.size();
    }



    //=========================下面的方法都不需要理会======================
 }

  • 测试代码
package com.itheima.pool_01;

import com.itheima.util.JdbcUtil02;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
 *  @创建时间:  2020/7/23 9:46
 *  @描述:    测试终极版本的连接池
 */
public class TestMyDataSource03 {


    @Test
    public void testDataSource() throws SQLException {

        //1. 拿连接
        MyDataSource03 ds = new MyDataSource03();

        System.out.println("连接数量1:" + ds.size());

        Connection conn = ds.getConnection();
        System.out.println("连接数量2:" + ds.size());

        String sql = "insert into user values (null , ? , ? , ?)";
        PreparedStatement ps = conn.prepareStatement(sql);

        ps.setString(1, "dd");
        ps.setString(2, "dd");
        ps.setString(3, "dd");

        ps.executeUpdate();


        JdbcUtil02.closeAll(ps, conn);
        System.out.println("连接数量3:" + ds.size());

    }

}

第三方连接池

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

用的比较多的是:

  • C3P0
  • druid
  • 光连接池

C3P0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w5Z9iUY1-1607964653420)(img/tu_3.jpg)]

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

步骤

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

实现:

package com.itheima.c3p0_03;

import com.itheima.util.JdbcUtil02;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;

/*
 *  @创建时间:  2020/7/23 11:17
 *  @描述:    硬编码,c3p0连接池, 使用代码来构建c3p0连接池。
 */

public class TestC3P0Demo01 {


    @Test
    public void testDataSource(){

        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //c3p0连接池
            ComboPooledDataSource ds = new ComboPooledDataSource();


            //设置连接池,设置如何去拿连接,账号,密码,拿的是哪个数据库的连接
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/day20");
            ds.setUser("root");
            ds.setPassword("root");






            System.out.println("初始化的大小:"+ds.getInitialPoolSize());

            System.out.println("最大的连接数:"+ds.getMaxPoolSize());
            System.out.println("最小的连接数:"+ds.getMinPoolSize());

            //获取连接
            conn = ds.getConnection();

            //可以执行操作
            String sql = "insert into user values (null , ? , ? , ?)";
            ps = conn.prepareStatement(sql);

            ps.setString(1, "zz");
            ps.setString(2, "zz");
            ps.setString(3, "zz");

            ps.executeUpdate();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil02.closeAll(ps, conn);

        }
    }

}


通过配置文件来编写

步骤:

  1. 拷贝jar包
  2. 在src源码路径下,定义一个xml文件,文件的名称固定是: c3p0-config.xml
  3. 内容如下:
<c3p0-config>
	<default-config>
		<!--连接数据库的信息-->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/day20</property>
        <property name="user">root</property>
        <property name="password">root</property>
		<property name="initialPoolSize">5</property>
	</default-config>
</c3p0-config>

  1. 如果对这个连接池的配置不满意,请查看c3p0的官方文档。
  2. 默认创建连接池对象,会使用default-config 的配置。 当然也可以指定使用具体的配置。
DataSource ds = new ComboPooledDataSource(); 

  • 实现代码
package com.itheima.c3p0_03;

import com.itheima.util.JdbcUtil02;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;

/*
 *  @创建时间:  2020/7/23 11:17
 *  @描述:    使用配置文件来配置连接池
 */

public class TestC3P0Demo02 {


    @Test
    public void testDataSource(){

        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //c3p0连接池
            ComboPooledDataSource ds = new ComboPooledDataSource();
            
            //这是使用指定名称的配置
            //ComboPooledDataSource ds = new ComboPooledDataSource("intergalactoApp");

            System.out.println("初始化的大小:"+ds.getInitialPoolSize());

            System.out.println("最大的连接数:"+ds.getMaxPoolSize());
            System.out.println("最小的连接数:"+ds.getMinPoolSize());


            //获取连接
            conn = ds.getConnection();

            //可以执行操作
            String sql = "insert into user values (null , ? , ? , ?)";
            ps = conn.prepareStatement(sql);

            ps.setString(1, "yy");
            ps.setString(2, "yy");
            ps.setString(3, "yy");

            ps.executeUpdate();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            JdbcUtil02.closeAll(ps, conn);

        }
    }
}

​我们之前写的工具类(JdbcUtils)每次都会创建一个新的连接, 使用完成之后, 都给销毁了; 所以现在我们要使用c3p0来改写工具类. 也就意味着,我们从此告别了JdbcUtils. 后面会使用c3p0写的工具类

思路:

  1. 创建C3P0Utils这个类
  2. 定义DataSource, 保证DataSource全局只有一个
  3. 定义getConnection()方法从DataSource获得连接
  4. 定义closeAll()方法 释放资源
package com.itheima.util;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/*
 *  @创建时间:  2020/7/23 11:56
 *  @描述:    c3p0连接池的工具类
 *  @思路:
 *      1. 首先得先有连接池,连接池做几个? 一个。  ComboPooledDataSource ds = new ComboPooledDataSource();
 *      2. 提供一个方法供别人获取连接
 *      3. 提供关闭释放的方法
 */
public class C3p0Util {

    static ComboPooledDataSource ds;
    static {

         ds = new ComboPooledDataSource();
    }


    //提供获取连接
    public static Connection getConn(){
        try {
            return ds.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            return null;
        }

    }


    //关闭释放资源
    //重载!!!!!
    public static void  closeAll( Statement st , Connection conn){
        closeAll(null , st ,conn);
    }

    //真正这个st和 conn 是
    public static void  closeAll(ResultSet rs , Statement st , Connection conn){

        try {
            if(rs!=null)
                rs.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

        try {
            if(st!=null)
                st.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }

        try {
            if(conn !=null)
                conn.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

}


  • 测试类的代码
package com.itheima.c3p0_03;

import com.itheima.util.C3p0Util;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;

/*
 *  @创建时间:  2020/7/23 11:17
 *  @描述:    测试c3p0的工具类
 */

public class TestC3P0Demo03 {


    @Test
    public void testDataSource(){


        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //c3p0连接池

            conn  = C3p0Util.getConn();

            //可以执行操作
            String sql = "insert into user values (null , ? , ? , ?)";
            ps = conn.prepareStatement(sql);

            ps.setString(1, "abc");
            ps.setString(2, "abc");
            ps.setString(3, "abc");

            ps.executeUpdate();


        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            C3p0Util.closeAll(ps , conn);
        }
    }

}


小结

  1. C3P0 配置文件方式使用

    • 拷贝jar包, jar包有两个
    • 拷贝配置文件
    • 直接创建连接池对象, ComboPooledDataSource
  2. C3P0工具类

    • 使用静态代码块来创建连接池,保证只有一个连接池
    • 对外提供一个方法,获取连接
    • 对外提供回收释放资源的方法 ,关闭所有

    代替昨天写的JdbcUtils

DRUID

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

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9GAAu51N-1607964653422)(img/tu_7.png)]

DRUID的使用
通过硬编码方式

步骤:

  1. 导入jar包
  2. 创建dataSource
  3. 设置连接的属性
  4. 获取连接

实现:

package com.itheima.druid_04;

import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
 *  @创建时间:  2020/7/23 12:13
 *  @描述:    硬编码配置, 代码配置druid连接池
 */
public class TestDruidDataSource01 {


    @Test
    public void testDataSource() throws SQLException {
        
        //技巧,如果想给这个对象设置东西,1. 在构造里面设置, 2. 采用setXXX设置
        DruidDataSource ds = new DruidDataSource();


        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/day20");
        ds.setUsername("root");
        ds.setPassword("root");


        Connection conn = ds.getConnection();
        //可以执行操作
        String sql = "insert into user values (null , ? , ? , ?)";
        PreparedStatement ps = conn.prepareStatement(sql);

        ps.setString(1, "abc");
        ps.setString(2, "yy");
        ps.setString(3, "yy");

        ps.executeUpdate();

        ps.close();
        conn.close();

    }
}


通过配置文件方式

步骤:

  1. 导入jar包
  2. 在src下面,创建一个properties文件,名字随意。一般:druid.properties, 内容如下:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day20
username=root
password=root

  1. 创建连接池,这里用的是工厂来创建。
package com.itheima.druid_04;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.Test;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.Properties;

/*
 *  @创建时间:  2020/7/23 12:13
 *  @描述:    配置文件配置druid连接池
 */
public class TestDruidDataSource02 {

    @Test
    public void testDataSource() throws Exception {

        //目标: 就是为了要构建一个数据源。
        //在Java语言里面创建对象有2种方式
        //    1. 直接new , 2.使用工厂方法创建

        Properties p = new Properties();
        p.load(TestDruidDataSource02.class.getClassLoader().getResourceAsStream("druid.properties"));
        DataSource ds = DruidDataSourceFactory.createDataSource(p);



        Connection conn = ds.getConnection();
        //可以执行操作
        String sql = "insert into user values (null , ? , ? , ?)";
        PreparedStatement ps = conn.prepareStatement(sql);

        ps.setString(1, "abc");
        ps.setString(2, "yy");
        ps.setString(3, "yy");

        ps.executeUpdate();

        ps.close();
        conn.close();

    }
}


小结

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

DBUtils

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

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

QueryRunner 是所有增删改查都要用的一个对象,它需要传递进去连接对象,现在的连接对象都是从连接池里面拿。

QueryRunner 会自动维护连接对象

  1. 执行增删改的SQL语句的API
QueryRunner runner = new QueryRunner(datasource);

//增删改,有参数和没有参数
runner.update(sql , "" , "" , "");
runner.update(sql);

  1. 执行查询的SQL语句的API
QueryRunner runner = new QueryRunner(datasource);
        
//查询数据
runner.query(sql , 结果集处理器 , 参数 );
runner.query(conn , sql , 结果集处理器 , 参数)      

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

1.目标

  • 掌握使用DBUtils完成增删改

2.步骤

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

3.实现

package com.itheima._05_dbutil;

import com.itheima.util.C3p0Util;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;

import java.sql.SQLException;

/*
 *  @创建时间:  2020/7/23 14:42
 *  @描述:    DBUtils的简单入门 (增删改)
 */
public class DBUtilsDemo01 {


    @Test
    public void testInsert() throws SQLException {

        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
        int result = runner.update("insert into user values(null , ? , ?,?)" , "aa","bb","cc" );
        
        if(result > 0 ){
            System.out.println("添加成功");
        }else{
            System.out.println("添加失败");
        }
    }

    @Test
    public void testDelete() throws SQLException {

        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
        int result = runner.update("delete from user where id = ?" , 42 );
        if(result > 0 ){
            System.out.println("删除成功");
        }else{
            System.out.println("删除失败");
        }
    }


    @Test
    public void testUpdate() throws SQLException {

        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
        int result = runner.update("update user set nickname = ? where id = ?" , "管理员" ,45);
        if(result > 0 ){
            System.out.println("更新成功");
        }else{
            System.out.println("更新失败");
        }
    }
}


4.小结

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

使用DBUtils完成查询

  1. 拷贝jar包
  2. 创建QueryRunner()对象 传入DataSource
  3. 调用query(sql, resultSetHandler,params)方法
ResultSetHandler结果集处理类介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6W7Roh3d-1607964653424)(img/05.png)]

代码实现
查询一条数据封装到JavaBean对象中(使用BeanHandler)
//查询一条完整的记录
    @Test
    public void testQueryForBean() throws SQLException {
        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());

        String sql = "select * from user where id = ?";
        
        //1. 这个Query方法返回的是一个实实在在的user对象
        //2. 那么就表明了,这个方法的内部一定会先创建出来user对象
        //3. 把查表得到的数据放到这个对象身上然后返回。

        //4. 问!!!!! BeanHandler的构造参数要的是一个字节码, 请问,要这个字节码有何用??
        User user = runner.query(sql ,  new BeanHandler<User>(User.class), 45);

        System.out.println("user=" + user);
    }

查询多条数据封装到List中(使用BeanListHandler)
    //查询多条完整的记录
    @Test
    public void testQueryAll() throws SQLException {

        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());

        String sql = "select * from user";

        //如果要查询多条记录,那么传递进去的是BeanListHandler 返回的是List<对象>
        List<User> list =  runner.query(sql ,new BeanListHandler<User>(User.class));


        for(User u : list){
            System.out.println("u=" + u);
        }

    }

查询一条数据,封装到Map对象中(使用MapHandler)
 //查询返回的结果装到一个map集合里面 KEY = VALUE
    // map 的key就是列的名字, value就是列的值。
    @Test
    public void testQueryMap() throws SQLException {
        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
        String sql = "select * from user where id = ?";

        //得到数组。
        Map<String ,  Object> m= runner.query(sql , new MapHandler(), 45);


        for(Map.Entry<String , Object> entry :  m.entrySet()){
            System.out.println(entry.getKey() + "=" + entry.getValue());

        }

    }

查询多条数据,封装到List<Map>对象中(使用MapListHandler)
  //查询很多数据放到 List<Map<String , Object>>
    @Test
    public void testQueryListMap() throws SQLException {
        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());
        String sql = "select * from user";

        //得到数组。
        List<Map<String ,  Object>> list= runner.query(sql , new MapListHandler());
        
        //遍历
        for(Map<String ,Object> m : list){
            for(Map.Entry<String , Object> entry :  m.entrySet()){
                System.out.println(entry.getKey() + "=" + entry.getValue());

            }
            System.out.println("------\n");
        }
    }

查询单个数据(使用ScalarHandler())
    //聚合查询 , 其实就是查询总数 、 最大值、 最小值,平均值,
    @Test
    public void testQueryScalar() throws SQLException {

        QueryRunner runner = new QueryRunner(C3p0Util.getDataSource());

        String sql = "select count(*) from user";

        //查询得到的只是一个数字
        Long count = (Long)runner.query(sql , new ScalarHandler());

        System.out.println("count=" + count);
    }


  1. 步骤

    a. 拷贝jar包

    b. 创建QueryRunner对象,执行操作,要记得传递进去DataSource

    c. update | query 对应是增删改 和 查询

  2. 查询的结果处理器的区别

    1. BeanHandler : 查询一条记录(完整记录)
    2. BeanListHandler : 查询多条记录
    3. ScalarHandler : 聚合查询
  3. 注意的地方

    1. JavaBean要规范, 要提供get & set方法 , 无参构造也要给上。

自定义DBUtils

​ 我们要自定义DBUtils, 就需要知道列名, 参数个数等, 这些可以通过数的元数据就是一些注明数据库信息的数据。

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

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

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

ParameterMetaData

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

​	[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRMOk4P4-1607964653426)(img/tu_10.png)]

​ 获得ParameterMetaData:

`ParameterMetaData parameterMetaData =  preparedStatement.getParameterMetaData ()`
ParameterMetaData相关的API
  • int getParameterCount(); 获得参数个数
  • int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
实例代码
  @Test
    public void testPs() throws SQLException {


        Connection conn = C3p0Util.getConn();

        String sql = "insert into user values(null , ? , ?, ?)";

        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setString(1, "aa");
        ps.setString(2, "aa");
        ps.setString(3, "aa");


        //得到元数据
        ParameterMetaData md = ps.getParameterMetaData();

        System.out.println("参数的个数:" + md.getParameterCount());

        //后续的这些方法会报错。
        System.out.println("第一个的参数类型名称:"+md.getParameterClassName(1));

    }


ResultSetMetaData

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k1OOqWtw-1607964653428)(img/tu_9-1573632938379.png)]

​ 获得ResultSetMetaData:

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


        Connection conn = C3p0Util.getConn();

        String sql = "select * from user where id = ?";

        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setInt(1, 45);


        ResultSet rs = ps.executeQuery();

        //重点关注元数据
        ResultSetMetaData md = rs.getMetaData();


        System.out.println("列的个数:" + md.getColumnCount());

        System.out.println("第1列的名字:"+md.getColumnName(1));

        
        System.out.println("第2列的数据库类型:"+md.getColumnTypeName(2));


        System.out.println("第2列的java类型:"+md.getColumnClassName(2));


    }


小结
  1. 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型…
  2. mysql元数据:
    • ParameterMetaData
    • ResultSetMetaData

案例-自定义DBUtils增删改

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

实现

package com.itheima._05_dbutil;

import com.itheima.util.C3p0Util;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/*
 *  @创建时间:  2020/7/23 16:13
 *  @描述:    TODO
 */
public class MyQueryRunner {

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


    //增删改的功能

    // MyQueryRunner runner = new MyQueryRunner(C3P0Util.getDataSource);
    // String sql = "insert into user values (null , ? , ? , ?)";
    //runner.update(sql , "aa","bb","cc")

    public int update(String  sql , Object ... param){

        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = ds.getConnection();
            ps = conn.prepareStatement(sql);


            //1. 不知道SQL语句里面的 ? 有几个, :: 元数据可以知道
            ParameterMetaData md = ps.getParameterMetaData();

            int paramCount = md.getParameterCount();

            //如果可变参数的个数和?的个数不对等
            if(param.length  != paramCount) {
                System.err.println("参数个数和?的总数不对等");

                throw new RuntimeException("参数个数和?的总数不对等");
                //return -1;
            }

            for (int i = 0; i <paramCount ; i++) {
                ps.setObject(i+1 , param[i] );
            }

            return ps.executeUpdate();


        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            C3p0Util.closeAll(ps , conn);
        }
        return 0 ;
    }
}


小结

  1. 创建一个类,MyQuerryRunner
  2. 通过构造函数,从外面传递进去DataSource
  3. 定义一个方法 update , 用于完成 增删改的功能
  4. 在update 函数内部,步骤如下:
    1. 通过dataSource获取连接对象
    2. 创建preparestatement对象
    3. 使用preparestatement对象获取元数据, 以便知道 ? 的个数有几个
    4. 循环给占位赋值
    5. 执行excuteUpdate .
    6. 关闭ps , conn.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值