引言:学习目标
| JDBC 概述 | 掌握 |
| 使用 JDBC 完成添加操作 | 掌握 |
| 使用 JDBC 完成更新和删除 | 掌握 |
| DBUtils 的简单封装 | 掌握 |
| 使用 JDBC 完成查询 | 掌握 |
| 使用 JDBC 完成分页查询 | 掌握 |
| 常用接口详解 | 掌握 |
| JDBC 批处理 | 掌握 |
| SQL 注入问题 | 掌握 |
| 事务处理解决转账问题 | 掌握 |
| 连接池 | 掌握 |
| 使用反射对 DBUtils 再次的封装 | 掌握 |
| BaseDAO 的封装 | 掌握 |
一、JDBC 概述
1.1 为什么要学习 JDBC?
如用户提交表单时,JDBC 能自动将数据存入数据库,而 Navicat 只能每次手动操作,无法对接程序实现自动化操作。
JDBC 的核心价值在于实现 Java 程序与数据库的无缝对接,支持自动化数据处理。
1.2 什么是 JDBC?
JDBC(Java DataBase Connectivity)即 Java 数据库连接,是用 Java 语言操作数据库的规范。传统操作数据库需在 Navicat 等工具中执行 SQL 语句,而 JDBC 允许通过 Java 代码向数据库发送 SQL 指令,实现程序驱动的数据交互。

1.3 JDBC 的原理
- SUN 公司提供 JDBC 核心接口(规范),定义数据库访问标准。
- 各数据库厂商(如 MySQL、Oracle)遵循该规范,提供实现接口的驱动程序(Driver)。
- Java 程序通过 JDBC 接口调用厂商驱动,间接操作数据库,实现跨数据库兼容。
- 第三方也可能提供非开源免费的数据库驱动。

1.4 JDBC 的 API
JDBC 核心功能可概括为三点:与数据库建立连接、发送 SQL 语句、处理查询结果。核心 API 组件如下:

- DriverManager:管理 JDBC 驱动,负责获取数据库连接。
- Connection:代表数据库物理连接,负责数据传输。
- Statement:由 Connection 创建,用于发送执行 SQL 语句。
- ResultSet:保存 Statement 执行查询后的结果集。
二、常用接口详解
2.1 DriverManager
用于管理 JDBC 驱动的服务类,核心方法:
java
public static Connection getConnection(String url, String user, String password) throws SQLException
功能:获取指定 URL 对应数据库的连接,参数分别为数据库地址、用户名、密码。
2.2 Connection
数据库连接对象,每个 Connection 对应一个物理连接会话。常用方法:
- 创建 SQL 执行对象:
- Statement createStatement():创建普通 Statement 对象。
- PreparedStatement prepareStatement(String sql):创建预编译 Statement 对象(防 SQL 注入)。
- CallableStatement prepareCall(String sql):创建调用存储过程的 Statement 对象。
- 事务控制方法:
- setAutoCommit(boolean autoCommit):关闭自动提交(开启事务)。
- commit():提交事务。
- rollback():回滚事务。
- setSavepoint():创建事务保存点。
- setTransactionIsolation(int level):设置事务隔离级别。
2.3 Statement
执行 SQL 语句的工具接口,支持 DDL、DML、查询语句,常用方法:
- ResultSet executeQuery(String sql):执行查询语句,返回结果集(仅支持 SELECT)。
- int executeUpdate(String sql):执行 DML(INSERT/UPDATE/DELETE)返回受影响行数;执行 DDL 返回 0。
- boolean execute(String sql):执行任意 SQL,返回 true 表示结果为 ResultSet,false 表示受影响行数或无结果。
2.4 PreparedStatement
Statement 的子接口,支持预编译 SQL(含参数占位符?),优势:
- 预编译后重复执行无需重新编译,提升效率。
- 通过参数绑定避免 SQL 注入,更安全。核心方法:
java
void setXxx(int parameterIndex, Xxx value)
根据参数类型(如 String、int)选择对应方法,按索引为 SQL 占位符赋值。
2.5 ResultSet
查询结果集对象,通过游标(指针)访问数据,常用方法:
- 游标移动:
- boolean next():游标向下移动一行,返回是否指向有效数据。
- boolean absolute(int row):移动到指定行(负数表示倒数)。
- boolean last():移动到最后一行。
- 数据获取:
- getInt(int colIndex)/getInt(String colLabel):通过列索引或列名获取 int 类型值。
- getString(int colIndex)/getString(String colLabel):获取字符串类型值。
- getObject(String colLabel):获取任意类型值。
- 资源释放:close():关闭结果集,释放资源。
三、JDBC 操作数据库的步骤
3.1 加载驱动
通过Class.forName()加载驱动类,常用数据库驱动加载语句:
java
// Oracle
Class.forName("oracle.JDBC.driver.OracleDriver");
// SQL Server
Class.forName("com.microsoft.JDBC.sqlserver.SQLServerDriver");
// DB2
Class.forName("com.ibm.db2.JDBC.app.DB2Driver");
// MySQL 5
Class.forName("com.mysql.jdbc.Driver");
// MySQL 8
Class.forName("com.mysql.cj.jdbc.Driver");
3.2 创建数据库连接
调用DriverManager.getConnection()获取连接:
java
Connection conn = null;
String url = "jdbc:mysql://localhost:3306/jdbc_db?useUnicode=true&useSSL=false&characterEncoding=UTF8";
String user = "root";
String password = "123456";
conn = DriverManager.getConnection(url, user, password);
3.3 创建 Statement 并发送命令
根据需求创建 Statement/PreparedStatement 对象,执行 SQL:
java
// 普通Statement
Statement stmt = conn.createStatement();
int affectedRows = stmt.executeUpdate("INSERT INTO student VALUES(1,'小刚',32,'男','武汉')");
// 预编译PreparedStatement
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO student VALUES(?, ?, ?, ?, ?)");
pstmt.setInt(1, 2);
pstmt.setString(2, "小明");
pstmt.setInt(3, 23);
pstmt.setString(4, "女");
pstmt.setString(5, "武汉");
pstmt.executeUpdate();
3.4 处理 ResultSet 结果
通过循环遍历结果集,提取数据:
java
ResultSet rs = stmt.executeQuery("SELECT * FROM student");
while (rs.next()) {
int id = rs.getInt(1); // 列索引(从1开始)
String name = rs.getString("name"); // 列名
int age = rs.getInt("age");
System.out.println(id + " " + name + " " + age);
}
3.5 关闭数据库资源
必须按「ResultSet → Statement → Connection」的顺序关闭(与创建顺序相反),避免资源泄漏:
java
rs.close();
stmt.close();
conn.close();
四、准备工作
4.1 创建数据库并创建 student 表

4.2 创建项目
使用 IDEA 创建 Java 项目(JDK 建议 1.8+),无需额外框架,保持基础 Java 工程结构。
4.3 创建 lib 目录并引入 MySQL 驱动包

4.4 把 lib 包引入项目环境

五、使用 JDBC 完成添加操作
5.1 案例实现
java
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class AddTest {
// 驱动器路径
private static final String DRIVER = "com.mysql.jdbc.Driver";
// 连接数据库地址
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
// 数据库用户名
private static final String USER_NAME = "root";
// 数据库密码
private static final String USER_PASSWORD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 加载JDBC访问mysql的驱动
Class.forName(DRIVER);
// 建立和数据库的连接
Connection conn = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
// 创建SQL命令发送器
Statement stmt = conn.createStatement();
// 使用SQL命令发送器发送SQL命令并得到结果
String sql = "insert into student values(1,'小刚',32,'男','湖北省武汉市')";
int n = stmt.executeUpdate(sql);
// 处理结果
if (n > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
// 关闭数据库资源
stmt.close();
conn.close();
}
}
5.2 URL 详解
URL 格式:协议://服务器主机:端口/服务器路径?查询参数
- 协议:jdbc:mysql:(固定格式)。
- 服务器主机:数据库所在地址(本地为localhost)。
- 端口:MySQL 默认 3306。
- 服务器路径:数据库名(如jdbc_db)。
- 查询参数:编码格式、SSL 设置等(如useSSL=false&characterEncoding=UTF8)。
六、使用 JDBC 完成更新和删除
6.1 案例原理
更新和删除操作流程与添加一致,核心区别在于 SQL 语句,均使用executeUpdate()方法执行。
6.2 修改操作代码
java
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class UpdateTest {
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
private static final String USER_NAME = "root";
private static final String USER_PASSWORD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
Statement stat = conn.createStatement();
// 编写更新SQL
String sql = "update student set name='小明',age=23,sex='女',address='武汉' where id=1";
int res = stat.executeUpdate(sql);
if (res > 0) {
System.out.println("修改成功");
} else {
System.out.println("处理失败");
}
stat.close();
conn.close();
}
}
6.3 删除操作代码
java
package com.hg.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class DeleteTest {
private static final String DRIVER = "com.mysql.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db?useSSL=false&characterEncoding=UTF8";
private static final String USER_NAME = "root";
private static final String USER_PASSWORD = "1111";
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName(DRIVER);
Connection conn = DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
Statement stat = conn.createStatement();
// 编写删除SQL
String sql = "delete from student where id=1";
int res = stat.executeUpdate(sql);
if (res > 0) {
System.out.println("删除成功");
} else {
System.out.println("删除失败");
}
stat.close();
conn.close();
}
}
七、DBUtils 的简单封装
7.1 为什么要封装
添加、更新、删除操作中,加载驱动、创建连接、关闭资源等代码重复度极高,封装为工具类可减少冗余,提高开发效率。
7.2 创建 db.properties 配置文件
在src目录下创建配置文件,存储数据库连接信息:
properties
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_db
username=root
password=1111
7.3 实现 DBUtils 工具类
java
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBUtils {
// 配置变量
private static String DRIVER;
private static String URL;
private static String USER_NAME;
private static String USER_PASSWORD;
// 静态代码块:加载配置+注册驱动
static {
try {
InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(is);
DRIVER = prop.getProperty("driverClass");
URL = prop.getProperty("url");
USER_NAME = prop.getProperty("username");
USER_PASSWORD = prop.getProperty("password");
// 注册驱动
Class.forName(DRIVER);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取数据库连接
public static Connection getConn() {
try {
return DriverManager.getConnection(URL, USER_NAME, USER_PASSWORD);
} catch (SQLException e) {
e.printStackTrace();
System.out.println("创建连接对象异常");
}
return null;
}
// 释放资源(支持Connection、Statement、ResultSet)
public static void close(AutoCloseable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.4 改造原有代码
以添加操作为例,改造后代码:
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Test01Add {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection conn = DBUtils.getConn();
Statement stmt = conn.createStatement();
String sql = "insert into student values(1,'小刚',32,'男','湖北省武汉市')";
int n = stmt.executeUpdate(sql);
if (n > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
// 调用工具类关闭资源
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
八、使用 JDBC 完成查询
8.1 环境准备(批量添加测试数据)
先批量插入 20 条测试数据,方便查询验证:
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;
public class Test01Add20 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection conn = DBUtils.getConn();
Statement stmt = conn.createStatement();
Random random = new Random();
for (int i = 1; i <= 20; i++) {
Integer id = i;
String name = "小明" + i;
int age = random.nextInt(100);
String sex = random.nextBoolean() ? "男" : "女";
String address = "武汉" + i;
String sql = "insert into student values(" + i + ",'" + name + "'," + age + ",'" + sex + "','" + address + "')";
int n = stmt.executeUpdate(sql);
if (n > 0) {
System.out.println("添加成功");
} else {
System.out.println("添加失败");
}
}
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
8.2 查询案例实现
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test04Query {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection conn = DBUtils.getConn();
Statement stmt = conn.createStatement();
String sql = "select * from student";
ResultSet rs = stmt.executeQuery(sql);
// 遍历结果集
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
String sex = rs.getString(4);
String address = rs.getString(5);
System.out.println(id + " " + name + " " + age + " " + sex + " " + address);
}
// 关闭资源(ResultSet需先关闭)
DBUtils.close(rs);
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
九、使用 JDBC 完成分页查询
MySQL 分页依赖LIMIT关键字,语法:LIMIT 起始索引, 每页条数(起始索引从 0 开始)。
9.1 分页查询案例
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test05QueryForPage {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection conn = DBUtils.getConn();
Statement stmt = conn.createStatement();
int pageNum = 2; // 页码(查询第2页)
int pageSize = 5; // 每页显示5条
// 计算起始索引:(页码-1)*每页条数
String sql = "select * from student limit " + (pageNum – 1) * pageSize + "," + pageSize;
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
String sex = rs.getString(4);
String address = rs.getString(5);
System.out.println(id + " " + name + " " + age + " " + sex + " " + address);
}
DBUtils.close(rs);
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
十、SQL 注入问题
10.1 问题引入
10.1.1 准备测试环境
创建sys_user表并初始化数据:

| 1 | admin | 123456 |
| 2 | zhangsan | 123 |
10.1.2 存在注入风险的登录代码
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Test06Login {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection conn = DBUtils.getConn();
Statement stmt = conn.createStatement();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 拼接SQL(存在注入风险)
String sql = "select * from sys_user where username='" + username + "' and password='" + password + "'";
ResultSet rs = stmt.executeQuery(sql);
System.out.println(sql);
if (rs.next()) {
System.out.println("登陆成功");
int id = rs.getInt(1);
String name = rs.getString(2);
String pwd = rs.getString(3);
System.out.println(id + " " + name + " " + pwd);
}
DBUtils.close(rs);
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
10.1.3 测试注入攻击
输入以下内容,无需正确账号密码即可登录:
- 用户名:aaa
- 密码:aaa'or'1'='1
- 拼接后的 SQL:select * from sys_user where username='aaa' and password='aaa'or'1'='1'
- 原理:'1'='1恒成立,SQL 条件永远为真,绕过登录验证。
10.2 解决办法:使用 PreparedStatement
10.2.1 技术原理
- PreparedStatement 预编译 SQL,占位符?替代直接拼接参数。
- 参数通过setXxx()方法绑定,数据库会自动过滤特殊字符,避免注入。
- 预编译后重复执行效率更高。
10.2.2 改造后的登录代码
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class Test07Login {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
Connection conn = DBUtils.getConn();
// 使用占位符编写SQL
String sql = "select * from sys_user where username=? and password=? ";
// 创建PreparedStatement对象(预编译SQL)
PreparedStatement pstmt = conn.prepareStatement(sql);
// 绑定参数(索引从1开始)
pstmt.setString(1, username);
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登陆成功");
int id = rs.getInt(1);
String name = rs.getString(2);
String pwd = rs.getString(3);
System.out.println(id + " " + name + " " + pwd);
} else {
System.out.println("登录失败");
}
DBUtils.close(rs);
DBUtils.close(pstmt);
DBUtils.close(conn);
}
}
10.2.3 测试验证
再次输入注入式密码aaa'or'1'='1,会被当作普通字符串处理,无法绕过验证,SQL 注入被防御。
十一、事务处理解决转账问题
11.1 什么是事务
事务是数据库操作的最小工作单元,是一系列不可分割的操作集合,要么全部执行,要么全部不执行(避免数据不一致)。
11.2 事务的四大特性(ACID)
- 原子性(Atomicity):事务中操作要么全成功,要么全回滚。
- 一致性(Consistency):事务执行后,数据库从一个一致状态切换到另一个一致状态。
- 隔离性(Isolation):并发事务之间互不干扰。
- 持久性(Durability):事务提交后,数据修改永久有效。
11.3 需求描述
实现转账功能:张三向李四转账 1000 元,要求扣减张三余额和增加李四余额两个操作要么全成功,要么全失败。
11.4 准备工作(创建 account 表)
sql
CREATE TABLE account (
aid INT PRIMARY KEY AUTO_INCREMENT,
aname VARCHAR(30) NOT NULL,
amount DECIMAL(10,2) NOT NULL
);
初始化数据:
| 1 | 张三 | 10000.00 |
| 2 | 李四 | 10000.00 |
11.5 代码实现
java
package com.hg.jdbc;
import com.hg.utils.DBUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class Test08Transaction {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
conn = DBUtils.getConn();
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
// 转账SQL:张三扣1000,李四加1000
String sql1 = "update account set amount = amount-1000 where aid=1";
String sql2 = "update account set amount = amount+1000 where aid=2";
stmt = conn.createStatement();
stmt.executeUpdate(sql1);
// 模拟异常(测试事务回滚):int i = 1/0;
stmt.executeUpdate(sql2);
// 无异常则提交事务
conn.commit();
System.out.println("转账成功");
} catch (Exception e) {
e.printStackTrace();
// 出现异常则回滚事务
try {
if (conn != null) {
conn.rollback();
System.out.println("转账失败,事务回滚");
}
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
// 关闭资源
DBUtils.close(stmt);
DBUtils.close(conn);
}
}
}
11.6 测试验证
- 正常情况:无异常时,事务提交,张三余额 9000,李四 11000。
- 异常情况:在sql1和sql2之间添加int i = 1/0;模拟异常,事务回滚,两人余额保持初始值 10000。
十二、BaseDAO 的封装(难点)
12.1 概述
ORM(对象关系映射)框架(如 MyBatis、Hibernate)可简化 JDBC 开发,BaseDAO 通过反射机制模拟 ORM 核心功能,实现通用的 CRUD 操作,减少重复代码。
12.2 BaseDAO 实现代码
java
import com.hg.utils.DBUtils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
public class BaseDao {
/**
* 通用查询(返回多条数据)
* @param sql SQL指令
* @param clss 映射的实体类
* @param params SQL占位符参数
* @param <T> 实体类泛型
* @return 实体类列表
*/
public <T> List<T> selectList(String sql, Class<T> clss, Object… params) {
List<T> data = new ArrayList<>();
Connection conn = DBUtils.getConn();
PreparedStatement prep = null;
ResultSet rs = null;
try {
prep = conn.prepareStatement(sql);
// 绑定参数
for (int i = 0; i < params.length; i++) {
prep.setObject(i + 1, params[i]);
}
// 执行查询
rs = prep.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
// 遍历结果集,通过反射封装实体类
while (rs.next()) {
T t = clss.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取列别名(需与实体类属性名一致)
String columnLabel = metaData.getColumnLabel(i + 1);
// 获取列值
Object columnValue = rs.getObject(columnLabel);
// 反射设置实体类属性值
Field field = clss.getDeclaredField(columnLabel);
field.setAccessible(true); // 允许访问私有属性
field.set(t, columnValue);
}
data.add(t);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.close(rs);
DBUtils.close(prep);
DBUtils.close(conn);
}
return data;
}
/**
* 通用查询(返回单条数据)
* @param sql SQL指令
* @param clss 映射的实体类
* @param params SQL占位符参数
* @param <T> 实体类泛型
* @return 单个实体对象
*/
public <T> T selectOne(String sql, Class<T> clss, Object… params) {
List<T> list = selectList(sql, clss, params);
return list.isEmpty() ? null : list.get(0);
}
/**
* 通用更新操作(INSERT/UPDATE/DELETE)
* @param sql SQL指令
* @param params SQL占位符参数
* @return 是否执行成功
*/
public boolean update(String sql, Object… params) {
Connection conn = DBUtils.getConn();
PreparedStatement prep = null;
try {
prep = conn.prepareStatement(sql);
// 绑定参数
for (int i = 0; i < params.length; i++) {
prep.setObject(i + 1, params[i]);
}
// 执行更新,返回受影响行数
int affectedRows = prep.executeUpdate();
return affectedRows >= 1;
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
DBUtils.close(prep);
DBUtils.close(conn);
}
return false;
}
}
12.3 BaseDAO 的使用
假设存在User实体类(属性与数据库表字段对应),创建 DAO 实现类:
java
package com.hg.dao.impl;
import com.hg.dao.BaseDao;
import com.hg.pojo.User;
public class UserDao extends BaseDao {
/**
* 根据用户名和密码查询用户
*/
public User selectUser(String username, String password) {
String sql = "select id, username, password, realname, role from user where username = ? and password=?";
return super.selectOne(sql, User.class, username, password);
}
/**
* 更新用户删除状态
*/
public void updateState(String id, Integer deleted) {
String sql = "update user set deleted = ?, deleted_time = now() where id = ?";
super.update(sql, deleted, id);
}
}
12.4 分页封装
12.4.1 创建分页结果封装类 PageInfo<T>
java
package com.hg.common;
import java.util.List;
public class PageInfo<T> {
private List<T> data; // 分页数据
private Long count; // 总数据条数
// getter/setter
public List<T> getData() { return data; }
public void setData(List<T> data) { this.data = data; }
public Long getCount() { return count; }
public void setCount(Long count) { this.count = count; }
}
12.4.2 扩展 BaseDAO 添加分页方法
java
/**
* 通用分页查询
* @param sql 基础SQL(不含LIMIT)
* @param cls 实体类
* @param page 页码
* @param limit 每页条数
* @param <T> 泛型
* @return 分页结果
*/
protected <T> PageInfo<T> selectPage(String sql, Class<T> cls, String page, String limit) {
// 计算起始索引
Integer pageNo = 0;
if (Integer.parseInt(page) > 0) {
pageNo = (Integer.parseInt(page) – 1) * Integer.parseInt(limit);
}
// 分页SQL
String listSql = sql + " limit " + pageNo + "," + limit;
// 查询分页数据
List<T> data = selectList(listSql, cls);
// 查询总条数
Long count = getCount(sql);
// 封装分页结果
PageInfo<T> pageInfo = new PageInfo<>();
pageInfo.setData(data);
pageInfo.setCount(count);
return pageInfo;
}
/**
* 查询总数据条数
* @param sql 基础SQL
* @return 总条数
*/
private Long getCount(String sql) {
String countSql = "select count(1) from (" + sql + ") rs";
Connection conn = DBUtils.getConn();
PreparedStatement prep = null;
ResultSet rs = null;
try {
prep = conn.prepareStatement(countSql);
rs = prep.executeQuery();
rs.next();
return rs.getLong(1);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
DBUtils.close(rs);
DBUtils.close(prep);
DBUtils.close(conn);
}
return 0L;
}
十三、数据库连接池
13.1 什么是连接池
连接池是管理数据库连接的缓冲池技术,初始化时创建一定数量的连接并存储在内存中,程序需要时从池中获取,使用完毕后归还(而非关闭),实现连接复用。
13.2 为什么使用连接池
- 连接的创建和销毁消耗资源,复用连接可提升系统性能。
- 控制最大连接数,避免数据库连接耗尽。
- 统一管理连接,简化资源释放逻辑。
13.3 连接池的工作原理

13.4 自定义连接池
java
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Queue;
public class MyDataSource {
// 存储连接的队列
private static final Queue<Connection> connectionQueue = new LinkedList<>();
// 数据库配置
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_db";
private static final String USER = "root";
private static final String PASSWORD = "1111";
// 初始连接数
private int initSize;
/**
* 构造方法:初始化连接池
* @param initSize 初始连接数
*/
public MyDataSource(int initSize) {
this.initSize = initSize;
try {
Class.forName("com.mysql.jdbc.Driver");
// 创建初始连接并放入队列
for (int i = 0; i < initSize; i++) {
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
connectionQueue.offer(conn);
}
System.out.println("连接池初始化完成,初始连接数:" + this.initSize);
} catch (ClassNotFoundException | SQLException e) {
throw new RuntimeException("连接池初始化失败", e);
}
}
/**
* 获取连接
*/
public Connection getConnection() {
Connection conn = connectionQueue.poll();
if (conn == null) {
System.out.println("当前无可用连接!");
return null;
}
System.out.println("获取连接成功,剩余可用连接数:" + connectionQueue.size());
return conn;
}
/**
* 归还连接
*/
public void releaseConnection(Connection conn) {
if (conn == null) {
return;
}
connectionQueue.offer(conn);
System.out.println("连接归还成功,剩余可用连接数:" + connectionQueue.size());
}
}
测试自定义连接池
java
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class MyDataSourceTest {
public static void main(String[] args) throws Exception {
// 1. 创建连接池(初始5个连接)
MyDataSource pool = new MyDataSource(5);
// 2. 获取连接
Connection conn1 = pool.getConnection();
// 3. 执行查询
String sql = "SELECT * FROM student";
PreparedStatement pst = conn1.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + "\\t" + rs.getString("name") + "\\t" + rs.getInt("age"));
}
// 4. 归还连接
pool.releaseConnection(conn1);
// 5. 再次获取连接
Connection conn2 = pool.getConnection();
pool.releaseConnection(conn2);
}
}
13.5 主流连接池介绍
| C3P0 | 老牌连接池,功能完善但性能一般,逐渐淘汰 |
| DBCP | Apache 开源,稳定但配置复杂 |
| Druid | 阿里开源,性能优异,支持监控、防 SQL 注入,推荐使用 |
| Hikari | 轻量级,性能极致,SpringBoot 默认集成 |
13.6 Druid 连接池实战
引入 Druid 依赖(Maven)
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.15</version>
</dependency>
Druid 使用案例
java
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class DruidDataSourceTest {
// 初始化Druid连接池
private static DruidDataSource initDruidDataSource() {
DruidDataSource ds = new DruidDataSource();
// 基础配置
ds.setUrl("jdbc:mysql://localhost:3306/jdbc_db");
ds.setUsername("root");
ds.setPassword("1111");
// 连接池配置
ds.setInitialSize(2); // 初始连接数
ds.setMaxActive(5); // 最大活跃连接数
ds.setMinIdle(1); // 最小空闲连接数
return ds;
}
public static void main(String[] args) throws SQLException {
DruidDataSource ds = initDruidDataSource();
// 获取连接(Druid自动管理连接生命周期)
Connection conn1 = ds.getConnection();
System.out.println("获取连接1成功,剩余可用连接数:" + ds.getPoolingCount());
// 执行查询
String sql = "SELECT * FROM student";
PreparedStatement pst = conn1.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + "\\t" + rs.getString("name") + "\\t" + rs.getInt("age"));
}
// 归还连接(Druid重写close()方法,实际是归还连接)
conn1.close();
System.out.println("归还连接1,剩余可用连接数:" + ds.getPoolingCount());
// 再次获取连接
Connection conn2 = ds.getConnection();
System.out.println("获取连接2成功,剩余可用连接数:" + ds.getPoolingCount());
conn2.close();
}
}
13.7 改造 DBUtils 整合 Druid
java
import com.alibaba.druid.pool.DruidDataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class DBUtils {
private static String DRIVER;
private static String URL;
private static String USER_NAME;
private static String USER_PASSWORD;
private static DruidDataSource ds; // Druid连接池
static {
try {
// 加载配置文件
InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop = new Properties();
prop.load(is);
DRIVER = prop.getProperty("driverClass");
URL = prop.getProperty("url");
USER_NAME = prop.getProperty("username");
USER_PASSWORD = prop.getProperty("password");
// 初始化Druid连接池
Class.forName(DRIVER);
ds = new DruidDataSource();
ds.setUrl(URL);
ds.setUsername(USER_NAME);
ds.setPassword(USER_PASSWORD);
// 可选:配置连接池参数
ds.setInitialSize(2);
ds.setMaxActive(5);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接(从Druid池获取)
public static Connection getConn() {
try {
return ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("创建连接对象异常");
}
return null;
}
// 释放资源(Druid自动归还连接)
public static void close(AutoCloseable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
网硕互联帮助中心






评论前必须登录!
注册