JDBC核心API详解与SQL注入攻防实战

张开发
2026/4/16 3:33:42 15 分钟阅读

分享文章

JDBC核心API详解与SQL注入攻防实战
JDBC核心API 详细整理一、核心API总览JDBCJava Database Connectivity是Java访问数据库的标准规范核心API共4个基础作用如下API类型核心作用DriverManager类数据库驱动管理类负责注册驱动、创建Java与数据库的连接获取Connection对象Connection接口代表Java程序与数据库的一次连接会话用于获取执行SQL的对象、管理事务Statement接口数据库操作对象负责向数据库发送并执行SQL语句ResultSet接口结果集对象虚拟表封装DQL查询语句的执行结果用于遍历获取数据二、DriverManager驱动管理类1. 注册驱动传统写法Class.forName(com.mysql.jdbc.Driver);原理com.mysql.jdbc.Driver类的静态代码块中会自动调用DriverManager.registerDriver(new Driver())完成驱动注册static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException(Cant register driver!); } }简化写法MySQL 5.1之后的驱动包可以省略注册驱动步骤。原理JDBC 4.0支持SPI服务提供者接口自动加载会自动读取jar包中META-INF/services/java.sql.Driver文件中的驱动类完成自动注册。2. 获取数据库连接通过getConnection()静态方法获取Connection对象static Connection getConnection(String url, String user, String password)参数说明url连接路径语法jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1参数键值对2...示例jdbc:mysql://127.0.0.1:3306/db1细节连接本机MySQL且端口为默认3306时url可简写为jdbc:mysql:///数据库名称?参数键值对配置useSSLfalse参数禁用安全连接解决SSL警告user数据库用户名password数据库密码三、Connection数据库连接接口Connection代表Java与数据库的一次会话核心作用有两个1. 获取执行SQL的对象方法返回值作用createStatement()Statement获取普通执行SQL对象直接拼接SQL执行存在SQL注入风险prepareStatement(sql)PreparedStatement获取预编译SQL执行对象用?占位防止SQL注入生产环境推荐prepareCall(sql)CallableStatement获取执行存储过程的对象用于调用数据库存储过程2. 事务管理JDBC通过Connection接口实现事务管理与MySQL原生事务对应操作MySQL原生语法JDBC对应方法说明开启事务BEGIN; / START TRANSACTION;setAutoCommit(boolean autoCommit)false表示关闭自动提交即开启事务true为默认自动提交提交事务COMMIT;commit()手动提交事务使操作永久生效回滚事务ROLLBACK;rollback()事务出错时回滚撤销所有操作注意MySQL默认自动提交事务每执行一条DML语句自动提交JDBC中需要手动调用setAutoCommit(false)开启事务在业务逻辑完成后commit()异常时rollback()。四、Statement执行SQL接口Statement用于向数据库发送并执行SQL语句核心方法方法返回值作用适用场景返回值说明executeUpdate(sql)int执行DML、DDL语句DMLINSERT/UPDATE/DELETE、DDLCREATE/ALTER/DROP1. DML返回影响的行数2. DDL执行成功也可能返回0executeQuery(sql)ResultSet执行DQL语句DQLSELECT查询返回ResultSet结果集对象封装查询结果注意Statement直接拼接SQL执行存在严重的SQL注入风险生产环境不推荐使用推荐用PreparedStatement。五、ResultSet结果集接口ResultSet封装了DQL查询语句的执行结果虚拟表用于遍历获取数据。1. 核心方法1next()方法boolean next()作用① 将光标从当前位置向前移动一行② 判断当前行是否为有效行是否有数据返回值true当前行是有效行有数据false当前行无数据遍历结束2getXxx(参数)方法// 示例int getInt(参数); String getString(参数); xxx getXxx(参数)作用获取当前行中指定列的数据参数说明int列的编号从1开始计数String列的名称推荐使用可读性高示例// 按列名获取 int id rs.getInt(id); String name rs.getString(name); // 按列号获取id是第1列name是第2列 int id rs.getInt(1); String name rs.getString(2);2. 遍历结果集示例while(rs.next()){ // 循环移动光标判断是否有数据 // 获取当前行数据 int id rs.getInt(id); String name rs.getString(name); double money rs.getDouble(money); System.out.println(id name money); }六、PreparedStatement预编译执行SQL接口Statement子接口PreparedStatement是Statement的子接口是生产环境推荐使用的SQL执行对象核心解决Statement的SQL注入问题同时提升性能。1. 核心作用预编译SQL语句并执行彻底防止SQL注入提升SQL执行性能同模板SQL只编译一次2. 什么是SQL注入SQL注入是一种网络攻击手段攻击者通过操作输入参数恶意拼接SQL语句修改原本的SQL逻辑从而非法访问/篡改数据库数据。典型场景登录时输入 or 11绕过密码验证直接登录。一正常登录 SQL 逻辑正常登录时SQL 语句结构如下select * from tb_user where username 真实用户名 and password 真实密码只有用户名和密码同时匹配才能查询到数据登录成功。二注入绕过恒真条件拼接无注释版假设恶意输入用户名hfk jshskj任意不存在的用户名密码 or 1 1注入 payload拼接后最终执行的 SQLusername hfk jshskj and password or 1 11. SQL 运算优先级AND 高于 OR数据库会先算 AND再算 OR因此条件等价于( username hfk jshskj AND password ) OR ( 1 1 )2. 分步计算逻辑先算括号 1username hfk jshskj AND password 含义用户名是hfk jshskj并且密码是空字符串数据库里几乎不可能存在这样的用户 → 结果为false再算括号 21 1含义1 等于 1 → 永远为true最终结果false OR true true整个 WHERE 条件恒成立数据库会返回tb_user表的所有用户数据所以rs.next()判断为true程序打印「登录成功」完美绕过密码验证三注入绕过注释截断彻底屏蔽密码假设登录 SQL 为SELECT * FROM users WHERE username [输入] AND password [输入]输入恶意 payload用户名输入admin --注意末尾必须有一个空格拼接后 SQL 变为SELECT * FROM users WHERE username admin -- AND password 任意原理注释符 -- 屏蔽后续代码--是 SQL 的单行注释符它会将后续所有代码注释掉不再参与查询逻辑判断。数据库实际执行的逻辑等价于SELECT * FROM users WHERE username admin密码条件被直接注释屏蔽无需验证密码即可登录这是更彻底、更常用的注入攻击方式。3. PreparedStatement 使用步骤① 获取PreparedStatement对象定义SQL模板用?占位// SQL中的参数用?占位不能直接写值 String sql select * from user where username ? and password ?; // 通过Connection获取PreparedStatement传入SQL模板 PreparedStatement pstmt conn.prepareStatement(sql);② 给?设置参数值通过setXxx(参数1, 参数2)方法给占位符赋值Xxx数据类型如setInt、setString参数1?的位置编号从1开始参数2?对应的值示例pstmt.setString(1, zhangsan); // 给第1个?赋值 pstmt.setString(2, 123456); // 给第2个?赋值③ 执行SQL调用executeUpdate()或executeQuery()不需要再传递SQL语句// 执行DML/DDL int rows pstmt.executeUpdate(); // 执行DQL ResultSet rs pstmt.executeQuery();4. PreparedStatement 原理与优势1核心优势防止SQL注入对输入的敏感字符如、or等进行转义无法修改SQL逻辑性能更高预编译SQL同模板SQL只编译一次重复执行时直接调用减少耗时2工作原理获取PreparedStatement对象时将SQL模板发送给MySQL服务器执行语法检查、编译这两个步骤非常耗时执行SQL时直接使用已经编译好的模板传入参数即可执行无需重复检查/编译同模板复用如果SQL模板完全一致只需要进行一次检查、编译后续执行直接复用大幅提升性能3优化配置开启MySQL服务器端预编译在url中添加useServerPrepStmtstrue让预编译在MySQL服务端生效进一步提升性能jdbc:mysql:///db1?useSSLfalseuseServerPrepStmtstrue配置MySQL执行日志重启服务生效可查看SQL执行细节log-outputFILE general-log1 general_log_fileD:\mysql.log slow-query-log1 slow_query_log_fileD:\mysql_slow.log long_query_time2七、Statement vs PreparedStatement 核心对比对比维度StatementPreparedStatementSQL注入风险高直接拼接SQL易被注入无预编译转义彻底防注入执行性能低每次执行都要编译SQL高同模板只编译一次复用代码可读性差SQL拼接繁琐易出错好?占位结构清晰适用场景仅用于简单、无参数的静态SQL生产环境所有场景推荐八、JDBC完整开发步骤串联所有API注册驱动MySQL5可省略Class.forName(com.mysql.cj.jdbc.Driver)新版驱动类名获取连接DriverManager.getConnection(url, user, password)定义SQL编写要执行的SQL语句PreparedStatement用?占位获取执行SQL的对象conn.prepareStatement(sql)推荐或conn.createStatement()设置参数PreparedStatementsetXxx()给?赋值执行SQLexecuteUpdate()/executeQuery()处理结果ResultSet遍历DQL/ 影响行数DML释放资源按rs → pstmt → conn的顺序关闭资源避免内存泄漏九、常见注意事项驱动类名变化MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver旧版是com.mysql.jdbc.Driverurl参数MySQL 8.0需要指定时区serverTimezoneUTC否则报错资源释放必须在finally块中关闭资源确保异常时也能释放事务管理涉及多步DML操作时必须开启事务setAutoCommit(false)保证数据一致性PreparedStatement预编译必须开启useServerPrepStmtstrue才能让MySQL服务端预编译生效否则仅为客户端预编译

更多文章