JDBC 基础: API、SQL 注入问题,事务、连接池

一、JDBC

  1. JDBC 全称 Java DataBase Connectivity,是 Java 数据库连接规范,用于通过 Java 代码操作数据库
  2. JDBC 是一套接口规范,实现类由各数据库厂商提供
  3. 数据库驱动是厂商提供的实现类,使用 MySQL 需导入 mysql-connector-java 驱动 jar 包。
  4. JDBC 核心作用是建立 Java 程序与数据库的连接,实现数据增删改查操作

二、JDBC 快速入门开发步骤

  1. 创建数据库与数据表
    1. 创建数据库:create database jdbcdemo;
    2. 使用数据库:use jdbcdemo;
    3. 创建用户表:
sql 复制代码
create table t_user(
id int primary key auto_increment,
username varchar(30),
password varchar(30),
email varchar(30)
);
  1. 插入测试数据:
sql 复制代码
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');
  1. 编写 Java 代码实现查询
    1. 加载驱动
    2. 获取数据库连接
    3. 编写 SQL 语句
    4. 获取执行 SQL 的对象
    5. 执行 SQL 并处理结果集
    6. 释放资源

查询代码:

java 复制代码
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 类

  1. 作用:管理 JDBC 驱动,获取数据库连接。
  2. 加载驱动
    1. Class.forName("com.mysql.jdbc.Driver");
    2. 避免直接使用 registerDriver 方法,防止驱动重复加载、降低耦合。
  3. 获取连接
    1. 方法:static Connection getConnection(String url, String user, String password)
    2. URL 格式:jdbc:mysql://localhost:3306/数据库名
    3. 本地简写:jdbc:mysql:///数据库名
    4. 参数:用户名、数据库密码

DriverManager 获取连接:

java 复制代码
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql:///jdbcdemo";
Connection conn = DriverManager.getConnection(url, "root", "root");

(二)Connection 接口

  1. 作用:代表数据库连接,资源稀有,使用后必须释放。
  2. 获取执行 SQL 对象
    1. Statement createStatement():普通执行对象
    2. PreparedStatement prepareStatement(String sql):预编译执行对象,防 SQL 注入
  3. 事务管理
    1. setAutoCommit(boolean autoCommit):设置事务自动提交
    2. commit():提交事务
    3. rollback():回滚事务

Connection 获取执行对象:

java 复制代码
// 获取普通Statement
Statement stmt = conn.createStatement();
// 获取预编译PreparedStatement
String sql = "select * from t_user where username=?";
PreparedStatement pstmt = conn.prepareStatement(sql);

(三)Statement 接口

  1. 作用:执行静态 SQL 语句。
  2. 核心方法
    1. executeQuery(String sql):执行查询语句,返回 ResultSet 结果集
    2. executeUpdate(String sql):执行增删改语句,返回受影响行数
  3. 支持批处理操作:addBatch、clearBatch、executeBatch

Statement 增删改代码

java 复制代码
// 添加
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 接口

  1. 是 Statement 子接口,支持预编译 SQL,解决 SQL 注入漏洞。
  2. 使用占位符?替代参数,SQL 先编译后传参。
  3. 常用方法
    1. setXxx(参数位置, 参数值):为占位符赋值
    2. executeQuery():执行查询
    3. executeUpdate():执行增删改

PreparedStatement 完整代码:

java 复制代码
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 接口

  1. 作用:封装查询结果集,以表格形式存储数据。
  2. 核心方法
    1. next():向下移动游标,判断是否有下一行数据
    2. getXxx(字段名/下标):获取指定字段值,下标从 1 开始
    3. getObject():获取任意类型数据,需自行强转

ResultSet 遍历:

java 复制代码
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);
}

四、资源释放

  1. 连接、执行对象、结果集使用后必须释放。
  2. 释放代码放在 finally 代码块中,保证一定执行。
  3. 释放顺序:ResultSet → Statement → Connection。
  4. 释放前判断对象非空,避免空指针异常。
java 复制代码
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 注入问题

  1. 漏洞原理:拼接 SQL 语句时,用户输入特殊字符改变 SQL 逻辑。
  2. 典型注入语句:aaa' or '1=1aaa' -- '
  3. 危害:已知用户名可任意登录,绕过密码验证。
  4. 解决方案:使用 PreparedStatement 预编译对象。

SQL 注入演示:

java 复制代码
// 危险:字符串拼接SQL
String username = "aaa' or '1=1";
String password = "任意密码";
String sql = "select * from t_user where username='" + username + "' and password='" + password + "'";

解决方法(PreparedStatement):

java 复制代码
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();

六、数据库事务

(一)事务

  1. 事务是数据库操作的最小单元,要么全部成功,要么全部失败。
  2. 典型场景:银行转账,扣款与加款必须同时成功或失败。

事务测试表 SQL:

java 复制代码
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 事务操作

  1. 命令行方式
    1. 开启事务:start transaction;
    2. 执行 SQL 语句
    3. 提交事务:commit;
    4. 回滚事务:rollback;
  2. 设置非自动提交
    1. 关闭自动提交:set autocommit = off;
    2. 手动执行 commit 或 rollback

(三)JDBC 事务操作

  1. 核心依赖 Connection 接口。
  2. 步骤
    1. 关闭自动提交:conn.setAutoCommit(false);
    2. 执行多个 SQL 操作
    3. 正常执行:conn.commit();
    4. 出现异常:conn.rollback();

JDBC 转账事务:

java 复制代码
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)

  1. 原子性:事务不可分割,全部执行或全部不执行。
  2. 一致性:事务执行前后数据总量保持一致。
  3. 隔离性:并发事务之间相互隔离,不干扰。
  4. 持久性:事务提交后数据永久保存到数据库。

(五)事务隔离性问题

  1. 脏读:读取到其他事务未提交的数据。
  2. 不可重复读:同一事务多次查询结果不一致,针对 update 操作。
  3. 幻读:同一事务多次查询结果不一致,针对 insert 操作。

(六)四种隔离级别

  1. Read uncommitted:最低级别,无法解决任何问题。
  2. Read committed:避免脏读,允许不可重复读、幻读。
  3. Repeatable read:MySQL 默认级别,避免脏读、不可重复读,允许幻读。
  4. Serializable:最高级别,避免所有问题,性能最低。
  5. 安全性:Serializable > Repeatable read > Read committed > Read uncommitted。
  6. 效率:Serializable < Repeatable read < Read committed < Read uncommitted。

七、数据库连接池

(一)连接池概述

  1. 解决频繁创建、销毁连接的性能损耗问题。
  2. 连接可复用,提升程序执行效率。
  3. 核心参数:初始连接数、最大连接数、最小空闲连接、最大等待时间。

(二)DataSource 接口

  1. SUN 公司定义标准接口,所有连接池必须实现。
  2. 核心方法:Connection getConnection() 获取连接。

(三)常用连接池

  1. DBCP:Apache 开源组织提供。
  2. C3P0:传统开源连接池。
  3. Druid:阿里巴巴开发,性能、功能最优,支持监控、加密等。

(四)Druid 连接池使用步骤

  1. 导入 druid 对应的 jar 包。
  2. 配置参数
    1. 驱动类:driverClassName=com.mysql.jdbc.Driver
    2. 连接地址:url=jdbc:mysql:///数据库名
    3. 用户名、密码
    4. 初始连接数、最大连接数、最大等待时间

druid.properties 配置文件:

XML 复制代码
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbcdemo
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
  1. 编写工具类
    1. 静态代码块加载配置文件,创建连接池对象。
    2. 提供获取连接、释放资源的静态方法。
  2. 连接释放:调用 close () 方法并非销毁连接,而是归还到连接池。

Druid 工具类:

java 复制代码
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 使用测试:

java 复制代码
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 工具类封装

  1. 作用:简化连接获取、资源释放操作。
  2. 实现方式
    1. 配置文件存储数据库参数,解耦代码。
    2. 静态代码块初始化连接池。
    3. 提供统一的 getConnection 方法。
    4. 重载 close 方法,支持不同场景资源释放。

工具类重载 close 方法:

java 复制代码
// 无结果集
public static void close(Statement stmt, Connection conn) {
    close(null, stmt, conn);
}
相关推荐
NineData2 小时前
玖章算术NineData成功入选杭州市“新雏鹰”企业
运维·数据库·后端
z4424753262 小时前
CSS如何实现元素悬浮在页面底部_利用fixed定位与底部间距
jvm·数据库·python
m0_596406372 小时前
mysql数据库用户密码加固策略_实施强密码策略与定期轮换
jvm·数据库·python
m0_676544382 小时前
CSS如何实现语义化样式编写_使用BEM规范提升命名直观性
jvm·数据库·python
oscar9992 小时前
OpenCode终端界面
数据库·tui·opencode
m0_676544382 小时前
MySQL数据库迁移后如何测试数据可读性_进行简单查询验证.txt
jvm·数据库·python
weixin_458580122 小时前
C#怎么实现定时任务 C#如何用Timer和Quartz.NET创建定时执行的后台任务【技巧】
jvm·数据库·python
User_芊芊君子2 小时前
从零入门!MySQL 约束、范式设计与联合查询核心精讲
数据库·人工智能·mysql
重生之我是Java开发战士2 小时前
【MySQL】视图:简化查询、保障安全的虚拟表
数据库·mysql