JDBC 基础: API、SQL 注入问题,事务、连接池
一、JDBC
- JDBC 全称 Java DataBase Connectivity,是 Java 数据库连接规范,用于通过 Java 代码操作数据库
- JDBC 是一套接口规范,实现类由各数据库厂商提供
- 数据库驱动是厂商提供的实现类,使用 MySQL 需导入 mysql-connector-java 驱动 jar 包。
- JDBC 核心作用是建立 Java 程序与数据库的连接,实现数据增删改查操作
二、JDBC 快速入门开发步骤
- 创建数据库与数据表
- 创建数据库:
create database jdbcdemo; - 使用数据库:
use jdbcdemo; - 创建用户表:
- 创建数据库:
create table t_user( id int primary key auto_increment, username varchar(30), password varchar(30), email varchar(30) );- 插入测试数据:
insert into t_user values (null,'aaa','123','aaa@163.com'); insert into t_user values (null,'bbb','456','bb@163.com'); insert into t_user values (null,'ccc','789','ccc@163.com');- 编写 Java 代码实现查询
- 加载驱动
- 获取数据库连接
- 编写 SQL 语句
- 获取执行 SQL 的对象
- 执行 SQL 并处理结果集
- 释放资源
查询代码:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcSelectDemo { public static void main(String[] args) throws Exception { // 1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获取连接 String url = "jdbc:mysql:///jdbcdemo"; Connection conn = DriverManager.getConnection(url, "root", "root"); // 3.获取执行SQL对象 Statement stmt = conn.createStatement(); // 4.编写并执行SQL String sql = "select * from t_user"; ResultSet rs = stmt.executeQuery(sql); // 5.遍历结果集 while (rs.next()) { int id = rs.getInt("id"); String username = rs.getString("username"); String password = rs.getString("password"); String email = rs.getString("email"); System.out.println(id + " " + username + " " + password + " " + email); } // 6.释放资源 rs.close(); stmt.close(); conn.close(); } }三、API
(一)DriverManager 类
- 作用:管理 JDBC 驱动,获取数据库连接。
- 加载驱动
Class.forName("com.mysql.jdbc.Driver");- 避免直接使用 registerDriver 方法,防止驱动重复加载、降低耦合。
- 获取连接
- 方法:
static Connection getConnection(String url, String user, String password) - URL 格式:
jdbc:mysql://localhost:3306/数据库名 - 本地简写:
jdbc:mysql:///数据库名 - 参数:用户名、数据库密码
- 方法:
DriverManager 获取连接:
Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql:///jdbcdemo"; Connection conn = DriverManager.getConnection(url, "root", "root");(二)Connection 接口
- 作用:代表数据库连接,资源稀有,使用后必须释放。
- 获取执行 SQL 对象
Statement createStatement():普通执行对象PreparedStatement prepareStatement(String sql):预编译执行对象,防 SQL 注入
- 事务管理
setAutoCommit(boolean autoCommit):设置事务自动提交commit():提交事务rollback():回滚事务
Connection 获取执行对象:
// 获取普通Statement Statement stmt = conn.createStatement(); // 获取预编译PreparedStatement String sql = "select * from t_user where username=?"; PreparedStatement pstmt = conn.prepareStatement(sql);(三)Statement 接口
- 作用:执行静态 SQL 语句。
- 核心方法
executeQuery(String sql):执行查询语句,返回 ResultSet 结果集executeUpdate(String sql):执行增删改语句,返回受影响行数
- 支持批处理操作:addBatch、clearBatch、executeBatch
Statement 增删改代码
// 添加 String sqlInsert = "insert into t_user values(null,'test','123','test@qq.com')"; int rowsInsert = stmt.executeUpdate(sqlInsert); // 修改 String sqlUpdate = "update t_user set password='888' where username='aaa'"; int rowsUpdate = stmt.executeUpdate(sqlUpdate); // 删除 String sqlDelete = "delete from t_user where id=3"; int rowsDelete = stmt.executeUpdate(sqlDelete);(四)PreparedStatement 接口
- 是 Statement 子接口,支持预编译 SQL,解决 SQL 注入漏洞。
- 使用占位符?替代参数,SQL 先编译后传参。
- 常用方法
setXxx(参数位置, 参数值):为占位符赋值executeQuery():执行查询executeUpdate():执行增删改
PreparedStatement 完整代码:
String sql = "insert into t_user values(null,?,?,?)"; PreparedStatement pstmt = conn.prepareStatement(sql); // 设置参数 pstmt.setString(1, "preUser"); pstmt.setString(2, "prePwd"); pstmt.setString(3, "pre@qq.com"); // 执行 int rows = pstmt.executeUpdate();(五)ResultSet 接口
- 作用:封装查询结果集,以表格形式存储数据。
- 核心方法
next():向下移动游标,判断是否有下一行数据getXxx(字段名/下标):获取指定字段值,下标从 1 开始getObject():获取任意类型数据,需自行强转
ResultSet 遍历:
ResultSet rs = stmt.executeQuery("select * from t_user"); while (rs.next()) { int id = rs.getInt(1); String name = rs.getString("username"); System.out.println(id + "---" + name); }四、资源释放
- 连接、执行对象、结果集使用后必须释放。
- 释放代码放在 finally 代码块中,保证一定执行。
- 释放顺序:ResultSet → Statement → Connection。
- 释放前判断对象非空,避免空指针异常。
ResultSet rs = null; Statement stmt = null; Connection conn = null; try { // 数据库操作 } catch (Exception e) { e.printStackTrace(); } finally { // 释放 if (rs != null) { try { rs.close(); } catch (Exception e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (Exception e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (Exception e) { e.printStackTrace(); } } }五、SQL 注入问题
- 漏洞原理:拼接 SQL 语句时,用户输入特殊字符改变 SQL 逻辑。
- 典型注入语句:
aaa' or '1=1、aaa' -- ' - 危害:已知用户名可任意登录,绕过密码验证。
- 解决方案:使用 PreparedStatement 预编译对象。
SQL 注入演示:
// 危险:字符串拼接SQL String username = "aaa' or '1=1"; String password = "任意密码"; String sql = "select * from t_user where username='" + username + "' and password='" + password + "'";解决方法(PreparedStatement):
String sql = "select * from t_user where username=? and password=?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.setString(2, password); ResultSet rs = pstmt.executeQuery();六、数据库事务
(一)事务
- 事务是数据库操作的最小单元,要么全部成功,要么全部失败。
- 典型场景:银行转账,扣款与加款必须同时成功或失败。
事务测试表 SQL:
create table t_account( id int primary key auto_increment, username varchar(20), money double ); insert into t_account values (null,'美美',10000); insert into t_account values (null,'冠希',10000);(二)MySQL 事务操作
- 命令行方式
- 开启事务:
start transaction; - 执行 SQL 语句
- 提交事务:
commit; - 回滚事务:
rollback;
- 开启事务:
- 设置非自动提交
- 关闭自动提交:
set autocommit = off; - 手动执行 commit 或 rollback
- 关闭自动提交:
(三)JDBC 事务操作
- 核心依赖 Connection 接口。
- 步骤
- 关闭自动提交:
conn.setAutoCommit(false); - 执行多个 SQL 操作
- 正常执行:
conn.commit(); - 出现异常:
conn.rollback();
- 关闭自动提交:
JDBC 转账事务:
public void transfer() { Connection conn = null; PreparedStatement p1 = null; PreparedStatement p2 = null; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///jdbcdemo", "root", "root"); // 开启事务 conn.setAutoCommit(false); // 冠希 -1000 String sql1 = "update t_account set money=money-1000 where username='冠希'"; p1 = conn.prepareStatement(sql1); p1.executeUpdate(); // 美美 +1000 String sql2 = "update t_account set money=money+1000 where username='美美'"; p2 = conn.prepareStatement(sql2); p2.executeUpdate(); // 提交 conn.commit(); System.out.println("转账成功"); } catch (Exception e) { e.printStackTrace(); // 回滚 try { if (conn != null) conn.rollback(); } catch (Exception ex) { ex.printStackTrace(); } System.out.println("转账失败"); } finally { // 释放资源 try { if (p1 != null) p1.close(); if (p2 != null) p2.close(); if (conn != null) conn.close(); } catch (Exception e) { e.printStackTrace(); } } }(四)事务四大特性(ACID)
- 原子性:事务不可分割,全部执行或全部不执行。
- 一致性:事务执行前后数据总量保持一致。
- 隔离性:并发事务之间相互隔离,不干扰。
- 持久性:事务提交后数据永久保存到数据库。
(五)事务隔离性问题
- 脏读:读取到其他事务未提交的数据。
- 不可重复读:同一事务多次查询结果不一致,针对 update 操作。
- 幻读:同一事务多次查询结果不一致,针对 insert 操作。
(六)四种隔离级别
- Read uncommitted:最低级别,无法解决任何问题。
- Read committed:避免脏读,允许不可重复读、幻读。
- Repeatable read:MySQL 默认级别,避免脏读、不可重复读,允许幻读。
- Serializable:最高级别,避免所有问题,性能最低。
- 安全性:Serializable > Repeatable read > Read committed > Read uncommitted。
- 效率:Serializable < Repeatable read < Read committed < Read uncommitted。
七、数据库连接池
(一)连接池概述
- 解决频繁创建、销毁连接的性能损耗问题。
- 连接可复用,提升程序执行效率。
- 核心参数:初始连接数、最大连接数、最小空闲连接、最大等待时间。
(二)DataSource 接口
- SUN 公司定义标准接口,所有连接池必须实现。
- 核心方法:
Connection getConnection()获取连接。
(三)常用连接池
- DBCP:Apache 开源组织提供。
- C3P0:传统开源连接池。
- Druid:阿里巴巴开发,性能、功能最优,支持监控、加密等。
(四)Druid 连接池使用步骤
- 导入 druid 对应的 jar 包。
- 配置参数
- 驱动类:
driverClassName=com.mysql.jdbc.Driver - 连接地址:
url=jdbc:mysql:///数据库名 - 用户名、密码
- 初始连接数、最大连接数、最大等待时间
- 驱动类:
druid.properties 配置文件:
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///jdbcdemo username=root password=root initialSize=5 maxActive=10 maxWait=3000- 编写工具类
- 静态代码块加载配置文件,创建连接池对象。
- 提供获取连接、释放资源的静态方法。
- 连接释放:调用 close () 方法并非销毁连接,而是归还到连接池。
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 JdbcUtils { private static DataSource dataSource; static { try { Properties prop = new Properties(); InputStream is = JdbcUtils.class.getResourceAsStream("/druid.properties"); prop.load(is); dataSource = DruidDataSourceFactory.createDataSource(prop); } catch (Exception e) { e.printStackTrace(); } } // 获取连接 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(); } } }Druid 使用测试:
public class DruidTest { public static void main(String[] args) throws Exception { Connection conn = JdbcUtils.getConnection(); String sql = "insert into t_user values(null,'druid','123','druid@qq.com')"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.executeUpdate(); JdbcUtils.close(null, pstmt, conn); } }八、JDBC 工具类封装
- 作用:简化连接获取、资源释放操作。
- 实现方式
- 配置文件存储数据库参数,解耦代码。
- 静态代码块初始化连接池。
- 提供统一的 getConnection 方法。
- 重载 close 方法,支持不同场景资源释放。
工具类重载 close 方法:
// 无结果集 public static void close(Statement stmt, Connection conn) { close(null, stmt, conn); }