目录
[1. 什么是JDBC](#1. 什么是JDBC)
[2. 为什么要使用JDBC?](#2. 为什么要使用JDBC?)
[3. 如何使用JDBC](#3. 如何使用JDBC)
[3.1 创建Maven工程](#3.1 创建Maven工程)
[3.2 配置MySQL驱动包](#3.2 配置MySQL驱动包)
[3.3 Connection 建立数据库连接](#3.3 Connection 建立数据库连接)
[a. 使用DriverManager类](#a. 使用DriverManager类)
[b. 使用DataSource类](#b. 使用DataSource类)
[3.4 创建Statement对象](#3.4 创建Statement对象)
[3.5 执行SQL语句并接收结果集](#3.5 执行SQL语句并接收结果集)
[3.6 ResultSet对于结果集的后续处理](#3.6 ResultSet对于结果集的后续处理)
[3.7 释放资源](#3.7 释放资源)
[4. 完整示例演示](#4. 完整示例演示)
[4.1 使用DriverManager的示例](#4.1 使用DriverManager的示例)
[4.2 使用DataSource的示例](#4.2 使用DataSource的示例)
[5. SQL注入问题](#5. SQL注入问题)
[5.1 什么是SQL注入?](#5.1 什么是SQL注入?)
[5.2 SQL语句的预处理对象PreparedStatement](#5.2 SQL语句的预处理对象PreparedStatement)
[6. JDBC的步骤总结](#6. JDBC的步骤总结)
1. 什么是JDBC
JDBC(Java Database Connectivity)是Java 官方提供的一套用于访问关系型数据库的标准 API。它定义了 Java 程序与数据库交互的统一接口和规范,而非具体的实现。

2. 为什么要使用JDBC?
不同的数据库对于同一个操作 不论是协议还是参数都各有不同 ,如果让程序员自己去实现,那就必须针对不同的数据库进⾏编码实现,这个⼯作量和维护成本显然太大。
而把具体的实现交给数据库厂商去做,且它们都遵循同一个规范(也就是JDBC),那么Java程序员只需要按照需要调⽤接口中定义的方法即可。这样不论使⽤什么数据库,都对于Java程序没有任 何影响,即便是换⼀个数据库,也只需要换⼀下相应⼚商的实现依赖。
3. 如何使用JDBC
3.1 创建Maven工程
创建一个新项目,在创建页面中选择"Maven"

项目创建完成后会有一个叫pom.xml的配置文件,我们把该文件打开

3.2 配置MySQL驱动包
我们要去Maven仓库中找到的MySQL 官方实现的 JDBC 驱动包,地址:https://mvnrepository.com/artifact/mysql/mysql-connector-java
进入Maven后,我们选择8.0.33版本的JDBC驱动包:

进入对应的驱动包后,下拉页面,会看到下面这串代码,复制这串代码。它是驱动包的依赖路径:

在pom.xml文件中,自己加上一对<dependencies></dependencies>标签。然后在标签的内部粘贴上刚刚复制的代码,如下图所示:

点击右上角的"加载"图标。如果不小心点到"X"号,可以像我这样把"加载"图标找出来再点击:

依赖添加成功后,在外部库(External Libaries)中会添加一个包:

这个com.mysql:mysql-connector-j:8.0.33包就是我们JDBC要用到的驱动包。
3.3 Connection 建立数据库连接
JDBC提供了两种不同的数据连接方式,分别是DriverManager 和 DataSource。
- 其中,DriverManager 是 JDBC 早期提供的工具类,直接通过驱动获取数据库连接。(无连接池)
- DataSource 是 JDBC 规范定义的接口 ,可以使用第三方框架支持的数据库连接池来管理连接的复用、分配。
a. 使用DriverManager类
首先要明确的是,MySQL驱动包中有两个Driver驱动类:
- 一个驱动类是 com.mysql.jdbc.Driver,它可以连接5.X系列版本的MySQL数据库。
- 另一个驱动类是 com.mysql.cj.jdbc.Driver,它可以连接 8.X系列版本的MySQL数据库。
使用时要注意区分,一定要用对应的驱动类来连接对应版本的数据库!!!
JDBC 的本质是 "统一的数据库访问标准",而 DriverManager 作为执行官,它要完成驱动类的统一入口的职责。
每个**(外部库)驱动类** 都要注册到JVM中,然后由 jdk(标准库)中的 DriverManager 类 进行管理、调用。
第一步:加载(注册)数据库厂商提供的驱动
所使用的代码如下:
java
//1.加载数据库厂商提供的驱动
Class.forName("com.mysql.cj.jdbc.Driver"); //mysql实现的Driver类
这段代码做了什么?
-
使用forName()方法 加载Driver类的元数据,同时 (如果Driver类还没被加载过) 它会触发一次Driver类的静态代码块。
-
Driver类的静态代码块中,会使用DriverManager类的registerDriver()方法:

-
registerDriver()方法的核心作用是:把当前驱动类的实例 ,添加到 ++DriverManager 内部维护的 "已注册驱动列表"++ 中(它是一个线程安全的集合),让 DriverManager 能管理并调用这个驱动。
-
由于forName()方法只会触发一次类加载(Java语法特性) ,所以同一个注册类只会执行一次registerDriver()方法,自然不会重复注册。
第二步:建立数据库连接
所使用的代码如下:
java
Connection connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=utf8" +
"&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false"
,"root"
,"123456"
);
这段代码做了什么?
简单来说:通过DriverManager.getConnection()方法,建立Java 程序与数据库之间的物理连接 ,并得到代表该连接的Connection对象。
getConnection()方法中的3个参数:
- String url: URL(Uniform Resource Locator,统一资源定位符) 是互联网上标准化的地址,用于定位和访问网络资源。让我们拆解一下url,看看其中含有哪些信息:
- " jdbc:mysql:// "------> JDBC 连接 MySQL 的协议标识,连接不同的数据库要使用不同的固定标识。
- " 127.0.0.1:3306/test1? "------>数据库所在的主机 IP (127.0.0.1 是本地)+ MySQL 的默认端口 (3306)。test1是要连接的具体数据库名。
- ?号后面的是连接配置参数:
- characterEncoding=utf8:设置数据库交互的字符编码为 UTF-8,避免中文乱码;
- serverTimezone=Asia/Shanghai:设置时区为上海时区(MySQL 8.x 必填,否则会报时区错误);
- allowPublicKeyRetrieval=true:允许获取 MySQL 的公钥(解决 8.x 的权限校验问题);
- useSSL=false:开发环境关闭 SSL 加密(生产环境建议开启)。
- String user: 数据库系统的登录用户名,这里使用的是root超级用户。
- String passward: 数据库系统的登录密码,这里是"123456"。
总的来说,DriverManager 会根据 url 来识别我们要连接的是哪一个数据库系统(此处是 MySQL),然后从已注册的驱动列表中找到适配该数据库的驱动类实例 (此处是com.mysql.cj.jdbc.Driver),调用其connect()方法 完成与数据库的物理连接,最终返回代表该连接的Connection对象。

b. 使用DataSource类
第一步:配置连接参数
所使用的代码如下:
java
//1.1 定义mysql数据源对象
MysqlDataSource mysqlDataSource = new MysqlDataSource();
//1.2 设置数据库的连接串、用户名、密码
mysqlDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=utf8" +
"&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false");
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("123456");
//1.3 定义JDBC数据源对象(接口编程)
DataSource dataSource = mysqlDataSource;
这段代码做了什么?
- MysqlDataSource类是mysql实现的数据库源对象 ,该类是DataSource的实现类。我们要使用setUrl()、setUser()和setPassword() 这三个方法来配置(封装)数据库源,方便后续通过getConnection()方法直接连接数据库。
- 【主流是使用第三方框架实现的 datasource(如 HikariCP 框架下的 HikariDataSource 类),它们会带有连接池机制。
- 【这里的 MysqlDataSource 类并不支持连接池,一般来说连接池不会由第二方(数据库厂商)实现,而是由第三方框架提供。
- DataSource是 JDBC 规范(javax.sql包)定义的标准接口,其定位是数据库连接的统一获取入口。
第二步:建立数据库连接
所使用的代码如下:
java
//2.获取数据库连接
connection = dataSource.getConnection();
这段代码做了什么?
getConnection()方法会根据配置的连接参数直接对数据库进行连接,最后返回连接对象Connection。
- 【如果此处的dataSource的实例是由第三方框架实现的,那么它的getConnection()方法会从连接池中获取Connection对象;如果连接池中没有,那么会创建一个Connection对象存入连接池 并 把该对象的引用作为方法的返回。
补充:
- jdk17的JDBC是4.0之后的版本了, 而JDBC 4.0 引入了SPI(服务提供者接口)自动加载机制 ,已经无需forName()方法就能加载驱动类了。也就是说前面使用DriverManager 的方式连接数据库时,不需要显式使用forName()方法。
- 在 DataSource 连接数据库方式中第2步所使用的Connection()方法,它还是会使用到com.mysql.cj.jdbc.Driver 对象**(已被自动加载)**中connect()方法进行数据库连接。如下图所示:
3.4 创建Statement对象
所使用的代码如下:
java
Statement statement = connection.createStatement();
Statement对象的作用:
Statement 是 JDBC中用于向数据库发送 SQL 语句并获取执行结果 的核心接口,依赖已建立的
Connection对象创建。
Statement中常用于发生SQL语句的方法有executeQuery() 和executeUpdate(),它们对应着不同类型的SQL语句。
executeQuery() 方法:
- 查询语句(SELECT) :SELECT 语句通过
executeQuery()方法执行,返回ResultSet对象。(封装查询结果集,供程序读取数据)executeUpdate() 方法:
- 数据定义语言(DDL) :如
CREATE(建表)、ALTER(修改表)、DROP(删表)等。通过executeUpdate()方法执行,返回值通常为0(DDL 无 "影响行数");- 数据操作语言(DML) :如
INSERT(插入)、UPDATE(更新)、DELETE(删除)等。通过executeUpdate()方法执行,返回值为 SQL 影响的行数;
3.5 执行SQL语句并接收结果集
如果使用的是查询语句,那么查询的结果需要ResultSet接收。所使用的代码如下:
java
//4.定义sql语句 (注意不要用*, 因为修改表结构的时候可能会 改变列的顺序)
String sql = "SELECT id, name, sno, age, gender, enroll_date, class_id FROM student";
//5.执行sql并获取结果
ResultSet resultSet = statement.executeQuery(sql);
如果使用的DDL或DML语句,则不需要用到ResultSet。
建议查询列表不要使用通配符 * ,因为当表结构被修改时,查询出来的字段数量和顺序可能会不一样。而在Java业务层中对结果集的处理一般是不变的,无法应对查询字段的变化。
3.6 ResultSet对于结果集的后续处理
- 默认滚动模式下,ResultSet对象只能用next()方法读取下一行数据 ,只能向下滚动,不能向上滚动。
- 到达下一行后,next()方法还会判断该行是否不为空: 如果不为空返回true,否则返回false。【该行为模式类似Scanner类中的++hasNext()方法 + nextLine()方法++的组合】
ResultSet通过以下方法读取当前行中的具体字段:
方法 作用 对应数据库类型 getInt(String/int)读取整数类型字段 INT、TINYINT(无符号)等 getString(...)读取字符串类型字段 VARCHAR、CHAR、TEXT 等 getLong(...)读取长整数类型字段 BIGINT getDouble(...)读取浮点类型字段 DOUBLE、FLOAT getBoolean(...)读取布尔类型字段 BOOLEAN、TINYINT(1) getDate(...)读取日期字段(仅日期) DATE getTime(...)读取时间字段(仅时间) TIME getTimestamp(...)读取时间戳字段(日期 + 时间) DATETIME、TIMESTAMP getObject(...)通用读取(返回 Object 类型) 任意类型(需手动强转) 这些方法的参数有两种:
- String类型参数:根据字段名称 读取指定的字段。如果查询时使用了别名,那么此处的参数必须也输入其别名。
- int类型参数:根据字段序号 读取指定的字段。注意:字段下标从 1 开始,不是从 0 开始。
-- 示例代码:遍历并打印结果集:
java
//每进一次循环读取下一行记录
while(resultSet.next()){
//根据列的数据类型 调用不同的获取方法
long id = resultSet.getLong(1); //第一列的下标是1
String name = resultSet.getString(2);
String sno = resultSet.getString(3);
int age = resultSet.getInt(4);
boolean gender = resultSet.getBoolean(5);
Date enroll_date = resultSet.getDate(6);
long class_id = resultSet.getLong(7);
// 打印结果(占位符的索引从0开始)
System.out.println(MessageFormat.format("学生编号={0},姓名={1},学号={2},年龄={3}," +
"性别={4},入学时间={5},班级编号={6}", id, name, sno, age, gender, enroll_date
, class_id));
}
MessageFormat的{}是索引式占位符:
- 占位符格式 :{数字} ,其中的数字是 "参数的索引"(从 0 开始)。比如:
{0}对应参数列表的第 1 个字段,{1}对应第 2 个字段,以此类推。- 作用 :按索引将后续传入的参数,替换到字符串中对应的
{数字}位置,实现动态文本拼接;
String类的format()方法也支持占位符,但它的占位符是**%+格式说明符** (如**%s代表字符串** 、%d代表整数 、%f代表浮点数),而非{}。语法和MessageFormat完全不同。
额外补充
创建Statement对象的createStatement()方法最多有3个参数:
光标放在createStatement()方法,按crtl + p可以查看该方法的所有重写版本下所需要的参数:
可以看到,createStatement()方法有3个版本:无参、带2个参数、带3个参数。
参数 1:resultSetType(结果集类型)
控制 ResultSet 的滚动能力与对数据库数据变化的敏感度,可选值:
ResultSet.TYPE_FORWARD_ONLY:仅向前滚动,只能通过next()向下移动。[无参方法的默认值]ResultSet.TYPE_SCROLL_INSENSITIVE:可自由滚动,且结果集是 "快照"(数据库数据变化后,结果集不会同步更新)。ResultSet.TYPE_SCROLL_SENSITIVE:可自由滚动 ,且结果集会随数据库数据的变化动态更新(需数据库驱动支持)。
- 【
TYPE_SCROLL_SENSITIVE的结果集,本质是数据库端的敏感游标在 Java 中的映射。- 【ResultSet确实没有主动发送 SQL的能力,但它依赖已存在的数据库连接 + 数据库端的敏感游标,实现了实时读取最新数据的效果。
参数 2:resultSetConcurrency(结果集并发模式)
控制 ResultSet 是否能直接修改数据库数据,可选值:
ResultSet.CONCUR_READ_ONLY:结果集只读,仅能读取数据 ,无法修改。[无参方法的默认值]ResultSet.CONCUR_UPDATABLE:结果集可更新,可通过ResultSet的updateXxx()方法修改数据并同步到数据库。
参数 3:resultSetHoldability(结果集保持性)
控制事务提交后,ResultSet 是否保持打开状态,可选值:
ResultSet.HOLD_CURSORS_OVER_COMMIT:事务提交后,ResultSet仍保持打开,可继续操作。[带两个参数的默认取值]ResultSet.CLOSE_CURSORS_AT_COMMIT:事务提交后,ResultSet会自动关闭,无法再操作(快照也无法读取)。
想要自由滚动,只需保证第一个参数是 TYPE_SCROLL_INSENSITIVE 或 TYPE_SCROLL_SENSITIVE。
创造Statement对象时,createStatement()方法至少要使用带2个参数的版本。如下:
java
Statement stmt = conn.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE
);
在可自由滚动模式下,除了next()方法外,用于滚动的方法还有:
| 方法 | 作用 |
|---|---|
previous() |
游标回退到上一行 :返回true表示回退成功,false表示已在第一行之前 |
first() |
游标直接跳到第一行 :返回true表示存在第一行,否则返回false |
last() |
游标直接跳到最后一行 :返回true表示存在最后一行,否则返回false |
absolute(int row) |
游标定位到指定行号 :返回true表示行存在 - 正数:从结果集开头数第row行。 - 负数:从结果集结尾数第(-row)行。 |
relative(int rows) |
游标相对当前位置移动指定行数 :返回true表示移动后行存在 - 正数:向下移动rows行。 - 负数:向上移动(-row)行。 |
beforeFirst() |
游标回到结果集开头(第一行之前),常用于重新遍历 |
afterLast() |
游标跳到结果集结尾(最后一行之后) |
getRow() |
获取当前游标所在的行号(从 1 开始;若游标在开头 / 结尾,返回 0) |
3.7 释放资源
Connection(数据库连接)、Statement(SQL 执行载体)、ResultSet(查询结果集)都属于外部资源 。它们依赖数据库服务,JVM 垃圾回收无法自动释放,若不主动释放会导致数据库连接泄漏、资源耗尽等问题。
释放资源最好遵循**"最晚获取的资源最早释放"**原则,避免一个资源关闭失败导致后续资源未释放。
- 资源的释放肯定是在最后的,所以它的处理一般放在try-catch-finnaly语句中的finnaly代码块。
- 使用close()方法即可释放资源。
- close()方法可能导致SQLException异常,需要使用try-catch语句捕获。
代码如下:
java
try{
//获取Connection资源
//获取Statement资源
//获取ResultSet资源
}catch (获取资源时产生的异常){
//异常的处理....
}finally{ //按顺序释放资源
//Result的释放
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
//异常的处理...
}
}
//Statement的释放
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
//异常的处理...
}
}
//Connection的释放
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
//异常的处理...
}
}
}
4. 完整示例演示
目前student表中有这些数据:

4.1 使用DriverManager的示例
-- 示例:获取数据库中每个学生的id, name, sno, age, gender, enroll_date, class_id,然后打印每个学生的信息
sql
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1.加载数据库厂商提供的驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.使用数据库连接对象Connection, 建立数据库连接
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=utf8" +
"&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false",
"root", "123456");
//3.创建Statement对象,用于发送SQL语句给数据库
statement = connection.createStatement();
//4.定义sql语句 (注意不要用*, 因为修改表结构的时候可能会 改变列的顺序)
String sql = "SELECT id, name, sno, age, gender, enroll_date, class_id FROM student;";
//5.执行sql并获取结果
resultSet = statement.executeQuery(sql);
//6.遍历结果集
while(resultSet.next()){
//每进一次循环读取下一行记录
long id = resultSet.getLong(1); //第一列的下标是1
String name = resultSet.getString(2);
String sno = resultSet.getString(3);
int age = resultSet.getInt(4);
boolean gender = resultSet.getBoolean(5);
Date enroll_date = resultSet.getDate(6);
long class_id = resultSet.getLong(7);
// 打印结果(占位符的索引从0开始)
System.out.println(MessageFormat.format("学生编号={0},姓名={1},学号={2},年龄={3}," +
"性别={4},入学时间={5},班级编号={6}", id, name, sno, age, gender, enroll_date, class_id));
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
//7.释放资源(从后往前释放)
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
执行结果:

4.2 使用DataSource的示例
-- 示例:根据学生姓名查询数据库,然后打印学生信息
sql
public static void main(String[] args) {
//1.1 定义mysql数据源对象
MysqlDataSource mysqlDataSource = new MysqlDataSource();
//1.2 设置数据库的连接串、用户名、密码
mysqlDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=utf8" +
"&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false");
mysqlDataSource.setUser("root");
mysqlDataSource.setPassword("123456");
//1.3 定义JDBC数据源对象(接口编程)
DataSource dataSource = mysqlDataSource;
//对象声明
Connection connection = null;
PreparedStatement statement = null; //预处理SQL注入
ResultSet resultSet = null;
try {
//2.获取数据库连接
connection = dataSource.getConnection();
//3.1 定义SQL语句
String sql = "SELECT id, name, sno, age, gender, enroll_date, class_id " +
"FROM student WHERE name = ?"; //使用?占位符
//3.2 接收用户请求
System.out.println("输入要查询的姓名:");
Scanner scanner = new Scanner(System.in);
String inputName = scanner.next();
//4. 用真实的姓名替换占位符【预处理阶段,自动会处理SQL注入问题】
statement = connection.prepareStatement(sql);
statement.setString(1, inputName); //?占位符的下标从1开始
//5.执行SQL语句并返回结果
resultSet = statement.executeQuery();
//6.遍历结果集
while(resultSet.next()){
//每进一次循环读取下一行记录
//根据列的数据类型 调用不同的获取方法
long id = resultSet.getLong("id"); //使用下标表示的话,查询列表下标从1开始
String name = resultSet.getString("name"); //使用名称表示的话,如果查询时使用了别名,那么此处的输入也必须列的别名
String sno = resultSet.getString("sno");
int age = resultSet.getInt("age");
boolean gender = resultSet.getBoolean("gender");
Date enroll_date = resultSet.getDate("enroll_date");
long class_id = resultSet.getLong("class_id");
// 打印结果(占位符的索引从0开始)
System.out.println(MessageFormat.format("学生编号={0},姓名={1},学号={2},年龄={3}," +
"性别={4},入学时间={5},班级编号={6}", id, name, sno, age, gender, enroll_date, class_id));
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
//7.释放资源(从后往前释放)
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
执行结果:

5. SQL注入问题
5.1 什么是SQL注入?
根据学生姓名进行查询,可以写出一下代码:

这里只是单纯的字符串拼接,只有拼接后的字符串符合SQL语法,那么它就能执行。
例如输入:'/**/or/**/class_id=1;#

为什么能查出一个合法的结果集呢?我们把输入的数据与字符串拼接起来看看:

/**/ 和 # 在SQL语法中都表示注释,在这里你可以把它看作一个空格,执行上面拼接后的语句就相当于执行了下面的这条语句:
sql
SELECT id, name, sno, age, gender, enroll_date, class_id
FROM student WHERE name = '' or class_id=1;
可以看到,明明我们把程序设计成"根据学生姓名查询数据库",结果别人通过特殊的输入可以"随意查询数据库"。假如表中还存储着像"密码"这样的敏感数据,那么不法分子就可以通过这样的方法获取敏感数据,这就是SQL注入产生的问题!!
SQL 注入是一种恶意攻击手段:用户通过构造特殊的输入内容,篡改原有 SQL 语句的逻辑,让数据库执行非预期的操作(如绕过登录、删除数据等)。
5.2 SQL语句的预处理对象PreparedStatement
为了解决SQL注入问题,设计出了PreparedStatement类,它被用于SQL语句的预处理。
底层逻辑:
- 预编译 SQL 模板 :先把带**占位符
?**的 SQL 模板(比如SELECT * FROM user WHERE username = ? AND password = ?)发送给数据库,数据库先编译这个模板(解析语法、生成执行计划),并缓存起来。- 传入参数 :再把用户输入的参数(比如用户名、密码)单独传给数据库。数据库会将参数作为 "纯数据" 处理,自动转义特殊字符(如
'、OR等),不会将其解析为 SQL 语法的一部分。- 执行 SQL :复用预编译的执行计划,代入处理过的参数并执行,避免了参数参与 SQL 语法解析的过程。
示例代码:
java
//1.完成Datasource的配置... 或 完成DriverManager的注册...
//2.完成Connection对象的创建...
//3.定义SQL语句,使用?占位符
String sql = "insert into student (name, sno, age, gender, enroll_date, class_id)
values (?,?,?,?,?,?)";
//4.定义SQL预处理对象
PreparedStatement statement = connection.prepareStatement(sql);
//5.用户输入数据...
//6.填充数据并处理参数
statement.setString(1, inputName);
statement.setString(2, inputSno);
statement.setInt(3, inputAge);
statement.setByte(4, inputGender);
statement.setString(5, inputEnrollDate);
statement.setLong(6, inputClassId);
//7.执行SQL
int row = statement.executeUpdate();
//影响行数的判断
if (row == 1) {
System.out.println("插入成功");
} else {
System.out.println("插入失败");
}
这三个方法分别做了什么?
- prepareStatement(sql) 方法
- 核心作用:
- ① 创建**
PreparedStatement对象**;- ② 将传入的带占位符(
?)的 SQL 模板 发送给数据库,由数据库对该 SQL 进行预编译(解析 SQL 语法、生成执行计划并缓存)。- 本质:让数据库先 "记住" SQL 的 "框架",后续只需填充参数,不用重复解析 SQL 语法。
- setXxx(索引, 参数值) 方法
- 核心作用:给 SQL 模板中的
?占位符按索引填充参数 ,Xxx对应参数的数据类型(比如setString对应字符串、setInt对应整数)。- 注意事项:占位符的索引是从 1 开始的。
- 本质:setXxx ()方法会触发 安全处理,不过Java 端本身并不直接做 "转义" 操作,真正的转义 / 安全处理 是在数据库层面完成的。
- executeUpdate() 方法
- 核心作用:执行当前
PreparedStatement对应的 SQL 语句【复用预编译的 SQL】,返回值是SQL 执行后受影响的行数 (比如插入 1 条数据成功则返回1)。- 延伸:PreparedStatement 对象使用的executeQuery()方法,由于已经预编译了SQL模板,所以该方法也不需要输入参数sql。
总结:PreparedStatement与Statement的区别
| 维度 | Statement | PreparedStatement |
|---|---|---|
| 使用流程 | 1. 拼接完整SQL 字符串(含参数) 2. connection.createStatement()方法 创建 Statement 对象 3. 执行时传入拼接好的 SQL (如statement.executeQuery(sql)) | 1. 写带?的SQL模板 2. connection.prepareStatement(sql) 方法创建 prepareStatement对象,绑定并预编译 SQL 3.setXxx()填参数 4. 执行时无需再传 SQL (如prepareStatement.executeQuery()) |
| SQL 构造 | 字符串拼接 | 预编译模板 + 参数绑定 |
| 性能 | 每次执行都编译 | 预编译一次,多次执行 |
| 安全性 | 易受 SQL 注入攻击 | 防止 SQL 注入 |
| 可读性 | 差(字符串拼接混乱) | 好(SQL 结构清晰) |
| 数据类型 | 自动类型转换 | 强类型检查 |
注意:
- PreparedStatement对象在最后也需要调用close()方法释放资源。
- PreparedStatement对象虽然可以向上转型成Statement类型,但这样的话就无法使用PreparedStatement类中的特定方法了(如setInt()、setString())!!!
所以此处不建议使用向上转型语法。
6. JDBC的步骤总结
- 步骤一:连接到数据库服务
- 步骤二:发送SQL语句
- 步骤三:得到返回结果并显示
- 步骤四:关闭连接

JDBC中在本博客涉及到的"事物",它们的下标起始位置分别是:
- 用于预编译的String sql 与 PreparedStatement的setXxx()方法:占位符是问号 ?,下标从 1 开始。
- ResultSet的getXxx()读取字段:列的下标从 1 开始。
- MessageFormat.format()方法打印数据:占位符是**{数字},下标从 0 开始**。
本期分享完毕,感谢大家的支持Thanks♪(・ω・)ノ




