JDBC总结分析

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() 等)为每个参数设置具体的值。这些参数可以是动态的,每次执行时可以设置不同的值。

举个例子:

ini 复制代码
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机制两种方式去加载数据库驱动。

typescript 复制代码
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流程:

ini 复制代码
// 获取连接
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) );
}
相关推荐
tan180°13 分钟前
MySQL表的操作(3)
linux·数据库·c++·vscode·后端·mysql
DuelCode1 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
优创学社21 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
why技术1 小时前
Stack Overflow,轰然倒下!
前端·人工智能·后端
幽络源小助理1 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
ai小鬼头2 小时前
AIStarter如何助力用户与创作者?Stable Diffusion一键管理教程!
后端·架构·github
简佐义的博客3 小时前
破解非模式物种GO/KEGG注释难题
开发语言·数据库·后端·oracle·golang
爬山算法3 小时前
MySQL(116)如何监控负载均衡状态?
数据库·mysql·负载均衡
Code blocks3 小时前
使用Jenkins完成springboot项目快速更新
java·运维·spring boot·后端·jenkins