源代码下载地址链接:https://download.csdn.net/download/weixin_46411355/87400304
目录
JDBC概述
1.1 前言
1.2 什么是JDBC
1.3 JDBC的原理
1.4 程序员,JDBC,JDBC驱动的关系及说明
1.4.1 JDBC API
1.4.2 JDBC 驱动
1.4.3 Java程序员
1.4.4 三方关系
1.5 总结
2.JDBC操作数据库的步骤
2.1 总体步骤
2.2 详细步骤
2.2.1 官网下载驱动包
2.2.2 加载驱动
2.2.3 创建数据库连接
2.2.4 创建Statement并发送命令
2.2.5 处理ResultSet结果
2.2.6 关闭数据库资源
3.准备工作
3.1 创建数据库并创建student表
3.2 创建项目
3.3 创建lib目录并引入MYSQL驱动包
3.4 把lib包引入项目环境
4.使用JDBC完成添加操作
4.1 步骤
4.2 代码
4.3 URL详解
4.3.1 为什么要定义URL
4.4查看数据库
4.4.1 一个URL由哪些部分组成
5.使用JDBC完成更新和删除
5.1步骤
5.2 修改代码
5.3 删除代码
6.DBUtils的简单封装
6.1 为什么要封装
6.2 创建DBUtils封装代码
6.3 创建上面的工具类对象,前面的代码进行改造
7.使用JDBC完成查询
7.1 循环向student表里面插入20条数
7.2 查询
8.使用JDBC完成分页查询
9.常用接口详解
9.1 DriverManager
9.2 Connection
9.3 Statement
9.4 PreparedStatement
10.SQL注入问题
10.1问题引入
10.1.1 创建sys_user表并初始化数据
10.1.2 编写代码实现登陆
10.1.3 测试登陆
10.2 解决办法【使用PreparedStatement】
10.2.1 技术原理
10.2.2 创建对象
10.2.3 传递参数
10.2.4 修改登录代码
10.2.5 再次测试
11.事务处理解决转账问题
11.1 什么是事务
11.2事务的四大特性:
11.2.1 原子性
11.2.2一致性
11.2.3 隔离性
11.2.4 持久性
11.3 需求描述
11.4 准备工作
11.4.1 创建表
11.4.2 初始化数据
11.5 代码实现
11.6 测试
12.JDBC批处理
12.1 什么是批处理
12.2需求描述及操作步骤
12.3 案例代码
13.连接池
13.1 什么是连接池
13.2 为什么使用连接池
13.3 连接池的工作原理
13.4我们如何手写【难点】
13.4.1编写说明
13.4.2 创建jdbc.properties
13.4.3 封装一个连接类
13.4.4 创建一个连接池接口
13.4.5 创建一个连接池实现类以及对应方法
13.4.6 创建一个连接池的维护类
13.4.7 案例测试
13.5 市面上有哪些可用的连接池
14.BaseDAO的封装【难点】
14.1 概述
14.2 BaseDao代码
14.3 UserDao子类
14.4 测试类
14.5 分页封装
14.5.1 创建分页结果封装类PageInfo
14.5.2 修改BaseDao
14.5.3 测试类
【JDBC】
【主要内容】
- JDBC概述
- 使用JDBC完成添加操作
- 使用JDBC完成更新和删除
- DBUtils的简单封装
- 使用JDBC完成查询
- 使用JDBC完成分页查询
- 常用接口详解
- JDBC批处理
- SQL注入问题
- 事务处理解决转账问题
- 数据库连接池
- 使用反射对DBUtils再次的封装
- BaseDAO的封装
- JdbcTemplate的使用
【学习目标】
知识点 | 要求 |
JDBC概述 | 掌握 |
使用JDBC完成添加操作 | 掌握 |
使用JDBC完成更新和删除 | 掌握 |
DBUtils的简单封装 | 掌握 |
使用JDBC完成查询 | 掌握 |
使用JDBC完成分页查询 | 掌握 |
常用接口详解 | 掌握 |
JDBC批处理 | 掌握 |
SQL注入问题 | 掌握 |
事务处理解决转账问题 | 掌握 |
连接池 | 掌握 |
使用反射对DBUtils再次的封装 | 掌握 |
BaseDAO的封装 | 掌握 |
-
JDBC概述
1.1 前言
学习到现在,我们是如何操作数据库的?
a、使用MySQL自带的命令行的方式进行操作;
b、使用第三方的数据库客户端工具来连接MySQL并且进行操作
但是这些都是一些单独的数据库操作工具,它没法让我们书写的程序自动执行这些工具,那么我们该如何在Java程序中来操作这些数据库呢?
1.2 什么是JDBC
JDBC(Java DataBase Connectivity)就是Java数据库连接,即一套使用Java语言来操作数据库的编程接口,也可以认为是一组规范。
1.3 JDBC的原理
早期SUN公司的天才们想编写一套可以连接天下所有数据库的API,但是当他们刚刚开始时就发现这是不可完成的任务,因为各个厂商的数据库服务器差异太大了。后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的,遵循了JDBC规范的,可以访问自己数据库的API被称之为驱动!
小结:
SUN提供一套访问数据库的规范(就是一组接口),规范命名为JDBC,并提供连接数据库的协议标准。
各个数据库厂商会遵循SUN的JDBC规范的,提供一套可以访问自己公司的数据库服务器的API,被称之为驱动。
JDBC是接口,而JDBC驱动才是接口的实现,没有驱动无法完成数据库连接!每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。
当然还有第三方公司专门为某一数据库提供驱动,这样的驱动往往不是开源免费的!
1.4 程序员,JDBC,JDBC驱动的关系及说明
1.4.1 JDBC API
提供者:Sun公司
内容:供程序员调用的接口与类,集成在java.sql和javax.sql包中,如
DriverManager类 作用:管理各种不同的JDBC驱动
Connection接口
Statement接口
ResultSet接口
1.4.2 JDBC 驱动
提供者:数据库厂商
作用:负责连接各种不同的数据库
1.4.3 Java程序员
JDBC对Java程序员而言是API,对实现与数据库连接的服务提供商而言是接口模型。
1.4.4 三方关系
SUN公司是规范制定者,制定了规范JDBC(连接数据库规范)
数据库厂商 微软、甲骨文等分别提供实现JDBC接口的驱动jar包
程序员学习JDBC规范来应用这些jar包里的类。
1.5 总结
简单地说,JDBC 可做三件事:与数据库建立连接、发送指令操作数据库并处理结果。
2.JDBC操作数据库的步骤
2.1 总体步骤
- 官网下载驱动包
- 加载一个Driver驱动
- 创建数据库连接(Connection)
- 创建SQL命令发送器Statement
- 创建SQL
- 通过Statement发送SQL命令并得到结果
- 处理SQL结果(select语句)
- 关闭数据库资源:ResultSet、Statement、Connection
2.2 详细步骤
2.2.1 官网下载驱动包
http://dev.mysql.com/downloads/connector/j/
在Select Operating System:中选择Platform Independent
选择 ZIP Archive 版本 点击Download
解压后得到 jar 库文件
然后在对应的项目中导入该库文件。
2.2.2 加载驱动
加载JDBC驱动是通过调用方法java.lang.Class.forName(),下面列出常用的几种数据库驱动程序加载语句的形式 :
注意,如果你的mysql版本是8或者以上的话,那么driver的写法需要改写成:com.mysql.cj.jdbc.Driver
2.2.3 创建数据库连接
与数据库建立连接的方法是调用
DriverManager.getConnection(String url, String user, String password )
方法
Connection conn=null;
String url="jdbc:mysql://localhost:3306/bjpowernode?charsetUnicode=UTF8&serverTimezone=UTC";
String user=“root";
String password=“root";
conn = DriverManager.getConnection(url, user, password);
2.2.4 创建Statement并发送命令
Statement对象用于将 SQL 语句发送到数据库中,或者理解为执行sql语句
有三种 Statement对象:
Statement:用于执行不带参数的简单SQL语句;
PreparedStatement(从 Statement 继承):用于执行带或不带参数的预编译SQL语句;
CallableStatement(从PreparedStatement 继承):用于执行数据库存储过程的调用。
2.2.5 处理ResultSet结果
ResultSet对象是executeQuery()方法的返回值,它被称为结果集,它代表符合SQL语句条件的所有行,并且它通过一套getXXX方法(这些get方法可以访问当前行中的不同列)提供了对这些行中数据的访问。
ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用ResultSet的next()方法 ,如果我们想要得到ResultSet里的所有记录,就应该使用while循环。
ResultSet对象自动维护指向当前数据行的游标。每调用一次next()方法,游标向下移动一行。
初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录。循环完毕后指向最后一条记录的后面。
2.2.6 关闭数据库资源
作为一种好的编程风格,应在不需要Statement对象和Connection对象时显式地关闭它们。关闭Statement对象和Connection对象的语法形式为:
public void close() throws SQLException
用户不必关闭ResultSet。当它的 Statement 关闭、重新执行或用于从多结果序列中获取下一个结果时,该ResultSet将被自动关闭。
注意:要按先ResultSet结果集,后Statement,最后Connection的顺序关闭资源,因为Statement和ResultSet是需要连接是才可以使用的,所以在使用结束之后有可能其他的Statement还需要连接,所以不能先关闭Connection。
3.准备工作
3.1 创建数据库并创建student表
3.2 创建项目
3.3 创建lib目录并引入MYSQL驱动包
3.4 把lib包引入项目环境
File->Project Structure
点击OK
4.使用JDBC完成添加操作
4.1 步骤
- 加载MySQL的JDBC驱动
- 建立数据的连接
- 创建SQL命令的发送器
- 编写SQL
- 使用SQL命令发送器发送SQL命令并得到结果
- 处理结果
- 关闭数据库资源
4.2 代码
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Test01Add {
//驱动器路径
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
//连接数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/powernode_jdbc?useUnicode=true&useSSL=false&characterEncoding=UTF8";
//数据库用户名
private static final String USER_NAME = "root";
//数据库密码
private static final String USER_PASSWORD = "root";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//加载JDBC访问Mysql的驱动
Class.forName(DRIVER);
//建立和数据库的连接
Connection connection = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
//创建SQL命令发送器
Statement statement = connection.createStatement();
//使用SQL命令发送器发送SQL命令并得到结果
String insertSql = "insert into student values(1,'小刚','32','男','湖北省武汉市')";
int resultNum = statement.executeUpdate(insertSql);
//处理结果
if(resultNum>0){
System.out.println("添加成功");
}else{
System.out.println("添加用户失败");
}
//关闭数据源
statement.close();
connection.close();
}
}
4.3 URL详解
4.3.1 为什么要定义URL
Java和MySQL是厂商的,Java程序和MySQL数据库此时不在同一个进程下,此时Java程序需要向MySQL发送请求。
4.3.2 如何发送请求呢?
jdbc:mysql://localhost:3306/powernode_jdbc?useUnicode=true&useSSL=false&characterEncoding=UTF8
使用URL的方式发送
jdbc | 主协议 |
mysql | 子协议 |
localhost/127.0.0.1 | MySQL服务器的地址,如果服务器就是我自己的主机,那么定义localhost或者127.0.0.1就可以了 |
3306 | MySQL服务器的端口号 |
powernode_jdbc | MySQL数据库服务器的数据库名称 |
useUnicode=true | Java和MySQL交互使用Unicode编码 |
useSSL=false | Java和MySQL交互不使用安全层协议 |
characterEncoding=UTF8 | Java和MySQL交互的编码方式为UTF8 【如果不设置会有乱码的】 |
4.4查看数据库
4.4.1 一个URL由哪些部分组成
- 协议://服务器主机:端口/服务器路径?查询参数
- 协议 jdbc:mysql:
- 服务器主机 localhost
- 端口 3306
- 服务器路径 whpowernode
- 参数useUnicode=true&useSSL=false&characterEncoding=UTF8
5.使用JDBC完成更新和删除
5.1步骤
- 加载MySQL的JDBC驱动
- 建立数据的连接
- 创建SQL命令的发送器
- 编写SQL
- 使用SQL命令发送器发送SQL命令并得到结果
- 处理结果
- 关闭数据库资源
5.2 修改代码
package com.bjpowernode.jdbc.使用JDBC完成更新操作;
import java.sql.*;
public class Test02Update {
//驱动器路径
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
//连接数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/powernode_jdbc?useUnicode=true&useSSL=false&characterEncoding=UTF8";
//数据库用户名
private static final String USER_NAME = "root";
//数据库密码
private static final String USER_PASSWORD = "root";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//加载MySQL的驱动
Class.forName(DRIVER);
//建立数据的连接
Connection connection = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
//创建SQL的命令发送器
Statement statement = connection.createStatement();
//编写SQL
String updateSql = "update student set name = '小明',age=23,sex='女',address='四川成都' where id = 1";
//使用SQL命令发送器发送SQL命令并得到结果
int resultNum = statement.executeUpdate(updateSql);
//处理结果
if(resultNum>0){
System.out.println("修改成功");
}else {
System.out.println("处理失败");
}
//关闭数据库资源
statement.close();
connection.close();
}
}
5.3 删除代码
package com.bjpowernode.jdbc._01基本的CRUD.使用JDBC完成删除操作;
import javax.print.DocFlavor;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Test03Delete {
// 驱动器路径
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
// 连接数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/powernode_jdbc?useUnicode=true&characterEncoding=UTF8&useSSL=false";
//数据库用户名
private static final String USER_NAME = "root";
//数据库密码
private static final String USER_PASSWORD = "root";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//加载MySQL的驱动
Class.forName(DRIVER);
//建立数据库的连接
Connection connection = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
//创建SQL命令的发送器
Statement statement = connection.createStatement();
//编写SQL
String deleteSql = "delete from student where id = 1";
//使用SQL命令发送器发送SQL命令并得到结果
int resultNum = statement.executeUpdate(deleteSql);
//处理结果
if(resultNum>0){
System.out.println("删除成功");
}else{
System.out.println("删除失败");
}
//关闭数据库资源
statement.close();
connection.close();
}
}
6.DBUtils的简单封装
6.1 为什么要封装
从我们上面的代码大家可以看到,每一次写我们创建一个连接,创建一个发送SQL的对象,最后还要关闭资源,那么我们可以考虑把这重复的代码提取出来!
6.2 创建DBUtils封装代码
package com.bjpowernode.jdbc._02DBUtils的简单封装;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtils {
// 驱动器路径
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
// 连接数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/powernode_jdbc?useUnicode=true&characterEncoding=UTF8&useSSL=false";
//数据库用户名
private static final String USER_NAME = "root";
//数据库密码
private static final String USER_PASSWORD = "root";
/**
* 静态加载驱动程序
*/
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
*
* @return 连接对象
*/
public static Connection getConnection(){
try {
Connection connection = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
return connection;
} catch (SQLException throwables) {
throwables.printStackTrace();
System.out.println("创建连接对象异常");
}
return null;
}
/**
* 关闭资源
*/
public static void close(AutoCloseable autoCloseable){
if(autoCloseable!=null){
try {
autoCloseable.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
6.3 创建上面的工具类对象,前面的代码进行改造
package com.bjpowernode.jdbc._02DBUtils的简单封装;
import sun.security.pkcs11.Secmod;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Test01Add {
public static void main(String[] args) throws SQLException {
Connection connection = DBUtils.getConnection();
//创建SQL命令发送器
Statement statement = connection.createStatement();
//使用SQL命令发送器发送SQL命令得到结果
String insertSql = "insert into student values(null,'HHH',23,'男','四川省成都市')";
int resultNum = statement.executeUpdate(insertSql);
//处理结果
if(resultNum>0){
System.out.println("添加成功");
}else{
System.out.println("添加失败");
}
//关闭数据库资源
DBUtils.close(statement);
DBUtils.close(connection);
}
}
7.使用JDBC完成查询
7.1 循环向student表里面插入20条数
package com.bjpowernode.jdbc._02DBUtils的简单封装;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
public class Test01Add20 {
public static void main(String[] args) throws SQLException {
Connection connection = DBUtils.getConnection();
// 创建SQL命令发送器
Statement statement = connection.createStatement();
// 使用SQL命令发送器发送SQL命令并得到结果
Random random = new Random();
for (int i = 1; i <=20 ; i++) {
Integer id = null;
String name = "小明"+i;
int age = random.nextInt(100);
String sex = random.nextBoolean()?"男":"女";
String address = "武汉"+i;
String insertSql = "insert into student values("+id+",'"+name+"',"+age+",'"+sex+"','"+address+"')";
int resultRowsNum = statement.executeUpdate(insertSql);
//处理结果
if(resultRowsNum>0){
System.out.println("添加成功");
}else {
System.out.println("添加失败");
}
}
//关闭数据源
DBUtils.close(statement);
DBUtils.close(connection);
}
}
7.2 查询
package com.bjpowernode.jdbc._03使用JDBC完成查询.循环向student表里面插入20条数;
import com.bjpowernode.jdbc._02DBUtils的简单封装.DBUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test04Query {
public static void main(String[] args) throws SQLException {
Connection connection = DBUtils.getConnection();
//创建SQL命令发送器
Statement statement = connection.createStatement();
//编写SQL语句
String selectSql = "select * from student";
//使用SQL命令发送器发送SQL命令并得到结果
ResultSet resultSet = statement.executeQuery(selectSql);
//处理结果
while(resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
String sex = resultSet.getString(4);
String address = resultSet.getString(5);
System.out.println(id+" "+name+" "+age+" "+sex+" "+address);
}
//关闭数据库资源
DBUtils.close(resultSet);
DBUtils.close(statement);
DBUtils.close(connection);
}
}
8.使用JDBC完成分页查询
javabean
Student,java
package com.bjpowernode.jdbc._03使用JDBC完成查询.循环向student表里面插入20条数;
import java.util.Objects;
public class Student {
private Integer id;
private String name;
private Integer age;
private String sex;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id) &&
Objects.equals(name, student.name) &&
Objects.equals(age, student.age) &&
Objects.equals(sex, student.sex) &&
Objects.equals(address, student.address);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age, sex, address);
}
}
package com.bjpowernode.jdbc._04使用JDBC完成分页查询;
import com.bjpowernode.jdbc.javabean.Student;
import com.bjpowernode.jdbc.util.DBUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test05QueryForPage {
public static void main(String[] args) throws SQLException {
Connection connection = DBUtils.getConnection();
//创建SQL命令发送器
Statement statement = connection.createStatement();
int pageNum = 2;//页码
int pageSize = 5;//每页显示的条数
int index = (pageNum-1)*pageSize;
//编写SQL
String sql = "select * from student limit "+index+","+pageSize;
//使用SQL命令发送器发送SQL命令并得到结果
ResultSet resultSet = statement.executeQuery(sql);
//处理结果
while (resultSet.next()){
Student student = new Student();
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
String address = resultSet.getString("address");
student.setId(id);
student.setName(name);
student.setAge(age);
student.setSex(sex);
student.setAddress(address);
System.out.println(student);
}
//关闭数据源
DBUtils.close(resultSet);
DBUtils.close(statement);
DBUtils.close(connection);
}
}
9.常用接口详解
9.1 DriverManager
用于管理JDBC驱动的服务类。程序中使用该类的的主要功能是获取Connection对象,该类包含如下方法:
public static Connection getConnection(String url, String user, String password) throws SQLException
该方法获得url对应数据库的连接;
9.2 Connection
代表数据库连接对象,每个Connection代表一个物理连接会话。要想访问数据库,必须先得到数据库连接。该接口的常用方法如下:
方法 | 说明 |
Statement createStatement() throws SQLException; | 该方法返回一个Statement对象; |
PreparedStatement prepareStatement(String sql)throws SQLException; | 该方法返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译; |
CallableStatement prepareCall(String sql) throws SQLException; | 该方法返回CallableStatement对象,该对象用于调用存储过程 |
上面3个方法都返回用于执行sql语句的Statement对象,PreparedStatement和CallableStatement是Statement的子类,只有获得了Statement之后才可以执行sql语句;
除此之外,Connection还有如下几个用于控制事务的方法。
方法 | 说明 |
Savepoint setSavepoint() throws SQLException; | 创建一个保存点; |
Savepoint setSavepoint(String name) throws SQLException; | 以指定名字来创建一个保存点; |
void setTransactionIsolation(int level) throws SQLException; | 设置事务的隔离级别; |
void rollback() throws SQLException; | 回滚事务; |
void rollback(Savepoint savepoint) throws SQLException; | 将事务回滚到指定的保存点; |
void setAutoCommit(boolean autoCommit) throws SQLException; | 关闭自动提交,打开事务; |
void commit() throws SQLException; | 提交事务; |
9.3 Statement
用于执行sql语句的工具接口。该对象既可以执行DDL,DCL语句,也可以用于执行DML语句,还可以用于执行sql查询。当执行sql查询时,返回查询到的结果集。
=========================================================================
知识点回顾:
DDL(data definition language):
DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表时使用;
DCL(Data Control Language):
是数据库控制功能。是用来设置或更改数据库用户或角色权限的语句,包括(grant,deny,revoke等)语句。在默认状态下,只有sysadmin,dbcreator,db_owner或db_securityadmin等人员才有权力执行DCL;
DML(data manipulation(操作) language):
有SELECT、UPDATE、INSERT、DELETE,就象它的名字一样,这4条命令是用来对数据库里的数据进行操作的语言;
=========================================================================
Statement的常用方法如下:
方法 | 说明 |
ResultSet executeQuery(String sql) throws SQLException; | 该方法用于执行查询语句,并返回查询结果对应ResultSet对象。该方法只能用于执行查询语句 |
int executeUpdate(String sql) throws SQLException | 该方法用于执行DML(数据操作 crud)语句,并返回受影响的行数; 该方法也可用于执行DDL(数据定于create、alter、drop)语句,执行DDL语句将返回0; |
boolean execute(String sql) throws SQLException; | 该方法可以执行任何sql语句。 如果执行后第一个结果为ResultSet对象,则返回true; 如果执行后第一个结果为受影响的行数或没有任何结果,则返回false; |
9.4 PreparedStatement
预编译的Statement对象,PreparedStatement是Statement的子接口,它允许数据库预编译sql语句(这些sql语句通常带有参数),以后每次只改变sql命令的参数,避免数据库每次都需要编译sql语句,无需再传入sql语句,只要为预编译的sql语句传入参数值即可。所以它比Statement多了如下方法:
方法 | 说明 |
void setXxx(int parameterIndex, Xxx value): | 该方法根据传入参数值的类型不同,需要使用不同的方法。传入的值根据索引传给sql语句中指定位置的参数。 |
9.5 ResultSet
结果集对象。该对象包含访问查询结果的方法,ResultSet可以通过列索引或列名获得列数据。它包含了如下常用方法来移动记录指针。
方法 | 说明 |
void close() throws SQLException; | 释放ResultSet对象; |
boolean absolute( int row ) throws SQLException; | 将结果集的记录指针移动到第row行, 如果row是负数,则移动到倒数第row行,如果移动后的记录指针指向一条有效记录,则该方法返回true; |
boolean next() throws SQLException; | 将结果集的记录指针定位到下一行,如果移动后的记录指针指向一条有效的记录,则该方法返回true |
boolean last() throws SQLException; | 将结果集的记录指针定位到最后一行,如果移动后的记录指针指向一条有效的记录,则该方法返回true; |
10.SQL注入问题
10.1问题引入
10.1.1 创建sys_user表并初始化数据
10.1.2 编写代码实现登陆
javaBean
SysUser.java
package com.bjpowernode.jdbc.javabean;
import java.util.Objects;
public class SysUser {
private Integer id;
private String username;
private String password;
public SysUser(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public SysUser() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "SysUser{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SysUser sysUser = (SysUser) o;
return Objects.equals(id, sysUser.id) && Objects.equals(username, sysUser.username) && Objects.equals(password, sysUser.password);
}
@Override
public int hashCode() {
return Objects.hash(id, username, password);
}
}
Test06Login.java
package com.bjpowernode.jdbc._05SQL注入问题.编写代码实现登录.问题引入;
import com.bjpowernode.jdbc.javabean.SysUser;
import com.bjpowernode.jdbc.util.DBUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Test06Login {
public static void main(String[] args) throws SQLException {
Connection connection = DBUtils.getConnection();
//创建SQL命令发送器
Statement statement = connection.createStatement();
//从键盘输入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String inputUsername = scanner.nextLine();
System.out.println("请输入密码:");
String inputPassword = scanner.nextLine();
//编写SQL
String sql = "select * from sys_user where username= '"+inputUsername+"' and password='"+inputPassword+"';";
System.out.println("sql = " + sql);
//使用SQL命令发送器发送SQL命令并得到结果
ResultSet resultSet = statement.executeQuery(sql);
//处理结果
if(resultSet.next()){
System.out.printf("登录成功");
SysUser sysUser = new SysUser();
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
sysUser.setId(id);
sysUser.setUsername(username);
sysUser.setPassword(password);
System.out.println(sysUser);
}
//关闭数据库资源
DBUtils.close(resultSet);
DBUtils.close(statement);
DBUtils.close(connection);
}
}
10.1.3 测试登陆
10.2 解决办法【使用PreparedStatement】
10.2.1 技术原理
该 PreparedStatement接口继承Statement,并与之在两方面有所不同:
一方面:
PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句“准备好”。
包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。
相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。
由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
另一方面:
作为 Statement 的子类,PreparedStatement 继承了 Statement 的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数占位符的值。同时,三种方法 execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要参数。这些方法的 Statement 形式(接受 SQL 语句参数的形式)不应该用于 PreparedStatement 对象。
10.2.2 创建对象
以下的代码段(其中 con 是 Connection 对象)创建包含带两个 IN 参数占位符的 SQL 语句的 PreparedStatement 对象:
PreparedStatement pstmt = con.prepareStatement("UPDATE table4 SET m = ? WHERE x = ?");
pstmt 对象包含语句 "UPDATE table4 SET m = ? WHERE x = ?",它已发送给DBMS(数据库管理系统),并为执行作好了准备。
10.2.3 传递参数
在执行 PreparedStatement 对象之前,必须设置每个 ? 参数的值。这可通过调用 setXXX 方法来完成,其中 XXX 是与该参数相应的类型。
例子:
例如,如果参数具有Java 类型 long,则使用的方法就是 setLong。setXXX 方法的第一个参数是要设置的参数的序数位置,第二个参数是设置给该参数的值。例如,以下代码将第一个参数设为 123456789,第二个参数设为 100000000:
pstmt.setLong(1, 123456789);
pstmt.setLong(2, 100000000);
一旦设置了给定语句的参数值,就可用它多次执行该语句,直到调用clearParameters 方法清除它为止。在连接的缺省模式下(启用自动提交),当语句完成时将自动提交或还原该语句。
如果基本数据库和驱动程序在语句提交之后仍保持这些语句的打开状态,则同一个 PreparedStatement 可执行多次。如果这一点不成立,那么试图通过使用PreparedStatement 对象代替 Statement 对象来提高性能是没有意义的。
10.2.4 修改登录代码
package com.bjpowernode.jdbc._05SQL注入问题.编写代码实现登录.解决办法;
import com.bjpowernode.jdbc.javabean.SysUser;
import com.bjpowernode.jdbc.util.DBUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class Test07Login {
public static void main(String[] args) throws SQLException {
//从键盘输入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String inputUsername = scanner.nextLine();
System.out.println("请输入密码:");
String inputPassword = scanner.nextLine();
Connection connection = DBUtils.getConnection();
//编写SQL
String sql = "select * from sys_user where username = ? and password = ?";
System.out.println("sql = " + sql);
//创建SQL命令发送器
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,inputUsername);
preparedStatement.setString(2,inputPassword);
//使用SQL命令发送器发送SQL命令并得到结果
ResultSet resultSet = preparedStatement.executeQuery();
//处理结果
if(resultSet.next()){
System.out.printf("登录成功");
SysUser sysUser = new SysUser();
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
sysUser.setId(id);
sysUser.setUsername(username);
sysUser.setPassword(password);
System.out.println(sysUser);
}
// 关闭数据库资源
DBUtils.close(resultSet);
DBUtils.close(preparedStatement);
DBUtils.close(connection);
}
}
10.2.5 再次测试
11.事务处理解决转账问题
11.1 什么是事务
是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)
11.2事务的四大特性:
11.2.1 原子性
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
11.2.2一致性
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是不一致的状态。
11.2.3 隔离性
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
11.2.4 持久性
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
11.3 需求描述
完成转账操作
要求,两次数据库操作,要么全成功。要么全失败
11.4 准备工作
11.4.1 创建表
CREATE TABLE account
(
aid INT PRIMARY KEY AUTO_INCREMENT,
aname VARCHAR(30) NOT NULL,
amount DECIMAL(10,2) NOT NULL
);
11.4.2 初始化数据
11.5 代码实现
package com.bjpowernode.jdbc._06事务处理解决转账问题;
import com.bjpowernode.jdbc.util.DBUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Test08Transaction {
public static void main(String[] args) {
//声明连接对象
Connection connection = null;
//声明发送SQL的接口对象
Statement statement = null;
try {
//创建连接对象
connection = DBUtils.getConnection();
//关闭自动提交事务
connection.setAutoCommit(false);
//编写SQL
String reducedSql = "update account set amount = amount - 1000 where aid = 1";
String increasedSql = "update account set amount = amount + 1000 where aid = 2";
// 创建SQL命令发送器
statement = connection.createStatement();
statement.executeUpdate(reducedSql);
statement.executeUpdate(increasedSql);
connection.commit();
}catch (Exception e){
e.printStackTrace();
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
//关闭数据库资源
DBUtils.close(statement);
DBUtils.close(connection);
}
}
}
11.6 测试
第一次成功
在两个执行的sql中间模拟异常,看事务是否会提交
package com.bjpowernode.jdbc._06事务处理解决转账问题;
import com.bjpowernode.jdbc.util.DBUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Test08Transaction {
public static void main(String[] args) {
//声明连接对象
Connection connection = null;
//声明发送SQL的接口对象
Statement statement = null;
try {
//创建连接对象
connection = DBUtils.getConnection();
//关闭自动提交事务
connection.setAutoCommit(false);
//编写SQL
String reducedSql = "update account set amount = amount - 1000 where aid = 1";
int i = 1/0;
String increasedSql = "update account set amount = amount + 1000 where aid = 2";
// 创建SQL命令发送器
statement = connection.createStatement();
statement.executeUpdate(reducedSql);
statement.executeUpdate(increasedSql);
connection.commit();
}catch (Exception e){
e.printStackTrace();
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
//关闭数据库资源
DBUtils.close(statement);
DBUtils.close(connection);
}
}
}
12.JDBC批处理
12.1 什么是批处理
批处理是建立一次连接(创建一个Connection对象)的情况下批量执行多个DML()语句,这些DML语句要么全部成功要么全部失败。如何确保全部成功or全部失败呢?在JDBC中开启事务,使用事务管理DML语句
12.2需求描述及操作步骤
使用批处理根据id批量的删除student表中的数据
1 定义SQL配置文件
2 创建Connection对象
3 创建PreparedStatement对象
4 将提交方式设置为手动提交,开启事务
5 设置占位符
6 将占位符添加到批处理中(相当于收集若干个本子,放入包包中。所以使用循环,在循环内部用preparedStatement.addBatch())
7 执行批处理 (preparedStatement.executeBatch())
8 提交事务(connection.commit)
9 如果批处理失败,在catch块中回滚事务
10 关闭资源
12.3 案例代码
package com.bjpowernode.jdbc._07JDBC批处理;
import com.bjpowernode.jdbc.util.DBUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
public class Test09Batch {
public static void main(String[] args) {
//模拟要删除的数据
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5);
//声明连接对象
Connection connection = null;
//声明发送SQL的接口对象
PreparedStatement preparedStatement = null;
try {
//创建连接对象
connection = DBUtils.getConnection();
//关闭自动提交事务,将提交方式设置为手动提交,开启事务
connection.setAutoCommit(false);
//编写SQL,设置占位符
String sql = "delete from student where id = ?;";
//创建SQL命令发送器
preparedStatement = connection.prepareStatement(sql);
/*
* 将占位符添加到批处理中(相当于收集若干个本子,放入包包中。所以使用循环,在循环内部用preparedStatement.addBatch();)
*/
//所以使用循环
for (Integer id : ids) {
//将占位符添加到批处理中
preparedStatement.setInt(1, id);
//相当于收集若干个本子,放入包包中。
preparedStatement.addBatch();
}
//执行批处理
int[] rows = preparedStatement.executeBatch();
System.out.println("受影响的行数为:" + Arrays.toString(rows));
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//如果批处理失败,在catch块中回滚事务
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}finally {
//关闭数据库资源
DBUtils.close(preparedStatement);
DBUtils.close(connection);
}
}
}
13.连接池
13.1 什么是连接池
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。
连接池是装有连接的容器,使用连接的话,可以从连接池中进行获取,使用完成之后将连接归还给连接池。
13.2 为什么使用连接池
连接对象创建和销毁是需要耗费时间的,在服务器初始化的时候就初始化一些连接。把这些连接放入到内存中,使用的时候可以从内存中获取,使用完成之后将连接放入连接池中。从内存中获取和归还的效率要远远高于创建和销毁的效率。(提升性能)。
13.3 连接池的工作原理
13.4我们如何手写【难点】
13.4.1编写说明
我们现在编写的时候一个使用连接池,一个不使用连接池,到时候测试的时候做下时间对比
13.4.2 创建jdbc.properties
jdbc.properties放在resources目录下
对应的数据库的各种信息,以及数据库连接池初始化时数量,最大连接数量以及自动增长数量
参数 | 说明 |
initConnectCount | 数据库连接池 初始化时数量 |
maxConnects | 数据库连接池 最大连接数量 |
incrementCount | 数据库连接池 自动增长数量 |
jdbcDriver = com.mysql.cj.jdbc.Driver
jdbcUrl = jdbc:mysql://localhost:3306/powernode_jdbc?&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username = root
password = root
initConnectCount = 20
maxConnects = 100
incrementCount = 3
13.4.3 封装一个连接类
该类中包含一个数据库连接,一个是否使用的标记以及一个close方法,用来将该连接置为可用状态,从而达到数据库连接的可复用,减少持续创建新连接的资源消耗
package com.bjpowernode.jdbc._08连接池.domain;
import java.sql.Connection;
public class PoolConnection {
/**
* 数据库连接
*/
private Connection conn = null;
/**
* 标记该连接是否使用
*/
private boolean isUse = false;
/**
* 构造方法
* @param conn 连接对象
* @param isUse 是否正在使用 【模拟关闭】
*/
public PoolConnection(Connection conn, boolean isUse) {
this.conn = conn;
this.isUse = isUse;
}
public Connection getConn() {
return conn;
}
public void setConn(Connection conn) {
this.conn = conn;
}
public boolean isUse() {
return isUse;
}
public void setUse(boolean use) {
isUse = use;
}
/**
* 将该连接置为可用状态
*/
public void close() {
this.isUse = false;
}
}
13.4.4 创建一个连接池接口
对外提供的连接池的接口
package com.bjpowernode.jdbc._08连接池.service;
import com.bjpowernode.jdbc._08连接池.domain.PoolConnection;
import java.sql.Connection;
public interface IPool {
/**
* 获取连接池中可用连接
*/
PoolConnection getConnection();
/**
* 获取一个数据库连接(不使用连接池)
*/
Connection getConnectionNoPool();
}
13.4.5 创建一个连接池实现类以及对应方法
首先加载对应配置文件中信息,初始化数据库连接池,然后用synchronized来实现多线程情况下线程安全的获取可用连接
package com.bjpowernode.jdbc._08连接池.service.impl;
import com.bjpowernode.jdbc._08连接池.domain.PoolConnection;
import com.bjpowernode.jdbc._08连接池.service.IPool;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Vector;
public class JdbcPool implements IPool {
//驱动
private static String jdbcDriver;
//连接地址
private static String jdbcUrl;
//数据库用户名
private static String username;
//数据库密码
private static String password;
//初始化连接数
private static Integer initConnectCount;
//最大连接数
private static Integer maxConnects;
//当连接不够时自动增长的数
private static Integer incrementCount;
//因为Vector是线程安全的,所有暂时选择它
private static Vector<PoolConnection> connections = new Vector<>();
/*
* 通过实例初始化块来初始化
*/
{
//读取对应的配置文件,加载入properties中,并设置到对应的参数中
InputStream is = JdbcPool.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
jdbcDriver = properties.getProperty("jdbcDriver");
jdbcUrl = properties.getProperty("jdbcUrl");
username = properties.getProperty("username");
password = properties.getProperty("password");
initConnectCount = Integer.valueOf(properties.getProperty("initConnectCount"));
maxConnects = Integer.valueOf(properties.getProperty("maxConnects"));
incrementCount = Integer.valueOf(properties.getProperty("incrementCount"));
try {
/*
* 注册jdbc驱动
* */
Driver driver = (Driver) Class.forName(jdbcDriver).newInstance();
DriverManager.registerDriver(driver);
/*
* 根据initConnectCount来初始化连接池
* */
createConnections(initConnectCount);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 获取可用连接
*/
@Override
public PoolConnection getConnection() {
if (connections.isEmpty()) {
System.out.println("连接池中没有连接");
throw new RuntimeException("连接池中没有连接");
}
return getActiveConnection();
}
/**
* 同步方法来获取连接池中可用连接,在多线程情况下,只有一个线程访问该方法来获取连接,防止由于多线程情况下多个线程获取同一个连接从而引起出错
*/
private synchronized PoolConnection getActiveConnection() {
/*
* 通过循环来获取可用连接,若获取不到可用连接,则依靠无限循环来继续获取
* */
while (true) {
for (PoolConnection con : connections) {
if (!con.isUse()) {
Connection trueConn = con.getConn();
try {
//验证连接是否失效 0表示不校验超时
if (!trueConn.isValid(0)) {
con.setConn(DriverManager.getConnection(jdbcUrl, username, password));
}
} catch (SQLException e) {
e.printStackTrace();
}
con.setUse(true);
return con;
}
}
/*
* 根据连接池中连接数量从而判断是否增加对应的数量的连接
* */
if (connections.size() <= maxConnects - incrementCount) {
createConnections(incrementCount);
} else if (connections.size() < maxConnects && connections.size() > maxConnects - incrementCount) {
createConnections(maxConnects - connections.size());
}
}
}
/*
* 创建对应数量的连接并放入连接池中
* */
private void createConnections(int count) {
for (int i = 0; i < count; i++) {
if (maxConnects > 0 && connections.size() >= maxConnects) {
System.out.println("连接池中连接数量已经达到最大值");
throw new RuntimeException("连接池中连接数量已经达到最大值");
}
try {
Connection connection = DriverManager.getConnection(jdbcUrl, username, password);
/*
* 将连接放入连接池中,并将状态设为可用
* */
connections.add(new PoolConnection(connection, false));
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/*
* 获取连接池中连接数量
* */
public int getSize() {
return connections.size();
}
@Override
public Connection getConnectionNoPool() {
Connection connection = null;
try {
connection = DriverManager.getConnection(jdbcUrl, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
13.4.6 创建一个连接池的维护类
通过静态内部类来实现连接池的单例模式
package com.bjpowernode.jdbc._08连接池;
import com.bjpowernode.jdbc._08连接池.service.impl.JdbcPool;
public class PoolManager {
/**
* 静态内部类实现连接池的单例
* */
private static class CreatePool{
private static JdbcPool pool = new JdbcPool();
}
public static JdbcPool getInstance(){
return CreatePool.pool;
}
}
13.4.7 案例测试
最后,上测试方法:起2000个线程,通过new两个CountDownLatch,其中一个来实现线程的同时并发执行,
熟话说,没有对比就没有伤害,我们先来用普通没有连接池的测试一下2000个连接并行执行的时间,代码如下:
package com.bjpowernode.jdbc._08连接池.test;
import com.bjpowernode.jdbc._08连接池.PoolManager;
import com.bjpowernode.jdbc._08连接池.service.impl.JdbcPool;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.CountDownLatch;
public class JdbcNoPoolMain {
static final int threadSize = 2000;
static JdbcPool jdbcPool = PoolManager.getInstance();
static CountDownLatch countDownLatch1 = new CountDownLatch(1);
static CountDownLatch countDownLatch2 = new CountDownLatch(threadSize);
public static void main(String[] args) throws InterruptedException {
threadTest();
}
public static void threadTest() throws InterruptedException {
long time1 = System.currentTimeMillis();
for (int i = 0; i < threadSize; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//使得线程阻塞到coutDownLatch1为0时才执行
countDownLatch1.await();
selectNoPool();
//每个独立子线程执行完后,countDownLatch2减1
countDownLatch2.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}).start();
}
//将countDownLatch1置为0,从而使线程并发执行
countDownLatch1.countDown();
//等待countDownLatch2变为0时才继续执行
countDownLatch2.await();
long time2 = System.currentTimeMillis();
System.out.println("thread size: " + threadSize + " no use pool :" + (time2 - time1));
}
public static void selectNoPool() throws SQLException {
Connection conn = jdbcPool.getConnectionNoPool();
Statement sm = null;
ResultSet rs = null;
try {
sm = conn.createStatement();
rs = sm.executeQuery("select * from student");
} catch (SQLException e) {
e.printStackTrace();
}
try {
while (rs.next()) {
System.out.println(Thread.currentThread().getName() + " ==== " + "name: " + rs.getString("name") + " age: " + rs.getInt("age"));
}
Thread.sleep(100);
} catch (SQLException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
rs.close();
sm.close();
conn.close();
}
}
13.5 市面上有哪些可用的连接池
- c3p0 老了
- druid 阿里的
- dbcp 老了
- Hikari 小日本的,springboot官方推荐的
14.BaseDAO的封装【难点】
14.1 概述
对于JDBC市面上有一些封装的非常好的ORM框架,如mybatis mybatisplus hibernate,但是我们现在还没有学到框架,如何自己做模拟一个ORM的框架呢,接下来我们来讲解BaseDAO的封装和使用
14.2 BaseDao代码
package com.bjpowernode.jdbc._09BaseDAO的封装.dao;
import com.bjpowernode.jdbc.util.DBUtils;
import com.sun.org.apache.bcel.internal.generic.ARETURN;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
public class BaseDao {
/**
*
* @param sql sql指令
* @param clss orm关联类
* @param params sql中占位符 对应的参数
* @param <T>
* @return
*/
public static <T> List<T> selectList(String sql, Class<T> clss, Object ... params){
//创建一个容器 规避空指针异常问题
List<T> data = new ArrayList<>();//需要改动
//2.创建连接 使用驱动管理器 创建连接
Connection conn = DBUtils.getConnection();
//3.获取指令的预编译对象
PreparedStatement prep = null;
ResultSet rs = null;
try {
//4.获取指令的预编译对象
prep = conn.prepareStatement(sql);
//设置预编译参数
for (int i = 0; i < params.length; i++) {
Object param = params[i];//获取参数
//设置参数
prep.setObject(i+1,param);
}
//5.执行sql指令
rs= prep.executeQuery();
//获取结果的元信息
ResultSetMetaData metaData = rs.getMetaData();
//获取列数
int columnCount = metaData.getColumnCount();
while (rs.next()) {
T t = clss.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取别名 也是属性名
String columnLabel = metaData.getColumnLabel(i + 1);
//根据别名获取值
Object columnValue = rs.getObject(columnLabel);
//获取属性
Field field = clss.getDeclaredField(columnLabel);//需要改动
//设置属性值
field.setAccessible(true);
field.set(t,columnValue);
}
data.add(t);
}
return data;
}catch (Exception e){
e.printStackTrace();
}finally {
DBUtils.close(conn);
DBUtils.close(prep);
DBUtils.close(rs);
}
return null;
}
public <T> T selectOne(String sql,Class<T> clss,Object ... params){
List<T> list = selectList(sql, clss, params);
if(!list.isEmpty()&&list.size()==1){
T t = list.get(0);
return t;
}
return null;
}
/**
* 通用的更新操作
* @param sql
* @param params
* @return
*/
public boolean update(String sql,Object ... params){
//1.获取连接
Connection connection = DBUtils.getConnection();
PreparedStatement prep = null;
try{
prep = connection.prepareStatement(sql);
//设置预编译参数
for (int i = 0; i < params.length; i++) {
Object param = params[i];//获取参数
//设置参数
prep.setObject(i+1,param);
}
//执行SQL指令
int i = prep.executeUpdate();
return i>=1;
}catch (Exception e){
e.printStackTrace();
}finally {
DBUtils.close(connection);
DBUtils.close(prep);
}
return false;
}
}
14.3 UserDao子类
package com.bjpowernode.jdbc._09BaseDAO的封装.dao;
import com.bjpowernode.jdbc.javabean.SysUser;
public class UserDao extends BaseDao{
public SysUser selectOneSysUser(String username,String password){
String selectSql = "select * from sys_user where username = ? and password = ?";
SysUser sysUser = super.selectOne(selectSql, SysUser.class, username, password);
return sysUser;
}
public void updateSysUser(String username,String password,Integer id){
String updateSql = "update sys_user set username = ?,password = ? where id = ?";
boolean b = super.update(updateSql, username, password,id);
System.out.println("b = " + b);
}
}
14.4 测试类
引入Junit单元测试的jar包
笔者的Junit单元测试jar包下载链接:https://download.csdn.net/download/weixin_46411355/87398726
package com.bjpowernode.jdbc._09BaseDAO的封装.test;
import com.bjpowernode.jdbc._09BaseDAO的封装.dao.UserDao;
import com.bjpowernode.jdbc.javabean.SysUser;
import org.junit.Test;
public class TestAPI {
private UserDao userDao = new UserDao();
@Test
public void testUserDaoSelectSysUser() {
String username = "hhh", password = "123";
SysUser sysUser = userDao.selectOneSysUser(username, password);
System.out.println(sysUser);
}
@Test
public void testUserDaoUpdateSysUser() {
Integer id = 5;
String username = "罗龙江",password="888888";
userDao.updateSysUser(username,password,id);
}
}
14.5 分页封装
14.5.1 创建分页结果封装类PageInfo<T>
package com.bjpowernode.jdbc._10分页的封装;
import java.util.List;
public class PageInfo <T>{
private List<T> data;//具体的数据
private Long count;//符合条件的数据条数
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
}
14.5.2 修改BaseDao
/**
* 分页查询
* @param clazz 查询的数据封装的对象对应的Class
* @param sql sql语句
* @param currentPage 当前页数,也就是第几页
* @param pageSize 每页显示的条数
* @return 分页封装的对象
*/
public static <T> PageInfo<T> selectByPage(Class<T> clazz,String sql,int currentPage,int pageSize){
//分页的时候limit跳过的条数
int index = 0;
index = (currentPage-1)*pageSize;
String selectSql = sql+" limit "+index+","+pageSize;
// 查询获取当前指定页数的数据
List<T> list = selectList(selectSql,clazz);
//获取总的条数
// select * from student
Long totalCount = getTotalCount(sql);
PageInfo<T> pageInfo = new PageInfo<>();
pageInfo.setData(list);
pageInfo.setCount(totalCount);
return pageInfo;
}
/**
* 获取总的条数
* @param sql sql语句
* @return 返回条数
*/
private static Long getTotalCount(String sql){
//子查询
//select count(1) from ( select * from student ) as s
String countSql = "select count(1) from ("+sql+") as s";
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBUtils.getConnection();
preparedStatement = connection.prepareStatement(countSql);
resultSet = preparedStatement.executeQuery();
resultSet.next();//先让光标指向数据的第一行
long totalCount = resultSet.getLong(1);
return totalCount;
}catch (Exception e){
e.printStackTrace();
}finally {
DBUtils.close(resultSet);
DBUtils.close(preparedStatement);
DBUtils.close(connection);
}
return 0L;
}
14.5.3 测试类
package com.bjpowernode.jdbc._10分页的封装;
import com.bjpowernode.jdbc._09BaseDAO的封装.dao.BaseDao;
import com.bjpowernode.jdbc.javabean.Student;
import org.junit.Test;
public class PageTest {
@Test
public void testPaging(){
String sql = "select * from student where id > 4";
PageInfo<Student> pageInfo = BaseDao.selectByPage(Student.class, sql, 1, 2);
//总条数
System.out.println(pageInfo.getCount());
// 第一页显示的数据
System.out.println(pageInfo.getData());
}
}