文章目录
1. 连接池和JdbcTemplate
1.1 核心API的功能:
类名 | 作用 |
---|---|
DriverManager实现类 | 注册驱动,创建建连接 |
Connection接口 | 表示数据库的连接 |
Statement接口 | 小货车,执行SQL语句的对象 |
ResultSet接口 | 数据库返回的结果集 |
1.2 Connection接口中的方法:
Connection接口中的方法 | 方法名 |
---|---|
得到Statement | createStatement |
1.3 Statement接口中的方法:
Statement接口中的方法 | 说明 |
---|---|
int executeUpdate(String sql) | 更新INSERT UPDATE DELETE, DML |
ResultSet executeQuery(String sql) | 查询SELECT |
1.4 ResultSet接口的方法:
ResultSet接口中的方法 | 描述 |
---|---|
boolean next() | 将游标向下移动,看有下一行数据 |
数据类型 getXxx(参数) | 获取这条记录某个字段的数据 |
学习目标
- 能够通过PreparedStatement完成增、删、改、查
- 能够完成PreparedStatement改造登录案例
- 能够理解连接池解决现状问题的原理
- 能够使用C3P0连接池
- 能够使用DRUID连接池
- 能够编写连接池工具类
- 掌握JdbcTemplate实现增删改
- 掌握JdbcTemplate实现查询操作
2. PreparedSatement预编译对象
目标
能够理解什么是SQL注入
能够理解PreparedSatement的执行原理
2.1 SQL注入问题
在我们前一天JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
请输入用户名:
hehe
请输入密码:
a' or '1'='1
问题分析:
// 代码中的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注入。
要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。
2.2 PreparedSatement的执行原理
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。
-
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万次,显然效率就低了。 -
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
能够使用预编译后的语句,多次传入不同的参数给PreparedStatement
对象并执行。这就相当于相当于Java中多次调用相同的方法传入不同的参数,达到一个复用的效果。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语句的编译次数,提高了执行效率。
2.3 PreparedSatement的好处
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用预编译后的结果,多次使用传入不同的参数给PreparedStatement
对象并执行。减少SQL编译次数,提高效率。- 安全性更高,没有SQL注入的隐患。
- 提高了程序的可读性
小结
什么是SQL注入?
我们自己输入的内容被拼接成了SQL语句的一部分
PreparedSatement的好处?
1.会预编译,传入不同参数重复使用,提高效率
2.没有SQL注入的隐患,更加安全
3.提高SQL语句的可读性
2.5 PreparedSatement的API介绍
目标
能够掌握PreparedSatement相应的API
获取PreparedSatement
在java.sql.Connection
有获取PreparedSatement
对象的方法
PreparedStatement prepareStatement(String sql)
会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。
2.5.1 PreparedSatement的API介绍
在java.sql.PreparedStatement
中有设置SQL语句参数,和执行参数化的SQL语句的方法
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 值。
//注意:以上参数index从1开始,从左到右第一个问号就是1;
ResultSet executeQuery() 执行 SQL 查询,并返回该查询生成的ResultSet对象。
int executeUpdate() 执行 SQL 语句,该语句必须是一个 SQL 数据操作语言DML语句,
比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
2.5.2 PreparedSatement使用步骤
-
编写SQL语句,未知内容使用?
占位:
"SELECT * FROM user WHERE name=? AND password=?;";
-
获得PreparedStatement对象
-
设置实际参数
-
执行参数化SQL语句
-
关闭资源
小结
PreparedSatement使用步骤?
- 编写SQL语句,未知内容使用?占位
- 获得PreparedStatement对象
- 设置实际参数
- 执行参数化SQL语句
- 关闭资源
2.6 PreparedSatement实现增删改
目标
能够掌握PreparedSatement实现增删查改
创建表结构
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
address VARCHAR(50)
);
添加数据
向Employee表添加3条记录
姓名 | 年龄 | 地址 |
---|---|---|
刘德华 | 57 | 香港 |
张学友 | 55 | 澳门 |
黎明 | 52 | 香港 |
@Test
public void add() throws SQLException {
//获取连接
Connection conn = JDBCUtils.getConnection();
//插入数据语句
String sql = "insert into employee (name,age,address) VALUE (?,?,?);";
//获取SQL预编译对象
PreparedStatement preSate = conn.prepareStatement(sql);
//插入第一条数据
preSate.setString(1, "刘德华");
preSate.setInt(2, 57);
preSate.setString(3, "香港");
int i1 = preSate.executeUpdate();
//插入第二条数据
System.out.println("插入数据:" + i1);
preSate.setString(1, "张学友");
preSate.setInt(2, 55);
preSate.setString(3, "澳门");
int i2 = preSate.executeUpdate();
System.out.println("插入数据:" + i2);
//插入第三条数据
preSate.setString(1, "黎明");
preSate.setInt(2, 52);
preSate.setString(3, "香港");
int i3 = preSate.executeUpdate();
System.out.println("插入数据:" + i3);
//关流
JDBCUtils.close(conn, preSate);
}
}
修改数据
将id为2的学生地址改成台湾
@Test
public void update() throws SQLException {
//将id为2的学生地址改成台湾
//获取连接
Connection conn = JDBCUtils.getConnection();
//插入数据语句
String sql = "update employee set address = ? where id=?";
//获取SQL预编译对象
PreparedStatement preSate = conn.prepareStatement(sql);
//插入第一条数据
preSate.setString(1, "台湾"); //address
preSate.setInt(2, 2); //id
int i1 = preSate.executeUpdate();
System.out.println("更新记录 = " + i1);
//关流
JDBCUtils.close(conn,preSate);
}
删除数据
删除id为2的员工
@Test
public void delete() throws SQLException {
//删除id为2的员工
//获取连接
Connection conn = JDBCUtils.getConnection();
//插入数据语句
String sql = "delete from employee where id=?";
//获取SQL预编译对象
PreparedStatement preSate = conn.prepareStatement(sql);
preSate.setInt(1, 2);
int i = preSate.executeUpdate();
System.out.println("更新记录: " + i);
//关流
JDBCUtils.close(conn, preSate);
}
小结
PreparedSatement使用步骤?
1.获取连接
2.先写SQL语句带?的
3.获取PreparedStatement,预编译
4.设置参数【给?赋值】
5.执行SQL
6.关闭资源
PreparedSatement实现增删查改使用哪个方法?
executeUpdate(); 执行增删改
exexuteQuery(); 执行查询
2.7 使用PreparedSatement改写登录案例
目标
能够使用PreparedSatement改写登录案例
-
输入正确的账号密码:
-
输入错误的密码:
PreparedSatement使用步骤
- 编写SQL语句,未知内容使用?占位
- 获得PreparedStatement对象
- 设置实际参数
- 执行参数化SQL语句
- 关闭资源
案例代码
public class Demo02 {
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=?;";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 指定?的值
// parameterIndex: 第几个?,从1开始算
// x: 具体的值
pstmt.setString(1, name);
pstmt.setString(2, password); // 正确的密码
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);
}
}
PreparedSatement查询数据
目标
能够掌握PreparedSatement实现查询数据
查询id小于8的员工信息,并保存到员工类中
实现步骤
-
得到连接对象
-
得到Statement对象
-
编写SQL语句并执行,保存ResultSet
-
创建一个集合用于封装所有的记录
-
每次循环封装一个学生对象
-
把数据放到集合中
-
关闭连接
-
遍历集合,循环输出学生对象
代码
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;
}
@Override
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);
}
小结
表对应? 类
一条记录对应? 对象
字段值对应? 一个成员变量的值
PreparedSatement实现查询使用哪个方法?
executeQuery();
3. 连接池介绍
目标
能够理解连接池的原理和好处
3.1 没有连接池的现状
-
之前JDBC访问数据库的步骤:
创建数据库连接 →运行SQL语句→关闭连接
每次数据库访问执行这样重复的动作 -
每次创建数据库连接的问题
-
获取数据库连接需要消耗比较多的资源
每次操作都要重新获取新的连接对象,执行一次操作就把连接关闭,而数据库创建连接通常需要消耗相对较多的资源,创建时间也较长,这样数据库连接对象的使用率低。
-
假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出
-
我们现实生活中每日三餐。我们并不会吃一餐饭就将碗丢掉,而是吃完饭后将碗放到碗柜中,下一餐接着使用。目的是重复利用碗,我们的数据库连接也可以重复使用,可以减少数据库连接的创建次数。提高数据库连接对象的使用率。
连接池的概念: 连接池是创建和管理数据库连接的缓冲池技术。连接池就是一个容器,连接池中保存了一些数据库连接,这些连接是可以重复使用的。
3.2 连接池解决现状问题的原理
- 程序一开始就创建一定数量的连接,放在一个容器中,这个容器称为连接池(相当于碗柜/容器)。
- 使用的时候直接从连接池中取一个已经创建好的连接对象。
- 关闭的时候不是真正关闭连接,而是将连接对象再次放回到连接池中。
3.3 连接池好处
连接池中保存了一些数据库连接,这些连接是可以重复使用的。节省数据库的资源消耗。
3.3.1 常用连接池的介绍
javax.sql.DataSource
表示数据库连接池,是JDK中提供的一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现。我们只需要学习这个工具如何使用即可。
public interface DataSource {
Connection getConnection();
}
常用的连接池实现组件有以下这些:
- 阿里巴巴-德鲁伊Druid连接池:Druid是阿里巴巴开源平台上的一个项目
- C3P0是一个开源的JDBC连接池,目前使用它的开源项目有Hibernate,Spring等。 (开源项目)
- DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目,也是Tomcat使用的连接池组件。
小结
连接池的好处?
连接池内部会保存好一些连接,这些连接可以反复使用,提高连接的使用率,降低数据库资源消耗
连接池的原理?
- 创建连接池时,连接池内部就会创建一些连接
- 当需要使用连接时,就直接从连接池里面取出连接
- 当连接使用完毕时,重新放回连接池
3.4 C3P0连接池
目标
能够使用C3P0连接池
3.4.1 C3P0连接池简介
C3P0地址:https://sourceforge.net/projects/c3p0/?source=navbar
C3P0是一个开源的连接池。Hibernate框架,默认推荐使用C3P0作为连接池实现。
C3P0的jar包:c3p0-0.9.1.2.jar
3.4.2 C3P0常用的配置参数解释
参数 | 说明 |
---|---|
initialPoolSize | 刚创建好连接池的时候连接数量 |
maxPoolSize | 连接池中最多可以放多少个连接 |
checkoutTimeout | 连接池中没有连接时最长等待时间 |
maxIdleTime | 连接池中的空闲连接多久没有使用就会回收。默认是0,0表示不回收 |
3.4.3 C3P0配置文件
我们看到要使用C3P0连接池,需要设置一些参数。那么这些参数怎么设置最为方便呢?使用配置文件方式。
配置文件的要求:
- 文件名:c3p0-config.xml 【注意,文件名要一样,否则加载会失败】
- 放在源代码即src目录下
配置文件c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</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="wangc3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</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.4.4 API介绍
创建连接池
com.mchange.v2.c3p0.ComboPooledDataSource
类表示C3P0的连接池对象,常用2种创建连接池的方式:
1.无参构造,使用默认配置
public ComboPooledDataSource()
无参构造使用默认配置(使用xml中default-config标签中对应的参数)
2.有参构造,使用命名配置
public ComboPooledDataSource(String configName)
有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数)
获取连接方法
public Connection getConnection() throws SQLException
从连接池中取出一个连接
使用步骤
- 导入jar包
- 在src目录中创建并编写
c3p0-config.xml
配置文件,配置对应参数 - 创建连接池对象
ComboPooledDataSource
,使用默认配置或命名配置 - 从连接池中获取连接对象
- 使用连接对象操作数据库
- 关闭资源
注意事项
C3P0配置文件名称必须为c3p0-config.xml
C3P0命名配置可以有多个
案例代码
-
准备数据
CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), age INT, score DOUBLE DEFAULT 0.0 );
-
配置文件
<c3p0-config> <!-- 使用默认的配置读取连接池对象 --> <default-config> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</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="wangac3p0"> <!-- 连接参数 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</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"); // for (int i = 0; i < 10; i++) { // Connection conn = ds.getConnection(); // System.out.println(conn); // } // 从连接池中取出连接 Connection conn = ds.getConnection(); // 执行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(); // 将连接还回连接池中 } }
注意:配置文件名称必须为:
c3p0-config.xml
,将配置文件放在src目录下
使用配置文件方式好处
只需要单独修改配置文件,不用修改代码
多个配置的好处:
- 可以连接不同的数据库:db1,db2
- 可以使用不同的连接池参数:maxPoolSize
- 可以连接不同厂商的数据库:Oracle或MySQL
小结
CP30使用步骤?
1.导入jar包
2.复制配置文件到src下:c3p0-config.xml
3.修改配置信息
4.创建ComboPooledDataSource()连接池
5.使用连接池获取连接:getConnection();
6.后续操作
7.关闭资源
C3P0常用参数?
initialPoolSize | 刚创建好连接池时,连接池内包含多少个连接 |
---|---|
maxPoolSize | 连接池最大可以存放多少连接 |
checkoutTimeout | 连接池没有连接可使用,最长的等待时间 |
maxIdleTime | 连接池中的连接多久没有被使用就会自动回收,默认0,不回收 |
3.5 Druid连接池
目标
能够掌握Druid连接池的使用
Druid是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
Druid地址:https://github.com/alibaba/druid
DRUID连接池使用的jar包:druid-1.0.9.jar
3.5.1 Druid常用的配置参数
参数 | 说明 |
---|---|
jdbcUrl | 连接数据库的url:jdbc:mysql://localhost:3306/day05 |
username | 数据库的用户名 |
password | 数据库的密码 |
driverClassName | 驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) |
initialSize | 刚创建好连接池的时候连接数量 |
maxActive | 连接池中最多可以放多少个连接 |
maxWait | 获取连接时最大等待时间,单位毫秒 |
3.5.2 Druid连接池基本使用
API介绍
com.alibaba.druid.pool.DruidDataSourceFactory
类有创建连接池的方法
public static DataSource createDataSource(Properties properties)
创建一个连接池,连接池的参数使用properties中的数据
我们可以看到Druid连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。
Druid连接池的配置文件名称随便,放到src目录下面方便加载
druid.properties
文件内容:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day05
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
3.5.3 使用步骤
-
导入druid的jar包
-
在src目录下创建一个properties文件,并设置对应参数
-
加载properties文件的内容到Properties对象中
-
创建Druid连接池,使用配置文件中的参数
-
从Druid连接池中取出连接
-
执行SQL语句
-
关闭资源
3.5.4 案例代码
- 在src目录下新建一个Druid配置文件,命名为:druid.properties,内容如下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/day05
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
java代码
public class Demo04 {
public static void main(String[] args) throws Exception {
// 加载配置文件中的配置参数
InputStream is = Demo04.class.getResourceAsStream("/druid.properties");
Properties pp = new Properties();
pp.load(is);
// 创建连接池,使用配置文件中的参数
DataSource ds = DruidDataSourceFactory.createDataSource(pp);
// for (int i = 0; i < 10; i++) {
// Connection conn = ds.getConnection();
// System.out.println(conn);
// }
// 从连接池中取出连接
Connection conn = ds.getConnection();
// 执行SQL语句
String sql = "INSERT INTO student VALUES (NULL, ?, ?, ?);";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "王五");
pstmt.setInt(2, 35);
pstmt.setDouble(3, 88.5);
int i = pstmt.executeUpdate();
System.out.println("影响的行数: " + i);
// 执行查询
sql = "SELECT * FROM student;";
ResultSet rs = pstmt.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
double score = rs.getDouble("score");
System.out.println("id: " + id + " ,name: " + name + " ,age = " + age + " ,score = " + score);
}
pstmt.close();
conn.close(); // 将连接还回连接池中
}
}
3.5.5 小结
Druid使用步骤?
1.导入jar包:druid-1.0.9.jar
2.复制配置文件到src下:druid.properties,修改参数
3.创建Properties对象,加载druid.properties中的内容
4.创建连接池: DruidDataSourceFactory.createDataSource
5.获取连接
6.后续使用
Druid常用的配置参数
参数 | 说明 |
---|---|
initialSize | 初始化时建立连接的个数。 |
maxActive | 连接池最大连接的个数。 |
maxWait | 获取连接时最大等待时间,单位毫秒。 |
连接池基本使用不管是C3P0连接池,还是DRUID连接池,配置大致都可以分为2种:
1.连接数据库的参数
2.连接池的参数
,
这2种配置大致参数作用都相同,只是参数名称可能不一样。
3.6 编写连接池工具类
目标
能够编写一个连接池的工具类,简化获取连接池中的连接对象
我们每次操作数据库都需要创建连接池,获取连接,关闭资源,都是重复的代码。我们可以将创建连接池和获取连接池的代码放到一个工具类中,简化代码。
连接池工具类步骤
- 声明静态数据源成员变量
- 创建连接池对象
- 定义公有的得到数据源的方法
- 定义得到连接对象的方法
- 定义关闭资源的方法
案例代码
DataSourceUtils.java
public class DataSourceUtils {
// 1. 声明静态数据源成员变量
private static DataSource ds;
// 2. 创建连接池对象
static {
// 加载配置文件中的数据
InputStream is = DataSourceUtils.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);
}
}
测试类代码
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);
}
}
使用连接池工具类后可以简化代码,我们重点是写SQL去执行。
小结
编写连接池工具类步骤
- 声明静态数据源成员变量
- 创建连接池对象
- 定义公有的得到数据源的方法
- 定义得到连接对象的方法
- 定义关闭资源的方法
3.7 JdbcTemplate基本使用
目标
能够掌握JdbcTemplate+Druid创建数据库
3.7.1 JdbcTemplate介绍
JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来管理数据库资源如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤。
JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分。JdbcTemplate处理了资源的建立和释放。他帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果。
Spring源码地址:https://github.com/spring-projects/spring-framework
在JdbcTemplate中执行SQL语句的方法大致分为3类:
-
execute
:可以执行所有SQL语句,一般用于执行DDL语句。 -
update
:用于执行INSERT
、UPDATE
、DELETE
等DML语句。 -
queryXxx
:用于DQL数据查询语句。
3.7.2 JdbcTemplate配置连接池
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
-
public JdbcTemplate(DataSource dataSource) 创建JdbcTemplate对象,方便执行SQL语句
-
public void execute(final String sql) execute可以执行所有SQL语句,因为没有返回值,一般用于执行DDL语句。
3.7.3 JdbcTemplate使用步骤
-
准备DruidDataSource连接池
-
导入依赖的jar包
spring-beans-4.1.2.RELEASE.jar
spring-core-4.1.2.RELEASE.jar
spring-jdbc-4.1.2.RELEASE.jar
spring-tx-4.1.2.RELEASE.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
-
创建
JdbcTemplate
对象,传入Druid
连接池 -
调用
execute
、update
、queryXxx
等方法
3.7.4 案例代码
使用JDBCTemplate创建一张产品表,sql如下:
CREATE TABLE product(pid INT PRIMARY KEY AUTO_INCREMENT,pname VARCHAR(20),price DOUBLE);
【代码实践】
public class Demo04 {
public static void main(String[] args) {
// 创建表的SQL语句
String sql = "CREATE TABLE product("
+ "pid INT PRIMARY KEY AUTO_INCREMENT,"
+ "pname VARCHAR(20),"
+ "price DOUBLE"
+ ");";
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
jdbcTemplate.execute(sql);
}
}
3.7.5 小结
JdbcTemplate使用步骤?
-
准备DruidDataSource连接池【工具类中获取】
-
导入依赖的jar包
spring-beans-4.1.2.RELEASE.jar
spring-core-4.1.2.RELEASE.jar
spring-jdbc-4.1.2.RELEASE.jar
spring-tx-4.1.2.RELEASE.jar
com.springsource.org.apache.commons.logging-1.1.1.jar
-
创建
JdbcTemplate
对象,传入Druid
连接池 -
调用
execute
、update
、queryXxx
等方法
3.8 JdbcTemplate实现增删改
目标
能够掌握JdbcTemplate实现增删改
讲解
API介绍
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
-
public int update(final String sql) 用于执行`INSERT`、`UPDATE`、`DELETE`等DML语句。
使用步骤
1.创建JdbcTemplate对象
2.编写SQL语句
3.使用JdbcTemplate对象的update方法进行增删改
案例代码
public class Demo05 {
public static void main(String[] args) throws Exception {
//test01();
//test02();
//test03();
}
// JDBCTemplate添加数据
public static void test01() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
String sql = "INSERT INTO product VALUES (NULL, ?, ?);";
jdbcTemplate.update(sql, "iPhone3GS", 3333);
jdbcTemplate.update(sql, "iPhone4", 5000);
jdbcTemplate.update(sql, "iPhone4S", 5001);
jdbcTemplate.update(sql, "iPhone5", 5555);
jdbcTemplate.update(sql, "iPhone5C", 3888);
jdbcTemplate.update(sql, "iPhone5S", 5666);
jdbcTemplate.update(sql, "iPhone6", 6666);
jdbcTemplate.update(sql, "iPhone6S", 7000);
jdbcTemplate.update(sql, "iPhone6SP", 7777);
jdbcTemplate.update(sql, "iPhoneX", 8888);
}
// JDBCTemplate修改数据
public static void test02() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
String sql = "UPDATE product SET pname=?, price=? WHERE pid=?;";
int i = jdbcTemplate.update(sql, "XVIII", 18888, 10);
System.out.println("影响的行数: " + i);
}
// JDBCTemplate删除数据
public static void test03() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
String sql = "DELETE FROM product WHERE pid=?;";
int i = jdbcTemplate.update(sql, 7);
System.out.println("影响的行数: " + i);
}
}
小结
JdbcTemplate的update
方法用于执行DML语句。同时还可以在SQL语句中使用?
占位,在update方法的Object... args
可变参数中传入对应的参数。
- JdbcTemplate查询-queryForObject
目标
能够掌握JdbcTemplate中queryForObject方法查询单个数据
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
API介绍
public <T> T queryForObject(String sql, Class<T> requiredType, @Nullable Object... args)
执行查询语句,指定要查询的类型,返回一个该指定类型的数据。有参数可以传入参数
使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForObject方法,并传入需要返回的数据的类型
- 输出结果
案例代码
-
获取product表中商品数量
JdbcTemplate jt = new JdbcTemplate(DataSourceUtil.getDataSource()); long count = jt.queryForObject("select count(*) from product;", Long.class); System.out.println("count = " + count);
-
获取product表中pid为2的价格
JdbcTemplate jt = new JdbcTemplate(DataSourceUtil.getDataSource()); double price = jt.queryForObject("select price from product where pid = ?", double.class, 2); System.out.println("price = " + price);
-
获取product表中pid为2的名称
JdbcTemplate jt = new JdbcTemplate(DataSourceUtil.getDataSource()); String name = jt.queryForObject("select pname from product where pid = ?", String.class, 2); System.out.println("name = " + name);
小结
queryForObject方法的作用?
查询表中单个数据
- JdbcTemplate查询-queryForMap
目标
能够掌握JdbcTemplate中queryForMap方法的使用返回一个Map集合
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
API介绍
public Map<String, Object> queryForMap(String sql)
public Map<String, Object> queryForMap(String sql, @Nullable Object... args)
执行查询语句,将一条记录放到一个Map中。
使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForMap方法
- 处理结果
案例代码
将pid =1的代码的记录查询出来
JdbcTemplate jt = new JdbcTemplate(DataSourceUtil.getDataSource());
Map<String, Object> map = jt.queryForMap("select * from product where pid=?", 1);
System.out.println("map = " + map);
//打印结果:map = {pid=1, pname=华为手机, price=2000.0}
小结
queryForMap方法的作用?将返回的一条记录保存在Map集合中,key是字段名,value是字段值
- JdbcTemplate查询-queryForList返回一个List集合
目标
能够掌握JdbcTemplate中queryForList方法的使用
讲解
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
API介绍
public List<Map<String, Object>> queryForList(String sql)
public List<Map<String, Object>> queryForList(String sql, @Nullable Object... args)
执行查询语句,返回一个List集合,List中存放的是Map类型的数据。
使用步骤
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的queryForList方法
- 处理结果
案例代码
将pid<5的商品信息查找出来
JdbcTemplate jt = new JdbcTemplate(DataSourceUtil.getDataSource());
String sql = "select * from product where pid<?;";
List<Map<String, Object>> mapList = jt.queryForList(sql, 5);
mapList.forEach(map-> System.out.println("map = " + map));//遍历打印结果
打印结果:
map = {pid=1, pname=华为手机, price=2000.0}
map = {pid=2, pname=华为手机, price=2000.0}
map = {pid=3, pname=桌子, price=12.0}
map = {pid=4, pname=沙发, price=2000.0}
小结
queryForList方法的作用?
将返回的一条记录保存在Map集合中,多条记录对应多个Map,多个Map存储到List集合中
- JdbcTemplate查询-RowMapper返回自定义对象
目标
能够掌握JdbcTemplate中RowMapper返回自定义对象
讲解
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
API介绍
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据。
使用步骤
- 定义Product类
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的query方法,并传入RowMapper匿名内部类
- 在匿名内部类中将结果集中的一行记录转成一个Product对象
案例代码
// query使用rowMap做映射返回一个对象
public static void test06() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
// 查询数据的SQL语句
String sql = "SELECT * FROM product;";
List<Product> query = jdbcTemplate.query(sql, new RowMapper<Product>() {
@Override
public Product mapRow(ResultSet rs, int arg1) throws SQLException {
Product p = new Product();
p.setPid(rs.getInt("pid"));
p.setPname(rs.getString("pname"));
p.setPrice(rs.getDouble("price"));
return p;
}
});
for (Product product : query) {
System.out.println(product);
}
}
小结
RowMapper的使用?
- 使用JdbcTemplate对象的query方法,并传入RowMapper匿名内部类
- 在匿名内部类中将结果集中的一行记录转成一个Product对象
- JdbcTemplate查询-BeanPropertyRowMapper返回自定义对象
目标
能够掌握JdbcTemplate中BeanPropertyRowMapper返回自定义对象
讲解
org.springframework.jdbc.core.JdbcTemplate
类方便执行SQL语句
API介绍
public <T> List<T> query(String sql, RowMapper<T> rowMapper)
执行查询语句,返回一个List集合,List中存放的是RowMapper指定类型的数据。
public <T> List<T> query(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
该方法支持占位符参数传递
public class BeanPropertyRowMapper<T> implements RowMapper<T>
BeanPropertyRowMapper类实现了RowMapper接口
使用步骤
- 定义Product类
- 创建JdbcTemplate对象
- 编写查询的SQL语句
- 使用JdbcTemplate对象的query方法,并传入BeanPropertyRowMapper对象
案例代码
// query使用BeanPropertyRowMapper做映射返回对象
public static void test07() throws Exception {
JdbcTemplate jdbcTemplate = new JdbcTemplate(DataSourceUtils.getDataSource());
// 查询数据的SQL语句
String sql = "SELECT * FROM product;";
List<Product> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
for (Product product : list) {
System.out.println(product);
}
}
小结
BeanPropertyRowMapper的使用?
List<Product> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Product.class));
总结
JDBCTemplate的query
方法用于执行SQL语句,简化JDBC的代码。同时还可以在SQL语句中使用?
占位,在query
方法的Object... args
可变参数中传入对应的参数。