学习目标
- 能够通过PreparedStatement完成增、删、改、查
- 能够完成PreparedStatement改造登录案例
- 能够理解连接池解决现状问题的原理
- 能够使用C3P0连接池
- 能够使用DRUID连接池
- 能够编写连接池工具类
- 能够说出动态代理的好处
- 能够使用动态代理
第1章 PreparedSatement预编译对象
1.1 SQL注入问题
在我们前一天JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了文章源自JAVA秀-https://www.javaxiu.com/667.html
请输入用户名:
hehe
请输入密码:
a' or '1'='1
问题分析:文章源自JAVA秀-https://www.javaxiu.com/667.html
// 代码中的SQL语句
"SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 将用户输入的账号密码拼接后
"SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。文章源自JAVA秀-https://www.javaxiu.com/667.html
要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。文章源自JAVA秀-https://www.javaxiu.com/667.html
1.2 PreparedSatement的执行原理
继承结构:文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。文章源自JAVA秀-https://www.javaxiu.com/667.html
Satement
对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');");
stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");
上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。文章源自JAVA秀-https://www.javaxiu.com/667.html
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement
对象并执行。相当于调用方法多次传入不同的参数。
String sql = "INSERT INTO users VALUES (?, ?, ?);";
// 会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, 1);
pstmt.setInt(2, "张三");
pstmt.setString(3, "123456");
pstmt.executeUpdate();
// 再次设置参数
pstmt.setString(1, 2);
pstmt.setInt(2, "李四");
pstmt.setString(3, "66666");
pstmt.executeUpdate();
上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。文章源自JAVA秀-https://www.javaxiu.com/667.html
示意图:文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.3 PreparedSatement的好处
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement
对象并执行。减少SQL编译次数,提高效率。- 安全性更高,没有SQL注入的隐患。
- 提高了程序的可读性
1.4 PreparedSatement的基本使用
1.4.1 API介绍
1.4.1.1 获取PreparedSatement的API介绍
在java.sql.Connection
有获取PreparedSatement
对象的方法文章源自JAVA秀-https://www.javaxiu.com/667.html
PreparedStatement prepareStatement(String sql)
会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。
1.4.1.2 PreparedSatement的API介绍
在java.sql.PreparedStatement
中有设置SQL语句参数,和执行参数化的SQL语句的方法文章源自JAVA秀-https://www.javaxiu.com/667.html
-
void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值。
-
void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。
-
void setInt(int parameterIndex, int x) 将指定参数设置为给定 Java int 值。
-
void setLong(int parameterIndex, long x) 将指定参数设置为给定 Java long 值。
-
void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值。
-
void setString(int parameterIndex, String x) 将指定参数设置为给定 Java String 值。
-
ResultSet executeQuery() 在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的ResultSet对象。
-
int executeUpdate() 在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
1.4.2 PreparedSatement使用步骤
- 编写SQL语句,未知内容使用?占位:
"SELECT * FROM user WHERE name=? AND password=?;";
- 获得PreparedStatement对象
- 设置实际参数
- 执行参数化SQL语句
- 关闭资源
1.4.3 案例代码
public class Demo08 {
public static void main(String[] args) throws Exception {
// 获取连接
Connection conn = JDBCUtils.getConnection();
// 编写SQL语句,未知内容使用?占位
String sql = "SELECT * FROM user WHERE name=? AND password=?;";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 指定?的值
// parameterIndex: 第几个?,从1开始算
// x: 具体的值
pstmt.setString(1, "admin");
pstmt.setString(2, "123"); // 正确的密码
// pstmt.setString(2, "6666"); // 错误的密码
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String name = rs.getString("name");
System.out.println("name:" + name);
} else {
System.out.println("没有找到数据...");
}
JDBCUtils.close(conn, pstmt, rs);
}
}
1.4.4 案例效果
- 输入正确的账号密码:
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 输入错误的密码:
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.5 PreparedSatement实现增删查改
1.5.1 添加数据
向Employee表添加3条记录文章源自JAVA秀-https://www.javaxiu.com/667.html
// 添加数据: 向Employee表添加3条记录
public static void addEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO employee VALUES (NULL, ?, ?, ?);";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, "刘德华");
pstmt.setInt(2, 57);
pstmt.setString(3, "香港");
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 再次设置参数
pstmt.setString(1, "张学友");
pstmt.setInt(2, 55);
pstmt.setString(3, "澳门");
i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 再次设置参数
pstmt.setString(1, "黎明");
pstmt.setInt(2, 52);
pstmt.setString(3, "香港");
i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
效果:文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.5.2 修改数据
将id为2的学生地址改成台湾文章源自JAVA秀-https://www.javaxiu.com/667.html
// 修改数据: 将id为2的学生地址改成台湾
public static void updateEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "UPDATE employee SET address=? WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "台湾");
pstmt.setInt(2, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
效果:文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.5.3 删除数据
删除id为2的员工文章源自JAVA秀-https://www.javaxiu.com/667.html
// 删除数据: 删除id为2的员工
public static void deleteEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "DELETE FROM employee WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
效果:文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.5.4 查询数据
查询id小于8的员工信息,并保存到员工类中文章源自JAVA秀-https://www.javaxiu.com/667.html
public class Employee {
private int id;
private String name;
private int age;
private String address;
public Employee() {
}
public Employee(int id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String toString() {
return "Employee2 [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
// 查询数据: 查询id小于8的员工信息,并保存到员工类中
public static void queryEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM employee WHERE id<?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 26);
ResultSet rs = pstmt.executeQuery();
// 创建集合存放多个Employee2对象
ArrayList<Employee> list = new ArrayList<>();
while (rs.next()) {
// 移动到下一行有数据,取出这行数据
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String address = rs.getString("address");
// 创建Employee2对象
Employee e = new Employee(id, name, age, address);
// 将创建好的员工添加到集合中
list.add(e);
}
// 输出对象
for (Employee e : list) {
System.out.println(e);
}
JDBCUtils.close(conn, pstmt, rs);
}
效果:文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.6 案例:PreparedStatement改造登录案例
1.6.1 案例需求
模拟用户输入账号和密码登录网站,防止SQL注入文章源自JAVA秀-https://www.javaxiu.com/667.html
1.6.2 案例效果
- 输入正确的账号,密码,显示登录成功
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 输入错误的账号,密码,显示登录失败
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 输入
"a' or '1'='1"
作为密码,解决SQL注入:
文章源自JAVA秀-https://www.javaxiu.com/667.html
1.6.3 案例分析
- 使用数据库保存用户的账号和密码
- 让用户输入账号和密码
- 编写SQL语句,账号和密码部分使用?占位
- 使用PreparedSatement给?设置参数
- 使用PreparedSatement执行预编译的SQL语句
- 如果查询到数据,说明登录成功
- 如果查询不到数据,说明登录失败
1.6.4 实现步骤
- 编写代码让用户输入账号和密码
public class Demo07 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
}
- 编写SQL语句,账号和密码部分使用?占位,使用PreparedSatement给?设置参数,使用PreparedSatement执行预编译的SQL语句
public class Demo11 {
public static void main(String[] args) throws Exception {
// 让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
// 获取连接
Connection conn = JDBCUtils.getConnection();
// 编写SQL语句,账号和密码使用?占位
String sql = "SELECT * FROM user WHERE name=? AND password=?;";
// 获取到PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, name);
pstmt.setString(2, password);
// pstmt.setString(2, "a' or '1'='1");
}
}
- 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
public class Demo11 {
public static void main(String[] args) throws Exception {
// 让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
// 获取连接
Connection conn = JDBCUtils.getConnection();
// 编写SQL语句,账号和密码使用?占位
String sql = "SELECT * FROM user WHERE name=? AND password=?;";
// 获取到PreparedStatement对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, name);
pstmt.setString(2, password);
// pstmt.setString(2, "a' or '1'='1");
// 如果查询到数据,说明登录成功,如果查询不到数据,说明登录失败
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
// 能进来查询到了数据.
String name2 = rs.getString("name");
System.out.println("欢迎您," + name2);
} else {
// 查询不到数据,说明登录失败
System.out.println("账号或密码错误...");
}
JDBCUtils.close(conn, pstmt, rs);
}
}
第2章 连接池
2.1 连接池概念
我们现实生活中每日三餐。我们并不会吃一餐饭就将碗丢掉,而是吃完饭后将碗放到碗柜中,下一餐接着使用。目的是重复利用碗,我们的数据库连接也可以重复使用,可以减少数据库连接的创建次数。提高数据库连接对象的使用率。文章源自JAVA秀-https://www.javaxiu.com/667.html
连接池的概念: 连接池是创建和管理数据库连接的缓冲池技术。连接池就是一个容器,连接池中保存了一些数据库连接,这些连接是可以重复使用的。文章源自JAVA秀-https://www.javaxiu.com/667.html
2.2 没有连接池的现状
- 之前JDBC访问数据库的步骤: 创建数据库连接 →运行SQL语句→关闭连接 每次数据库访问执行这样重复的动作
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 每次创建数据库连接的问题
-
- 获取数据库连接需要消耗比较多的资源,而每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,而数据库创建连接通常需要消耗相对较多的资源,创建时间也较长。这样数据库连接对象的使用率低。
- 假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出
2.3 连接池解决现状问题的原理
- 程序一开始就创建一定数量的连接,放在一个容器中,这个容器称为连接池(相当于碗柜/容器)。
- 使用的时候直接从连接池中取一个已经创建好的连接对象。
- 关闭的时候不是真正关闭连接,而是将连接对象再次放回到连接池中。
文章源自JAVA秀-https://www.javaxiu.com/667.html
2.4 数据库连接池相关API
Java为数据库连接池提供了公共的接口: javax.sql.DataSource
,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池。文章源自JAVA秀-https://www.javaxiu.com/667.html
在javax.sql.DataSource
接口中有一个方法:Connection getConnection()
: 从连接池获取一个连接文章源自JAVA秀-https://www.javaxiu.com/667.html
归还连接的时候直接调用连接的close()
方法即可.文章源自JAVA秀-https://www.javaxiu.com/667.html
第3章 C3P0连接池
3.1 C3P0连接池简介
C3P0地址:https://sourceforge.net/projects/c3p0/?source=navbar C3P0是一个开源的连接池。Hibernate框架,默认推荐使用C3P0作为连接池实现。 C3P0的jar包:c3p0-0.9.5.2.jar
和 mchange-commons-java-0.2.12.jar
文章源自JAVA秀-https://www.javaxiu.com/667.html
3.2 API介绍
com.mchange.v2.c3p0.ComboPooledDataSource
类表示C3P0的连接池对象,常用2种创建连接池的方式:文章源自JAVA秀-https://www.javaxiu.com/667.html
1.无参构造,使用默认配置
文章源自JAVA秀-https://www.javaxiu.com/667.html
2.有参构造,使用命名配置
文章源自JAVA秀-https://www.javaxiu.com/667.html
-
public ComboPooledDataSource() 无参构造使用默认配置(使用xml中default-config标签中对应的参数)
-
public ComboPooledDataSource(String configName) 有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数)
-
public Connection getConnection() throws SQLException 从连接池中取出一个连接
3.3 常用的配置参数解释
常用的配置参数:文章源自JAVA秀-https://www.javaxiu.com/667.html
参数
说明
initialPoolSize
初始连接数
maxPoolSize
最大连接数
checkoutTimeout
最大等待时间
maxIdleTime
最大空闲回收时间
初始连接数
:刚创建好连接池的时候准备的连接数量 最大连接数
:连接池中最多可以放多少个连接 最大等待时间
:连接池中没有连接时最长等待时间 最大空闲回收时间
:连接池中的空闲连接多久没有使用就会回收文章源自JAVA秀-https://www.javaxiu.com/667.html
完整参数文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html
3.4 硬编码方式实现C3P0连接池(了解)
3.4.1 案例需求
使用代码给C3P0连接池设置相应的参数。文章源自JAVA秀-https://www.javaxiu.com/667.html
3.4.2 案例步骤
- 导入jar包
c3p0-0.9.5.2.jar
和mchange-commons-java-0.2.12.jar
- 创建连接池对象ComboPooledDataSource对象
- 设置连接参数:driverClass,jdbcUrl,user,password
- 设置连接池参数
- 初始连接数initialPoolSize
- 最大连接数maxPoolSize
- 最大等待时间checkoutTimeout
- 最大空闲回收时间maxIdleTime
- 获取连接对象(
getConnection()
方法)
3.4.3 案例代码
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
score DOUBLE DEFAULT 0.0
);
public class Demo01 {
public static void main(String[] args) throws Exception {
// 2.创建连接池对象ComboPooledDataSource对象
ComboPooledDataSource ds = new ComboPooledDataSource();
// 连接到数据库的相关参数
// 3.设置连接参数:driverClass , jdbcUrl, user, password
ds.setDriverClass("com.mysql.jdbc.Driver"); // 设置驱动的名称
ds.setJdbcUrl("jdbc:mysql://localhost:3306/day25");
ds.setUser("root");
ds.setPassword("root");
// 4.设置连接池参数
// a.初始连接数initialPoolSize
ds.setInitialPoolSize(5);
// b.最大连接数maxPoolSize
ds.setMaxPoolSize(10);
// c.最大等待时间checkoutTimeout
// Java程序去连接池中取连接,最长的等待时间,超过这个时间会有异常
ds.setCheckoutTimeout(5000);
// d.最大空闲回收时间maxIdleTime
// 连接池中的连接超过这个时间没有人使用,就会自动销毁
ds.setMaxIdleTime(3000);
// for (int i = 0; i < 10; i++) {
// Connection conn = ds.getConnection();
// System.out.println(conn);
// }
Connection conn = ds.getConnection(); // 从一个连接池中取出一个连接
// jdbc操作sql语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "张三");
pstmt.setInt(2, 25);
pstmt.setDouble(3, 99.5);
int i = pstmt.executeUpdate();
System.out.println("影响的行数: " + i);
pstmt.close();
conn.close(); // 将连接还回连接池中
}
}
3.3.5 案例效果
- 正常获取连接
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 获取连接超时
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 使用连接池中连接执行SQL语句
文章源自JAVA秀-https://www.javaxiu.com/667.html
总结:使用纯代码获取配置C3P0连接池的弊端,所有配置信息都写在代码中。一旦需要改配置信息,就需要改动源代码,非常麻烦。文章源自JAVA秀-https://www.javaxiu.com/667.html
3.5 配置文件方式实现C3P0连接池
3.5.1 C3P0配置文件
我们看到要使用C3P0连接池,需要设置一些参数。那么这些参数怎么设置最为方便呢?使用配置文件方式。文章源自JAVA秀-https://www.javaxiu.com/667.html
配置文件的要求:文章源自JAVA秀-https://www.javaxiu.com/667.html
- 文件名:c3p0-config.xml
- 放在源代码即src目录下
- 配置方式一:使用默认配置(default-config)
- 配置方式二:使用命名配置(named-config)
配置文件c3p0-config.xml文章源自JAVA秀-https://www.javaxiu.com/667.html
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">10</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</default-config>
<named-config name="itheimac3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</named-config>
</c3p0-config>
3.5.2 使用步骤
- 导入jar包
c3p0-0.9.5.2.jar``mchange-commons-java-0.2.12.jar
- 编写
c3p0-config.xml
配置文件,配置对应参数 - 将配置文件放在src目录下
- 创建连接池对象
ComboPooledDataSource
,使用默认配置或命名配置 - 从连接池中获取连接对象
- 使用连接对象操作数据库
- 关闭资源
3.5.3 注意事项
C3P0配置文件名称必须为c3p0-config.xml
C3P0命名配置可以有多个文章源自JAVA秀-https://www.javaxiu.com/667.html
3.5.4 案例代码
- 配置文件
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">10</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</default-config>
<named-config name="itheimac3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="checkoutTimeout">2000</property>
<property name="maxIdleTime">1000</property>
</named-config>
</c3p0-config>
- java代码
public class Demo01 {
public static void main(String[] args) throws Exception {
// 方式一: 使用默认配置(default-config)
// new ComboPooledDataSource();
// ComboPooledDataSource ds = new ComboPooledDataSource();
// 方式二: 使用命名配置(named-config:配置名)
// new ComboPooledDataSource("配置名");
ComboPooledDataSource ds = new ComboPooledDataSource("otherc3p0");
// 从连接池中取出连接
Connection conn = ds.getConnection();
// jdbc执行SQL语句省略
System.out.println(conn);
conn.close(); // 将连接还回连接池中
}
}
3.5.5 总结
配置文件名称必须为:c3p0-config.xml
,将配置文件放在src目录下 使用配置文件方式好处:只需要单独修改配置文件,不用修改代码 多个配置的好处:文章源自JAVA秀-https://www.javaxiu.com/667.html
- 可以连接不同的数据库:db1,db2
- 可以使用不同的连接池参数:maxPoolSize
- 可以连接不同厂商的数据库:Oracle或MySQL
文章源自JAVA秀-https://www.javaxiu.com/667.html
第4章 DRUID连接池
4.1 DRUID简介
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。Druid地址:https://github.com/alibaba/druid DRUID连接池使用的jar包:druid-1.0.9.jar
文章源自JAVA秀-https://www.javaxiu.com/667.html
4.2 DRUID常用的配置参数
常用的配置参数:文章源自JAVA秀-https://www.javaxiu.com/667.html
参数
说明
jdbcUrl
连接数据库的url:mysql : jdbc:mysql://localhost:3306/druid2
username
数据库的用户名
password
数据库的密码
driverClassName
驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize
初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive
最大连接池数量
maxIdle
已经不再使用,配置了也没效果
minIdle
最小连接池数量
maxWait
获取连接时最大等待时间,单位毫秒。
4.3 DRUID连接池基本使用
4.3.1 API介绍
com.alibaba.druid.pool.DruidDataSourceFactory
类有创建连接池的方法文章源自JAVA秀-https://www.javaxiu.com/667.html
public static DataSource createDataSource(Properties properties)
创建一个连接池,连接池的参数使用properties中的数据
我们可以看到DRUID连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。 DRUID连接池的配置文件名称随便,建议放到src目录下面方便加载。 druid.properties
文件内容:文章源自JAVA秀-https://www.javaxiu.com/667.html
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day25
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
maxIdle=6
minIdle=3
4.3.2 使用步骤
- 在src目录下创建一个properties文件,并设置对应参数
- 加载properties文件的内容到Properties对象中
- 创建DRUID连接池,使用配置文件中的参数
- 从DRUID连接池中取出连接
- 执行SQL语句
- 关闭资源
4.3.3 案例代码
- 在src目录下新建一个DRUID配置文件,命名为:druid.properties,内容如下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day25
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
maxIdle=6
minIdle=3
java代码文章源自JAVA秀-https://www.javaxiu.com/667.html
public class Demo02 {
public static void main(String[] args) throws Exception {
// 加载配置文件中的配置参数
InputStream is = Demo03.class.getResourceAsStream("/druid.properties");
Properties pp = new Properties();
pp.load(is);
// 创建连接池,使用配置文件中的参数
DataSource ds = DruidDataSourceFactory.createDataSource(pp);
// 从连接池中取出连接
Connection conn = ds.getConnection();
// 使用jdbc执行SQL语句 省略
conn.close(); // 将连接还回连接池中
}
}
4.3.4 案例效果
- 正常获取连接池中的连接
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 获取连接池中的连接超时
文章源自JAVA秀-https://www.javaxiu.com/667.html
- 使用DRUID连接池中的连接操作数据库
文章源自JAVA秀-https://www.javaxiu.com/667.html
4.3.5 总结
DRUID连接池根据Properties对象中的数据作为连接池参数去创建连接池,我们自己定义properties类型的配置文件,名称自己取,也可以放到其他路径,建议放到src目录下方便加载。 不管是C3P0连接池,还是DRUID连接池,配置大致都可以分为2种:1.连接数据库的参数
,2.连接池的参数
,这2种配置大致参数作用都相同,只是参数名称可能不一样。文章源自JAVA秀-https://www.javaxiu.com/667.html
4.4 Jdbc工具类
我们每次操作数据库都需要创建连接池,获取连接,关闭资源,都是重复的代码。我们可以将创建连接池和获取连接池的代码放到一个工具类中,简化代码。文章源自JAVA秀-https://www.javaxiu.com/667.html
Jdbc工具类步骤:文章源自JAVA秀-https://www.javaxiu.com/667.html
- 声明静态数据源成员变量
- 创建连接池对象
- 定义公有的得到数据源的方法
- 定义得到连接对象的方法
- 定义关闭资源的方法
案例代码 DataSourceUtils.java文章源自JAVA秀-https://www.javaxiu.com/667.html
public class DataSourceUtils {
// 1. 声明静态数据源成员变量
private static DataSource ds;
// 2. 创建连接池对象
static {
// 加载配置文件中的数据
InputStream is = JdbcUtils.class.getResourceAsStream("/druid.properties");
Properties pp = new Properties();
try {
pp.load(is);
// 创建连接池,使用配置文件中的参数
ds = DruidDataSourceFactory.createDataSource(pp);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
// 3. 定义公有的得到数据源的方法
public static DataSource getDataSource() {
return ds;
}
// 4. 定义得到连接对象的方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
// 5.定义关闭资源的方法
public static void close(Connection conn, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
// 6.重载关闭方法
public static void close(Connection conn, Statement stmt) {
close(conn, stmt, null);
}
}
测试类代码文章源自JAVA秀-https://www.javaxiu.com/667.html
public class Demo03 {
public static void main(String[] args) throws Exception {
// 拿到连接
Connection conn = DataSourceUtils.getConnection();
// 执行sql语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "李四");
pstmt.setInt(2, 30);
pstmt.setDouble(3, 50);
int i = pstmt.executeUpdate();
System.out.println("影响的函数: " + i);
// 关闭资源
DataSourceUtils.close(conn, pstmt);
}
}
小结:使用Jdbc工具类后可以简化代码,我们只需要写SQL去执行。文章源自JAVA秀-https://www.javaxiu.com/667.html
第5章 动态代理
刚才我们学习完了常用的连接池,我们知晓了如果连接需要归还给连接池,那么直接调用close方法即可。 但是我们在学习jdbc时调用connection.close() 是关闭连接,而现在是将连接还回连接池。这个是怎么做到的呢 ? 其实是对Connection的close方法进行了增强,我们现在来学习下使用动态代理来增强方法。文章源自JAVA秀-https://www.javaxiu.com/667.html
5.1 代理模式
5.1.1 现实生活中的代理
假如我们要去买电脑,我们通常需要去找电脑代理商购买,电脑代理商去电脑工厂提货,电脑代理商可以赚取中间的差价。 假如我们想买火车票,我们可以直接去12306网站买票。可是太难抢到票了,于是我们去找火车票的黄牛,让黄牛帮我们去12306买票,黄牛买到票再加价卖给我们,赚取中间的差价。文章源自JAVA秀-https://www.javaxiu.com/667.html
你买电脑 -> 电脑代理商 -> 电脑工厂
你买火车票 -> 黄牛 -> 12306
调用者 代理对象 真正干活的目标对象
我们发现代理对象
和真正干活的目标
都具有相同的功能(卖电脑/卖票
),代理可以在中间赚取差价(增强功能)。文章源自JAVA秀-https://www.javaxiu.com/667.html
5.1.2 代理模式的作用
代理对象可以在调用者
和目标对象
之间起到中介的作用。代理对象
可以对目标对象
的功能进行增强
。文章源自JAVA秀-https://www.javaxiu.com/667.html
5.1.3 代理模式涉及到四个要素
- 调用者: 你。
- 代理对象: 联想电脑代理商/黄牛。
- 目标对象: 电脑工厂/12306。
- 抽象对象: 代理对象和目标对象都共有的接口,保证代理对象和真实对象都有相应的方法,如电脑代理商和电脑工厂都需要有卖电脑的功能。
文章源自JAVA秀-https://www.javaxiu.com/667.html
5.2 动态代理
5.2.1 什么是动态代理
在程序运行的过程中,动态创建出代理对象。文章源自JAVA秀-https://www.javaxiu.com/667.html
5.2.2 动态代理类相应的API
- Proxy类
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
作用:生成实现指定接口的代理对象
参数说明:
loader参数:目标对象的类加载器
interfaces:代理对象实现的接口数组
h: 具体的代理操作,InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。
返回值:实现指定接口的代理对象。
- InvocationHandler接口
Object invoke(Object proxy, Method method, Object[] args)
作用:在这个方法中实现对真实方法的增强
参数说明:
proxy:即方法newProxyInstance()方法返回的代理对象,该对象一般不要在invoke方法中使用。
method: 代理对象调用的方法对象。
args:代理对象调用方法时传递的参数。
返回值:是真实对象方法的返回值。
5.2.3 动态代理模式的开发步骤
- 直接创建真实对象
- 通过Proxy类,创建代理对象
- 调用代理方法
- 在InvocationHandler的invoke进行处理
5.2.4 动态代理模式使用
文章源自JAVA秀-https://www.javaxiu.com/667.html
- Providable接口
/*
提供商品的接口
*/
public interface Providable {
// 卖电脑
public abstract void sellComputer(double price);
// 维修电脑
public abstract void repairComputer(double price);
}
- ComputerFactory目标对象
/*
电脑工厂,真正生产电脑的厂商
*/
public class ComputerFactory implements Providable {
public void sellComputer(double price) {
System.out.println("电脑工厂卖出一台电脑,价格: " + price);
}
public void repairComputer(double price) {
System.out.println("电脑工厂修好一台电脑,价格: " + price);
}
}
- 调用者,动态生成代理对象
public class Demo05 {
public static void main(String[] args) {
// 1. 直接创建真实对象
ComputerFactory computerFactory = new ComputerFactory();
// 2. 通过Proxy类,创建代理对象
Providable proxy = (Providable) Proxy.newProxyInstance(
ComputerFactory.class.getClassLoader(),
new Class[] {Providable.class},
new MyInvocationHandler(computerFactory)
);
// 3. 调用代理方法
proxy.sellComputer(5000);
proxy.repairComputer(800);
}
}
// InvocationHandler实现类
class MyInvocationHandler implements InvocationHandler {
private ComputerFactory computerFactory;
public MyInvocationHandler(ComputerFactory computerFactory) {
this.computerFactory = computerFactory;
}
// 4. 在InvocationHandler的invoke进行处理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sellComputer")) { // 对于是要增强的方法进行增强
// 代理对象处理卖电脑的方法。
double price = (double) args[0];
double realPrice = price* 0.75;
System.out.println("代理商收到:" + price + "元,实际使用" + realPrice + "去电脑工厂买电脑");
computerFactory.sellComputer(realPrice);
System.out.println("代理商赚到:" + (price - realPrice) + "元钱");
return null;
} else { // 不需要增强的方法直接调用目标对象的方法
return method.invoke(computerFactory, args);
}
}
}
文章源自JAVA秀-https://www.javaxiu.com/667.html
5.2.5 动态代理模式小结
- 什么是动态代理 在程序运行的过程中,动态创建出代理对象。
- 动态代理使用到的API Proxy类的newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)方法 InvocationHandler接口的invoke(Object proxy, Method method, Object[] args)方法
- Proxy.newProxyInstance()方法的本质 创建了代理对象,代理对象实现了传入的接口。
- InvocationHandler接口的作用 调用代理对象的方法就会执行到InvocationHandler接口的invoke方法。
- 动态代理使用步骤
- 直接创建真实对象
- 通过Proxy类,创建代理对象
- 调用代理方法
- 在InvocationHandler的invoke进行处理
- 代理模式的好处 在不改变目标类的代码情况下,
代理对象
可以对目标对象
的功能进行增强
。
5.2.6 动态代理解决close问题 (了解)
在创建Connection连接时使用动态代理产生Connection代理对象,使用代理对象。修改createConnection
方法,创建Connection代理对象。文章源自JAVA秀-https://www.javaxiu.com/667.html
- 自定义连接池类:MyPool2.java
public class MyPool2 implements DataSource {
private int initCount = 5; //初始化的个数
private int maxCount = 10; //最大的连接个数
private int curCount = 0; //当前已经创建的连接个数
// 存储连接的容器
private LinkedList<Connection> list = new LinkedList<Connection>();
// 构造方法,初始化连接池的,连接池一旦创建那么连接池中就应该要有指定个数的连接。
public MyPool2() {
for (int i = 0; i < initCount; i++) {
Connection connection = createConnection();
// 把连接存储到容器中
list.add(connection);
}
}
// 创建Connection的方法
public Connection createConnection() {
Connection proxyConnection = null;
try {
Connection connection = JDBCUtils.getConnection();
// 使用动态代理,产生代理对象
proxyConnection = (Connection) Proxy.newProxyInstance(
MyPool2.class.getClassLoader(),
new Class[]{Connection.class},
new ConnectionHandler(connection));
} catch (SQLException e) {
e.printStackTrace();
}
curCount++;
return proxyConnection;
}
// 自定义处理器 ---内部类
class ConnectionHandler implements InvocationHandler {
private Connection connection; // 在内部维护一个需要被增强的对象(被代理对象)
public ConnectionHandler(Connection connection) {
this.connection = connection;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 先得到当前调用的方法的方法名
String methodName = method.getName();
if ("close".equalsIgnoreCase(methodName)) {
// 需要增强的方法,进行增强
list.addLast((Connection) proxy);
} else {
// 其他方法正常调用
return method.invoke(connection, args);
}
return null;
}
}
// 别人问你的连接池要连接
public Connection getConnection() {
// 情况一: 先判断连接池是否有连接
if (list.size() > 0) {
//如果连接池有连接,那么直接取出连接,返回即可
Connection connection = list.removeFirst();
return connection;
}
// 情况二: 连接池中没有连接,然后先判断目前的连接个数是否已经超过了最大的连接个数
if (curCount < maxCount) {
// 创建连接
Connection connection = createConnection();
return connection;
} else {
// 没有连接,并且已经超过了最大的连接个数
throw new RuntimeException("已经达到了最大的连接个数,请稍后");
}
}
// 回收Connection
public void close(Connection connection) {
list.addLast(connection);
}
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
public PrintWriter getLogWriter() throws SQLException {
return null;
}
public void setLogWriter(PrintWriter out) throws SQLException {
}
public void setLoginTimeout(int seconds) throws SQLException {
}
public int getLoginTimeout() throws SQLException {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
- 测试类:Demo05.java
public class Demo05 {
public static void main(String[] args) throws SQLException {
//创建自定义的连接池
MyPool2 myPool = new MyPool2();
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
System.out.println("连接:"+ myPool.getConnection());
Connection connection = myPool.getConnection() ; ///代理COnnection
System.out.println("连接:"+ connection );
/*
目前的问题: 由于我们编程习惯的问题,我们都习惯关闭资源的时候直接调用close方法,不习惯传递进去。
目标: 如果连接需要归还给连接池,那么直接调用close方法即可。
对connection的close方法不满意,需要增强。
*/
// 把连接还回给连接池
connection.close(); // 代理对象的Connection调用close,那么还回给连接池的对象是原有connection还是代理对象?
System.out.println("连接:"+ myPool.getConnection());
}
}
- 效果
文章源自JAVA秀-https://www.javaxiu.com/667.html
文章源自JAVA秀-https://www.javaxiu.com/667.html

评论