1. 数据库连接池
1.1 连接池技术
1) 什么是连接池
- 实际开发中“获取连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能的问题,通常情况下我们采用连接池技术,来共享连接Connection。这样我们就不需要每次创建连接、释放连接了。这些操作都交给连接池。
2) 连接池的好处
- 用连接池来管理Connection,这样可以重复使用Connection。当使用完Connection以后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。
1.2 JDBC方式与连接池方式
- 普通JDBC方式:
- 连接池方式
1.3 如何使用数据库连接池
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文件夹下
2) 添加myJar库 到项目的依赖中
1.5.2 编写工具类
- 连接数据库表的工具类,采用DBCP连接池的方式来完成。
- Java中提供了一个连接池的规则接口:
DataSource
,它是java中提供的连接池 - 在DBCP包中提供了DataSource接口的实现类,我们需要使用具体的连接池
BasicDataSource
类
- Java中提供了一个连接池的规则接口:
代码示例:
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文件夹(专门存放资源文件)
(4) 选择文件夹,右键 将resource文件夹指定为资源文件夹
(5) 将文件放在resource目录下即可,创建连接池对象的时候会去加载这个配置文件
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 常见属性
1.7 Druid连接池
Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。
1.7.1 导入jar包及配置文件
(1) 导入jar 包
(2) 导入配置文件
- 是properties形式的
- 可以叫任意名称,可以放在任意目录下,我们统一放resources资源目录
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。
- JDBC开发步骤:
- 获取驱动(可以省略)
- 获取连接
- 获取Statement对象
- 处理结果集(只在查询时处理)
- 释放资源
2.1.1 DBUtils核心功能介绍
QueryRunner
中提供对sql语句操作的API。ResultSetHandler
接口用于定义select
操作后,怎样封装结果集。- DBUtils类就是一个工具类,定义了关闭资源与事务处理相关方法。
2.2 案例相关准备
2.2.1 表和类之间的关系
- 整个表可以看做是一个类
- 表中的一行记录,对应一个类的实例(对象)
- 表中的一列,对应类中的一个成员属性
2.2.2 JavaBean组件
(1) JavaBean就是一个类,开发中通常用于封装数据,有如下特点:
- 需要实现序列化接口,Serializable(暂时可省略)
- 提供私有字段:private 类型 变量名;
- 提供 getter 和 setter
- 提供空参构造
(2) 创建Employee类和数据库的employee表对应
- 我们可以创建一个entity包,专门用来存放JavaBean类
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
常用方法
手动模式
//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)
步骤
- 创建QueryRunner(手动或自动)
- 占位符方式 编写SQL
- 设置占位符参数
- 执行
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接口的几个常见实现类对数据库进行增删改查操作,可以大大减少代码量,优化程序。
每一种实现类都代表了对查询结果集的一种处理方式。
3. ResultSetHandler 常用实现类测试
QueryRunner的查询方法
query()
方法的返回值都是泛型,具体的返回值类型,会根据结果集的处理方式发生变化
- 创建一个测试类,对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);
}