首页 > 编程笔记 > Java笔记 阅读:5

Java PreparedStatement预编译语句的用法(附带实例)

预编译语句 PreparedStatement 是 java.sql 包中的一个接口,也是 Statement 接口的子接口。

当通过 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}

相关文章