likes
comments
collection
share

JDBC总结分析

作者站长头像
站长
· 阅读数 5

JDBC

规定了 java 应用应该如何连接和操作数据库,它是规范,而非实现,具体的实现由不同的数据库厂商提供。对我们来说,JDBC 有效地将我们的代码和具体的数据库实现解耦合,这是非常有好处的,例如,当我的数据库从 mysql 切换到 oracle 时,几乎不需要调整代码。

几个重要的类

JDBC 的 API 中,重点关注下面几个类

  • DriverManager 驱动管理器,用于管理驱动以及获取Connection对象
  • Connection 与指定数据库的连接/会话,用于获取Statement对象、管理事务、获取数据库元数据等。下面的几个类都是在Connection的基础上工作的
  • Statement 静态sql执行对象
  • PreparedStatement 预编译sql执行对象。相比Statement,它可以有效避免 sql 注入,同一 sql 多次执行时性能更好
  • ResultSet 用于封装查询结果集。包扩存储查询结果,遍历结果集

Statement和PreparedStatement的区别

1.Statement:
  • Statement 接口用于执行静态 SQL 语句,并且每次执行 SQL 语句都会进行编译和解释,因此效率相对较低。
  • 可以执行任何 SQL 语句,但在执行前需要手动拼接 SQL 字符串,这可能会导致 SQL 注入的风险。
  • 适用于执行一次性的、不需要参数化的 SQL 语句。
2.PreparedStatement:
  • PreparedStatement 接口继承自 Statement 接口,它预编译 SQL 语句,可以多次执行,因此效率更高。
  • 在创建 PreparedStatement 对象时,SQL 语句中的参数以问号 (?) 形式表示,然后可以通过设置参数的方法动态地传入参数值,从而避免了手动拼接 SQL 字符串,提高了安全性。
  • 适用于需要多次执行、需要参数化的 SQL 语句。

总的来说,PreparedStatement 比 Statement 更高效、更安全,特别是在需要执行多次相似 SQL 语句或者涉及用户输入的情况下。因此,通常推荐使用 PreparedStatement 来执行 SQL 查询和更新操作。

PreparedStatement 的多次执行

是指在执行同一个预编译的 SQL 语句时,可以多次改变参数值并执行,而不需要重新编译 SQL 语句。这种机制可以提高性能,因为 SQL语句只需要编译一次,然后可以多次执行,而不必每次执行都重新编译。

在创建 PreparedStatement 对象时,SQL 语句中的参数通常以问号 (?) 表示。然后,可以使用 setXxx() 系列方法(如 setInt(), setString(), setDate() 等)为每个参数设置具体的值。这些参数可以是动态的,每次执行时可以设置不同的值。

举个例子:

codeString sql = "INSERT INTO users (name, age) VALUES (?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);

// 第一次执行
preparedStatement.setString(1, "John");
preparedStatement.setInt(2, 30);
preparedStatement.executeUpdate();

// 第二次执行
preparedStatement.setString(1, "Alice");
preparedStatement.setInt(2, 25);
preparedStatement.executeUpdate();

SQL编译

在 JDBC 中,SQL 语句的编译是在数据库服务器端进行的,而不是在 Java 运行时环境中。当创建 PreparedStatement 对象时,传入的 SQL 语句会被发送到数据库服务器,并在数据库服务器上进行编译。

数据库服务器会将 SQL 语句编译成可执行的查询计划(execution plan),这个计划会告诉数据库系统如何执行这个 SQL 语句以获得结果。编译过程包括语法分析、语义分析、查询优化等步骤。编译后的查询计划通常会被缓存,以便在后续执行相同 SQL 语句时可以重用,提高性能。

当多次执行同一个 PreparedStatement 对象时,实际上只是在向数据库发送已经编译好的 SQL 语句,并传入不同的参数值。数据库服务器不会重新编译这个 SQL 语句,而是直接使用之前编译好的查询计划来执行。因此,PreparedStatement 可以多次执行同一个 SQL 语句而不会导致额外的编译开销。

为什么不需要Class.forName("com.mysql.cj.jdbc.Driver")也能注册驱动?

JDK6 之后,DriverManager增加了以下静态代码块,在这段静态代码块中,会通过查询系统参数(jdbc.drivers)和SPI机制两种方式去加载数据库驱动。

static {
        loadInitialDrivers();
    }
    //这个方法通过两个方式加载所有数据库驱动:
    //1. 查询系统参数jdbc.drivers获得数据驱动类名,多个以“:”分隔
    //2. SPI机制
    private static void loadInitialDrivers() {
        // 通过系统参数jdbc.drivers读取数据库驱动的全路径名
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // 使用SPI机制加载驱动
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // 读取META-INF/services/java.sql.Driver文件的类全路径名
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                // 实例化对象java.sql.Driver实现类
                // 这个过程会自动注册
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });

        if (drivers == null || drivers.equals("")) {
            return;
        }
        // 加载jdbc.drivers参数配置的实现类
        String[] driversList = drivers.split(":");
        for (String aDriver : driversList) {
            try {
                // 这个过程会自动注册
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

SPI机制本质上提供了一种服务发现机制,通过配置文件的方式,实现服务的自动装载,有利于解耦和面向接口编程。具体实现过程为:在项目的META-INF/services文件夹下放入以接口全路径名命名的文件,并在文件中加入实现类的全限定名,接着就可以通过ServiceLoder动态地加载实现类。

在mysql 的驱动包就可以看到一个java.sql.Driver文件,里面就是mysql驱动的全路径名。

JDBC流程:

// 获取连接
Connection connection = DriverManager.getConnection(url , username , password);
//预编译sql执行对象
PreparedStatement preparedStatement = connection.prepareStatement("select * from user where id = ? and user_id > ? ");
//设置参数
preparedStatement.setQueryTimeout(60);
preparedStatement.setInt(1 , 1);
preparedStatement.setInt(2 , 1);
// 执行 SQL 查询,获取返回结果
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
	System.out.println("id:" + resultSet.getInt(1) + " userId:" + resultSet.getString(2) );
}
转载自:https://juejin.cn/post/7354233858063908883
评论
请登录