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}