JDBC工具类和SQL 注入问题

发布于:2025-06-29 ⋅ 阅读:(17) ⋅ 点赞:(0)

在软件开发中,数据库安全与高效访问一直是关键课题。本文将围绕 SQL 注入问题的原理、解决方案,以及 JDBC 开发中的工具类演进和连接池技术展开探讨,结合实际代码示例,为开发者提供清晰的技术实践指南。

SQL 注入问题的核心原理与典型场景

运算符优先级引发的安全漏洞

SQL 注入问题中,AND 和 OR 的优先级差异是一个容易被忽视的风险点。AND 的运算优先级高于 OR,这一特性若被恶意利用,会导致 SQL 语句的逻辑被篡改。例如,当用户输入 “aaa'or'1=1” 作为用户名时,拼接后的 SQL 语句可能变为 “SELECT * FROM users WHERE username = 'aaa'or'1=1' AND password = '123'”。由于 “1=1” 恒成立,且 OR 的存在使得整个条件为真,攻击者无需正确密码即可绕过认证。

字符串拼接:注入漏

SQL 注入产生的根本原因是直接使用字符串拼接方式构建 SQL 语句。这种方式使得用户输入的内容直接参与 SQL 语句的结构组成,若用户输入包含 SQL 关键字或特殊字符,就会改变语句的原意。如在登录功能中,若采用 “SELECT * FROM users WHERE username = '” + username + “' AND password = '” + password + “'” 的拼接方式,当用户名输入 “aaa'or'1=1” 时,语句会被解析为无条件查询,导致安全漏洞。

 

预编译技术:SQL 注入的高效解决方案

PreparedStatement 的工作机制

解决 SQL 注入问题的核心方案是采用预编译的 SQL 语句,通过 PreparedStatement 对象实现。其原理是将 SQL 语句的结构与参数分离,先对 SQL 语句进行编译,格式固定后,再为占位符 “?” 传入具体值。数据库会将这些值作为纯粹的数据处理,而非 SQL 代码,从而避免了关键字被恶意解析的风险。

代码实现示例

public class JdbcTest4 {
    public String login2(String username, String password) {
        // 预编译SQL语句,使用占位符
        String sql = "SELECT * FROM t_user WHERE username = ? AND password = ?";
        try (Connection conn = JdbcUtils.getConnection();
             PreparedStatement stmt = conn.prepareStatement(sql)) {
            // 为占位符赋值
            stmt.setString(1, username);
            stmt.setString(2, password);
            ResultSet rs = stmt.executeQuery();
            if (rs.next()) {
                return "登录成功";
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return "登录失败";
    }
}

JDBC 工具类的演进与实践优化

 为啥需要工具类呢? 因为在上个案例中的dao层的代码,很多都是重复的

1. 数据库连接的创建

每次进行数据库操作前,都需要创建数据库连接。例如:

Connection conn = null;
try {
    // 加载驱动类
    Class.forName("com.mysql.jdbc.Driver");
    // 创建连接
    conn = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/mydb", 
        "username", 
        "password"
    );
} catch (ClassNotFoundException | SQLException e) {
    e.printStackTrace();
}

这段代码在每个需要访问数据库的方法中几乎都会重复出现。

2. SQL 语句的执行和结果处理

执行查询、插入、更新或删除操作时,需要编写类似的代码结构:

Statement stmt = null;
ResultSet rs = null;
try {
    // 创建Statement对象
    stmt = conn.createStatement();
    // 执行SQL查询
    rs = stmt.executeQuery("SELECT * FROM users");
    // 处理结果集
    while (rs.next()) {
        // 提取数据
        String username = rs.getString("username");
        // ...
    }
} catch (SQLException e) {
    e.printStackTrace();
}

无论执行何种 SQL 语句,都需要创建 Statement 对象、处理异常和结果集。

3. 资源的关闭

操作完成后,需要关闭 ResultSet、Statement 和 Connection 对象:

finally {
    // 关闭资源(顺序:ResultSet → Statement → Connection)
    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();
        }
    }
}

这段关闭资源的代码必须在每个数据库操作中重复编写,且顺序不能错(先关闭 ResultSet,再关闭 Statement,最后关闭 Connection)。

1.0 版本:硬编码与局限性

早期的 JDBC 1.0 版本工具类中,驱动注册、数据库连接地址、用户名和密码等信息均为硬编码方式。例如:

DriverManager.registerDriver(new Driver());
conn = DriverManager.getConnection("jdbc:mysql:///jdbcdemo", "root", "root");

这种方式存在明显缺陷,当数据库配置发生变化时,需要修改代码并重新部署,灵活性和可维护性较差。

 

2.0 版本:属性文件与动态配置

改进后的 2.0 版本通过属性文件实现配置信息的动态读取,提高了工具类的通用性。以 db.properties 文件为例:

driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbcdemo
username=root
password=root

代码中通过静态代码块加载属性文件:

public class JdbcUtils {
    private static final String driverClass;
    private static final String url;
    private static final String username;
    private static final String password;
    
    static {
        Properties pro = new Properties();
        InputStream inputStream = JdbcUtils.class.getResourceAsStream("/db.properties");
        pro.load(inputStream);
        driverClass = pro.getProperty("driverClass");
        url = pro.getProperty("url");
        username = pro.getProperty("username");
        password = pro.getProperty("password");
        // 加载驱动
        Class.forName(driverClass);
    }
}

final 静态常量一开始就要有赋值,赋值后不可改变。 static代码块在方法前启动。

这种方式实现了配置与代码的分离,便于维护和扩展。

连接池技术:提升数据库访问效率的关键

池化思想的核心价值

连接池技术基于 “资源复用” 的池化思想,其核心优势在于:

  • 避免频繁创建和销毁数据库连接的开销,提升系统性能。
  • 控制连接数量,防止数据库因连接过多而负载过高。
  • 实现连接的统一管理和回收,提高资源利用率。

连接池的工作机制

连接池的工作流程如下:

  1. 初始化时创建一定数量的连接放入池中(如 initialSize=5)。
  2. 应用程序需要连接时,从池中获取可用连接,若池中空闲连接不足,根据配置规则(如 maxWait=3000)等待或创建新连接(不超过最大连接数)。
  3. 使用完毕后,将连接放回池中,而非直接关闭,以供后续使用。
  4. 池化管理会自动维护连接状态,清理失效连接,保持合理的连接数量(如 minIdle=3,maxIdle=6)。

以 Druid 连接池为例,其配置文件如下:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///jdbcdemo
username=root
password=root
initialSize=5
maxWait=3000
maxIdle=6
minIdle=3

 


网站公告

今日签到

点亮在社区的每一天
去签到