JDBC的“那些事“之应当注意的问题(上)

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: JDBC的“那些事“之应当注意的问题(上)

Mysql版本:8.0.26

可视化客户端:sql yog

编译软件:IntelliJ IDEA 2019.2.4 x64

运行环境:win10 家庭中文版

jdk版本:1.8.0_361

前言

随着人工智能与大数据的不断发展,与数据库打交道越来越频繁,很多开发者都开启了学习数据库的脚步,相应地,特别是对Java开发者而言,使用jdbc与数据库的交互难免遇到各种问题,如避免sql注入攻击等问题。本篇着重介绍该部分。


提示:以下是本篇文章正文内容,下面案例可供参考

一、避免sql拼接问题

1.1 准备测试数据

👉创建数据表t_employee

CREATE TABLE `t_employee` (
  `eid` int NOT NULL AUTO_INCREMENT COMMENT '员工编号',
  `ename` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工姓名',
  `salary` double NOT NULL COMMENT '薪资',
  `commission_pct` decimal(3,2) DEFAULT NULL COMMENT '奖金比例',
  `birthday` date NOT NULL COMMENT '出生日期',
  `gender` enum('男','女') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '男' COMMENT '性别',
  `tel` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号码',
  `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱',
  `address` varchar(150) DEFAULT NULL COMMENT '地址',
  `work_place` set('北京','深圳','上海','武汉') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '北京' COMMENT '工作地点',
  `hiredate` date NOT NULL COMMENT '入职日期',
  `job_id` int DEFAULT NULL COMMENT '职位编号',
  `mid` int DEFAULT NULL COMMENT '领导编号',
  `did` int DEFAULT NULL COMMENT '部门编号',
  PRIMARY KEY (`eid`),
  KEY `job_id` (`job_id`),
  KEY `did` (`did`),
  KEY `mid` (`mid`),
  CONSTRAINT `t_employee_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `t_job` (`jid`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_ibfk_2` FOREIGN KEY (`did`) REFERENCES `t_department` (`did`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_ibfk_3` FOREIGN KEY (`mid`) REFERENCES `t_employee` (`eid`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_chk_1` CHECK ((`salary` > 0)),
  CONSTRAINT `t_employee_chk_2` CHECK ((`hiredate` > `birthday`))
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

1.2 问题演示

👉问题描述:

JDBC的SQL拼接问题指的是在使用JDBC进行数据库操作时,如果直接将用户输入的参数拼接到SQL语句中,可能会存在SQL注入的安全风险。

SQL注入是一种常见的网络攻击方式,攻击者通过在输入参数中注入恶意的SQL代码,从而可以执行非授权的数据库操作,例如删除或修改数据、窃取敏感信息等。

👉应对措施

在编写JDBC程序时应该避免直接将用户输入的参数拼接到SQL语句中,而应该使用预编译语句或参数化查询的方式来构建SQL语句。

👉何为参数化查询与预编译语句?

二者实现的基本思路都是一样的:都是将SQL语句和参数分开处理

代码演示如下:

//我们定义了一个以指定名字为条件查询特定用户信息的sql语句
//使用一个占位符"?"来代替where中name的值(这里用到了参数化查询技术)
String sql = "SELECT * FROM users WHERE name = ?";
//创建一个PreparedStatement对象包裹sql语句
/*
PreparedStatement与一般的Statment流不一样,它会将sql语句提前编译好,并将编译结果缓存起来,避免每次执行sql都需要编译的时间花销。(这里用到了预编译技术)
*/
PreparedStatement stmt = conn.prepareStatement(sql);
//用setString方法将参数值设置为"Jack"
//并将它传递给PreparedStatement对象的第一个参数位置,即"name= ?"的"?"位置
stmt.setString(1, "jack");
//执行executeQuery(),真正地发送sql语句过去查询并返回查询的结果集
ResultSet rs = stmt.executeQuery();

案例:尝试给t_employee表添加一条完整的记录,记录内容自定义

👉未使用参数化查询

代码演示如下:

@Test
    public void test01() throws SQLException {
        Scanner input=new Scanner(System.in);
        System.out.print("请输入姓名:");
        String ename=input.next();
        System.out.print("请输入薪资:");
        String salary=input.next();
        System.out.print("请输入出生日期:");
        String birthday=input.next();
        System.out.print("请输入性别:");
        String gender=input.next();
        System.out.print("请输入电话:");
        String tel=input.next();
        System.out.print("请输入邮箱:");
        String email=input.next();
        String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");
//        System.out.println(root);
        //把上面的数据插入到数据库中atguigu中的t_employee表中
        //这样一个个添加变量,太麻烦了
        String sql="insert into t_employee(ename,salary,birthday,gender,tel,email,hiredate) values('"+ename+"',"+salary+",'"+birthday+"','"+gender+"','"+tel+"','"+email+"',curdate())";
        PreparedStatement pst = root.prepareStatement(sql);
        int len = pst.executeUpdate();
        System.out.println(len>0?"添加成功":"添加失败");
        input.close();
        pst.close();
        root.close();
    }

👉使用参数化查询

代码演示如下:

@Test
    public void test02() throws SQLException {
        Scanner input=new Scanner(System.in);
        System.out.print("请输入姓名:");
        String ename=input.next();
        System.out.print("请输入薪资:");
        String salary=input.next();
        System.out.print("请输入出生日期:");
        String birthday=input.next();
        System.out.print("请输入性别:");
        String gender=input.next();
        System.out.print("请输入电话:");
        String tel=input.next();
        System.out.print("请输入邮箱:");
        String email=input.next();
        String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");
        //多加一步,把?用具体的变量、表达式等值代替
        String sql="insert into t_employee(ename,salary,birthday,gender,tel,email,hiredate) values(?,?,?,?,?,?,?)";
        PreparedStatement pst = root.prepareStatement(sql);
        pst.setObject(1,ename);//这里的1代替第一个?,用ename变量的值代替第一个?的位置
        pst.setObject(2,salary);//这里的2代替第一个?,用salary变量的值代替第二个?的位置,如下类推
        pst.setObject(3,birthday);
        pst.setObject(4,gender);
        pst.setObject(5,tel);
        pst.setObject(6,email);
        pst.setObject(7,new Date());
        int len = pst.executeUpdate();
        System.out.println(len>0?"添加成功":"添加失败");
        input.close();
        pst.close();
        root.close();
    }

二、避免sql注入问题

2.1 准备测试数据

👉创建数据表t_employee

CREATE TABLE `t_employee` (
  `eid` int NOT NULL AUTO_INCREMENT COMMENT '员工编号',
  `ename` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工姓名',
  `salary` double NOT NULL COMMENT '薪资',
  `commission_pct` decimal(3,2) DEFAULT NULL COMMENT '奖金比例',
  `birthday` date NOT NULL COMMENT '出生日期',
  `gender` enum('男','女') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '男' COMMENT '性别',
  `tel` char(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '手机号码',
  `email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱',
  `address` varchar(150) DEFAULT NULL COMMENT '地址',
  `work_place` set('北京','深圳','上海','武汉') CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '北京' COMMENT '工作地点',
  `hiredate` date NOT NULL COMMENT '入职日期',
  `job_id` int DEFAULT NULL COMMENT '职位编号',
  `mid` int DEFAULT NULL COMMENT '领导编号',
  `did` int DEFAULT NULL COMMENT '部门编号',
  PRIMARY KEY (`eid`),
  KEY `job_id` (`job_id`),
  KEY `did` (`did`),
  KEY `mid` (`mid`),
  CONSTRAINT `t_employee_ibfk_1` FOREIGN KEY (`job_id`) REFERENCES `t_job` (`jid`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_ibfk_2` FOREIGN KEY (`did`) REFERENCES `t_department` (`did`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_ibfk_3` FOREIGN KEY (`mid`) REFERENCES `t_employee` (`eid`) ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `t_employee_chk_1` CHECK ((`salary` > 0)),
  CONSTRAINT `t_employee_chk_2` CHECK ((`hiredate` > `birthday`))
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

2.2 问题演示

👉问题描述:

SQL注入是一种攻击技术,攻击者通过在应用程序中输入恶意的SQL语句,从而访问或篡改数据库中的数据。这种攻击技术可以导致数据泄露、数据损坏或者整个系统崩溃。

代码演示如下:

@Test
    public void test01() throws SQLException {
        Scanner input=new Scanner(System.in);
        System.out.print("请输入员工编号:");
        String eid=input.next();
        //输入 eid=1 or 1=1  表中的数据全都出现了,有问题,
        //连接数据看库
        String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");
    //存在sql拼接问题
        String sql="select * from t_employee where eid ="+eid;
        //使用了预编译技术
        PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进PreparedStatement对象中,准备发送
        ResultSet resultSet = pst.executeQuery();//返回一个结果集;真正发送sql
        //结果集中有一个 元数据对象
        //元数据:描述数据的数据,对数据进行描述的信息,例如:数据列有多少列,数据的列名称等
        //获取atguigu下的t_employeet表的元数据
        ResultSetMetaData metaData = pst.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集中有几列
        while (resultSet.next()){//循环查看表的每一行记录
            for (int i = 1; i <= columnCount ; i++) {//for循环遍历每一行记录的每一个列,即单元格
                System.out.print(resultSet.getObject(i)+"\t");
            }
            System.out.println();
        }
        input.close();
        pst.close();
        root.close();
    }

👉备注:

此处查询到的结果集中有一个 元数据对象metaData

元数据:描述数据的数据,对数据进行描述的信息。

例如:数据列有多少列,即数据的列名称

//获取atguigu下的t_employeet表的元数据
   ResultSetMetaData metaData = pst.getMetaData();
   int columnCount = metaData.getColumnCount();//获取结果集中有几列
   //for循环遍历元数据的每一个列,即单元格
   for (int i = 1; i <= columnCount ; i++) {
      //获取一行记录中每个单元格的内容
        System.out.print(resultSet.getObject(i)+"\t");
  }

👉原因分析:

当我输入 1 or 1=1 时,完全的sql语句如下:

#这是一个经典的sql注入攻击语句
select * from t_employee where eid =1 or 1=1 ;
#查询t_employee表中eid为1或1=1的员工信息

其中的“or 1"=1”条件会使查询返回所有记录,无论eid是否等于1。攻击者可以通过这种方式绕过身份验证和权限控制,获取到数据库中t_employeeb表的所有数据。

👉解决措施:

使用参数化查询

@Test
    public void test02() throws SQLException {
        Scanner input=new Scanner(System.in);
        System.out.print("请输入员工编号:");
        String eid=input.next();
        //输入 eid=1 or 1=1  表中的数据全都出现了,有问题,
        //连接数据看库
        String url="jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");
        //使用”?“避免sql注入问题
        String sql="select * from t_employee where eid = ?";
        //select * from t_employee where eid =1 or 1=1  ; 1=1 永远成立
        PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进PreparedStatement对象中,准备发送
        pst.setObject(1,eid);//这里1代表sql语句中的?,用eid的值代替?的位置
        ResultSet resultSet = pst.executeQuery();//返回一个结果集;真正发送sql
        //结果集中有一个 元数据对象
        //元数据:描述数据的数据,对数据进行描述的信息,例如:数据列有多少列,数据的列名称等
        //获取atguigu下的t_employeet表的元数据(即数据表中各个字段名)
        ResultSetMetaData metaData = pst.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集中有几列
        while (resultSet.next()){//循环查看表的每一行记录
            for (int i = 1; i <= columnCount ; i++) {//for循环遍历每一行记录的每一个列,即单元格
                System.out.print(resultSet.getObject(i)+"\t");
            }
            System.out.println();
        }
        input.close();
        pst.close();
        root.close();
    }


三、java程序读取图片等给MySQL数据库字段中的blob等二进制类型数据赋值

3.1 准备测试数据

👉 建立数据表t_user

CREATE TABLE `t_user` (
  `username` VARCHAR(20) NOT NULL,
  `password` VARCHAR(50) NOT NULL,
  `photo` BLOB
);

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
3月前
|
SQL Java 关系型数据库
JDBC详解
Java Database Connectivity(JDBC)是Java语言中用于访问关系型数据库的标准接口,定义了一组API,使Java程序能以统一方式连接、访问和操作不同关系型数据库,简化数据库操作,提高Java应用程序的可移植性和可维护性。使用JDBC进行数据库操作主要包括创建数据源对象、建立连接、构造和执行SQL语句及释放资源等步骤。此外,还可以使用Set系列方法设置值,并使用get系列方法获取查询结果。
83 2
JDBC详解
|
3月前
|
SQL 数据库
jdbc10
jdbc10
23 1
|
7月前
|
SQL Java 数据库连接
JDBC是真的啰里啰嗦啊-但是很重要!(下)
JDBC是真的啰里啰嗦啊-但是很重要!
30 1
|
7月前
|
Java 关系型数据库 MySQL
JDBC是真的啰里啰嗦啊-但是很重要!(上)
JDBC是真的啰里啰嗦啊-但是很重要!
42 1
|
XML 存储 Java
什么是jdbc?为什么要用jdbc
什么是jdbc?为什么要用jdbc
|
SQL Oracle 数据可视化
|
7月前
|
存储 Java 数据库连接
JDBC简单总结
JDBC简单总结
|
SQL 存储 关系型数据库
JDBC的“那些事“之应当注意的问题(中)
JDBC的“那些事“之应当注意的问题(中)
|
SQL 缓存 关系型数据库
JDBC的“那些事“之应当注意的问题(下)
JDBC的“那些事“之应当注意的问题(下)
|
Java 关系型数据库 MySQL
JDBC1.0
JDBC是JAVA十三大规范之一,由JAVA定义顶级接口API,各个数据库厂商去实现。常用的四大接口——Driver,DriverManager,Connection,Statement。 这里先给出结论: 所谓的JAVA制定标准,就是指JAVA给出接口,厂家各自去实现。 比如Driver,Connection,Statement等都是有接口,有实现。 通过向DriverManager注册不同Driver来确定Connection,Statement的类型
68 0