1. 数据库连接池

1.1 连接池技术

1) 什么是连接池

  • 实际开发中“获取连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能的问题,通常情况下我们采用连接池技术,来共享连接Connection。这样我们就不需要每次创建连接、释放连接了。这些操作都交给连接池。

2) 连接池的好处

  • 用连接池来管理Connection,这样可以重复使用Connection。当使用完Connection以后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。

1.2 JDBC方式与连接池方式

  • 普通JDBC方式:

image.png

  • 连接池方式

数据库连接池.jpg

1.3 如何使用数据库连接池

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

常见的连接池有DBCP连接池,C3P0连接池,Druid的连接池,接下来我们就详细学习一下~

1.4 数据准备

#创建数据库
CREATE DATABASE db5 CHARACTER SET utf8;
#使用数据库
USE db5;
#创建员工表
CREATE TABLE employee (
    eid INT PRIMARY KEY AUTO_INCREMENT ,
    ename VARCHAR (20), -- 员工姓名
    age INT , -- 员工年龄
    sex VARCHAR (6), -- 员工性别
    salary DOUBLE , -- 薪水
    empdate DATE -- 入职日期
);
#插入数据
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'李清照',22,'女',4000,'2018-11-12');
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'林黛玉',20,'女',5000,'2019-03-14');
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'杜甫',40,'男',6000,'2020-01-01');
INSERT INTO employee (eid, ename, age, sex, salary, empdate) VALUES(NULL,'李白',25,'男',3000,'2017-10-01');

1.5 DBCP连接池

DBCP也是一个开源的连接池,是Apache成员之一,在企业开发中也比较常见,tomcat内置的连接池。

1.5.1 创建项目 导入 jar包

1) 导入如下两个jar包到myJar文件夹下
image.png
2) 添加myJar库 到项目的依赖中
image.png

1.5.2 编写工具类

  • 连接数据库表的工具类,采用DBCP连接池的方式来完成。
    • Java中提供了一个连接池的规则接口:DataSource,它是java中提供的连接池
    • 在DBCP包中提供了DataSource接口的实现类,我们需要使用具体的连接池BasicDataSource

代码示例:

import org.apache.commons.dbcp.BasicDataSource;

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

/**
 * @author 西风月
 * @date 2020/9/22
 * @description
 */
public class DBCPUtils {
    //1. 定义常量,保存数据库连接的相关信息
    public static final String DRIVERNAME = "com.mysql.jdbc.Driver";
    public static final String URL = "jdbc:mysql://localhost:3306/db5?characterEncoding=UTF-8";
    public static final String USERNAME = "root";
    public static final String PASSWORD = "em1tu0f";

    //2. 创建连接池对象(有DBCP提供的实现类)
    public static BasicDataSource dataSource = new BasicDataSource();

    //3. 使用静态代码块进行配置
    static {
        dataSource.setDriverClassName(DRIVERNAME);
        dataSource.setUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
    }

    //4. 获取连接的方法
    public static Connection getConnection() throws SQLException {
        //从连接池获取连接
        Connection connection = dataSource.getConnection();
        return connection;
    }

    //5. 释放资源的方法
    public static void close(Connection connection, Statement statement) {
        if(null != statement) {
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(null != connection) {
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void close(Connection connection, Statement statement, ResultSet resultSet) {
        if(null != resultSet) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        close(connection, statement);
    }
}

1.5.3 测试工具类

  • 需求: 查询所有员工的姓名 ```java import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement;

/**

  • @author 西风月
  • @date 2020/9/22
  • @description / public class TestDBCP { /*

    • 测试DBCP连接池
    • @param args */ public static void main(String[] args) throws SQLException { //1. 从DBCP连接池中拿到连接 Connection connection = DBCPUtils.getConnection();

      //2. 获取Statement对象 Statement statement = connection.createStatement();

      //3. 查询所有员工的姓名 String sql = “select ename from employee”; ResultSet resultSet = statement.executeQuery(sql);

      //4. 处理结果集 while(resultSet.next()) {

       String name = resultSet.getString("ename");
       System.out.println("ename = " + name);
      

      }

      //5. 释放资源 DBCPUtils.close(connection, statement, resultSet); } }

<a name="CgUG9"></a>
### 1.5.4 常见配置项
| **属性** | **描述** |
| --- | --- |
| driverClassName | 数据库驱动名称 |
| url | 数据库地址 |
| username | 用户名 |
| password | 密码 |
| maxActive | 最大连接数量 |
| maxldle | 最大空闲连接 |
| minldle | 最小空闲连接 |
| initialSize | 初始化连接 |


<a name="SuU1l"></a>
## 1.6 C3P0连接池
CP30是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。<br />目前使用它的开源项目有Hibernate、Spring等。
<a name="kXDrB"></a>
### 1.6.1 导入Jar包及配置文件
(1) 将jar包复制到myJar文件夹即可,IDEA会自动导入<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1567843/1600861084883-57ec2354-e7f8-41a5-bf6e-8be012a3fd85.png#align=left&display=inline&height=107&margin=%5Bobject%20Object%5D&name=image.png&originHeight=167&originWidth=939&size=141098&status=done&style=none&width=600)<br />(2) 导入配置文件c3p0-config.xml

- `c3p0-config.xml`文件名不可更改
- 直接放到src下,也可以放到资源文件夹中
```xml
<c3p0-config>

  <!--默认配置-->
    <default-config>  

        <!-- initialPoolSize:初始化时获取三个连接,
              取值应在minPoolSize与maxPoolSize之间。 --> 
        <property name="initialPoolSize">3</property>  

        <!-- maxIdleTime:最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。-->
        <property name="maxIdleTime">60</property>  

        <!-- maxPoolSize:连接池中保留的最大连接数 -->
        <property name="maxPoolSize">100</property>  
        <!-- minPoolSize: 连接池中保留的最小连接数 -->
        <property name="minPoolSize">10</property>  

    </default-config>  

   <!--配置连接池mysql-->

    <named-config name="mysql">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/db5?characterEncoding=UTF-8</property>
        <property name="user">root</property>
        <property name="password">em1tu0f</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </named-config>
    <!--配置连接池2,可以配置多个-->

</c3p0-config>

(3) 在项目下创建一个resource文件夹(专门存放资源文件)
image.png
(4) 选择文件夹,右键 将resource文件夹指定为资源文件夹
image.png
(5) 将文件放在resource目录下即可,创建连接池对象的时候会去加载这个配置文件
image.png

1.6.2 编写C3P0工具类

  • C3P0提供的核心工具类,ComboPooledDataSource,如果想使用连接池,就必须创建该类的对象
    • new ComboPooledDataSource(); 使用默认配置
    • new ComboPooledDataSource(“mysql”);使用命名配置 ```java package C3P0Test;

import com.mchange.v2.c3p0.ComboPooledDataSource;

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

/**

  • @author 西风月
  • @date 2020/9/23
  • @description */ public class C3P0Utils { //1. 创建连接池对象C3P0对DataSource接口的实现类 //使用的配置是配置文件中的默认配置 //public static ComboPooledDataSource dataSource = new ComboPooledDataSource();

    //使用指定的配置 public static ComboPooledDataSource dataSource = new ComboPooledDataSource(“mysql”);

    //获取连接的方法 public static Connection getConnection() throws SQLException {

     return dataSource.getConnection();
    

    }

    //释放资源 public static void close(Connection con, Statement statement) {

     if(null != con && null != statement) {
         try {
             statement.close();
             con.close();    //归还连接
         } catch (SQLException e) {
             e.printStackTrace();
         }
     }
    

    }

    public static void close(Connection con, Statement statement, ResultSet resultSet) {

     if(null != resultSet) {
         try {
             resultSet.close();
         } catch (SQLException e) {
             e.printStackTrace();
         }
     }
     close(con, statement);
    

    } }

<a name="MNi87"></a>
### 1.6.3 测试工具类
> 需求: 查询姓名为 李白的员工信息

```java
package C3P0Test;

import java.sql.*;

/**
 * @author 西风月
 * @date 2020/9/23
 * @description
 */
public class TestC3P0 {
    public static void main(String[] args) throws SQLException {
        //1. get Connection
        Connection connection = C3P0Utils.getConnection();

        //2. get PreparedStatement预处理对象
        String sql = "select * from employee where ename = ?";
        PreparedStatement ps = connection.prepareStatement(sql);

        //3. 设置占位符的值
        ps.setString(1, "李白");

        ResultSet resultSet = ps.executeQuery();

        //4. 处理结果集
        while (resultSet.next()) {
            int eid = resultSet.getInt("eid");
            String ename =  resultSet.getString("ename");
            int age = resultSet.getInt("age");
            String sex = resultSet.getString("sex");
            double salary = resultSet.getDouble("salary");
            Date date = resultSet.getDate("empdate");

            System.out.println("eid" + " " + ename + " " + age + " " + sex + " " + salary + " " + date);
        }

        C3P0Utils.close(connection, ps, resultSet);
    }
}

1.6.4 常见属性

image.png

1.7 Druid连接池

Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。

1.7.1 导入jar包及配置文件

(1) 导入jar 包
image.png
(2) 导入配置文件

  • 是properties形式的
  • 可以叫任意名称,可以放在任意目录下,我们统一放resources资源目录

image.png

1.7.2 编写Druid工具类

  • 获取数据库连接池对象
    • 通过工厂来获取DruidDataSourceFactory类的createDataSource方法。
    • createDataSource(Preperties p) 方法参数是一个属性集对象。
package DruidTest;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * @author 西风月
 * @date 2020/9/23
 * @description
 */
public class DruidUtils {
    //1. 定义成员变量
    public static DataSource dataSource;

    //2. 静态代码块
    static {
        try {
            //3. 创建属性集对象
            Properties p = new Properties();

            //4. 加载配置文件,Druid连接池b不能够主动加载配置文件,需要指定文件
            InputStream inputStream = DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties");

            //5. 使用Properties对象的load方法从字节流中读取配置信息
            p.load(inputStream);

            //6. 通过工厂类获取连接池对象
            dataSource = DruidDataSourceFactory.createDataSource(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 获取连接的方法
    public static Connection getConnection() {
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }

    //释放资源
    public static void close(Connection con, Statement statement) {
        if(null != con && null != statement) {
            try {
                statement.close();
                con.close();    //归还连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    public static void close(Connection con, Statement statement, ResultSet resultSet) {
        if(null != resultSet) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        //归还连接
        close(con, statement);
    }
}

1.7.3 测试工具类

需求: 查询薪资在3000 - 5000元之间的员工姓名

package DruidTest;

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

/**
 * @author 西风月
 * @date 2020/9/23
 * @description
 */
public class TestDruid {
    public static void main(String[] args) throws SQLException {
        //1. 获取连接
        Connection connection = DruidUtils.getConnection();

        //2. 获取Statement对象
        Statement statement = connection.createStatement();

        //3. 执行查询
        String sql = "select ename from employee where salary between 3000 and 5000";
        ResultSet resultSet = statement.executeQuery(sql);

        //4. 轮询 get结果集
        while(resultSet.next()) {
            String name = resultSet.getString("ename");
            System.out.println(name);
        }

        //释放资源
        DruidUtils.close(connection, statement, resultSet);
    }
}

2.DBUtils工具类

2.1 DBUtils简介

使用JDBC我们发现冗余的代码太多了,为了简化开发,我们选择使用DBUtils。
Common DBUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的性能。

  • 使用方式:
    • DBUtils就是JDBC的简化开发工具包,需要项目中导入 commons-dbutils-1.6.jar。

image.png

  • JDBC开发步骤:

  • 获取驱动(可以省略)
  • 获取连接
  • 获取Statement对象
  • 处理结果集(只在查询时处理)
  • 释放资源

2.1.1 DBUtils核心功能介绍

  1. QueryRunner中提供对sql语句操作的API。
  2. ResultSetHandler接口用于定义select操作后,怎样封装结果集。
  3. DBUtils类就是一个工具类,定义了关闭资源事务处理相关方法。

2.2 案例相关准备

2.2.1 表和类之间的关系

  • 整个表可以看做是一个类
  • 表中的一行记录,对应一个类的实例(对象)
  • 表中的一列,对应类中的一个成员属性

image.png

2.2.2 JavaBean组件

(1) JavaBean就是一个类,开发中通常用于封装数据,有如下特点:

  1. 需要实现序列化接口,Serializable(暂时可省略)
  2. 提供私有字段:private 类型 变量名;
  3. 提供 getter 和 setter
  4. 提供空参构造

(2) 创建Employee类和数据库的employee表对应

  • 我们可以创建一个entity包,专门用来存放JavaBean类

image.png

package entity;

import java.io.Serializable;
import java.util.Date;

/**
 * @author 西风月
 * @date 2020/9/24
 * @description
 *   `eid` int(11) NOT NULL AUTO_INCREMENT,
 *   `ename` varchar(20) DEFAULT NULL,
 *   `age` int(11) DEFAULT NULL,
 *   `sex` varchar(6) DEFAULT NULL,
 *   `salary` double DEFAULT NULL,
 *   `empdate` date DEFAULT NULL,
 */
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;
    private int eid;
    private String ename;
    private int age;
    private String sex;
    private double salary;
    private Date empdate;

    public Employee() {
    }

    public Employee(int eid, String ename, int age, String sex, double salary, Date empdate) {
        this.eid = eid;
        this.ename = ename;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
        this.empdate = empdate;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "eid=" + eid +
                ", ename='" + ename + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", salary=" + salary +
                ", empdate=" + empdate +
                '}';
    }

    public int getEid() {
        return eid;
    }

    public String getEname() {
        return ename;
    }

    public int getAge() {
        return age;
    }

    public String getSex() {
        return sex;
    }

    public double getSalary() {
        return salary;
    }

    public Date getEmpdate() {
        return empdate;
    }

    public void setEid(int eid) {
        this.eid = eid;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public void setEmpdate(Date empdate) {
        this.empdate = empdate;
    }
}

2.3 DBUtils完成CRUD

2.3.1 QueryRunner核心类

  • 构造方法
    • QueryRunner()
    • QueryRunner(DateSource ds)提供数据源(连接池),DBUtils底层自动维护连接Connection
  • 常用方法

    • update(Connection con, String sql, Object... params),用来完成表数据的增加、删除、更新操作
    • query(Connection con, String sql, ResultSetHandler<T> rsh, Object... params), 用来完成表数据的查询操作

      2.3.2 QueryRunner的创建

  • 手动模式

    //1. 创建QueryRunner,手动模式
    QueryRunner qr = new QueryRunner();
    
  • 自动模式

    //1. 创建QueryRunner对象,自动模式,传入数据库连接池
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
    
  • 自动模式需要传入连接池对象

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

    2.3.3 QueryRunner实现增、删、改操作


  • 核心方法 :

update(Connection conn, String sql, Object... params)
image.png
步骤

  1. 创建QueryRunner(手动或自动)
  2. 占位符方式 编写SQL
  3. 设置占位符参数
  4. 执行

    1. 添加

    @Test
    public void testInsert() throws SQLException {
     //1. 创建QueryRunner,手工模式
     QueryRunner qr = new QueryRunner();
     //2. 编写占位符方式 SQL
     String sql = "insert into employee values(?,?,?,?,?,?)";
     //3. 设置占位符的参数
     Object[] param = {null,"张百万",20,"女",10000,"1995-01-01"};
     //4. 执行update方法
     Connection con = DruidUtils.getConnection();
     qr.update(con, sql, param);
     //5. 释放资源
     DbUtils.close(con);
    }
    

    2. 修改

    @Test
    public void testUpdate() throws SQLException {
     //1. 创建QueryRunner对象,自动模式,传入数据库连接池
     QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
     //2. 编写sql
     String sql = "update employee set salary = ? where ename = ?";
     //3. 设置占位符参数
     Object[] param = {20000, "张百万"};
     //4. 执行update,不需要传入连接对象
     qr.update(sql, param);
    }
    

    3. 删除

    @Test
    public void testDelete() throws SQLException {
     QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
     String sql = "delete from employee where eid = ?";
     //只有一个参数,不需要创建数组
     qr.update(sql, 1);
    }
    

    2.3.4 QueryRunner实现查询操作

    1. ResultSetHandler接口简介

  • ResultSetHandler可以查询出来的ResultSet结果集进行处理,达到一些业务上的要求。

    2. ResultSetHandler结果集处理类

    本例展示的是使用ResultSetHandler接口的几个常见实现类对数据库进行增删改查操作,可以大大减少代码量,优化程序。
    每一种实现类都代表了对查询结果集的一种处理方式。
    image.png

    3. ResultSetHandler 常用实现类测试

  • QueryRunner的查询方法

  • query()方法的返回值都是泛型,具体的返回值类型,会根据结果集的处理方式发生变化

image.png

  • 创建一个测试类,对ResultSetHandler接口的几个常见实现类进行实现。
    • 查询id为5的记录,封装到数组中
    • 查询所有数据,封装到List集合中
    • 查询id为5的记录,封装到指定JavaBean中
    • 查询薪资大于 3000 的所员工信息,封装到JavaBean中再封装到List集合中
    • 查询姓名是 张百万的员工信息,将结果封装到Map集合中
    • 查询所有员工的薪资总额

(1) 查询id为5的记录,封装到数组中。

 @Test
 public void testArrayHandler() throws SQLException {
     //1. 创建QueryRunner
     QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
     //2. 编写sql
     String sql = "select * from employee where eid = ?";
     //3. 执行查询
     Object[] query = qr.query(sql, new ArrayHandler(), 5);
     //4. 获取数据
     System.out.println(Arrays.toString(query));
 }

(2) 查询所有数据,封装到List集合中

@Test
public void testArrayListHandler() throws SQLException {
    //1. 创建QueryRunner
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
    //2. 编写sql
    String sql = "select * from employee";
    //3. 执行查询
    List<Object[]> query = qr.query(sql, new ArrayListHandler());
    //4. 遍历集合获取数据  iter 快捷键
    for (Object[] objects : query) {
        System.out.println(Arrays.toString(objects));
    }
}

(3) 根据ID查询,封装到指定JavaBean中

@Test
public void testBeanHandler() throws SQLException {
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
    String sql = "select * from employee where eid";
    //Employee employee = qr.query(sql, new BeanHandler<Employee>(Employee.class), 3);
    Employee employee = qr.query(sql, new BeanHandler<Employee>(Employee.class));
    System.out.println(employee);
}

(4) 查询薪资大于 3000 的所员工信息,封装到JavaBean中再封装到List集合中

@Test
public void testBeanListHandler() throws SQLException {
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
    String sql = "select * from employee where salary > ?";
    List<Employee> employees = qr.query(sql, new BeanListHandler<Employee>(Employee.class), 3000);
    for (Employee employee : employees) {
        System.out.println(employee);
    }
}

(5) 查询姓名是 张百万的员工信息,将结果封装到Map集合中

@Test
public void testMapHandler() throws SQLException {
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
    String sql = "select * from employee where ename = ?";
    Map<String, Object> map = qr.query(sql, new MapHandler(), "张百万");
    Set<Map.Entry<String, Object>> entries = map.entrySet();
    for (Map.Entry<String, Object> entry : entries) {
        System.out.println(entry.getKey() + " = " + entry.getValue());
    }
}

(6) 查询所有员工的薪资总额

@Test
public void testSalarHandler() throws SQLException {
    QueryRunner qr = new QueryRunner(DruidUtils.getDataSource());
    String sql = "select sum(salary) from employee";
    Double sum = qr.query(sql, new ScalarHandler<Double>());
    System.out.println("员工薪资总额:" + sum);
}