目录
一、JDBC 基础
1.1 什么是 JDBC?
1.2 JDBC 核心组件
1.3 JDBC 操作基本步骤
步骤 1:准备环境
步骤 2:编写 JDBC 代码(查询示例)
关键说明:
1.4 SQL 注入深度解析
(1)什么是 SQL 注入?
(2)SQL 注入案例(登录场景)
(3)如何防止 SQL 注入?
1.5 JDBCUtil 工具类开发
(1)工具类设计思路
(2)实现步骤
步骤 1:创建配置文件(src/main/resources/jdbc.properties)
步骤 2:编写 JDBCUtil 工具类
(3)工具类使用示例
二、连接池技术
2.1 为什么需要连接池?
2.2 连接池工作原理
2.3 连接池核心参数
2.4 常用连接池
HikariCP
步骤 1:引入依赖(Maven)
步骤 2:使用 HikariCP 连接池
Druid(阿里)
(1)引入依赖(Maven)
(2)编写 Druid 工具类(基于配置文件)
步骤 1:创建 Druid 配置文件(src/main/resources/druid.properties)
步骤 2:Druid 工具类实现
(3)Druid 监控功能使用
2.5 常用连接池对比
三、总结与实践建议
在全栈开发中,Java 作为后端开发的核心语言,经常需要与数据库进行交互。JDBC(Java Database Connectivity)是 Java 操作数据库的基础,而连接池则是优化数据库连接性能的关键技术。本文将详细讲解 JDBC 的核心知识与连接池的使用,帮助你掌握 Java 与数据库交互的核心技能。
一、JDBC 基础
1.1 什么是 JDBC?
JDBC 是 Java 提供的一套用于操作关系型数据库的标准 API(应用程序编程接口),它定义了 Java 程序与数据库交互的规范,使得开发者可以通过统一的代码操作不同的数据库(如 MySQL、Oracle、SQL Server 等)。
简单来说,JDBC 就像一座 “桥梁”:一边连接 Java 程序,另一边连接数据库,屏蔽了不同数据库的底层差异(如 MySQL 和 Oracle 的通信协议不同),让开发者无需关注具体数据库的实现细节,只需使用统一的接口即可完成数据操作。
1.2 JDBC 核心组件
JDBC 的核心操作依赖于以下几个核心组件,它们共同构成了 Java 与数据库交互的完整流程:
Driver(驱动程序):由数据库厂商提供(如 MySQL 的com.mysql.cj.jdbc.Driver),负责 Java 程序与数据库的底层通信。没有驱动,Java 无法识别具体的数据库。驱动是 Java 程序与数据库通信的 “翻译官”。不同数据库的通信协议不同(如 MySQL 用 3306 端口,Oracle 用 1521 端口),驱动负责将 JDBC 的统一调用转换为数据库能识别的底层指令。JDBC 驱动分为 4 类,实际开发中最常用的是 “第 4 类驱动”(纯 Java 驱动,如 MySQL 的com.mysql.cj.jdbc.Driver),无需依赖本地库,直接通过网络与数据库通信。驱动类被加载时(如Class.forName("com.mysql.cj.jdbc.Driver")),会自动向DriverManager注册自身实例(通过静态代码块实现)。
Connection(连接):表示 Java 程序与数据库的连接通道,是所有数据库操作的基础。只有获取 Connection 后,才能执行后续的 SQL 操作。Connection是数据库连接的 “通道”,所有 SQL 操作必须基于Connection执行。一个Connection对应一个数据库会话(Session),会话中可以开启事务、执行 SQL 等。其核心方法有createStatement()(创建Statement对象)、prepareStatement(String sql)(创建PreparedStatement对象)、setAutoCommit(boolean autoCommit)(设置事务是否自动提交)、close()(关闭连接)等。
Statement 与 PreparedStatement:用于向数据库发送 SQL 语句。Statement直接发送 SQL 字符串到数据库执行,适合静态 SQL(无需参数的 SQL),但存在 SQL 注入问题。PreparedStatement是Statement的子类,它能预编译 SQL,SQL 语句会先发送到数据库编译,参数通过?占位符传入,避免字符串拼接导致的 SQL 注入,且相同结构的 SQL(如仅参数不同的查询)可复用编译结果,性能更优。
ResultSet(结果集):存储 SQL 查询(如SELECT)的返回结果,可通过游标遍历数据。它类似 “表格” 结构,包含行(记录)和列(字段)。通过next()方法移动游标(初始游标在第一行之前),next()返回true表示有下一行数据,可通过getXxx(列名)或getXxx(列索引)获取字段值(索引从 1 开始)。
DriverManager(驱动管理器):管理数据库连接驱动,负责根据数据库 URL 获取数据库连接。其核心方法有registerDriver(Driver driver)(注册驱动)、getConnection(String url, String user, String password)(获取数据库连接)等,会遍历已注册的驱动,调用驱动的connect()方法尝试创建连接。
1.3 JDBC 操作基本步骤
使用 JDBC 操作数据库的流程可概括为 “获取连接→执行 SQL→处理结果→释放资源”,以下是详细步骤及代码示例(以 MySQL 数据库为例)。
步骤 1:准备环境
安装数据库(如 MySQL 8.0)并创建测试表,例如: CREATE DATABASE jdbc_demo;
USE jdbc_demo;
CREATE TABLE user (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
age INT
);
INSERT INTO user (username, age) VALUES ('张三', 20), ('李四', 22); 引入数据库驱动(以 Maven 项目为例,在pom.xml中添加依赖):
步骤 2:编写 JDBC 代码(查询示例)
import java.sql.*;
public class JDBCDemo {
public static void main(String[] args) {
// 数据库连接信息(根据实际情况修改)
String url = "jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=UTC";
String username = "root"; // 数据库用户名
String password = "123456"; // 数据库密码
// 声明核心对象(需在try外定义,以便在finally中关闭)
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 1. 注册驱动(MySQL 5.1后可省略,驱动会自动注册)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取数据库连接
conn = DriverManager.getConnection(url, username, password);
// 3. 创建SQL执行对象(PreparedStatement可防止SQL注入)
String sql = "SELECT id, username, age FROM user WHERE age > ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 18); // 给SQL中的“?”赋值
// 4. 执行SQL(查询用executeQuery,增删改用executeUpdate)
rs = pstmt.executeQuery();
// 5. 处理结果集
while (rs.next()) { // 遍历结果集
int id = rs.getInt("id");
String name = rs.getString("username");
int age = rs.getInt("age");
System.out.println("id: " + id + ", 姓名: " + name + ", 年龄: " + age);
}
} catch (ClassNotFoundException e) {
System.out.println("驱动类未找到,请检查依赖");
e.printStackTrace();
} catch (SQLException e) {
System.out.println("数据库操作失败");
e.printStackTrace();
} finally {
// 6. 释放资源(按ResultSet→Statement→Connection的顺序关闭,避免内存泄漏)
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
关键说明:
驱动注册:MySQL 5.1 及以上版本的驱动会自动注册,Class.forName()可省略,但建议保留(明确指定驱动)。
SQL 注入防护:PreparedStatement通过预编译 SQL,将参数与 SQL 语句分离,避免Statement的拼接 SQL 风险(如SELECT * FROM user WHERE username = '"+name+"',若 name 为' OR '1'='1会查询所有数据)。
资源释放:数据库连接是稀缺资源,必须确保关闭(可使用try-with-resources自动关闭,简化代码):
// try-with-resources写法(资源会自动关闭,无需手动finally)
try (Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// 执行SQL和处理结果
} catch (SQLException e) {
e.printStackTrace();
}
1.4 SQL 注入深度解析
(1)什么是 SQL 注入?
SQL 注入是黑客通过输入特殊字符串,篡改原有 SQL 语句的逻辑,达到非法操作数据库的目的。它是 Web 安全中最常见的攻击方式之一,而Statement的字符串拼接是主要诱因。
(2)SQL 注入案例(登录场景)
假设登录逻辑如下(使用Statement):
String username = "黑客输入的' OR '1'='1";
String sql = "SELECT * FROM user WHERE username = '" + username + "'";
// 最终SQL变为:SELECT * FROM user WHERE username = '' OR '1'='1',会查询所有数据
黑客输入特定内容后,会导致 SQL 逻辑被篡改,可能造成数据泄露等严重后果。
(3)如何防止 SQL 注入?
核心方案:使用PreparedStatement的?占位符传递参数,避免字符串拼接。
辅助方案:输入验证(限制用户输入的字符)、最小权限原则(数据库账号仅授予必要权限)。
改用PreparedStatement后的安全代码能有效避免 SQL 注入。
1.5 JDBCUtil 工具类开发
在实际开发中,JDBC 的连接获取、资源释放等代码会被频繁重复使用。我们可以封装一个JDBCUtil工具类,简化代码编写。
(1)工具类设计思路
配置分离:数据库连接信息(URL、用户名、密码)放在配置文件中(如jdbc.properties),避免硬编码。
封装核心方法:提供获取连接、释放资源、执行增删改查的通用方法。
线程安全:工具类方法设计为静态方法,避免创建实例。
(2)实现步骤
步骤 1:创建配置文件(src/main/resources/jdbc.properties)
# MySQL连接配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
步骤 2:编写 JDBCUtil 工具类
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtil {
// 静态变量存储配置信息
private static String driver;
private static String url;
private static String username;
private static String password;
// 静态代码块:加载配置文件并初始化驱动
static {
try {
// 读取配置文件
Properties props = new Properties();
// 通过类加载器获取资源输入流
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
props.load(is);
// 读取配置项
driver = props.getProperty("jdbc.driver");
url = props.getProperty("jdbc.url");
username = props.getProperty("jdbc.username");
password = props.getProperty("jdbc.password");
// 注册驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException("JDBC工具类初始化失败", e);
}
}
/**
* 获取数据库连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
/**
* 释放资源(适用于查询操作:ResultSet + Statement + Connection)
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 释放资源(适用于增删改操作:Statement + Connection,无ResultSet)
*/
public static void close(Statement stmt, Connection conn) {
close(null, stmt, conn);
}
/**
* 通用增删改方法(INSERT/UPDATE/DELETE)
* @param sql SQL语句(带?占位符)
* @param params 参数数组(与?顺序对应)
* @return 影响的行数
*/
public static int executeUpdate(String sql, Object... params) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
// 为?赋值(参数类型可能是String、Integer等,需判断类型)
if (params != null) {
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]); // setObject可接收任意类型
}
}
return pstmt.executeUpdate(); // 执行增删改,返回影响行数
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("SQL执行失败", e);
} finally {
close(pstmt, conn);
}
}
}
(3)工具类使用示例
public class JDBCUtilDemo {
public static void main(String[] args) {
// 1. 新增数据(使用工具类的executeUpdate)
String insertSql = "INSERT INTO user(username, age) VALUES (?, ?)";
int insertRows = JDBCUtil.executeUpdate(insertSql, "赵六", 28);
System.out.println("新增行数:" + insertRows);
// 2. 修改数据
String updateSql = "UPDATE user SET age = ? WHERE username = ?";
int updateRows = JDBCUtil.executeUpdate(updateSql, 29, "赵六");
System.out.println("修改行数:" + updateRows);
// 3. 查询数据(使用工具类获取连接)
String selectSql = "SELECT username, age FROM user WHERE username = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtil.getConnection();
pstmt = conn.prepareStatement(selectSql);
pstmt.setString(1, "赵六");
rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("查询结果:" + rs.getString("username") + "," + rs.getInt("age"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.close(rs, pstmt, conn);
}
}
}
工具类能减少重复代码,便于维护,提高安全性。
二、连接池技术
2.1 为什么需要连接池?
传统 JDBC 操作中,每次执行 SQL 都需要创建连接和关闭连接。但连接的创建和关闭是非常消耗资源的操作,如果频繁操作数据库(如高并发场景),会导致系统响应缓慢、数据库压力过大等问题。
连接池的核心思想:提前创建一批连接,存放在 “池” 中,当程序需要连接时直接从池里取,使用完后放回池里(而非关闭),实现连接的复用。
2.2 连接池工作原理
初始化:连接池启动时,创建一定数量的初始连接(如 5 个),并保持与数据库的连接状态。
获取连接:程序请求连接时,若池中有空闲连接,直接返回;若没有空闲连接且未达到最大连接数,创建新连接;若已达最大连接数,等待其他程序释放连接(超时则报错)。
释放连接:程序使用完连接后,将连接放回池(标记为 “空闲”),而非关闭物理连接。
动态管理:连接池会定期检测无效连接(如超时未使用的连接)并回收,避免资源浪费。
2.3 连接池核心参数
不同连接池的参数名称可能不同,但核心参数一致:
初始连接数:连接池启动时创建的连接数量(如 5)。
最大连接数:连接池允许创建的最大连接数量(如 20),超过则等待。
最大等待时间:当没有空闲连接且已达最大连接数时,程序等待连接的最长时间(如 3000 毫秒),超时则抛出异常。
空闲连接超时时间:连接在池中空闲超过该时间(如 60000 毫秒),会被自动关闭(避免长期闲置的无效连接)。
2.4 常用连接池
HikariCP
HikariCP 是目前性能最优的连接池(Spring Boot 默认集成),以轻量、高效、启动快著称。
步骤 1:引入依赖(Maven)
步骤 2:使用 HikariCP 连接池
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class HikariDemo {
public static void main(String[] args) {
// 1. 配置连接池参数
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=UTC");
config.setUsername("root");
config.setPassword("123456");
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 核心连接池参数(根据实际需求调整)
config.setInitializationFailTimeout(1000); // 初始化失败超时时间
config.setMinimumIdle(5); // 最小空闲连接数(保持5个空闲连接)
config.setMaximumPoolSize(10); // 最大连接数
config.setConnectionTimeout(3000); // 获取连接超时时间(3秒)
config.setIdleTimeout(60000); // 空闲连接超时时间(60秒)
// 2. 创建数据源(连接池的核心对象,代表连接池)
try (HikariDataSource dataSource = new HikariDataSource(config)) {
// 3. 从连接池获取连接(多次获取演示连接复用)
for (int i = 0; i < 3; i++) {
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement("SELECT username FROM user LIMIT 1")) {
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("第" + (i+1) + "次查询结果:" + rs.getString("username"));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
HikariDataSource是 HikariCP 的核心类,通过getConnection()从池获取连接,连接使用完后会自动 “归还” 到池。
Druid(阿里)
Druid 是阿里开源的连接池,除了连接管理功能,还提供强大的监控能力(如 SQL 执行耗时、连接池状态等),适合生产环境使用。
(1)引入依赖(Maven)
(2)编写 Druid 工具类(基于配置文件)
步骤 1:创建 Druid 配置文件(src/main/resources/druid.properties)
# 驱动类
druid.driverClassName=com.mysql.cj.jdbc.Driver
# 连接URL
druid.url=jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=UTC
# 账号密码
druid.username=root
druid.password=123456
# 初始连接数
druid.initialSize=5
# 最大连接数
druid.maxActive=10
# 最大等待时间(毫秒)
druid.maxWait=3000
# 最小空闲连接数
druid.minIdle=3
# 空闲连接超时时间(毫秒)
druid.minEvictableIdleTimeMillis=60000
# 开启监控统计(默认false)
druid.filters=stat
步骤 2:Druid 工具类实现
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.Statement;
import java.util.Properties;
public class DruidUtil {
// 数据源(连接池),全局唯一
private static DataSource dataSource;
static {
try {
// 加载配置文件
Properties props = new Properties();
InputStream is = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");
props.load(is);
// 创建Druid数据源
dataSource = DruidDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Druid连接池初始化失败");
}
}
// 获取连接
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
// 释放资源
public static void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close(); // 归还到连接池
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据源(用于监控)
public static DataSource getDataSource() {
return dataSource;
}
}
(3)Druid 监控功能使用
Druid 提供内置的监控页面,可查看连接池状态、SQL 执行情况等。通过添加 Servlet 和 Filter(Web 项目)配置,启动项目后访问相应地址即可查看监控数据。
2.5 常用连接池对比
HikariCP:性能最优(Spring Boot 默认),轻量、启动快,配置简单。
Druid:功能丰富(支持监控、SQL 拦截、防 SQL 注入等),适合需要监控和扩展的场景。
C3P0/DBCP:老牌连接池,性能和功能不如 HikariCP 和 Druid,逐渐被替代。
实际开发中,推荐优先使用HikariCP(追求性能)或Druid(需要监控)。
三、总结与实践建议
JDBC 核心流程:注册驱动→获取连接→执行 SQL→处理结果→释放资源,重点掌握PreparedStatement的使用(防 SQL 注入)和资源自动关闭(try-with-resources)。连接池作用:通过连接复用解决传统 JDBC 的性能问题,核心是 “提前创建、复用连接、按需分配”。实践技巧:
连接池参数配置需根据业务场景调整(如高并发场景可适当提高最大连接数)。
避免长时间占用连接(如执行 SQL 后及时释放,不将连接暴露到方法外)。
开发中可结合 Spring 框架(如JdbcTemplate)进一步简化 JDBC 操作,底层仍依赖连接池。