Java PreparedStatement预编译语句的用法(附带实例)
当通过 Statement 对象执行 SQL 语句时,需要将 SQL 语句发送给数据库管理系统,由数据库管理系统编译后再执行。和 Statement 对象不同,在创建 PreparedStatement 对象时就指定了 SQL 语句,语句中的参数使用占位符“?”来表示。该语句会立即被发送给数据库管理系统进行编译,并将编译后的执行流程缓存起来,这个过程被称为预编译。
当需要执行具体的语句时,数据库管理系统会使用缓存的执行流程,将其中的占位符“?”替换成用户实际发来的参数,执行使用缓存的执行流程,将其中的占位符“?”替换成用户实际发来的参数,执行该 SQL 语句,而不需要像 Statement 对象那样每次都需要先编译再执行。
下图展示了 Statement 对象的 SQL 执行流程,可以看到,想要执行相同语义的插入操作,语句的编译过程需要执行多次。

图 1 Statement 对象的 SQL 执行流程
下图展示了 PreparedStatement 对象的 SQL 执行流程,可以看到,想要执行相同语义的插入操作,语句的编译过程仅需要执行一次,后续通过多次执行绑定参数的过程来插入不同的数据。

图 2 PreparedStatement对象的SQL执行流程
在实际开发中,经常出现需要多次执行相同语义的语句的场景。例如,在商品查询业务中,需要基于商品品类查询商品信息。在并发度较高的场景下,这样的查询每秒可能需要执行上万次。如果使用 Statement 对象来执行,那么语句的编译过程需要执行上万次,这会严重影响语句的执行效率。如果使用 PreparedStatement 对象,那么语句的编译过程仅需要执行一次,后续反复执行的是占位符替换(也称为绑定参数)到执行语句的阶段,可以有效提高语句的执行效率。因此,在实际开发中,应优先使用 PreparedStatement 对象。
Java PreparedStatement预编译语句的使用
PreparedStatement 对象的使用方法与 Statement 对象的略有不同,主要的不同体现在两个方面:创建和绑定参数。与 Statement 对象相似,PreparedStatement 对象也是通过 Connection 对象的 API 来创建的,具体的方法为 preparedStatement(String sql)。与创建 Statement 对象不同的是,调用该方法需要传入要执行的 SQL 语句。需要注意的是,此时传入的 SQL 语句中不应该包含具体的参数,而应使用占位符“?”来表示这些参数,以便在后续复用预编译好的 SQL 语句。
当需要实际执行 SQL 语句时,先调用 PreparedStatement 对象的 setXxx() 方法将具体的数据绑定到指定的占位符上。setXxx() 方法有两个参数,第一个是设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值。setXxx() 是通用的叫法,在具体调用时,如果要绑定的是 int 型的参数,那么调用 setInt() 方法。
下面通过一个编程实例来演示 PreparedStatement 对象的具体的使用方法。该实例使用的数据库结构如下:
# 创建 ex 库
create database ex;
# 使用 ex 库
use ex;
# 创建 info 表
create table info(
id int primary key auto_increment comment '用户 id',
name varchar(50) comment '用户姓名',
gender char(1) comment '用户性别'
);
info 表的结构为:
MariaDB [ex]> desc info;
+-------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(50) | YES | | NULL | |
| gender| char(1) | YES | | NULL | |
+-------+--------------+------+-----+-------------------+----------------+
3 rows in set (0.01 sec)
先向 info 表中插入两条测试数据,SQL 语句如下:
insert into info values(1,'Tom','M'), (2,'Jerry','F');
【实例】PreparedStatement对象的应用。
public class Demo {
public static final String URL = "jdbc:mysql://localhost:3306/ex?serverTimezone=Asia/Shanghai";
// 数据库用户名
public static final String USER = "root";
// 数据库密码
public static final String PASSWORD = "root";
static {
// 1.加载驱动,在当前类加载时执行一次
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
System.out.println("---------查询表中的全部数据--------");
List<User> list = selectAll();
list.forEach(System.out::println);
// 执行插入操作
User user1 = new User(null, "LiLei", 'M');
boolean flag1 = insertUser(user1);
System.out.println("--------插入操作的结果: " + flag1 + "--------");
System.out.println("---------执行插入操作后,再次查询表中的全部数据--------");
selectAll().forEach(System.out::println);
// 再次执行插入操作,获取自增的id
User user2 = new User(null, "MaLi", 'M');
int genKeys = insertAndGetID(user2);
System.out.println("--------新的插入操作返回的id值为: " + genKeys + "--------");
System.out.println("---------执行插入操作后,再次查询表中的全部数据--------");
selectAll().forEach(System.out::println);
// 执行更新操作,修改用户性别为女
user2.setGender('F');
boolean flag2 = updateUserByID(genKeys, user2);
System.out.println("--------更新操作的结果: " + flag2 + "--------");
System.out.println("---------执行更新操作后,再次查询表中的全部数据--------");
selectAll().forEach(System.out::println);
// 删除新添加的用户记录
boolean flag3 = deleteUserByID(genKeys);
System.out.println("--------删除操作的结果: " + flag3 + "--------");
System.out.println("---------执行删除操作后,再次查询表中的全部数据--------");
selectAll().forEach(System.out::println);
}
/**
* 基于id删除用户记录
* @param id 用户id
* @return true 表示删除成功,false 表示删除失败
*/
public static boolean deleteUserByID(int id) {
String sql = "delete from info where id = ?";
boolean flag = false;
try (
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement(sql);
) {
ps.setInt(1, id);
int rows = ps.executeUpdate();
if (rows == 1) {
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 基于id更新用户信息的方法
* @param id: 用户id
* @param user: 包含更新数据的用户对象
* @return true表示更新成功,false表示更新失败
*/
public static boolean updateUserByID(int id, User user) {
String sql = "update info set name = ?, gender = ? where id = ?";
boolean flag = false;
try (
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement(sql);
) {
// 绑定参数,注意参数顺序与语句中的“?”顺序一致
ps.setString(1, user.getName());
ps.setString(2, user.getGender().toString());
ps.setInt(3, id);
int rows = ps.executeUpdate();
if (rows == 1) {
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 执行插入操作并获得自增的id值
* @param user: 准备添加的用户记录
* @return 自增的id值或0
*/
public static Integer insertAndGetID(User user) {
String sql = "insert into info values(?, ?, ?)";
int generatedKeys = 0;
try (
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
) {
// 绑定参数
// 为第一个参数绑定null,使用数据库的自增功能生成id值
ps.setNull(1, Types.INTEGER);
// 绑定第二个参数
ps.setString(2, user.getName());
// 绑定第三个参数
ps.setString(3, user.getGender().toString());
// 执行SQL语句
int rows = ps.executeUpdate();
// 获取数据库生成的自增的id
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
generatedKeys = rs.getInt(1);
}
} catch (Exception e) {
e.printStackTrace();
}
return generatedKeys;
}
/**
* 添加一行用户记录的方法
* @param user: 准备添加的用户记录
* @return true 表示添加成功,false 表示添加失败
*/
public static boolean insertUser(User user) {
String sql = "insert into info values(?, ?, ?)";
boolean flag = false;
try (
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement(sql);
) {
// 绑定参数
// 为第一个参数绑定null,使用数据库的自增功能生成id值
ps.setNull(1, Types.INTEGER);
// 绑定第二个参数
ps.setString(2, user.getName());
// 绑定第三个参数
ps.setString(3, user.getGender().toString());
// 执行SQL语句
int rows = ps.executeUpdate();
// 判断受影响的行数
if (rows == 1) {
// 设置方法的返回值为true
flag = true;
}
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* 查询数据库表中全部数据的方法
* @return 封装了用户信息的集合
*/
public static List<User> selectAll() {
// 声明要执行的SQL语句
String sql = "select * from info";
// 保存用户对象的集合
List<User> list = new ArrayList<>();
// 2.建立连接
try (
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
PreparedStatement ps = conn.prepareStatement(sql);
) {
// 4.执行SQL语句,注意这里不再绑定SQL语句
ResultSet rs = ps.executeQuery();
// 5.对结果集进行操作,封装Bean
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
char gender = rs.getString("gender").charAt(0);
// 创建用户对象,用来封装该行记录
User user = new User(id, name, gender);
// 将用户对象添加到集合中
list.add(user);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
/**
* 用于封装用户信息的实体类
*/
class User {
private Integer id;
private String name;
private Character gender;
public User() {
}
public User(Integer id, String name, Character gender) {
this.id = id;
this.name = name;
this.gender = gender;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Character getGender() {
return gender;
}
public void setGender(Character gender) {
this.gender = gender;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
}
运行结果为:
--------查询表中的全部数据--------
User{id=1, name='Tom', gender=M}
User{id=2, name='Jerry', gender=F}
--------插入操作的结果: true--------
--------执行插入操作后,再次查询表中的全部数据--------
User{id=1, name='Tom', gender=M}
User{id=2, name='Jerry', gender=F}
User{id=3, name='LiLei', gender=M}
--------新的插入操作返回的id值为: 4--------
--------执行插入操作后,再次查询表中的全部数据--------
User{id=1, name='Tom', gender=M}
User{id=2, name='Jerry', gender=F}
User{id=3, name='LiLei', gender=M}
User{id=4, name='MaLi', gender=M}
--------更新操作的结果: true--------
--------执行更新操作后,再次查询表中的全部数据--------
User{id=1, name='Tom', gender=M}
User{id=2, name='Jerry', gender=F}
User{id=3, name='LiLei', gender=M}
User{id=4, name='MaLi', gender=F}
--------删除操作的结果: true--------
--------执行删除操作后,再次查询表中的全部数据--------
User{id=1, name='Tom', gender=M}
User{id=2, name='Jerry', gender=F}
User{id=3, name='LiLei', gender=M}
ICP备案:
公安联网备案: