数据库驱动加载过程(JDBC)-11月30日讲课内容
JDBC
数据库驱动加载过程
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
/*
* Register ourselves with the DriverManager
* 在.class文件加载到内存时运行,并且有且只执行一次
* 代码初始化过程!!!
*/
static {
try {
// DriverManager驱动管理器注册了当前com.mysql.jdbc.Driver
// 相对于当前Java程序拥有了连接MySQL数据库的必要的驱动条件
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
【注意】后续会用到静态代码块去完成一些初始化的操作
JDBC 核心API
// 驱动管理类
class java.sql.DriverManager
/*
* 这里是根据数据库连接URL,对应的user用户名和password密码,获取数据库连接对象
*/
static java.sql.Connection getConnection(String url, String user, String password);
// 数据库连接接口
interface java.sql.Connection
/*
* 获取数据库SQL语句搬运工对象,从Java程序搬运SQL语句到数据库中,同时Statement也是一个资源对象。
*/
java.sql.Statement createStatement();
/*
* 获取数据库SQL语句【预处理】搬运工对象,Java程序的SQL语句,在创建PreparedStatement对象时,将SQL语句交给数据库预处理操作,可以解决一定的【SQL语句注入问题】,同时提高一定的效率,PreparedStatement也是一个资源对象
*/
java.sql.PreparedStatement prepareStatement(String sql);
// 数据库SQL语句搬运工对象接口
interface java.sql.Statement
/*
* 执行数据库修改数据,insert,update,delete...,返回值类型是int类型,是当前SQL语句搬运到数据库执行之后,数据库运行对于当前操作受到影响的行数
* 2 rows affected in 5 ms
*/
int executeUpdate(String sql);
/*
* 执行数据库查询语句,select操作,执行的结果是一个java.sql.ResultSet,结果集对象,当前操作返回值never null
*/
java.sql.ResultSet executeQuery(String sql);
// 数据库SQL语句【预处理】搬运工对象接口
public interface java.sql.PreparedStatement extends java.sql.Statement
/*
* 执行数据库修改操作,insert,update,delete...处理的SQL语句是在创建PreparedStatement对象过程预处理的SQL语句,并且返回值是int类型,为当前操作对于数据表中收到影响的行数
*/
int executeUpdate();
/*
* 执行数据库查询语句,select操作,的SQL语句是在创建PreparedStatement对象过程预处理的SQL语句,执行的结果是一个java.sql.ResultSet,结果集对象,当前操作返回值never null
*/
java.sql.ResultSet executeQuery();
/*
* PreparedStatement预处理的SQL语句是可以带有参数的,这里是对于SQL语句参数进行赋值操作,这里有指定的操作下标,和对应的数据,数据类型繁多
*/
setXXX(int parameterIndex, XXX value)
// 数据库结果集接口
interface java.sql.ResultSet
/*
* 根据查询结果中,字段所处的位置下标获取对应数据,XXX是指定类型(int、String用的最多)
*/
XXX getXXX(int columnIndex);
/*
* 根据查询结果中,字段所处的字段名获取对应数据,XXX是指定类型(int、String用的最多)
* 例int getInt(String columnLabel)
*/
XXX getXXX(String columnLabel);
/*
* 判断当前查询结果集中是否还有数据可以继续遍历,如果没有。或则当前结果集中是无数据情况 Empty Set,直接返回fasle
*/
boolean next();
Statement 操作 SQL 语句【鸡肋】
增删改操作步骤【重点】
1、加载驱动
2、准备连接数据库所需要的参数
3、获取数据库连接
4、获取Statement搬运工对象
5、准备SQL语句
6、执行SQL语句获取受影响的行数
7、关闭资源
Statement 插入 SQL 数据操作
public class Demo2 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 声明连接数据库所需要的参数,包括但不限于IP地址、端口号、连接到哪个数据库、用户名、密码
String url = "jdbc:mysql://localhost:3306/FC2020?useSSL=true&characterEncoding=utf8";
String user = "root";
String password = "root";
// 通过参数获取数据库连接
connection = DriverManager.getConnection(url, user, password);
// 获取Statement对象
statement = connection.createStatement();
// 准备SQL语句
String sql = "insert into student() values(1, '张三', 16, '男', '真帅');";
// 执行SQL语句获取受影响的行数
int affectedRows = statement.executeUpdate(sql);
System.out.println("受影响的行数:" + affectedRows);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Statement 修改 SQL 数据操作
public class Demo3 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 声明连接数据库所需要的参数,包括但不限于IP地址、端口号、连接到哪个数据库、用户名、密码
String url = "jdbc:mysql://localhost:3306/FC2020?useSSL=true&characterEncoding=utf8";
String user = "root";
String password = "root";
// 通过参数获取数据库连接
connection = DriverManager.getConnection(url, user, password);
// 获取Statement对象
statement = connection.createStatement();
// 准备SQL语句
String sql = "update student set name = '彭于晏' where id = 1";
// 执行SQL语句获取受影响的行数
int affectedRows = statement.executeUpdate(sql);
System.out.println("受影响的行数:" + affectedRows);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Statement 删除 SQL 数据操作
public class Demo4 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 声明连接数据库所需要的参数,包括但不限于IP地址、端口号、连接到哪个数据库、用户名、密码
String url = "jdbc:mysql://localhost:3306/FC2020?useSSL=true&characterEncoding=utf8";
String user = "root";
String password = "root";
// 通过参数获取数据库连接
connection = DriverManager.getConnection(url, user, password);
// 获取Statement对象
statement = connection.createStatement();
// 准备SQL语句
String sql = "delete from student where id = 1;";
// 执行SQL语句获取受影响的行数
int affectedRows = statement.executeUpdate(sql);
System.out.println("受影响的行数:" + affectedRows);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
查询操作步骤【重点】
1、加载驱动
2、准备连接数据库所需要的参数
3、获取数据库连接
4、获取Statement搬运工对象
5、准备SQL语句
6、执行SQL语句获取结果集对象
7、判断结果集对象中是否有数据
8、如果结果集对象中存在数据,获取每个数据库字段对应类型的数据
9、关闭资源
Statement 查询 SQL 数据操作
准备实体类
public class Student {
private Integer id;
private String name;
private Integer age;
private String gender;
private String info;
// Constructor、Getter and Setter、toString
}
【注意】根据阿里巴巴开发手册,实体类成员变量要用包装类!!!
案例代码一:查询单行
public class Demo5 {
public static void main(String[] args) {
// 声明资源
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 准备参数
String url = "jdbc:mysql://localhost:3306/FC2020?useSSL=true&characterEncoding=UTF8";
String user = "root";
String password = "root";
// 获取连接
connection = DriverManager.getConnection(url, user, password);
// 获取搬运工对象
statement = connection.createStatement();
// 准备SQL语句
String sql = "select * from student where id = 1";
// 执行SQL语句并获取结果集
resultSet = statement.executeQuery(sql);
Student student = null;
// 判断结果集中是否还有下一行数据
while (resultSet.next()) {
// 从结果集中获取对应类型的数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
String gender = resultSet.getString(4);
String info = resultSet.getString(5);
// 通过获取到的数据创建实体类对象
student = new Student(id, name, age, gender, info);
}
// 展示
System.out.println(student);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
案例代码二:查询多行
public class Demo6 {
public static void main(String[] args) {
// 声明资源
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 准备参数
String url = "jdbc:mysql://localhost:3306/FC2020?useSSL=true&characterEncoding=UTF8";
String user = "root";
String password = "root";
// 获取连接
connection = DriverManager.getConnection(url, user, password);
// 获取搬运工对象
statement = connection.createStatement();
// 准备SQL语句
String sql = "select * from student";
// 执行SQL语句并获取结果集
resultSet = statement.executeQuery(sql);
// 声明一个集合用来存储多条数据对应的实体类对象
ArrayList<Student> arrayList = new ArrayList<>();
// 判断结果集中是否还有下一行数据
while (resultSet.next()) {
// 从结果集中获取对应类型的数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
int age = resultSet.getInt(3);
String gender = resultSet.getString(4);
String info = resultSet.getString(5);
// 通过获取到的数据创建实体类对象
Student student = new Student(id, name, age, gender, info);
// 将实体类对象添加到集合中
arrayList.add(student);
}
// 展示
System.out.println(arrayList);
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
封装工具类简化操作
核心API
// 获取一个属性对象
Properties();
// 通过字符输入流加载数据到属性对象中
void load(Reader reader);
// 获取属性文件中的键的对应值
String getProperty(String key)
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/FC2020?useSSL=true&characterEncoding=UTF8
user=root
password=root
JdbcUtils
import java.io.FileReader;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* JDBC工具类
*/
public class JdbcUtils {
private static String url = null;
private static String user = null;
private static String password = null;
private static Connection connection = null;
private static Statement statement = null;
private static ResultSet resultSet = null;
// 使用静态代码块进行初始化操作:加载驱动
static {
try {
// 创建Properties对象用于从Properties文件中读取数据
Properties properties = new Properties();
// 通过字符输入流加载数据到properties中
properties.load(new FileReader("./src/db.properties"));
// 获取对应的数据
String driver = properties.getProperty("driver");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
// 加载驱动
Class.forName(driver);
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
/**
* 获取连接
*
* @return 返回数据库的连接
*/
public static Connection getConnection() {
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
/**
* 获取搬运工对象
*
* @return 返回一个搬运工对象
*/
private static Statement getStatement() {
if (connection == null) {
connection = getConnection();
}
try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
return statement;
}
/**
* 增删改方法
*
* @param sql 传入一个SQL语句
* @return 返回受影响的行数
*/
public static int executeUpdate(String sql) {
if (statement == null) {
statement = getStatement();
}
int affectedRows = 0;
try {
affectedRows = statement.executeUpdate(sql);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
close(statement, connection);
}
return affectedRows;
}
/**
* 查询方法
*
* @param sql 传入一个SQL语句
* @return 返回一个受影响的行数
*/
public static ResultSet executeQuery(String sql) {
try {
resultSet = getStatement().executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return resultSet;
}
/*
* 重载关闭资源方法,以满足多种场景
*/
public static void close(Connection connection) {
close(resultSet, statement, connection);
}
public static void close(ResultSet resultSet) {
close(resultSet, statement, connection);
}
public static void close(Statement statement, Connection connection) {
close(resultSet, statement, connection);
}
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
使用单元测试简化操作
【注意】需要导入一个单元测试所需要的 jar 包
junit-4.10.jar
使用单元测试不需要再声明 main 方法,只需要在方法声明上加上 [@Test]() 注解即可,各个测试方法之间相互独立不受影响
案例代码
public class Demo1 {
/**
* 测试修改数据
*/
@Test
public void testUpdate() {
String sql = "update student set age = 20 where id = 1;";
int affectedRows = JdbcUtils.executeUpdate(sql);
System.out.println("受影响的行数为:" + affectedRows);
}
/**
* 测试删除数据
*/
@Test
public void testDelete() {
String sql = "delete from student where id = 4;";
int affectedRows = JdbcUtils.executeUpdate(sql);
System.out.println("受影响的行数为:" + affectedRows);
}
/**
* 测试插入数据
*/
@Test
public void testInsert() {
String sql = "insert into student(name, age, gender, info) values('张三', 18, '男', '多财多亿');";
int affectedRows = JdbcUtils.executeUpdate(sql);
System.out.println("受影响的行数为:" + affectedRows);
}
/**
* 测试查询单条数据
*/
@Test
public void testQueryOne() {
String sql = "select * from student where id = 1";
ResultSet resultSet = JdbcUtils.executeQuery(sql);
try {
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String gender = resultSet.getString("gender");
String info = resultSet.getString("info");
Student student = new Student(id, name, age, gender.charAt(0), info);
System.out.println(student);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(resultSet);
}
}
/**
* 测试查询多条数据
*/
@Test
public void testQueryAll() {
ArrayList<Student> list = new ArrayList<>();
String sql = "select * from student";
ResultSet resultSet = JdbcUtils.executeQuery(sql);
try {
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String gender = resultSet.getString("gender");
String info = resultSet.getString("info");
Student student = new Student(id, name, age, gender.charAt(0), info);
list.add(student);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(resultSet);
}
System.out.println(list);
}
}
SQL注入【重点】
SQL 注入是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息,甚至篡改数据库
案例代码
public class Demo1 {
// 使用正确的用户名和密码登录成功
@Test
public void testLogin() {
String sql = "select * from account where username = '张三' and password = '123456';";
ResultSet resultSet = JdbcUtils.executeQuery(sql);
try {
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(resultSet);
}
}
// 通过SQL注入使用异常的密码登录成功
@Test
public void testSqlInject() {
String sql = "select * from account where username = '张三' and (password = 'iglrne' or 1 = 1);";
ResultSet resultSet = JdbcUtils.executeQuery(sql);
try {
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.close(resultSet);
}
}
}
【注意】Statement 存在 SQL 注入问题,而 PreparedStatement 可以有效的避免 SQL 注入!
以后只能使用 PreparedStatement ,因为操作性更强,并且安全性更高
通过 PreparedStatement 操作 SQL 语句
PreparedStatement 实例包含已编译的 SQL 语句。这就是使语句“准备好”。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX 方法来提供。
由于 PreparedStatement 对象已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。
作为 Statement 的子类,PreparedStatement 继承了 Statement 的所有功能。另外它还添加了一整套方法,用于设置发送给数据库以取代 IN 参数占位符的值。同时,三种方法 execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要参数。这些方法的 Statement 形式(接受 SQL 语句参数的形式)不应该用于 PreparedStatement 对象。
【注意】应该始终以PreparedStatement代替Statement,也就是说,在任何时候都不要使用Statement。
PreparedStatement 查询操作
public class Demo2 {
@Test
public void testPreparedStatement() {
// 获取数据库连接
Connection connection = JdbcUtils.getConnection();
// 提取资源
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
// 准备SQL语句,? 是占位符
String sql = "select * from account where username = ? and password = ?;";
try {
// 获取预处理搬运工对象,并对SQL语句进行预处理
preparedStatement = connection.prepareStatement(sql);
// 设置参数
preparedStatement.setObject(1, "张三");
preparedStatement.setObject(2, "123456");
// 执行SQL语句
resultSet = preparedStatement.executeQuery();
// 判断是否还有数据
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
JdbcUtils.close(resultSet, preparedStatement, connection);
}
}
}
PreparedStatement 增加操作
public class Demo3 {
@Test
public void testInsert() {
// 准备SQL语句
String sql = "insert into student(name, age, gender, info) values(?, ?, ?, ?)";
// 获取连接
Connection connection = JdbcUtils.getConnection();
PreparedStatement preparedStatement = null;
try {
// 获取预处理对象
preparedStatement = connection.prepareStatement(sql);
// 设置参数
preparedStatement.setObject(1, "赵四");
preparedStatement.setObject(2, 17);
preparedStatement.setObject(3, "男");
preparedStatement.setObject(4, "你愁啥");
// 执行SQL语句
int affectedRows = preparedStatement.executeUpdate();
System.out.println("受影响的行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
JdbcUtils.close(preparedStatement, connection);
}
}
}
PreparedStatement 修改操作
public class Demo3 {
@Test
public void testUpdate() {
// 准备SQL语句
String sql = "update student set age = ? where id = ?";
// 获取连接
Connection connection = JdbcUtils.getConnection();
PreparedStatement preparedStatement = null;
try {
// 获取预处理对象
preparedStatement = connection.prepareStatement(sql);
// 设置参数
preparedStatement.setObject(1, 61);
preparedStatement.setObject(2, 6);
// 执行SQL语句
int affectedRows = preparedStatement.executeUpdate();
System.out.println("受影响的行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
JdbcUtils.close(preparedStatement, connection);
}
}
}
PreparedStatement 删除操作
class Demo3{
@Test
public void testDelete() {
// 准备SQL语句
String sql = "delete from student where id = ?";
// 获取连接
Connection connection = JdbcUtils.getConnection();
PreparedStatement preparedStatement = null;
try {
// 获取预处理对象
preparedStatement = connection.prepareStatement(sql);
// 设置参数
preparedStatement.setObject(1, 3);
// 执行SQL语句
int affectedRows = preparedStatement.executeUpdate();
System.out.println("受影响的行数:" + affectedRows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭资源
JdbcUtils.close(preparedStatement, connection);
}
}
}