news 2026/8/10 8:12:16

JDBC以及工具类介绍

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JDBC以及工具类介绍

JDBC的概述

JDBC(Java Database Connectivity)是Java语言操作关系型数据库的标准化应用程序接口(API),由 Java 官方定义并包含于 java.sql 与 javax.sql 包中。它通过一套统一的接口规范,为Java程序提供了访问各类关系型数据库的标准方式——开发者无需关注不同数据库的底层实现差异,只需遵循JDBC接口进行编程,即可实现对不同数据库的通用操作,有效避免了针对特定数据库编写差异化代码的问题。
作为一套标准化的数据库接口规范,JDBC的核心是定义了Connection(数据库连接)、Statement(SQL执行器)、ResultSet(结果集)等关键接口(及少量辅助类),明确了数据库操作的标准流程与方法。这些接口的具体实现则由数据库厂商提供,即数据库驱动程序——驱动作为Java程序与数据库之间的桥梁,负责将JDBC接口的调用转化为数据库可识别的底层协议(如MySQL的TCP协议),实现具体适配。
数据库驱动本质上是实现了JDBC接口的类库(通常打包为JAR文件),使用时需手动导入对应数据库的驱动包。例如,MySQL的驱动实现类为com.mysql.cj.jdbc.Driver,它实现了JDBC的Driver接口,用于建立应用程序与MySQL数据库的连接。基于JDBC规范与驱动的配合,开发者可便捷地完成执行SQL语句、处理查询结果、管理事务等核心数据库操作。

JDBC 的工作流程

加载并注册数据库驱动

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

1.显式加载驱动

java.sql.DriverManager 类提供了静态方法 registerDriver(Driver driver),用于手动注册驱动,需要传入一个具体的驱动实现类实例
创建MySQL驱动实例,显式注册驱动到DriverManager
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); } }

2.通过反射加载驱动类(触发自动注册)

MySQL 驱动类的内部包含静态代码块,当类被加载时,静态代码块会自动调用 DriverManager.registerDriver() 完成注册,因此只需通过 Class.forName() 反射加载驱动类,即可间接完成注册。
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版本

/** * 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属性文件管理数据库连接参数而非硬编码,从而方便配置修改并减少代码冗余

/** * 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>
/** * 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代码。
  1. setXxx() 方法内容
setXxx() 系列方法是参数化查询机制在代码层面的关键实现,通过双重机制强化安全性:
  • 强制类型匹配:根据方法名(如setInt()对应整数类型、setString()对应字符串类型)对传入参数进行类型校验,确保参数类型与SQL语句中对应字段的类型要求一致,从源头避免因类型不匹配导致的注入风险;
  • 自动转义特殊字符:对于字符串等可能包含特殊符号的参数类型,数据库驱动会自动对单引号、分号、注释符等SQL注入常用字符进行转义处理(如将单引号转换为数据库可识别的转义形式),确保这些字符仅作为数据的一部分被处理,而非用于篡改SQL结构。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/9 15:05:35

pgconf_asia_2017_logical_replication_us_20171204-1

Logical Replication Internals Agenda What is Logical Replication?Let’s try!ArchitectureRestrictionsTrouble shooting What is Logical Replication? What is Logical Replication? Is PostgreSQL 10 new featuresReplicate per tableReplicate per transaction…

作者头像 李华
网站建设 2026/8/7 14:15:47

leetcode 762. 二进制表示中质数个计算置位

Problem: 762. 二进制表示中质数个计算置位 解题过程 log2计算二进制长度&#xff0c;然后统计1个数&#xff0c;查看集合是否是素数&#xff0c;计算是否是素数&#xff0c;若是则放入集合 Code class Solution { public:int countPrimeSetBits(int left, int right) {int le…

作者头像 李华
网站建设 2026/8/10 3:20:44

为啥yyyy-MM-dd HH:mm:ss的MM和HH设计为大写

yyyy-MM-dd HH:mm:ss 中的大写 MM 和 HH 是 Java 日期格式化中的约定&#xff0c;原因如下&#xff1a; 1. 区分不同的时间单位&#xff08;主要目的&#xff09; 月份 (Month) vs 分钟 (Minute) // 大写的 M 表示月份 (Month) // 小写的 m 表示分钟 (minute)SimpleDateForm…

作者头像 李华
网站建设 2026/8/9 2:24:41

基于java的SpringBoot/SSM+Vue+uniapp的身体健康管理系统的详细设计和实现(源码+lw+部署文档+讲解等)

文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言 &#x1f31e;博主介绍&#xff1a;✌全网粉丝15W,CSDN特邀作者、211毕业、高级全…

作者头像 李华
网站建设 2026/8/9 4:02:38

走gateway接口偶现返回Internal Server Error

1.现象&#xff1a;业务反馈接口返回Internal Server Error经排查sringcloudgateway中有这个日志报错&#xff1a;18:49:40.317 [reactor-http-epoll-4] ERROR org.springframework.core.log.CompositeLog.error(CompositeLog.java:102) [traceId: ] - [009a73dc-74671082] 500…

作者头像 李华
网站建设 2026/8/7 22:19:09

35 岁以后的运维工程师该何去何从?出路究竟在何方?

运维工程师的出路在哪里&#xff0c;尤其是 35 岁以后&#xff1f; 最近在某乎看到个问题&#xff0c;“运维的出路在哪里&#xff0c;特别是35以后?”&#xff0c; 网友 1&#xff1a;孩子快跑 打好基础&#xff0c;网络&#xff0c;安全&#xff0c;数据库&#xff0c;服务…

作者头像 李华