ToB企服应用市场:ToB评测及商务社交产业平台

标题: 第四章 SQL注入 [打印本页]

作者: 西河刘卡车医    时间: 2025-1-4 15:55
标题: 第四章 SQL注入
SQL注入标题

SQL注入标题说的是:用户输入的信息中含有SQL语句关键字,和步伐中的SQL语句举行字符串拼接,导致步伐中的SQL语句改变了原意。(SQL注入标题是一种系统安全标题)接下来我们来演示一下SQL注入标题。以用户登录为例。使用表:t_user业务形貌:系统启动后,给出登录页面,用户可以输入用户名和密码,用户名和密码全部正确,则登录成功,反之,则登录失败。分析一下要执行怎样的SQL语句?是不是如许的?
  1. select * from t_user where name = 用户输入的用户名 and password = 用户输入的密码;
复制代码
假如以上的SQL语句可以大概查询到结果,说明用户名和密码是正确的,则登录成功。假如查不到,说明是错误的,则登录失败。代码实现如下:
  1. package com.powernode.jdbc;
  2. import java.sql.*;
  3. import java.util.ResourceBundle;
  4. import java.util.Scanner;
  5. /**
  6. * 用户登录案例演示SQL注入问题
  7. */
  8. public class JDBCTest02 {
  9.     public static void main(String[] args) {
  10.         // 输出欢迎页面
  11.         System.out.println("欢迎使用用户管理系统,请登录!");
  12.         // 接收用户名和密码
  13.         Scanner scanner = new Scanner(System.in);
  14.         System.out.print("用户名:");
  15.         String loginName = scanner.nextLine();
  16.         System.out.print("密码:");
  17.         String loginPwd = scanner.nextLine();
  18.         // 读取属性配置文件,获取连接数据库的信息。
  19.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  20.         String driver = bundle.getString("driver");
  21.         String url = bundle.getString("url");
  22.         String user = bundle.getString("user");
  23.         String password = bundle.getString("password");
  24.         // JDBC程序验证用户名和密码是否正确
  25.         Connection conn = null;
  26.         Statement stmt = null;
  27.         ResultSet rs = null;
  28.         try {
  29.             // 1.注册驱动
  30.             Class.forName(driver);
  31.             // 2.获取连接
  32.             conn = DriverManager.getConnection(url, user, password);
  33.             // 3.获取数据库操作对象
  34.             stmt = conn.createStatement();
  35.             // 4.执行SQL语句
  36.             String sql = "select realname from t_user where name = '"+loginName+"' and password = '"+loginPwd+"'";
  37.             rs = stmt.executeQuery(sql);
  38.             // 5.处理查询结果集
  39.             if (rs.next()) { // 如果可以确定结果集中最多只有一条记录的话,可以使用if语句,不一定非要用while循环。
  40.                 String realname = rs.getString("realname");
  41.                 System.out.println("登录成功,欢迎您" + realname);
  42.             } else {
  43.                 System.out.println("登录失败,用户名不存在或者密码错误。");
  44.             }
  45.         } catch (ClassNotFoundException | SQLException e) {
  46.             throw new RuntimeException(e);
  47.         } finally {
  48.             // 6.释放资源
  49.             if (rs != null) {
  50.                 try {
  51.                     rs.close();
  52.                 } catch (SQLException e) {
  53.                     throw new RuntimeException(e);
  54.                 }
  55.             }
  56.             if (stmt != null) {
  57.                 try {
  58.                     stmt.close();
  59.                 } catch (SQLException e) {
  60.                     throw new RuntimeException(e);
  61.                 }
  62.             }
  63.             if (conn != null) {
  64.                 try {
  65.                     conn.close();
  66.                 } catch (SQLException e) {
  67.                     throw new RuntimeException(e);
  68.                 }
  69.             }
  70.         }
  71.     }
  72. }
复制代码
  1. /**
  2. * ClassName: JDBCTest08
  3. * Description: 实现用户登录功能
  4. * 这个程序在实现了基本的登录功能的同时,演示了SQL注入的现象。
  5. * 导致SQL注入现象的根本原因是什么?
  6. *      第一:用户提供的信息中含有SQL语句的关键字了。
  7. *      第二:这些用户提供的信息中的关键字参与了SQL语句的编译。
  8. *
  9. * SQL注入是一种安全隐患。黑客通常使用SQL注入来攻击你的系统。
  10. * 怎么避免SQL注入现象呢?
  11. *      java.sql.Statement 是存在SQL注入现象的,因为它的原理是:先拼接SQL语句字符串,然后再进行编译,所以它必然存在SQL注入的问题。
  12. *      为了避免SQL注入的发生,JDBC API中为Statement接口提供了一个子接口:java.sql.PreparedStatement,被称为预编译的数据库操作对象。
  13. *      java.sql.PreparedStatement可以解决SQL注入问题。
  14. *      具体怎么解决的?PreparedStatement可以对SQL语句进行预先编译,然后给编译好的SQL语句占位符传值,通过这种方式来解决SQL注入问题。
  15. *      最本质的解决方法是:用户虽然提供了SQL语句关键字,但是这些关键字不再参与SQL语句的编译了。因此解决了SQL注入的问题。
  16. *
复制代码
假如用户名和密码正确的话,执行结果如下:


假如用户名不存在或者密码错误的话,执行结果如下:


接下来,见证古迹的时候,当我分别输入以下的用户名和密码时,系统被攻破了:

这种征象就叫做:SQL注入。为什么会发生以上的事儿呢?原因是:用户提供的信息中有SQL语句关键字,并且和底层的SQL字符串举行了拼接,变成了一个全新的SQL语句。例如:原来步伐想表达的是如许的SQL:
  1. select realname from t_user where name = 'sunwukong' and password = '123';
复制代码
结果被SQL注入之后,SQL语句就变成如许了:
  1. select realname from t_user where name = 'aaa' and password = 'bbb' or '1'='1';
复制代码
我们可以执行一下这条SQL,看看结果是什么?


把所有结果全部查到了,这是因为 '1'='1' 是恒成立的,并且使用的是 or 运算符,所以 or 前面的条件便是是没有的。如许就会把所有数据全部查到。而在步伐中的判断逻辑是只要结果集中有数据,则表示登录成功。所以以上的输入方式最终的结果就是登录成功。你设想一下,假如这个系统是一个高级别保密系统,只有登录成功的人才有权限,那么这个系统是不是极其伤害了。
解决SQL注入标题

导致SQL注入的根本原因是什么?只有找到真正的原因,标题才气得到解决。
最根本的原因是:Statement造成的。先举行SQL语句的字符串拼接,然后再举行SQL语句的编译
Statement执行原理是:先举行字符串的拼接,将拼接好的SQL语句发送给数据库服务器,数据库服务器举行SQL语句的编译,然后执行。因此用户提供的信息中假如含有SQL语句的关键字,那么这些关键字恰好参加了SQL语句的编译,所以导致原SQL语句被扭曲。
因此,JDBC为了解决这个标题,引入了一个新的接口:PreparedStatement,我们称为:预编译的数据库操作对象。PreparedStatement是Statement接口的子接口。它俩是继承关系。
PreparedStatement执行原理是:先对SQL语句举行预先的编译,然后再向SQL语句指定的位置传值,也就是说:用户提供的信息中纵然含有SQL语句的关键字,那么这个信息也只会被当做一个值转达给SQL语句,用户提供的信息不再参与SQL语句的编译了,如许就解决了SQL注入标题。
使用PreparedStatement解决SQL注入标题:
  1. package com.powernode.jdbc;
  2. import java.sql.*;
  3. import java.util.ResourceBundle;
  4. import java.util.Scanner;
  5. /**
  6. * PreparedStatement解决SQL注入问题
  7. */
  8. public class JDBCTest03 {
  9.     public static void main(String[] args) {
  10.         // 输出欢迎页面
  11.         System.out.println("欢迎使用用户管理系统,请登录!");
  12.         // 接收用户名和密码
  13.         Scanner scanner = new Scanner(System.in);
  14.         System.out.print("用户名:");
  15.         String loginName = scanner.nextLine();
  16.         System.out.print("密码:");
  17.         String loginPwd = scanner.nextLine();
  18.         // 读取属性配置文件,获取连接数据库的信息。
  19.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  20.         String driver = bundle.getString("driver");
  21.         String url = bundle.getString("url");
  22.         String user = bundle.getString("user");
  23.         String password = bundle.getString("password");
  24.         // JDBC程序验证用户名和密码是否正确
  25.         Connection conn = null;
  26.         PreparedStatement pstmt = null;
  27.         ResultSet rs = null;
  28.         try {
  29.             // 1.注册驱动
  30.             Class.forName(driver);
  31.             // 2.获取连接
  32.             conn = DriverManager.getConnection(url, user, password);
  33.             // 3.获取数据库操作对象(获取的是预编译的数据库操作对象)
  34.             String sql = "select realname from t_user where name=? and password=?";
  35.             pstmt = conn.prepareStatement(sql);
  36.             pstmt.setString(1, loginName);
  37.             pstmt.setString(2, loginPwd);
  38.             // 4.执行SQL语句
  39.             rs = pstmt.executeQuery();
  40.             // 5.处理查询结果集
  41.             if (rs.next()) {
  42.                 String realname = rs.getString("realname");
  43.                 System.out.println("登录成功,欢迎您" + realname);
  44.             } else {
  45.                 System.out.println("登录失败,用户名不存在或者密码错误。");
  46.             }
  47.         } catch (ClassNotFoundException | SQLException e) {
  48.             throw new RuntimeException(e);
  49.         } finally {
  50.             // 6.释放资源
  51.             if (rs != null) {
  52.                 try {
  53.                     rs.close();
  54.                 } catch (SQLException e) {
  55.                     throw new RuntimeException(e);
  56.                 }
  57.             }
  58.             if (pstmt != null) {
  59.                 try {
  60.                     pstmt.close();
  61.                 } catch (SQLException e) {
  62.                     throw new RuntimeException(e);
  63.                 }
  64.             }
  65.             if (conn != null) {
  66.                 try {
  67.                     conn.close();
  68.                 } catch (SQLException e) {
  69.                     throw new RuntimeException(e);
  70.                 }
  71.             }
  72.         }
  73.     }
  74. }
复制代码
  1. /**
  2. * ClassName: JDBCTest09
  3. * Description: 使用PreparedStatement解决SQL注入问题。
  4. *
  5. * PreparedStatement原理:
  6. *      第一步:先对SQL语句进行预先编译
  7. *      第二步:给SQL语句中占位符传值
  8. *
  9. * 重点注意事项:
  10. *      在使用预编译的数据库操作对象PreparedStatement时,需要先编写SQL语句,然后再获取PreparedStatement对象。
  11. *      这里编写的SQL语句中,所有“值”的位置都要使用占位符来代替,占位符采用 ?
  12. *      每一个问号 ? 是一个值。是一个占位符。
  13. *      另外,特别要注意:这个占位符问号 ? 两边不能使用单引号或双引号括起来。
  14. *
  15. * 解决SQL注入的本质是:先将带有占位符的SQL语句进行预先编译,然后给占位符传值。
  16. * 即使用户提供的信息中含有SQL语句关键字,但是这些关键字不会参与SQL语句的编译,自然不会扭曲SQL语句的原意。
  17. *
  18. * Datetime: 2024/4/11 17:45
  19. * Author: 老杜@动力节点
  20. * Version: 1.0
  21. */
  22. public class JDBCTest09 {
  23.     public static void main(String[] args) {
  24.         Scanner scanner = new Scanner(System.in);
  25.         // 初始化登录界面
  26.         System.out.println("欢迎使用用户管理系统,请登录!");
  27.         System.out.print("用户名:");
  28.         String loginName = scanner.nextLine();
  29.         System.out.print("密码:");
  30.         String loginPwd = scanner.nextLine();
  31.         // 连接数据库,验证用户名和密码是否正确。
  32.         Connection conn = null;
  33.         PreparedStatement ps = null;
  34.         ResultSet rs = null;
  35.         boolean loginSuccess = false;
  36.         String realname = null;
  37.         try {
  38.             // 获取连接
  39.             conn = DbUtils.getConnection();
  40.             // 获取预编译的数据库操作对象
  41.             String sql = "select * from t_user where name = ? and password = ?";
  42.             ps = conn.prepareStatement(sql);
  43.             // 给 ? 占位符 传值
  44.             // 再次强调:在JDBC当中,所有的下标都是从1开始。不是从0开始。
  45.             // 以下代码的含义是:给第1个占位符 ? 传值
  46.             ps.setString(1, loginName);
  47.             // 以下代码的含义是:给第2个占位符 ? 传值
  48.             ps.setString(2, loginPwd);
  49.             // 执行SQL语句
  50.             rs = ps.executeQuery();
  51.             // 处理结果集(结果集中有数据,表示登录成功,反之表示登录失败)
  52.             if(rs.next()){
  53.                 // 登录成功
  54.                 loginSuccess = true;
  55.                 // 获取真实的名字
  56.                 realname = rs.getString("realname");
  57.             }
  58.         } catch (SQLException e) {
  59.             throw new RuntimeException(e);
  60.         } finally {
  61.             // 释放资源
  62.             DbUtils.close(conn, ps, rs);
  63.         }
  64.         System.out.println(loginSuccess ? "登录成功,欢迎" + realname : "登录失败,您的用户名不存在或者密码错误!");
  65.     }
  66. }
复制代码
用户名和密码正确的话,执行结果如下:


用户名和密码错误的话,执行结果如下:


实验SQL注入,看看还能不能?


通过测试得知,SQL注入标题已经解决了。根本原因是:bbb' or '1'='1 这个字符串中虽然含有SQL语句的关键字,但是只会被当做普通的值传到SQL语句中,并没有参与SQL语句的编译
关于使用PreparedStatement要留意的是:



PreparedStatement和Statement都是用于执行SQL语句的接口,它们的主要区别在于:

PreparedStatement的使用

新增操作

需求:向 emp 表中插入如许一条记载:
empno:8888
ename:张三
job:贩卖员
mgr:7369
hiredate:2024-01-01
sal:1000.0
comm:500.0
deptno:10
  1. package com.powernode.jdbc;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.time.LocalDate;
  7. import java.util.ResourceBundle;
  8. public class JDBCTest04 {
  9.     public static void main(String[] args) {
  10.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  11.         String driver = bundle.getString("driver");
  12.         String url = bundle.getString("url");
  13.         String user = bundle.getString("user");
  14.         String password = bundle.getString("password");
  15.         Connection conn = null;
  16.         PreparedStatement pstmt = null;
  17.         try {
  18.             // 1. 注册驱动
  19.             Class.forName(driver);
  20.             // 2. 获取连接
  21.             conn = DriverManager.getConnection(url, user, password);
  22.             // 3. 获取预编译的数据操作对象
  23.             String sql = "insert into emp(empno,ename,sal,comm,job,mgr,hiredate,deptno) values(?,?,?,?,?,?,?,?)";
  24.             // 预编译SQL语句
  25.             pstmt = conn.prepareStatement(sql);
  26.             // 给 ? 传值
  27.             pstmt.setInt(1, 8888);
  28.             pstmt.setString(2, "张三");
  29.             pstmt.setDouble(3, 10000.0);
  30.             pstmt.setDouble(4, 500.0);
  31.             pstmt.setString(5, "销售员");
  32.             pstmt.setInt(6, 7369);
  33.             LocalDate localDate = LocalDate.parse("2024-01-01");
  34.             pstmt.setDate(7, java.sql.Date.valueOf(localDate));
  35.             pstmt.setInt(8, 10);
  36.             // 4. 执行SQL语句
  37.             int count = pstmt.executeUpdate();
  38.             if (1 == count) {
  39.                 System.out.println("成功更新" + count + "条记录");
  40.             }
  41.         } catch (Exception e) {
  42.             e.printStackTrace();
  43.         } finally {
  44.             // 6. 释放资源
  45.             if (pstmt != null) {
  46.                 try {
  47.                     pstmt.close();
  48.                 } catch (SQLException e) {
  49.                     throw new RuntimeException(e);
  50.                 }
  51.             }
  52.             if (conn != null) {
  53.                 try {
  54.                     conn.close();
  55.                 } catch (SQLException e) {
  56.                     throw new RuntimeException(e);
  57.                 }
  58.             }
  59.         }
  60.     }
  61. }
复制代码
重点学习内容:如何给占位符 ? 传值。执行结果如下:

修改操作

需求:将员工编号为8888的员工,姓名修改为李四,岗位修改为产品司理,月薪修改为5000.0,其他稳固。
  1. package com.powernode.jdbc;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.time.LocalDate;
  7. import java.util.ResourceBundle;
  8. public class JDBCTest05 {
  9.     public static void main(String[] args) {
  10.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  11.         String driver = bundle.getString("driver");
  12.         String url = bundle.getString("url");
  13.         String user = bundle.getString("user");
  14.         String password = bundle.getString("password");
  15.         Connection conn = null;
  16.         PreparedStatement pstmt = null;
  17.         try {
  18.             // 1. 注册驱动
  19.             Class.forName(driver);
  20.             // 2. 获取连接
  21.             conn = DriverManager.getConnection(url, user, password);
  22.             // 3. 获取预编译的数据操作对象
  23.             String sql = "update emp set ename = ?, job = ?, sal = ? where empno = ?";
  24.             // 预编译SQL语句
  25.             pstmt = conn.prepareStatement(sql);
  26.             // 给 ? 传值
  27.             pstmt.setString(1, "李四");
  28.             pstmt.setString(2, "产品经理");
  29.             pstmt.setDouble(3, 5000.0);
  30.             pstmt.setInt(4, 8888);
  31.             // 4. 执行SQL语句
  32.             int count = pstmt.executeUpdate();
  33.             if (1 == count) {
  34.                 System.out.println("成功更新" + count + "条记录");
  35.             }
  36.         } catch (Exception e) {
  37.             e.printStackTrace();
  38.         } finally {
  39.             // 6. 释放资源
  40.             if (pstmt != null) {
  41.                 try {
  42.                     pstmt.close();
  43.                 } catch (SQLException e) {
  44.                     throw new RuntimeException(e);
  45.                 }
  46.             }
  47.             if (conn != null) {
  48.                 try {
  49.                     conn.close();
  50.                 } catch (SQLException e) {
  51.                     throw new RuntimeException(e);
  52.                 }
  53.             }
  54.         }
  55.     }
  56. }
复制代码
执行结果如下:

删除操作

需求:将员工编号为8888的删除。
  1. package com.powernode.jdbc;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.util.ResourceBundle;
  7. public class JDBCTest06 {
  8.     public static void main(String[] args) {
  9.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  10.         String driver = bundle.getString("driver");
  11.         String url = bundle.getString("url");
  12.         String user = bundle.getString("user");
  13.         String password = bundle.getString("password");
  14.         Connection conn = null;
  15.         PreparedStatement pstmt = null;
  16.         try {
  17.             // 1. 注册驱动
  18.             Class.forName(driver);
  19.             // 2. 获取连接
  20.             conn = DriverManager.getConnection(url, user, password);
  21.             // 3. 获取预编译的数据操作对象
  22.             String sql = "delete from emp where empno = ?";
  23.             // 预编译SQL语句
  24.             pstmt = conn.prepareStatement(sql);
  25.             // 给 ? 传值
  26.             pstmt.setInt(1, 8888);
  27.             // 4. 执行SQL语句
  28.             int count = pstmt.executeUpdate();
  29.             if (1 == count) {
  30.                 System.out.println("成功更新" + count + "条记录");
  31.             }
  32.         } catch (Exception e) {
  33.             e.printStackTrace();
  34.         } finally {
  35.             // 6. 释放资源
  36.             if (pstmt != null) {
  37.                 try {
  38.                     pstmt.close();
  39.                 } catch (SQLException e) {
  40.                     throw new RuntimeException(e);
  41.                 }
  42.             }
  43.             if (conn != null) {
  44.                 try {
  45.                     conn.close();
  46.                 } catch (SQLException e) {
  47.                     throw new RuntimeException(e);
  48.                 }
  49.             }
  50.         }
  51.     }
  52. }
复制代码
执行结果如下:

含糊查询

需求:查询员工名字中第二个字母是 O 的。
  1. package com.powernode.jdbc;
  2. import java.sql.*;
  3. import java.util.ResourceBundle;
  4. public class JDBCTest07 {
  5.     public static void main(String[] args) {
  6.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  7.         String driver = bundle.getString("driver");
  8.         String url = bundle.getString("url");
  9.         String user = bundle.getString("user");
  10.         String password = bundle.getString("password");
  11.         Connection conn = null;
  12.         PreparedStatement pstmt = null;
  13.         ResultSet rs = null;
  14.         try {
  15.             // 1. 注册驱动
  16.             Class.forName(driver);
  17.             // 2. 获取连接
  18.             conn = DriverManager.getConnection(url, user, password);
  19.             // 3. 获取预编译的数据操作对象
  20.             String sql = "select ename from emp where ename like ?";
  21.             // 预编译SQL语句
  22.             pstmt = conn.prepareStatement(sql);
  23.             // 给 ? 传值
  24.             pstmt.setString(1, "_O%");
  25.             // 4. 执行SQL语句
  26.             rs = pstmt.executeQuery();
  27.             // 5. 处理查询结果集
  28.             while (rs.next()) {
  29.                 String ename = rs.getString("ename");
  30.                 System.out.println(ename);
  31.             }
  32.         } catch (Exception e) {
  33.             e.printStackTrace();
  34.         } finally {
  35.             // 6. 释放资源
  36.             if (rs != null) {
  37.                 try {
  38.                     rs.close();
  39.                 } catch (SQLException e) {
  40.                     throw new RuntimeException(e);
  41.                 }
  42.             }
  43.             if (pstmt != null) {
  44.                 try {
  45.                     pstmt.close();
  46.                 } catch (SQLException e) {
  47.                     throw new RuntimeException(e);
  48.                 }
  49.             }
  50.             if (conn != null) {
  51.                 try {
  52.                     conn.close();
  53.                 } catch (SQLException e) {
  54.                     throw new RuntimeException(e);
  55.                 }
  56.             }
  57.         }
  58.     }
  59. }
复制代码
执行结果如下:


通过这个例子主要告诉大家,步伐不能如许写:
  1. String sql = "select ename from emp where ename like '_?%'";
  2. pstmt.setString(1, "O");
复制代码
由于占位符 ? 被单引号包裹,因此这个占位符是无效的。
分页查询

对于MySQL来说,通用的分页SQL语句:
假设每页显示3条记载:pageSize = 3
第1页:limit 0, 3
第2页:limit 3, 3
第3页:limit 6, 3
第pageNo页:limit (pageNo - 1)*pageSize, pageSize
需求:查询所有员工姓名,每页显示3条(pageSize),显示第2页(pageNo)。
  1. package com.powernode.jdbc;
  2. import java.sql.*;
  3. import java.util.ResourceBundle;
  4. public class JDBCTest08 {
  5.     public static void main(String[] args) {
  6.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  7.         String driver = bundle.getString("driver");
  8.         String url = bundle.getString("url");
  9.         String user = bundle.getString("user");
  10.         String password = bundle.getString("password");
  11.         Connection conn = null;
  12.         PreparedStatement pstmt = null;
  13.         ResultSet rs = null;
  14.         // 每页显示记录条数
  15.         int pageSize = 3;
  16.         // 显示第几页
  17.         int pageNo = 2;
  18.         try {
  19.             // 1. 注册驱动
  20.             Class.forName(driver);
  21.             // 2. 获取连接
  22.             conn = DriverManager.getConnection(url, user, password);
  23.             // 3. 获取预编译的数据操作对象
  24.             String sql = "select ename from emp limit ?, ?";
  25.             // 预编译SQL语句
  26.             pstmt = conn.prepareStatement(sql);
  27.             // 给 ? 传值
  28.             pstmt.setInt(1, (pageNo - 1) * pageSize);
  29.             pstmt.setInt(2, pageSize);
  30.             // 4. 执行SQL语句
  31.             rs = pstmt.executeQuery();
  32.             // 5. 处理查询结果集
  33.             while (rs.next()) {
  34.                 String ename = rs.getString("ename");
  35.                 System.out.println(ename);
  36.             }
  37.         } catch (Exception e) {
  38.             e.printStackTrace();
  39.         } finally {
  40.             // 6. 释放资源
  41.             if (rs != null) {
  42.                 try {
  43.                     rs.close();
  44.                 } catch (SQLException e) {
  45.                     throw new RuntimeException(e);
  46.                 }
  47.             }
  48.             if (pstmt != null) {
  49.                 try {
  50.                     pstmt.close();
  51.                 } catch (SQLException e) {
  52.                     throw new RuntimeException(e);
  53.                 }
  54.             }
  55.             if (conn != null) {
  56.                 try {
  57.                     conn.close();
  58.                 } catch (SQLException e) {
  59.                     throw new RuntimeException(e);
  60.                 }
  61.             }
  62.         }
  63.     }
  64. }
复制代码
执行结果如下:


blob数据的插入和读取

预备一张表:t_img,三个字段,一个id主键,一个图片名字name,一个img。建表语句如下:
  1. create table `t_img` (
  2.   `id` bigint primary key auto_increment,
  3.   `name` varchar(255),
  4.   `img` blob
  5. ) engine=innodb;
复制代码
预备一张图片:


需求1:向t_img 表中插入一张图片。
  1. package com.powernode.jdbc;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.sql.Connection;
  6. import java.sql.DriverManager;
  7. import java.sql.PreparedStatement;
  8. import java.sql.SQLException;
  9. import java.util.ResourceBundle;
  10. public class JDBCTest09 {
  11.     public static void main(String[] args) {
  12.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  13.         String driver = bundle.getString("driver");
  14.         String url = bundle.getString("url");
  15.         String user = bundle.getString("user");
  16.         String password = bundle.getString("password");
  17.         Connection conn = null;
  18.         PreparedStatement pstmt = null;
  19.         InputStream in = null;
  20.         try {
  21.             // 1. 注册驱动
  22.             Class.forName(driver);
  23.             // 2. 获取连接
  24.             conn = DriverManager.getConnection(url, user, password);
  25.             // 3. 获取预编译的数据操作对象
  26.             String sql = "insert into t_img(img) values(?)";
  27.             pstmt = conn.prepareStatement(sql);
  28.             // 获取文件输入流
  29.             in = new FileInputStream("d:/dog.jpg");
  30.             pstmt.setBlob(1, in);
  31.             // 4. 执行SQL语句
  32.             int count = pstmt.executeUpdate();
  33.             System.out.println("插入了" + count + "条记录");
  34.         } catch (Exception e) {
  35.             e.printStackTrace();
  36.         } finally {
  37.             // 6. 释放资源
  38.             if (in != null) {
  39.                 try {
  40.                     in.close();
  41.                 } catch (IOException e) {
  42.                     throw new RuntimeException(e);
  43.                 }
  44.             }
  45.             if (pstmt != null) {
  46.                 try {
  47.                     pstmt.close();
  48.                 } catch (SQLException e) {
  49.                     throw new RuntimeException(e);
  50.                 }
  51.             }
  52.             if (conn != null) {
  53.                 try {
  54.                     conn.close();
  55.                 } catch (SQLException e) {
  56.                     throw new RuntimeException(e);
  57.                 }
  58.             }
  59.         }
  60.     }
  61. }
复制代码
执行结果如下:


需求2:从t_img 表中读取一张图片。(从数据库中读取一张图片保存到本地。)
  1. package com.powernode.jdbc;
  2. import java.io.FileOutputStream;
  3. import java.io.InputStream;
  4. import java.io.OutputStream;
  5. import java.sql.*;
  6. import java.util.ResourceBundle;
  7. public class JDBCTest10 {
  8.     public static void main(String[] args) {
  9.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  10.         String driver = bundle.getString("driver");
  11.         String url = bundle.getString("url");
  12.         String user = bundle.getString("user");
  13.         String password = bundle.getString("password");
  14.         Connection conn = null;
  15.         PreparedStatement pstmt = null;
  16.         ResultSet rs = null;
  17.         try {
  18.             // 1. 注册驱动
  19.             Class.forName(driver);
  20.             // 2. 获取连接
  21.             conn = DriverManager.getConnection(url, user, password);
  22.             // 3. 获取预编译的数据操作对象
  23.             String sql = "select img from t_img where id = ?";
  24.             pstmt = conn.prepareStatement(sql);
  25.             pstmt.setInt(1, 1);
  26.             // 4. 执行SQL语句
  27.             rs = pstmt.executeQuery();
  28.             // 5. 处理查询结果集
  29.             if (rs.next()) {
  30.                 // 获取二进制大对象
  31.                 Blob img = rs.getBlob("img");
  32.                 // 获取输入流
  33.                 InputStream binaryStream = img.getBinaryStream();
  34.                 // 创建输出流,该输出流负责写到本地
  35.                 OutputStream out = new FileOutputStream("d:/dog2.jpg");
  36.                 byte[] bytes = new byte[1024];
  37.                 int readCount = 0;
  38.                 while ((readCount = binaryStream.read(bytes)) != -1) {
  39.                     out.write(bytes, 0, readCount);
  40.                 }
  41.                 out.flush();
  42.                 binaryStream.close();
  43.                 out.close();
  44.             }
  45.         } catch (Exception e) {
  46.             e.printStackTrace();
  47.         } finally {
  48.             // 6. 释放资源
  49.             if (rs != null) {
  50.                 try {
  51.                     rs.close();
  52.                 } catch (SQLException e) {
  53.                     throw new RuntimeException(e);
  54.                 }
  55.             }
  56.             if (pstmt != null) {
  57.                 try {
  58.                     pstmt.close();
  59.                 } catch (SQLException e) {
  60.                     throw new RuntimeException(e);
  61.                 }
  62.             }
  63.             if (conn != null) {
  64.                 try {
  65.                     conn.close();
  66.                 } catch (SQLException e) {
  67.                     throw new RuntimeException(e);
  68.                 }
  69.             }
  70.         }
  71.     }
  72. }
复制代码
执行完毕之后,查看一下图片大小是否和原图片相同,打开看看是否可以正常显示。
JDBC批处理操作

预备一张商品表:t_product建表语句如下:
  1. create table t_product(
  2.   id bigint primary key,
  3.   name varchar(255)
  4. );
复制代码
不使用批处理

不使用批处理,向 t_product 表中插入一万条商品信息,并记载耗时!
  1. package com.powernode.jdbc;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.util.ResourceBundle;
  7. public class NoBatchTest {
  8.     public static void main(String[] args) {
  9.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  10.         String driver = bundle.getString("driver");
  11.         String url = bundle.getString("url");
  12.         String user = bundle.getString("user");
  13.         String password = bundle.getString("password");
  14.         long begin = System.currentTimeMillis();
  15.         Connection conn = null;
  16.         PreparedStatement pstmt = null;
  17.         try {
  18.             // 1. 注册驱动
  19.             Class.forName(driver);
  20.             // 2. 获取连接
  21.             conn = DriverManager.getConnection(url, user, password);
  22.             // 3. 获取预编译的数据操作对象
  23.             String sql = "insert into t_product(id, name) values (?, ?)";
  24.             pstmt = conn.prepareStatement(sql);
  25.             int count = 0;
  26.             for (int i = 1; i <= 10000; i++) {
  27.                 pstmt.setInt(1, i);
  28.                 pstmt.setString(2, "product" + i);
  29.                 // 4. 执行SQL语句
  30.                 count += pstmt.executeUpdate();
  31.             }
  32.             System.out.println("插入了" + count + "条记录");
  33.         } catch (Exception e) {
  34.             e.printStackTrace();
  35.         } finally {
  36.             // 6. 释放资源
  37.             if (pstmt != null) {
  38.                 try {
  39.                     pstmt.close();
  40.                 } catch (SQLException e) {
  41.                     throw new RuntimeException(e);
  42.                 }
  43.             }
  44.             if (conn != null) {
  45.                 try {
  46.                     conn.close();
  47.                 } catch (SQLException e) {
  48.                     throw new RuntimeException(e);
  49.                 }
  50.             }
  51.         }
  52.         long end = System.currentTimeMillis();
  53.         System.out.println("总耗时" + (end - begin) + "毫秒");
  54.     }
  55. }
复制代码
执行结果如下:


使用批处理

使用批处理,向 t_product 表中插入一万条商品信息,并记载耗时!留意:启用批处理需要在URL背面添加这个的参数:rewriteBatchedStatements=true

  1. package com.powernode.jdbc;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6. import java.util.ResourceBundle;
  7. public class BatchTest {
  8.     public static void main(String[] args) {
  9.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  10.         String driver = bundle.getString("driver");
  11.         String url = bundle.getString("url");
  12.         String user = bundle.getString("user");
  13.         String password = bundle.getString("password");
  14.         long begin = System.currentTimeMillis();
  15.         Connection conn = null;
  16.         PreparedStatement pstmt = null;
  17.         try {
  18.             // 1. 注册驱动
  19.             Class.forName(driver);
  20.             // 2. 获取连接
  21.             conn = DriverManager.getConnection(url, user, password);
  22.             // 3. 获取预编译的数据操作对象
  23.             String sql = "insert into t_product(id, name) values (?, ?)";
  24.             pstmt = conn.prepareStatement(sql);
  25.             int count = 0;
  26.             for (int i = 1; i <= 10000; i++) {
  27.                 pstmt.setInt(1, i);
  28.                 pstmt.setString(2, "product" + i);
  29.                 pstmt.addBatch();
  30.             }
  31.             // 循环结束之后,再次执行批处理,防止数据丢失。
  32.             count += pstmt.executeBatch().length;
  33.             System.out.println("插入了" + count + "条记录");
  34.         } catch (Exception e) {
  35.             e.printStackTrace();
  36.         } finally {
  37.             // 6. 释放资源
  38.             if (pstmt != null) {
  39.                 try {
  40.                     pstmt.close();
  41.                 } catch (SQLException e) {
  42.                     throw new RuntimeException(e);
  43.                 }
  44.             }
  45.             if (conn != null) {
  46.                 try {
  47.                     conn.close();
  48.                 } catch (SQLException e) {
  49.                     throw new RuntimeException(e);
  50.                 }
  51.             }
  52.         }
  53.         long end = System.currentTimeMillis();
  54.         System.out.println("总耗时" + (end - begin) + "毫秒");
  55.     }
  56. }
复制代码
  1. public class JDBCTest18 {
  2.     public static void main(String[] args) {
  3.         long begin = System.currentTimeMillis();
  4.         Connection conn = null;
  5.         PreparedStatement ps = null;
  6.         try {
  7.             conn = DbUtils.getConnection();
  8.             String sql = "insert into t_batch(id,name) values(?,?)";
  9.             ps = conn.prepareStatement(sql);
  10.             int count = 0;
  11.             for (int i = 1; i <= 10000; i++) {
  12.                 ps.setLong(1, i);
  13.                 ps.setString(2, "batch" + i);
  14.                 // 打包
  15.                 ps.addBatch();
  16.                 // 如果打包够500个,则执行一次(则磁盘IO一次)
  17.                 if(i % 500 == 0){
  18.                     count += ps.executeBatch().length;
  19.                 }
  20.             }
  21.             // 循环结束之后,再次执行批处理,防止数据丢失。
  22.             count += ps.executeBatch().length;
  23.             System.out.println("插入了" + count + "条记录");
  24.         } catch (SQLException e) {
  25.             throw new RuntimeException(e);
  26.         } finally {
  27.             DbUtils.close(conn, ps, null);
  28.         }
  29.         long end = System.currentTimeMillis();
  30.         System.out.println("总耗时" + (end - begin) + "毫秒"); // 总耗时1652毫秒
  31.     }
  32. }
复制代码
执行结果如下:


在举行大数据量插入时,批处理为什么可以进步步伐的执行服从?
DbUtils工具类的封装

JDBC编程六步中,许多代码是重复出现的,可以为这些代码封装一个工具类。让JDBC代码变的更简洁。
  1. package com.powernode.jdbc;
  2. import java.sql.*;
  3. import java.util.ResourceBundle;
  4. /**
  5. * ClassName: DbUtils
  6. * Description: JDBC工具类
  7. * Datetime: 2024/4/10 22:29
  8. * Author: 老杜@动力节点
  9. * Version: 1.0
  10. */
  11. public class DbUtils {
  12.     private static String url;
  13.     private static String user;
  14.     private static String password;
  15.     static {
  16.         // 读取属性资源文件
  17.         ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.jdbc.jdbc");
  18.         String driver = bundle.getString("driver");
  19.         url = bundle.getString("url");
  20.         user = bundle.getString("user");
  21.         password = bundle.getString("password");
  22.         // 注册驱动
  23.         try {
  24.             Class.forName(driver);
  25.         } catch (ClassNotFoundException e) {
  26.             throw new RuntimeException(e);
  27.         }
  28.     }
  29.     /**
  30.      * 获取数据库连接
  31.      * @return
  32.      * @throws SQLException
  33.      */
  34.     public static Connection getConnection() throws SQLException {
  35.         Connection conn = DriverManager.getConnection(url, user, password);
  36.         return conn;
  37.     }
  38.     /**
  39.      * 释放资源
  40.      * @param conn 连接对象
  41.      * @param stmt 数据库操作对象
  42.      * @param rs 结果集对象
  43.      */
  44.     public static void close(Connection conn, Statement stmt, ResultSet rs){
  45.         if (rs != null) {
  46.             try {
  47.                 rs.close();
  48.             } catch (SQLException e) {
  49.                 throw new RuntimeException(e);
  50.             }
  51.         }
  52.         if (stmt != null) {
  53.             try {
  54.                 stmt.close();
  55.             } catch (SQLException e) {
  56.                 throw new RuntimeException(e);
  57.             }
  58.         }
  59.         if (conn != null) {
  60.             try {
  61.                 conn.close();
  62.             } catch (SQLException e) {
  63.                 throw new RuntimeException(e);
  64.             }
  65.         }
  66.     }
  67. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4