八、JDBC
8.1 JDBC 介绍
JDBC (Java Database Connectivity,Java数据库连接)
8.1.1 使用 JDBC 的原因
- Java 通过 JDBC 技术实现对各种数据库的访问,充当 Java 应用程序与各种数据库之间进行对话的媒介
- 通过 JDBC 可以将程序中的数据持久的保存到数据库当中
- Sun 公司提供了 JDBC 的接口规范------JDBC API
8.1.2 JDBC 的工作原理
- JDBC 主要有 JDBC API、JDBC Driver Manager 和 JDBC 驱动组成
1、JDBC API
- JDBC API 提供了一套 Java 应用程序与各种数据库交互的标准接口
- 其中 Connection(连接)接口、Statement 接口、ResultSet(结果集)接口、preparedStatement 接口等都属于 JDBC 接口
2、JDBC Driver Manager
- JDBC Driver Manager 是 JDBC 体系结构的支柱
- 负责管理各种 JDBC 驱动
- JDBC Driver Manager 位于 JDK 的 java.sql 包中
3、JDBC 驱动
- JDBC 驱动不包含在 JDK中,由第三方中间厂商提供
- 负责连接各种数据库
8.1.3 JDBC API
- JDBC API 是 Java 应用与各种数据库交互的标准接口
- 主要功能是建立与数据库的连接,发送 SQL 语句,返回处理结果
- JDBC API 主要类/接口 的功能
类/接口 | 作用 |
---|---|
DriverManager 类 | 装载驱动程序,并为创建新的数据库连接提供支持 |
Connection 接口 | 负责连接数据库并担任传送数据的任务 |
Statement 接口 | 由 Connection 产生,负责执行 SQL 语句 |
PreparedStatement 接口 | Statement 的子接口,也由 Connection 产生,负责执行 Sql语句 与 Statement 接口的区别:PreparedStatement 借口具有高安全性、 高性能、高可读性和高可维护性等优点 |
ResultSet 接口 | 负责保存和处理 Statement 执行后产生的查询结果 |
8.2 使用 JDBC 连接数据库
8.2.1 使用 JDBC 连接数据库的方法
1、加载 JDBC 驱动
- JDBC 驱动由数据库厂商或第三方中间件厂商提供,使用 JDBC 连接数据库前要先下载对应版本的驱动
- 可以在 MySQL 广为下载对应的 JDBC 驱动 JAR 包
- 导入 JDBC 的驱动 JAR 包后,需要加载驱动
- 驱动类的类名为 com.mysql.jdbc.Driver
- 通常使用 Class.forName 加载 JDBC 驱动
- 使用 Class.forName 加载驱动 语法
try{
Class.forName("JDBC驱动类的名称")
}catch(ClassNotFoundException){
//异常输出代码
}
2、与数据库建立连接
- JDBC 驱动类加载后,需要建立与 MySQL 数据库连接
- 建立连接使用 DriverManager 类,DriverManager 类是 JDBC 的管理层,作用于用户和驱动程序之间
- getConnection() 方法用于建立与数据库的连接
- 调用 getConnection() 方法 需要放入参数:数据库连接地址字符串、连接数据库的用户名和密码
- 建立连接数据库 语法
Connection conn=DriverManager.getConnection(数据库连接地址字符串,数据库用户名,密码)
- 数据库连接字符串 语法格式
jdbc:数据库://ip:端口/数据库名称 [?连接参数=参数值]
- 数据库: JDBC 连接的目标数据库,如 MySQL 数据库
- ip:JDBC 所连接的目标数据库地址,如果是本地数据库,则可以使用 localhost。即本地主机名
- 端口:连接数据库的端口号,如果连接 MySQL 数据库,则默认端口号为 3306
- 数据库名称:目标数据库的名称,如 hospital
- 连接参数:连接数据库时的参数配置。
- Connection 接口常用的方法
方法名称 | 作用 |
---|---|
Statement createStatement() | 创建一个 Statement 对象并将 SQL 语句发送到数据库 |
PreparedStatement preparedStatement(String sql) | 创建一个 PreparedStatement 对象并将参数化的 SQL 语句发送到数据库 |
boolean isClosed() | 查询此 Connection 对象是否已经被关闭。若已关闭,返回true |
void close() | 立即释放此 Connection 对象的数据库和 JDBC 资源 |
8.3 使用 Statement 执行数据库操作
8.3.1 使用 Statement 接口更新数据
-
获取 Connection 对象后,需要使用 Connection 对象的 createStatement() 方法创建 Statement 对象,在通过 Statement 对象 将 SQL 语句发送到 MySQL 服务器中执行操作
-
在 Statement 接口中执行 SQL 命令的 3 种常用方法
方法名称 | 作用 |
---|---|
ResultSet executeQuery(String sql) | 可以执行 SQL 查询并获取 ResultSet 对象 |
int executeUpdate(Sting sql) | 可以执行插入、删除、更新等操作,返回值时执行该操作所影响的行数 |
boolean execute(String sql) | 可以执行任意 SQL 语句。若结果为 Result 对象,则返回 true;若结果为 更新计数或不存在任何结果,则返回 false |
- 使用 Statement 接口更新数据库中数据的步骤,与插入、修改、删除数据类似,都可以使用 executeUpdate() 方法
- 使用Statement执行对象实现新增操作 示例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
/**
* 准备工作:
* 1.从官网下载MySql驱动包
* 2.将驱动包引入到项目工程中
*/
public class LX_insert {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner input=new Scanner(System.in);
//设置连接数据库需要的参数
//获取驱动类
String driver="com.mysql.jdbc.Driver";
//数据库连接地址
String url="jdbc:mysql://localhost:3306/hospital?useUnicode=true&characterEncoding=UTF-8";
//数据库用户名
String user="root";
//数据库密码
String password="root";
//加载驱动
Class.forName(driver);
//建立数据库连接
Connection conn= DriverManager.getConnection(url,user,password);
//创建 Statement 对象
Statement statement=conn.createStatement();
//获取要添加的数据
System.out.print("请输入患者密码:");
String patientPassWord=input.next();
System.out.print("请输入患者性别:");
String patientGender=input.next();
System.out.print("请输入患者姓名:");
String patientName=input.next();
//拼接 SQL 语句
StringBuffer sql=new StringBuffer("INSERT INTO patient(password,gender,patientName) VALUES ('");
sql.append(patientPassWord);
sql.append("','");
sql.append(patientGender);
sql.append("','");
sql.append(patientName);
sql.append("');");
System.out.println(sql);
//发送 SQL 语句,并接收结果
int effectRowNum= statement.executeUpdate(sql.toString());
//判断是否添加成功
if(effectRowNum>0){
System.out.println("添加成功");
}else{
System.out.println("添加失败");
}
//关闭资源
statement.close();
conn.close();
}
}
运行结果
请输入患者密码:111111
请输入患者性别:男
请输入患者姓名:无名氏
INSERT INTO patient(password,gender,patientName) VALUES ('111111','男','无名氏');
添加成功
8.3.2 使用 Statement 接口和 ResultSet 接口查询数据
-
使用 JDBC 查询数据库中的数据时,需要使用 ResultSet 对象来接收从数据库中返回的数据
-
ResultSet 常用方法
方法名 | 说明 |
---|---|
boolean next() | 将游标从当前位置向下移动一行 |
boolean previous() | 游标从当前位置向上移动一行 |
void close | 关闭 ResultSet 对象 |
int getInt(int colIndex) | 以 int 形式获取结果集当前行指定列号值 |
int getInt(String colLabel) | 以 int 形式获取结果集当前行指定列名值 |
float getFloat(int colIndex) | 以 float 形式获取结果集当前行指定列号值 |
float getFloat(String colLabel) | 以float形式获取结果集当前行指定列名值 |
String getString(int colIndex) | 以String形式获取结果集当前行指定列号值 |
String getString(String colLabel) | 以String形式获取结果集当前行指定列名值 |
- 使用 Statement 接口和 ResultSet 接口查询数据 示例
import java.sql.*;
/**
* 准备工作:
* 1.从官网下载MySql驱动包
* 2.将驱动包引入到项目工程中
*/
public class LX_Select {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//设置连接数据库需要的参数
//获取驱动类
String driver="com.mysql.jdbc.Driver";
//数据库连接地址
String url="jdbc:mysql://localhost:3306/hospital?useUnicode=true&characterEncoding=UTF-8";
//数据库用户名
String user="root";
//数据库密码
String password="root";
//加载驱动
Class.forName(driver);
//建立数据库连接
Connection conn= DriverManager.getConnection(url, user, password);
//创建 Statement 对象
Statement statement=conn.createStatement();
//SQL 语句
String sql="SELECT * FROM patient";
//向 MySQL 发送 SQL 语句,并接收结果
ResultSet rs=statement.executeQuery(sql);
//输出结果
//判断结果是否还有下一个
while(rs.next()){
//如果有下一个 输出 patientID列、patientName列
System.out.println("病人编号:"+rs.getString("patientID")+"病人姓名:"+rs.getString("patientName"));
}
//关闭资源
rs.close();
statement.close();
conn.close();
}
}
运行结果
病人编号:1病人姓名:夏颖
病人编号:2病人姓名:李政
病人编号:3病人姓名:李沁
病人编号:4病人姓名:李思雨
病人编号:5病人姓名:夏天
病人编号:6病人姓名:刘占波
病人编号:7病人姓名:廖慧颖
病人编号:8病人姓名:李伟忠
病人编号:9病人姓名:姚维新
病人编号:10病人姓名:陈建
病人编号:11病人姓名:林永清
病人编号:12病人姓名:李亚
病人编号:13病人姓名:张菲
8.4 使用 PreparedStatement 接口防止 SQL 注入
8.4.1 SQL 注入攻击
- SQL 注入攻击是是一种注入攻击,应用程序没有对用户输入数据的合法性进行判断,导致应用程序存在安全隐患,最终达到欺骗服务器执行恶意的SQL命令
- 用户提交一段 SQL 代码,执行超出其权限的数据操作就被成为 SQL 注入攻击
- 现在为医院管理系统开发登录功能,要求用户输入密码和姓名,判断是否通过验证
代码演示
import java.sql.*;
import java.util.Scanner;
import java.sql.*;
/**
* 准备工作:
* 1.从官网下载MySql驱动包
* 2.将驱动包引入到项目工程中
*/
public class LX_SQL {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner input=new Scanner(System.in);
//设置连接数据库需要的参数
//获取驱动类
String driver="com.mysql.jdbc.Driver";
//数据库连接地址
String url="jdbc:mysql://localhost:3306/hospital?useUnicode=true&characterEncoding=UTF-8";
//数据库用户名
String user="root";
//数据库密码
String password="root";
//接收用户输入的姓名和密码
System.out.print("请输入患者姓名:");
String patientName=input.next();
System.out.print("请输入患者密码:");
String patientPassword=input.next();
//加载驱动
Class.forName(driver);
//建立数据库连接
Connection conn= DriverManager.getConnection(url, user, password);
//创建 Statement 对象
Statement statement=conn.createStatement();
//SQL 语句
StringBuffer sql=new StringBuffer("SELECT * FROM patient where patientName='");
sql.append(patientName);
sql.append("'and password='");
sql.append(patientPassword);
sql.append("';");
//向 MySQL 发送 SQL 语句,并接收结果
ResultSet rs=statement.executeQuery(sql.toString());
//判断输入的是否正确
if(rs.next()){
System.out.println("登录成功");
System.out.println("病人姓名:"+rs.getString("patientName"));
}else{
System.out.println("登录失败");
}
//关闭资源
rs.close();
statement.close();
conn.close();
}
}
效果演示
请输入患者姓名:夏颖
请输入患者密码:123456
登录成功
病人姓名:夏颖
使用 SQL注入攻击 效果演示
请输入患者姓名:*-*
请输入患者密码:100'OR'1'='1
登录成功
病人姓名:夏颖
- 由上方演示可看出,这是典型的 SQL 注入攻击
- 原因时在使用 Statement 接口方法时要进行 SQL 语句的拼接。拼接不仅繁琐且容易出错
- 使用 PreparedStatement 可以规避这些问题
8.4.2 使用 PreparedStatement 接口查询数据
- PreparedStatement 接口继承自 Statement 接口
- PreparedStatement 接口的常用方法
方法 | 作用 |
---|---|
boolean execute() | 执行 SQL 语句,该语句可以是任何 SQL 语句。若结果为 Result 对象, 则返回 true;若结果是更新计数或没有结果,则返回 false |
ResultSet executeQuery() | 执行 SQL 查询,并返回该查询生成的 ResultSet 对象 |
int executeUpdate() | 执行 SQL 语句,该语句必须是一个 DML 语句,如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句和 DDL 语句。返回值时执行该操作所影响的行数 |
void sexXxx(int index,xxx x) | 方法名 Xxx 和第二个参数的 xxx 均表示如 int、float、double 等基本数据类型, 并且两个类型必须一致,参数列表中的 x 表示方法的形式参数。把指定数据类型 (xxx) 的值 x 设置为 index 位置的参数。根据参数类型的不同,常见的方法有 setInt(int index,int x)、setFloat(int index,float x),setDouble(int index,double x) 等 |
void setObject(int index,Object x) | 除基本数据类型外,参数类型也可以使 Object,可以将 Object 对象 x 设置为 index 位置的参数 |
8.4.3 创建 PreparedStatement 对象
1、创建 PreparedStatement 对象
-
通过 Connection 接口的 prepareStatement (String sql) 方法创建 PreparedStatement 对象
-
在创建 PreparedStatement 对象时应设置号该对象要执行的 SQL 语句
-
SQL 语句可以具有一个或多个输入参数时,这些输入参数的值在 PreparedStatement 创建时违背指定,而
是为每个参数保留一个 ? 作为占位符
-
因为 SQL 语句的参数已经固定,所以不会出现 SQL 注入的漏洞
-
在 SQL 语句中使用 ? 作为占位符 演示
String sql="SELECT * FROM patient where patientName=? and password=?"
2、设置输入参数的值
- 通过调用 setXxx() 方法完成参数赋值
- Xxx 时与该参数类型相应的类型,若参数是 String 类型,则使用方法 setString()
- setXxx() 方法的第一个参数选择设置某个参数 (从1开始计数)
- 第二个参数用于设置该参数的值
- 设置 SQL 语句中参数的值 演示
String sql="SELECT * FROM patient where patientName=? and password=?";
PreparedStatement pps=conn.prepareStatement(sql);
pps.setString(1,"夏颖");
pss.setString(2,"123456")
3、使用 PreparedStatement 升级医院管理系统开发登录功能
- 代码演示
import java.sql.*;
import java.util.Scanner;
import java.sql.*;
/**
* 准备工作:
* 1.从官网下载MySql驱动包
* 2.将驱动包引入到项目工程中
*/
public class LX_SQL {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner input=new Scanner(System.in);
//设置连接数据库需要的参数
//获取驱动类
String driver="com.mysql.jdbc.Driver";
//数据库连接地址
String url="jdbc:mysql://localhost:3306/hospital?useUnicode=true&characterEncoding=UTF-8";
//数据库用户名
String user="root";
//数据库密码
String password="root";
//接收用户输入的姓名和密码
System.out.print("请输入患者姓名:");
String patientName=input.next();
System.out.print("请输入患者密码:");
String patientPassword=input.next();
//加载驱动
Class.forName(driver);
//建立数据库连接
Connection conn= DriverManager.getConnection(url, user, password);
//SQL 语句
String sql="SELECT * FROM patient where patientName=? and password=?";
//创建 PreparedStatement 对象
PreparedStatement pps=conn.prepareStatement(sql);
//设置参数值
pps.setString(1,patientName);
pps.setString(2,patientPassword);
//输出 SQL 语句
System.out.println(pps.toString());
//接收查询数据
ResultSet rs=pps.executeQuery();
//判断输入的是否正确
if(rs.next()){
System.out.println("登录成功");
System.out.println("病人姓名:"+rs.getString("patientName"));
}else{
System.out.println("登录失败");
}
//关闭资源
rs.close();
pps.close();
conn.close();
}
}
运行效果展示
请输入患者姓名:夏颖
请输入患者密码:123456
com.mysql.jdbc.JDBC4PreparedStatement@20ad9418: SELECT * FROM patient where patientName='夏颖' and password='123456'
登录成功
病人姓名:夏颖
测试 SQL 注入攻击 运行效果展示
请输入患者姓名:夏颖
请输入患者密码:100'OR'1'='1
com.mysql.jdbc.JDBC4PreparedStatement@20ad9418: SELECT * FROM patient where patientName='夏颖' and password='100\'OR\'1\'=\'1'
登录失败
8.4.4 PreparedStatement 的优势
- 可读性高和可维护性高
- SQL 语句的执行性能高
- 安全性高