JDBC
JDBC
本篇整理 JDBC 的概念与原理、入门流程(建立连接、执行 SQL、处理结果、释放资源)、查询操作与 ResultSet、PreparedStatement 防止 SQL 注入,以及如何封装 JDBC 工具类。
1. 概述
1.1 什么是 JDBC
JDBC(Java DataBase Connectivity,Java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问。
简单说,JDBC 就是用 Java 语言来操作数据库。原来我们是在控制台使用 SQL 语句操作数据库,而 JDBC 是用 Java 语言向数据库发送 SQL 语句。
1.2 JDBC 原理
早期 SUN 公司想编写一套可以连接天下所有数据库的 API,但刚起步就发现这几乎不可完成,因为各厂商的数据库服务器差异太大。后来 SUN 与数据库厂商讨论,最终得出的结论是:由 SUN 提供一套访问数据库的规范(一组接口),并提供连接数据库的协议标准,各数据库厂商遵循该规范,提供访问自家数据库服务器的 API 实现。SUN 提供的规范命名为 JDBC,而厂商提供的、遵循 JDBC 规范、可以访问自家数据库的 API 被称为驱动。
JDBC 是接口,JDBC 驱动才是接口的实现,没有驱动就无法完成数据库连接。每个数据库厂商都有自己的驱动,用来连接自家的数据库。二者的对应关系如下:
| 角色 | 提供方 | 性质 | 定位 |
|---|---|---|---|
| JDBC | Java 官方 | 一系列接口 | 规范 |
| 驱动 | 数据库厂商 | JDBC 接口的实现类 | 实现 |

2. JDBC 入门
2.1 准备工作
2.1.1 建库建表
DROP DATABASE IF EXISTS mydbjdbc;
CREATE DATABASE mydbjdbc;
USE mydbjdbc;
CREATE TABLE tb_stu (
sid INT PRIMARY KEY auto_increment,
sname VARCHAR (50),
sage INT,
sgender VARCHAR (10)
);
INSERT INTO tb_stu(sname, sage, sgender) VALUES('John', 20, "male");
INSERT INTO tb_stu(sname, sage, sgender) VALUES('bob', 20, "male");
CREATE TABLE USER(
username VARCHAR (20),
password VARCHAR (20)
);
INSERT INTO USER VALUES('Peter', '123');
INSERT INTO USER VALUES('John', '123');2.1.2 新建项目
- 新建 Java 项目;
- 在项目下新建
lib目录; - 将 MySQL 驱动 jar 包拷贝到
lib目录下; - 选中
lib目录右键Add as Library,单击OK。
2.2 建立连接
2.2.1 准备四大参数
注
本文代码的驱动类名统一使用 MySQL 8.x 的 com.mysql.cj.jdbc.Driver(MySQL 5.x 为 com.mysql.jdbc.Driver)。8.x 驱动会通过 SPI 自动注册,Class.forName(driverName) 这一步可以省略;若连接时出现时区相关报错,可在 URL 后追加 serverTimezone=Asia/Shanghai 等参数。
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";2.2.2 加载驱动
//手动加载字节码文件到JVM中
Class.forName(driverName);2.2.3 准备 SQL 语句
//SQL语句
String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES('Peter', 20, 'male')";2.2.4 建立连接
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//验证是否成功建立连接
System.out.println(connection);提示
如果能够正常输出连接信息,说明连接建立成功,这是后续一切操作的基础。
2.2.5 常见问题
| 异常信息 | 原因 |
|---|---|
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver | 项目中没有添加 MySQL 驱动,或驱动的名字写错 |
java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES) | 用户名或密码错误 |
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database 'mydbjdbc1' | 数据库名称不正确 |
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure ... | MySQL 服务没有启动或网络故障 |
2.3 获取发送 SQL 的对象
Statement statement = connection.createStatement();2.4 执行 SQL 语句
//使用Statement发送SQL语句,返回受影响的行数
int i = statement.executeUpdate(sql);注
增、删、改使用 executeUpdate;查询使用 executeQuery。
2.5 处理结果
if(i == 1) {
System.out.println("添加成功");
}2.6 释放资源
遵循先开后关原则,释放所使用到的资源对象。
//释放资源
statement.close();
connection.close();注意
资源对系统来说非常重要,而且是有限的,用完之后一定要释放。
2.7 完整代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJdbc1 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//SQL语句
String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES('Peter', 20, 'male')";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//System.out.println(connection);
Statement statement = connection.createStatement();
//发送SQL语句
int i = statement.executeUpdate(sql);
//处理结果
if(i == 1) {
System.out.println("添加成功");
}
//释放资源
statement.close();
connection.close();
}
}举一反三,可以按照上面的套路进行删除、修改操作。
2.8 程序改进
在进行添加或其他操作时,SQL 语句中的内容不可能在程序中写死,数据库中操作的数据一定是可变的。对程序做如下修改,其他部分不变:
//假设用户输入的数据
String name = "zs";
int age = 10;
String gender = "male";
//SQL语句
//String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES('Peter', 20, 'male')";
String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES('" + name + "', " + age + ", '" + gender + "')";通过上面的修改,就可以通过 Java 代码向数据库中添加变化的数据,而不是在代码中写死。同理,删除和修改操作也可以按照这样的套路进行。
2.9 改进后的完整代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class TestJdbc2 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//假设用户输入的数据
String name = "zs";
int age = 10;
String gender = "male";
//SQL语句
//String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES('Peter', 20, 'male')";
String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES('" + name + "', " + age + ", '" + gender + "')";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//System.out.println(connection);
Statement statement = connection.createStatement();
//发送SQL语句
int i = statement.executeUpdate(sql);
//处理结果
if(i == 1) {
System.out.println("添加成功");
}
//释放资源
statement.close();
connection.close();
}
}3. 查询操作
查询操作和增删改操作在结果处理上有很大区别,有必要深入研究。
3.1 获取结果集
//假设用户输入的数据
int stuid = 1;
//SQL语句
String sql = "SELECT * FROM tb_stu WHERE sid=" + stuid;
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//System.out.println(connection);
Statement statement = connection.createStatement();
//发送SQL语句,获取结果集
ResultSet resultSet = statement.executeQuery(sql);注
查询使用 executeQuery。
3.2 处理结果集
ResultSet 以表(table)结构进行临时结果的存储,需要通过 JDBC API 将其中数据依次获取。
- 数据行指针:初始位置在第一行数据之前,每调用一次
boolean next()方法,ResultSet 的指针向下移动一行,结果为true表示当前行有数据; resultSet.getXxx(整数):根据列的编号顺序获取,从 1 开始(一定要注意);resultSet.getXxx("列名"):根据列名获取。
int getInt(int columnIndex) throws SQLException //获得当前行第N列的int值
int getInt(String columnLabel) throws SQLException //获得当前行columnLabel列的int值
double getDouble(int columnIndex) throws SQLException //获得当前行第N列的double值
double getDouble(String columnLabel) throws SQLException //获得当前行columnLabel列的double值
String getString(int columnIndex) throws SQLException //获得当前行第N列的String值
String getString(String columnLabel) throws SQLException //获得当前行columnLabel列的String值
//处理结果
while(resultSet.next()) {
int id = resultSet.getInt("sid");
String sname = resultSet.getString("sname");
int sage = resultSet.getInt("sage");
String sgender = resultSet.getString("sgender");
System.out.println(id);
System.out.println(sname);
System.out.println(sage);
System.out.println(sgender);
}3.3 查询操作完整代码
import java.sql.*;
public class TestJdbc3 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//假设用户输入的数据
int stuid = 1;
//SQL语句
String sql = "SELECT * FROM tb_stu WHERE sid=" + stuid;
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//System.out.println(connection);
Statement statement = connection.createStatement();
//发送SQL语句
ResultSet resultSet = statement.executeQuery(sql);
//处理结果
while(resultSet.next()) {
int id = resultSet.getInt("sid");
String sname = resultSet.getString("sname");
int sage = resultSet.getInt("sage");
String sgender = resultSet.getString("sgender");
System.out.println(id);
System.out.println(sname);
System.out.println(sage);
System.out.println(sgender);
}
//释放资源
resultSet.close();
statement.close();
connection.close();
}
}4. PreparedStatement
4.1 SQL 注入
4.1.1 什么是 SQL 注入
SQL 注入是指用户输入的数据中含有 SQL 关键字或语法,并且参与了 SQL 语句的编译,导致 SQL 语句编译后的条件恒为 true,总能得到正确的结果。这种现象称为 SQL 注入。
4.1.2 SQL 注入案例
import java.sql.*;
public class TestJdbc4 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//假设用户输入的数据
String n = "abc' OR 1=1 OR '1=1";
String p = "123";
//SQL语句
String sql = "SELECT * FROM user WHERE username='" + n + "' AND password='" + p + "'";
System.out.println(sql);
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//System.out.println(connection);
Statement statement = connection.createStatement();
//发送SQL语句
ResultSet resultSet = statement.executeQuery(sql);
//处理结果
while(resultSet.next()) {
System.out.println(resultSet.getString("username"));
System.out.println(resultSet.getString("password"));
}
//释放资源
resultSet.close();
statement.close();
connection.close();
}
}4.1.3 如何避免 SQL 注入
由于上面编写的 SQL 语句是先让用户输入数据、拼接之后再整体编译,所以才给了 SQL 注入可乘之机。要避免这个问题,就要让 SQL 语句在用户输入数据之前已经编译成完整的 SQL 语句(结构已固定),之后再填充数据。
解决方案就是使用 PreparedStatement。
4.2 PreparedStatement 使用
PreparedStatement 继承了 Statement 接口,执行 SQL 语句的方法与 Statement 无异。其作用如下:
- 预编译 SQL 语句,效率高;
- 安全,避免 SQL 注入;
- 可以动态填充数据,执行多个同构的 SQL 语句。
4.2.1 预编译 SQL 语句
//SQL语句
String sql = "SELECT * FROM user WHERE username=? AND password=?";
//预编译SQL语句
PreparedStatement statement = connection.prepareStatement(sql);4.2.2 设置参数
String n = "abc' OR 1=1 OR '1=1";
String p = "123";
//设置参数
statement.setString(1, n);
statement.setString(2, p);4.2.3 完整代码
import java.sql.*;
public class TestJdbc5 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//假设用户输入的数据
String n = "abc' OR 1=1 OR '1=1";
String p = "123";
//SQL语句
String sql = "SELECT * FROM user WHERE username=? AND password=?";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//预编译SQL语句
PreparedStatement statement = connection.prepareStatement(sql);
//设置参数
statement.setString(1, n);
statement.setString(2, p);
//发送SQL语句
ResultSet resultSet = statement.executeQuery();
//处理结果
while(resultSet.next()) {
System.out.println(resultSet.getString("username"));
System.out.println(resultSet.getString("password"));
}
//释放资源
resultSet.close();
statement.close();
connection.close();
}
}5. 使用 JDBC 进行 CRUD 操作(掌握)
在项目实战中,推荐使用 PreparedStatement 而不是 Statement。以下代码必须熟练掌握,能够举一反三,这是后续学习的基础。
5.1 添加操作
import java.sql.*;
public class TestJdbc6 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//SQL语句
String sql = "INSERT INTO tb_stu(sname, sage, sgender) VALUES(?, ?, ?)";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//创建PreparedStatement
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1, "zhangsan");
pstmt.setInt(2, 20);
pstmt.setString(3, "male");
//发送SQL语句
int i = pstmt.executeUpdate();
//处理结果
if(i == 1) {
System.out.println("添加成功");
}
//释放资源
pstmt.close();
connection.close();
}
}5.2 删除操作
import java.sql.*;
public class TestJdbc7 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//SQL语句
String sql = "DELETE FROM tb_stu WHERE sid=?";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//创建PreparedStatement
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, 5);
//发送SQL语句
int i = pstmt.executeUpdate();
//处理结果
if(i == 1) {
System.out.println("删除成功");
}
//释放资源
pstmt.close();
connection.close();
}
}5.3 修改操作
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestJdbc8 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//SQL语句
String sql = "UPDATE tb_stu SET sage=? WHERE sid=?";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//创建PreparedStatement
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setInt(1, 25);
pstmt.setInt(2, 1);
//发送SQL语句
int i = pstmt.executeUpdate();
//处理结果
if(i == 1) {
System.out.println("修改成功");
}
//释放资源
pstmt.close();
connection.close();
}
}5.4 查询操作
import java.sql.*;
public class TestJdbc9 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//驱动名
String driverName = "com.mysql.cj.jdbc.Driver";
//连接数据库的url
String url = "jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false";
//用户名
String username = "root";
//密码
String password = "root";
//加载驱动
Class.forName(driverName);
//SQL语句
String sql = "SELECT * FROM tb_stu WHERE sgender=?";
//建立连接
Connection connection = DriverManager.getConnection(url, username, password);
//预编译SQL语句
PreparedStatement statement = connection.prepareStatement(sql);
//设置参数
statement.setString(1, "male");
//发送SQL语句
ResultSet resultSet = statement.executeQuery();
//处理结果
while(resultSet.next()) {
int sid = resultSet.getInt("sid");
String sname = resultSet.getString("sname");
int sage = resultSet.getInt("sage");
String sgender = resultSet.getString("sgender");
System.out.println(sid + "---" + sname + "---" + sage + "---" + sgender);
}
//释放资源
resultSet.close();
statement.close();
connection.close();
}
}6. 封装工具类
上面的代码大部分是重复的,有必要把重复的代码提取出来。
6.1 工具类
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtil {
//定义成员变量用来保存四大参数
private static String driverName;
private static String url;
private static String username;
private static String password;
static {
try {
//读取配置文件到输入流
InputStream in = JdbcUtil.class.getResourceAsStream("/jdbc.properties");
Properties properties = new Properties();
//加载配置文件中的数据到Properties中
properties.load(in);
//获取四大参数
driverName = properties.getProperty("jdbc.driverName");
url = properties.getProperty("jdbc.url");
username = properties.getProperty("jdbc.username");
password = properties.getProperty("jdbc.password");
//加载驱动
Class.forName(driverName);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
//释放资源
public static void close(Connection connection, Statement statement, ResultSet rSet) {
try {
if(rSet != null) {
rSet.close();
}
if(statement != null) {
statement.close();
}
if(connection != null) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}6.2 工具类使用
在 src 下创建 jdbc.properties 文件:
jdbc.driverName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydbjdbc?useSSL=false
jdbc.username=root
jdbc.password=root在 Java 代码中使用工具类:
import com.qfedu.utils.JdbcUtil;
import java.sql.*;
public class TestJdbc10 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//SQL语句
String sql = "SELECT * FROM tb_stu WHERE sgender=?";
//建立连接 --- 使用工具类
Connection connection = JdbcUtil.getConnection();
//预编译SQL语句
PreparedStatement statement = connection.prepareStatement(sql);
//设置参数
statement.setString(1, "male");
//发送SQL语句
ResultSet resultSet = statement.executeQuery();
//处理结果
while(resultSet.next()) {
int sid = resultSet.getInt("sid");
String sname = resultSet.getString("sname");
int sage = resultSet.getInt("sage");
String sgender = resultSet.getString("sgender");
System.out.println(sid + "---" + sname + "---" + sage + "---" + sgender);
}
//释放资源 --- 使用工具类
JdbcUtil.close(connection, statement, resultSet);
}
}通过使用工具类,代码变得简洁、更容易维护。
7. 小结
JDBC 操作数据库的流程是固定的:加载驱动 → 获取连接 → 获取发送 SQL 的对象 → 执行 SQL → 处理结果 → 释放资源。实际开发中记住三点:
- 优先使用 PreparedStatement:预编译效率更高,且能防止 SQL 注入;
- 连接四大参数写在配置文件中,配合连接池与工具类统一管理连接;
- 释放资源遵循「先开后关」,并放在
finally中保证一定执行。
