Java学习——Java JDBC(上)
一、JDBC的连接
构建JDBC应用程序涉及以下六个步骤:
- 导入包:需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用import java.sql.*就足够
了。 - 注册JDBC驱动程序:要求您初始化驱动程序,以便您可以打开与数据库的通信通道。
- 打开连接:需要使用DriverManager.getConnection()方法创建一个Connection对象,该对象表
示与数据库的物理连接。 - 执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
- 从结果集中提取数据:需要使用相应的ResultSet.getXXX()方法从结果集中检索数据。
- 释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
建立JDBC连接是简单的四个步骤:
- 导入JDBC包:将Java语言的import语句添加到Java代码中导入所需的类。
- 注册JDBC驱动程序:此步骤将使JVM将所需的驱动程序实现加载到内存中,以便它可以满足您的JDBC
请求。 - 数据库URL配置:这是为了创建一个格式正确的地址,指向要连接到的数据库。
- 创建连接对象:最后,调用DriverManager对象的getConnection()方法来建立实际的数据库连接。
Class.forName();
注册驱动程序最常见的方法是使用Java的Class.forName()方法,将驱动程序的类文件动态加载到内存中,并将其自动注册
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException ex) {
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
DriverManager.registerDriver()
第二种方法是使用静态DriverManager.registerDriver()方法。
try {
Driver myDriver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(myDriver);
} catch (ClassNotFoundException ex) {
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
数据库URL配置
加载驱动程序后,可以使用DriverManager.getConnection()方法建立连接。为了方便参考,让我
列出三个重载的DriverManager.getConnection()方法
- getConnection(String url)
- getConnection(String url,Properties prop)
- getConnection(String url,String user,String password)
创建数据库连接对象
String URL = "jdbc:mysql://localhost:3306/数据库名?useSSL=false&useUnicode=true&characterEncoding=UTF-8";
String USER = "username";
String PASS = "password"
Connection conn = DriverManager.getConnection(URL, USER, PASS);
使用数据库URL和属性对象
DriverManager.getConnection()方法的第三种形式需要一个数据库URL和一个Properties对象
DriverManager.getConnection(String url, Properties info);
关闭数据库连接
为确保连接关闭,您可以在代码中的“finally”块,调用对应对象的close()方法。
例:
import java.util.*; String URL = "jdbc:mysql://localhost:3306/yhp2?serverTimezone=UTC";
Properties info = new Properties( );
info.put( "user", "username" );
info.put( "password", "password" );
Connection conn = DriverManager.getConnection(URL, info);
JDBC的 连接示例:
数据库需要提前定义好字段,并存入数据,方便接下来代码中的操作:
数据库名:mydatabase
表:department
表employee:
import java.sql.*;
/**
* JDBC操作步骤
*/
public class Demo1 {
public static void main(String[] args) {
//创建需要使用到的类的对象
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1.加载驱动
// 方法一、将驱动程序的类文件动态加载到内存中,并将其自动注册
Class.forName("com.mysql.cj.jdbc.Driver");
// 方法二、是使用静态DriverManager.registerDriver()方法。
/*
Driver myDriver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(myDriver);
*/
//2.获得链接
String username = "root"; //数据库用户名
String password = "111111"; //密码
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC"; //连接的数据库信息:数据库地址:使用端口号/使用数据库名
conn = DriverManager.getConnection(url, username, password); //与该url下的数据库建立连接
//3.定义sql,用于执行sql语句(进行sql语句的发送)
// 方法一、创建状态通道 Statement
statement = conn.createStatement();
// 方法二、创建预状态通道 PreparedStatement (参考预状态通道,可防止sql注入)
// PreparedStatement statement1 = conn.prepareStatement("");
//通过statement对象调用方法,执行sql语句
// (1)、对数据库进行查询操作(DQL),并返回结果集
resultSet = statement.executeQuery("select * from employee");
// (2)、对数据库执行 增删改 操作(DML语句),使用Statement类中的executeUpdate方法
/*
int result1 = statement.executeUpdate("insert into department(depid,depname) values('555','行政部')"); //对数据库进行数据添加操作,并返回影响行数
int result2 = statement.executeUpdate("update employee set sex = '女' where name = '张三'"); //对数据库进行数据修改操作,并返回影响行数
int result3 = statement.executeUpdate("delete from employee where name = '赵六'"); //对数据库进行数据添加操作,并返回影响行数
System.out.println("返回行数:" + result1);
*/
// (3)、对数据库执行数据库定义操作(执行SQL DDL语句或需要使用真正的动态SQL时),返回的是布尔类型
// 如果执行sql语句得到的是resultSet结果集则返回true,如果是得到的是update count或是没产生结果则返回false
/*
boolean resultCondition = statement.execute("create table test(id int primary key auto_increment,name varchar(20),sex char(2) not null)");
System.out.println("返回情况:" + resultCondition);
*/
//4.取出结果集信息
while (resultSet.next()) { //首先判断下是否存在下条数据(类似迭代器)
//取出数据:resultSet.getXX()方法
System.out.println("姓名:" + resultSet.getString("name") + ",性别:" + resultSet.getString("sex") +
",职称:" + resultSet.getString("title") + ",生日:" + resultSet.getDate("birthday") + ",员工id:" + resultSet.getString("empid"));
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
//5.关闭资源
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
二、SQL注入与预处理通道
1. SQL注入
此时我们的数据库test表中如下数据:
在代码中如是写下:
public static void main(String[] args) {
//创建需要使用到的类的对象
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String username = "root"; //数据库用户名
String password = "111111"; //密码
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url,username,password);
statement = conn.createStatement();
String uname = "aa",upass = "123"; //表中数据查询
resultSet = statement.executeQuery("select * from test where name = '"+ uname +"' and password = " + upass);
if (resultSet.next()) { //查询到结果时,视为登陆成功
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
//关闭资源
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
此时的uname 与 upass 的数据正确:
SQL注入情况:当我们的uname 与 upass 传入下面的情况时,upass里的值 表达的是恒成立,所以会出现登录成功的情况:
String uname = "111",upass = "' ' or 1=1";
这种情况便称为:SQL注入,用户名和密码与我们数据库数据对不上号,但还是能够查询到结果。
2. 预处理通道
为防止SQL注入的情况出现我们使用预处理通道 PrepareStatement
该PreparedStatement的接口扩展了Statement接口,它为您提供了一个通用的Statement对象有两个优点附加功能。
此语句使您可以动态地提供参数。
格式:
PreparedStatement pstmt = null;
try{
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
. . .
}catch(
SQLException e){
. . .
}finally{
. . .
}
示例:
public static void main(String[] args) {
//预状态通道的使用
Connection conn = null;
// Statement statement = null;
ResultSet resultSet = null;
PreparedStatement pps = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String username = "root"; //数据库用户名
String password = "111111"; //密码
String url = "jdbc:mysql://localhost:3306/mydatabase?serverTimezone=UTC";
conn = DriverManager.getConnection(url,username,password);
String sql = "select * from test where name=? and password=?"; //在变量的位置使用占位符?
pps = conn.prepareStatement(sql);
String uname = "111",upass = "' ' or 1=1";
//给占位符?赋值:方法(下标,参数传递)
pps.setString(1,uname);
pps.setString(2,upass);
resultSet = pps.executeQuery(); //直接调用空参方法
if (resultSet.next()) { //查询到结果时,视为登陆成功
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
//关闭资源
if (resultSet != null) {
resultSet.close();
}
if (pps != null) {
pps.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
使用了预状态通道,SQL注入便失效了,只有当使用正确的数据时,才能登入:
对比statement和PreparedStatement
- statement属于状态通道,PreparedStatement属于预状态通道。
- 预状态通道会先编译sql语句,再去执行,比statement执行效率高。
- 预状态通道支持占位符?,给占位符赋值的时候,位置从1开始。
- 预状态通道可以防止sql注入,原因:预状态通道在处理值的时候以字符串的方式处理。