JDBC入门

发布于:2025-09-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

JDBC的概述

JDBC的概述
JDBC(Java Database Connectivity)是 Java 语言操作关系型数据库的标准 API,它为 Java 程序提供了一套统一的接口来访问各种关系型数据库,屏蔽了不同数据库底层的实现差异,使开发者无需针对不同数据库编写不同的代码。

JDBC 是一套标准化的 Java 数据库接口规范
提供统一的 Java 类和接口,实现了对不同数据库的通用编程方式
通过数据库厂商提供的驱动程序(Driver)实现具体适配,屏蔽底层数据库差异,开发者无需关心底层数据库的差异
支持核心数据库操作功能包括执行 SQL 语句、处理结果集、管理事务等核心数据库操作。

数据库驱动是数据库厂商提供的实现类,作为数据传输的桥梁,用于建立应用程序与数据库之间的连接
要使用这些驱动类,需要导入对应数据库厂商提供的驱动jar包,常见的数据库驱动接口由各数据库厂商实现,例如MySQL的驱动实现类为com.mysql.cj.jdbc.Driver。

JDBC 的工作流程

加载并注册数据库驱动

目标:确保 JVM 能够识别并加载指定数据库的驱动程序(Driver),以便后续通过驱动建立与数据库的连接。
原理:数据库厂商提供的驱动类实现了 JDBC 标准接口,程序需将该类加载到内存并自动注册到 DriverManager(驱动管理器)中

两种实现方式:

  1. 显式加载驱动
    java.sql.DriverManager 类提供了静态方法 registerDriver(Driver driver),用于手动注册驱动,需要传入一个具体的驱动实现类实例
    创建MySQL驱动实例,显式注册驱动到DriverManager
import java.sql.Driver;
import java.sql.DriverManager;
import com.mysql.cj.jdbc.Driver; // MySQL 8.x驱动类

public class DriverLoad1 {
    public static void main(String[] args) throws Exception {
        // 创建MySQL驱动实例
        Driver driver = new Driver();
        // 显式注册驱动到DriverManager
        DriverManager.registerDriver(driver);
        
        // 后续可通过DriverManager获取连接
        // Connection conn = DriverManager.getConnection(url, user, password);
    }
}
  1. 通过反射加载驱动类(触发自动注册)
    MySQL 驱动类的内部包含静态代码块,当类被加载时,静态代码块会自动调用 DriverManager.registerDriver() 完成注册,因此只需通过 Class.forName() 反射加载驱动类,即可间接完成注册。
import java.sql.DriverManager;

public class DriverLoad2 {
    public static void main(String[] args) throws Exception {
        // 反射加载MySQL驱动类,触发静态代码块自动注册
        Class.forName("com.mysql.cj.jdbc.Driver");
        
        // 后续可通过DriverManager获取连接
        // Connection conn = DriverManager.getConnection(url, user, password);
    }
}

建立数据库连接

使用驱动管理器 DriverManager 的 getConnection 方法获取连接:DriverManager.getConnection(url, username, password),该方法返回的 Connection 对象代表 Java 与数据库之间的会话
url参数格式说明:jdbc:mysql://localhost:3306/day07
jdbc 代表的主协议,mysql 代表子协议,localhost 是ip地址,3306 是默认的端口号,day07 是数据库名称
当访问本地数据库时,可省略主机和端口信息,简写为:jdbc:mysql:///day07

创建 SQL 执行载体

Connection 接口作为 Java 与数据库交互的桥梁,代表 Java 程序与数据库之间建立的会话连接,该连接使用完毕后必须及时关闭以释放资源。
Connection 接口主要提供两大功能:

  • 创建用于执行 SQL 语句的载体
    Statement createStatement():创建 Statement 接口实现对象
    PreparedStatement prepareStatement(String sql):创建 Statement 接口的子接口 PreparedStatement 的实现对象,有效防止 SQL 注入漏洞
  • 事务管理
    void setAutoCommit(boolean autoCommit):设置事务的自动提交模式
    void commit():提交当前事务
    void rollback():回滚当前事务

执行 SQL 语句

Statement 接口用于向数据库发送静态 SQL 语句并获取执行结果
executeQuery(String sql):执行查询语句(如SELECT),返回ResultSet对象(包含查询结果);
executeUpdate(String sql):执行更新语句(如INSERT、UPDATE、DELETE),返回受影响的行数(int);
execute(String sql):执行任意 SQL 语句(可执行查询或更新),返回boolean值(true表示有结果集,false表示无结果集);
Statement 的子类 PreparedStatement,预编译 SQL 语句,支持动态参数(通过?占位符),解决了静态 SQL 的局限性,可防止 SQL 注入,执行效率更高(适合重复执行的 SQL)。

处理结果

ResultSet 接口用于封装 SQL 查询结果集,以表格形式存储查询数据,通过遍历该接口可获取查询结果

  • 封装数据
    内部维护游标,初始位置在第一行数据之前
    调用 next() 方法使游标下移(返回 true 表示有下一行数据),通过 getXxx(列索引/列名) 方法逐行获取数据(默认仅支持向下移动)
  • 数据获取(根据字段类型调用对应方法)
    整型(int/bigint):使用getInt()或者getLong()
    字符串(varchar/char):使用 getString()
    通用类型:getObject()(需自行强制转换)
  • 获取数据的方法是重载的
    按列索引:getInt(int index)(索引从1开始)
    按列名:getInt(String columnName) 通过字段的名称来取值(比较常用)

释放资源

关闭所有数据库相关资源(ResultSet、Statement、Connection),避免资源泄漏(数据库连接是稀缺资源,不关闭会导致连接耗尽)
注意关闭顺序:先开后关(ResultSet → Statement → Connection),且必须在finally块中关闭,确保无论是否发生异常,资源都会被释放。

finally{

	if(resultSet != null){
		try {
			// 释放资源
			resultSet.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	if(statement != null){
		try {
			// 释放资源
			statement.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	if(connection != null){
		try {
			// 释放资源
			connection.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

数据库操作中加载驱动、获取连接、关闭资源等,这些操作的逻辑是通用的,可以进行向上提取

JDBC工具类三版本

1.0版本

package com.qcby.util;

import java.sql.*;
/**
 * JDBC的工具类1.0版本
 */
public class JdbcUtil1 {
    //加载驱动
    public static void loadDriver() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    // 获取连接
    public static Connection getConnection() {
        //加载驱动
        loadDriver();
        //获取连接对象
        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/ssm", "root", "123456");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    /**
     * 关闭资源,适用查询有结果集
     * @param conn
     * @param stmt
     * @param rs
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (conn != null) {
            try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
    }

    /**
     * 关闭资源,适用增删改无结果集
     * @param conn
     * @param stmt
     */
    public static void close(Connection conn, Statement stmt) {
        if (stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (conn != null) {
            try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
    }
}

为什么在这个JdbcUtil1工具类中,所有方法都用static修饰?

如果不用static修饰,使用时必须先创建JDBCUtil1的实例:

JDBCUtil1 util = new JdbcUtil1(); // 多余的实例化
Connection conn = util.getConnection();

在 Java 中,static 关键字修饰方法表示该方法是类方法,属于类本身,而非类的某个具体实例,用 static 修饰后,可以直接通过类名调用,无需创建实例,更简洁高效:

Connection conn = JdbcUtil1.getConnection(); // 直接调用,无需new对象

2.0版本

其核心优化在于通过读取properties属性文件管理数据库连接参数而非硬编码,从而方便配置修改并减少代码冗余

package com.qcby.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * JDBC的工具类2.0版本
 */
public class JdbcUtil2 {
    private static final String driverclass;
    private static final String url;
    private static final String username;
    private static final String password;

    static {
        //加载属性文件
        Properties prop = new Properties();
        InputStream inputStream = JdbcUtil2.class.getResourceAsStream("/db.properties");
        try {
            // 加载配置文件
            prop.load(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
            // 从配置文件获取参数
            driverclass = prop.getProperty("driver");
            url = prop.getProperty("url");
            username = prop.getProperty("user");
            password = prop.getProperty("password");
    }

    /**
     * 加载驱动
     */
    public static void loadDriver() {
        try {
            Class.forName(driverclass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    // 获取连接
    public static Connection getConnection() {
        //加载驱动
        loadDriver();
        //获取连接对象,返回
        Connection conn = null;
        try {
            // 获取到链接
            conn = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    // 关闭资源,适用增删改无结果集
    public static void close(Connection conn, Statement stmt) {
        if (stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (conn != null) {
            try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
    }

    // 关闭资源,适用查询有结果集
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (conn != null) {
            try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
    }
}

在这里插入图片描述

该类定义了四个 private static final 修饰的静态成员变量,这些变量由类加载时自动执行的静态代码块初始化,初始化过程为:当前类的类对象从项目的类路径中读取 db.properties 资源文件,并返回一个用于读取该文件内容的 InputStream 输入流对象,Properties 是 Java 中专门用于处理键值对形式配置文件的工具类(能方便地存储和获取配置项),通过调用 Properties 的load(InputStream inStream) 方法,从获取到的输入流中读取属性列表(键和元素对),并将其解析后存储到 prop 对象中,后续通过 getProperty(String key) 用指定的键在此属性列表中搜索属性,获取对应配置值,并赋值给静态变量,这样初始化静态成员变量就完成了。这样就可以通过读取 properties 配置文件获取数据库连接参数(而非硬编码在代码中),方便后期修改数据库配置。

该工具类中还提供了三个核心方法:loadDriver方法通过反射调用Class.forName(driverclass)加载数据库驱动;getConnection方法先调用loadDriver加载驱动,再通过DriverManager.getConnection(url, username, password)获取数据库连接并返回;此外还有两个重载的close方法,分别用于增删改(无结果集)和查询(有结果集)操作后的资源关闭,前者关闭Connection和Statement,后者按ResultSet→Statement→Connection的顺序关闭资源,且每个资源关闭前都会判断非空以避免空指针异常。

3.0版本

简化 Java 程序操作数据库的流程 —— 通过连接池复用数据库连接,避免频繁创建 / 关闭连接的性能损耗

导入坐标依赖

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.8</version>
        </dependency>
    </dependencies>
package com.qcby.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * JDBC工具类 3.0 版本
 * 加入数据库连接池
 */
public class JdbcUtil3 {

    // 连接池对象
    private static DataSource DATA_SOURCE;

    static {
            // 加载属性文件
            Properties prop = new Properties();
            InputStream inputStream = JdbcUtil3.class.getResourceAsStream("/druid.properties");
        try {
            // 加载配置文件
            prop.load(inputStream);
            // 创建连接池对象
            DATA_SOURCE = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从连接池获取连接
     */
    public static Connection getConnection() {
        Connection conn = null;
        try {
           conn = DATA_SOURCE.getConnection(); // 从连接池拿连接
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 关闭资源(连接归还到池,而非真正关闭)
     */
    public static void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (conn != null) {
            try {
                // 连接池的连接close()是归还,不是关闭
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    // 重载:无结果集时归还
    public static void close(Connection conn, Statement stmt) {
        if (stmt != null) {
            try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
        }
        if (conn != null) {
            try {
                // 连接池的连接close()是归还,不是关闭
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述

这是一个基于Druid连接池的JDBC工具类,其核心逻辑是通过数据库连接池高效管理数据库连接。在类加载时,静态代码块会加载druid.properties配置文件(该文件包含数据库驱动类、连接URL、用户名、密码以及连接池参数如初始连接数、最大活跃连接数等),并利用DruidDataSourceFactory创建连接池对象DARA_SOURCE;提供getConnection()方法从连接池获取数据库连接,而非直接创建新连接,以减少资源消耗;提供两个重载的close()方法,用于关闭Statement、ResultSet并将连接归还给连接池而非真正关闭连接),其中一个处理包含ResultSet的情况,另一个处理无ResultSet的情况,通过这种方式实现连接的复用,提升数据库操作效率。

SQL 注入漏洞

SQL 注入漏洞分析

当使用字符串拼接方式构造SQL语句时:String sql = "select * from t_user where username = '"+username+"' and password ='"+password+"'";

攻击者可通过特殊输入绕过验证:
输入用户名为 aaa’or’1=1(密码任意) 最终SQL语句变为: String sql = "select * from t_user where username = 'aaa'or'1=1' and password = 'sfsdfsds";
输入用户名为 aaa’-- '(密码任意) 最终SQL语句变为:String sql = "select * from t_user where username = 'aaa'‐‐ '' and password = 'sfsdfsdfs";

本质是拼接SQL语句,最终 SQL 会返回数据库中所有用户数据,攻击者无需正确密码即可登录

解决方案

“参数化查询”是一种旨在防御SQL注入的安全机制,其核心思想是通过严格分离SQL语句的结构与参数数据,确保用户输入仅作为“数据”参与数据库操作,而非被解析为SQL代码的一部分。

在Java语言中,PreparedStatement 接口( Statement 的子接口)是遵循JDBC规范、实现参数化查询机制的标准工具,也是Java开发者防御SQL注入的首选方案。其实现逻辑与核心功能如下:

  1. 基于“预编译+参数占位符”的结构锁定
    PreparedStatement 通过以下流程实现SQL结构与参数的分离:

    • 使用 ? 作为参数占位符,替代SQL语句中需要动态传入的参数
    • 调用 Connection.prepareStatement(String sql) 方法,将包含占位符的SQL模板发送至数据库服务器进行预编译,生成固定的执行计划,此时SQL语句的逻辑结构已被永久锁定,无法被后续传入的参数修改;
    • 后续通过 setXxx() 系列方法(如setInt()、setString()、setObject()等)向占位符传入具体参数值,这些参数仅作为“数据”填充至预设位置,不会被数据库引擎解析为SQL代码。
  2. setXxx() 方法内容
    setXxx() 系列方法是参数化查询机制在代码层面的关键实现,通过双重机制强化安全性:

    • 强制类型匹配:根据方法名(如setInt()对应整数类型、setString()对应字符串类型)对传入参数进行类型校验,确保参数类型与SQL语句中对应字段的类型要求一致,从源头避免因类型不匹配导致的注入风险;
    • 自动转义特殊字符:对于字符串等可能包含特殊符号的参数类型,数据库驱动会自动对单引号、分号、注释符等SQL注入常用字符进行转义处理(如将单引号转换为数据库可识别的转义形式),确保这些字符仅作为数据的一部分被处理,而非用于篡改SQL结构。

网站公告

今日签到

点亮在社区的每一天
去签到