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
);

在这里插入图片描述

3.2 问题演示

👉问题描述:

当我们尝试给上述刚建好的数据表t_user中的blob类型的字段photo存储图片时,可能发生以下问题:

  • A.blob类型的字段装不下很大的图片(比如6MB的图片)
  • B.数据表的字段的数据类型可以容纳JDBC传送的图片,但是传送图片的数据产生了异常,例如(com.mysql.cj.jdbc.exceptions.PacketTooBigException)

💡小tips:

在Mysql里,BLOB类型可以存储0-64K(小图片)的二进制字符串数据
MEDIUMBLOB类型可以存0-16MB的二进制形式的长文本数据(大图片)LONGBLOB类型可以存储0-4GB的二进制形式的极大文本数据(大图片)

案例:演示A问题

代码演示如下:

👉①测试64kb以下的图片:

在这里插入图片描述

@Test
    public void test01() throws SQLException, FileNotFoundException, UnsupportedEncodingException {
        Scanner input=new Scanner(System.in);

        System.out.print("请输入用户名:");
        String username=input.next();
        System.out.print("请输入密码:");
        String password=input.next();
        System.out.print("请输入图片路径:");
        String path=input.next();

        //连接数据库
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        String sql="INSERT INTO t_user VALUES(?,md5(?),?)";

       

        //测试图片路径:C:\Users\king\Desktop\桌面清理\starsky.png

        PreparedStatement pst = root.prepareStatement(sql);
        pst.setObject(1,username);
        pst.setObject(2,password);
        pst.setObject(3,new FileInputStream(path));//文件字节IO流包含文件,它自动把文件写进去了

        int len = pst.executeUpdate();
        System.out.println(len>0?"更新成功":"更新失败");


        pst.close();
        root.close();

    }

在这里插入图片描述
在这里插入图片描述
❓这里存在一个问题?

在sqlyog中对应的blob类型的字段接收来自JDBC传送的图片字节数据后,显示RIFF�,我的MySQL数据库的默认字符集已经是utf8mb4,我的sql yog上的字段确实是blob类型的,就是无法显示图片,只有图片的字节数据
在这里插入图片描述
在这里插入图片描述

💡tips:

为了时间效率,这个问题我先搁置一旁,如果有知道的大佬,欢迎评论区指正

👉② 测试6MB的图片:

在这里插入图片描述
代码演示:还是在刚才的代码上运行

在这里插入图片描述
在这里插入图片描述

👉 解决措施:将数据表photo字段的数据类型修改为"MEDIUMBLOB"类型,它可以容纳16MB及以下的图片字节数据。

  • ⭐sql yog改变字段数据类型的步骤如下:

在这里插入图片描述

  • ⭐ sql语句修改: ALTER TABLE t_user MODIFY photo MEDIUMBLOB;

在这里插入图片描述
代码演示:再次运行代码成功

在这里插入图片描述
在这里插入图片描述

案例:演示B问题

👉测试图片:

在这里插入图片描述

代码演示如下:

还是刚才上面的案例代码

在这里插入图片描述

/*
com.mysql.cj.jdbc.exceptions.PacketTooBigException: Packet for query is too large (6,671,689 > 4,194,304).
You can change this value on the server by setting the 'max_allowed_packet' variable.
*/

👉原因分析:

MySQL 8.0的默认max_allowed_packet参数值为4MB。
这个参数控制着MySQL服务器能够接收和发送的最大数据包大小。

👉解决措施:

  1. 管理员身份运行cmd命令,执行net stop 你自己的mysql的服务名称 命令,以停止MySQL服务,不然改了参数也没用。
net  stop  mysql80  #(mysql80是我自己起的服务名称)

在这里插入图片描述
2. 找到你的MySQL服务端的配置文件my.ini,修改max_allowed_packet的参数为16MB,保存更改,然后执行"net start 你自己的MySQL服务名称"命令重启mysql的服务,

在这里插入图片描述
在这里插入图片描述
再执行代码演示如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


四、获取自增长键值

4.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

在这里插入图片描述

4.2 问题演示

👉问题描述:

希望自增长的字段在添加完成后,可以及时的反馈给Java程序端。

👉解决方案:

(1)

Preparedstatement pst =   conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS):

ps:这里的statement是一个接口,它是Preparedstatement的父接口。

为何不用statement?因为它不支持占位符 ”?“的写法

Statement接口中有一个常量值
RETURN GENERATED KEYS,表示执行sql的同时,返回自增长的键值

(2)

ResultSet rs = pst.getGeneratedKeys();//返回的是一个结果集

👉注意:要想拿到ResultSet rs的内容,必须要先遍历判断一下,因为它的游标在刚开始时指向的是表头,而不是指向第一个自增列

案例:尝试给t_employee添加一条记录,并返回它的员工编号(自增长列

代码演示如下:

import org.junit.Test;

import java.sql.*;
import java.util.Date;
import java.util.Scanner;

public class TestSqlAuto {

    @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();

        //连接数据库,类似于网络编程中的socket
        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(?,?,?,?,?,?,?)";
        //此时对sql进行预编译,里面是带?的
        PreparedStatement pst = root.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);//Statement.RETURN_GENERATED_KEYS表示,执行sql后,返回自增长键值


        pst.setObject(1,ename);//这里的1代替第一个?,用ename变量的值代替第一个?的位置,下面的依次类推
        pst.setObject(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();//执行sql语句
        System.out.println(len>0?"添加成功":"添加失败");

        //执行完毕后,通过 PreparedStatement对象pst获取它的自增长键值对
        ResultSet generatedKeys = pst.getGeneratedKeys();//返回的是一个结果集
        while (generatedKeys.next()){//它必须要先遍历一下,让游标指向首行记录
            System.out.println("你的员工编号:"+generatedKeys.getObject(1));//此处只有一个自增长的键值
        }

        //释放资源
        input.close();
        pst.close();
        root.close();


    }
    
}

在这里插入图片描述
在这里插入图片描述


五、怎么执行批处理?

5.1 准备测试数据

👉 创建数据表t_department


CREATE TABLE `t_department` (
  `did` int NOT NULL AUTO_INCREMENT COMMENT '部门编号',
  `dname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工名称',
  `description` varchar(200) DEFAULT NULL COMMENT '员工简介',
  PRIMARY KEY (`did`),
  UNIQUE KEY `dname` (`dname`)
) ENGINE=InnoDB AUTO_INCREMENT=5009 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

在这里插入图片描述

5.2 问题演示

👉概述:

批处理就是执行一组sql语句,大多数情况下都是批量执行insert语句。

👉目的:

为了提高效率

👉批处理方法原理:

  1. 每次设置完 “?” 之后,先不执行,先攒着pst.addBatch();
    把要执行sgL先添加到批处理队伍中,在缓存中攒着。

  2. 等所有的"?"设置完了之后,一口气执行

💡tips:

如果缓冲区溢满的时候,它也会自动执行一次。
如果缓冲区没有溢出,那么会最后一起执行。

❓怎么开启批处理?

MySQL服务器端,默认批处理功能没有开启。需要通过参数告知mysql服务器,开启批处理功能。

  1. 在url后面再加一个参数 rewriteBatchedStatements=true
//告知mysql服务器开启批处理功能
 String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC&rewriteBatchedStatements=true";
  1. PreparedStatement对象调用
    A:addBatch()
    B:executeBatch()

  2. 不要把values写错value,因为value效率太低了

案例:给部门表批量添加1000条部门测试信息

👉ps: 部门表由于之前测试过案例程序,自增长量为5009

在这里插入图片描述

⚪A.不使用批处理功能插入数据:

代码演示如下:

 	@Test
    public void test01() throws SQLException {
        long start=System.currentTimeMillis();

        //开始连接数据库,相当于网络编程中的socket
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        String sql="INSERT INTO t_department VALUES(null,?,?)";//t_employee中第一个字段是自增工长列,可不用填写

        PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进去;相当于通过PreparedStatement对象把sql发送给MySQL服务端

        for (int i = 1; i <=1000 ; i++) {
            pst.setObject(1,"测试部门"+i);
            pst.setObject(2,"测试部门简介"+i);
            pst.executeUpdate();
        }


        //释放资源
        pst.close();
        root.close();


        long end=System.currentTimeMillis();
        //耗时:100141毫秒
        System.out.println("耗时:"+(end-start));

    }

⚪B.Java程序开了pst.executeBatch();//执行批处理命令,但是没告知MySQL服务器要开启批处理功能,不起作用,非常慢,必须二者才行。

不信?请看如下代码演示:

 	@Test
    //感觉还是很慢
    public void test02() throws SQLException {

        long start=System.currentTimeMillis();

        //开始连接数据库,相当于网络编程中的socket
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        String sql="INSERT INTO t_department VALUES(null,?,?)";//t_employee中第一个字段是自增工长列,可不用填写

        PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进去;相当于通过PreparedStatement对象把sql发送给MySQL服务端

        for (int i = 1; i <=1000 ; i++) {
            pst.setObject(1,"测试部门"+i);
            pst.setObject(2,"测试部门简介"+i);
            pst.addBatch();//把所有的sql语句放到批处理队伍中,于缓存中先攒一波
        }

        pst.executeBatch();//执行批处理命令

        //释放资源
        pst.close();
        root.close();


        long end=System.currentTimeMillis();
        //MySQL没有开启批处理功能,耗时:86547
        System.out.println("耗时:"+(end-start));
    }

⚪ C.使用批处理功能并用values的sql:

代码演示如下:

 	@Test
    //sql语句使用values,感觉飞快,坐火箭一样
    public void test03() throws SQLException {

        long start=System.currentTimeMillis();

        //开始连接数据库,相当于网络编程中的socket
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC&rewriteBatchedStatements=true";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        String sql="INSERT INTO t_department VALUES(null,?,?)";//t_employee中第一个字段是自增工长列,可不用填写

        PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进去;相当于通过PreparedStatement对象把sql发送给MySQL服务端

        for (int i = 1; i <=1000 ; i++) {
            pst.setObject(1,"测试部门"+i);
            pst.setObject(2,"测试部门简介"+i);
            pst.addBatch();//把所有的sql语句放到批处理队伍中,于缓存中先攒一波
        }

        pst.executeBatch();//执行批处理命令

        //释放资源
        pst.close();
        root.close();


        long end=System.currentTimeMillis();
        //MySQL开启批处理后,耗时:1614毫秒
        System.out.println("耗时:"+(end-start));

    }

⚪ D.使用批处理但使用value的sql:

代码演示如下:

	@Test
    //其他代码不变,观察sql语句insert 表名 value(值列表)的执行效率
    //value效率太低了
    public void test04() throws SQLException {

        long start=System.currentTimeMillis();

        //开始连接数据库,相当于网络编程中的socket
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC&rewriteBatchedStatements=true";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        String sql="INSERT INTO t_department VALUE(null,?,?)";//t_employee中第一个字段是自增工长列,可不用填写

        PreparedStatement pst = root.prepareStatement(sql);//把sql语句装进去;相当于通过PreparedStatement对象把sql发送给MySQL服务端

        for (int i = 1; i <=1000 ; i++) {
            pst.setObject(1,"测试部门"+i);
            pst.setObject(2,"测试部门简介"+i);
            pst.addBatch();//把所有的sql语句放到批处理队伍中,于缓存中先攒一波
        }

        pst.executeBatch();//执行批处理命令

        //释放资源
        pst.close();
        root.close();


        long end=System.currentTimeMillis();
        System.out.println("耗时:"+(end-start));//MySQL开启批处理后,耗时:87386

    }

👉以上四种方式运行耗时差异图如下所示:

在这里插入图片描述

在这里插入图片描述


六、怎么执行事务?

6.1 准备测试数据

👉创建数据表t_department


CREATE TABLE `t_department` (
  `did` int NOT NULL AUTO_INCREMENT COMMENT '部门编号',
  `dname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '员工名称',
  `description` varchar(200) DEFAULT NULL COMMENT '员工简介',
  PRIMARY KEY (`did`),
  UNIQUE KEY `dname` (`dname`)
) ENGINE=InnoDB AUTO_INCREMENT=5009 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

在这里插入图片描述

6.2 问题演示

👉如何管理事务?

  1. mysql默认是自动提交事务,每执行一条语句成功后,自动提交。
    需要开启手动提交模式。

Connection连接对象.setAutoCommit(false);
//取消自动提交模式,开始手动提交模式

  1. sql执行成功,别忘了提交事务

Connection连接对象.commit();

  1. sql执行失败,回滚事务

Connection连接对象.rollback();

案例:修改部门表中的 两条记录,故意分开用两个update语句来修改。

然后希望这两条update语句,构成一个事务,要么一起成功,然后提交,要么一起失败,还原回滚。
update t_department set description = ‘xx’ where did = 2;
update t_department set description = ‘yy’ where did = 3;

代码演示如下:

👉 ①故意写错sql2语句的,观察是否回滚

  	@Test
    //故意写错sql2语句的,观察是否回滚;观察结果:发生了回滚
    public void test01() throws SQLException {
        //开始连接本机的MySQL服务端
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        root.setAutoCommit(false);//不开启这个,更新失败后不能回滚,

        String sql1="update t_department set description = 'xx' where did = 2";
        String sql2="update t_department set description = 'yy' what did = 3";
        PreparedStatement pst1 = root.prepareStatement(sql1);//把sql1语句装进PreparedStatement对象中,准备发送
        PreparedStatement pst2 = root.prepareStatement(sql2);//把sql2语句装进PreparedStatement对象中,准备发送
        try {
            pst1.executeUpdate();//真正发送sql1语句
            pst2.executeUpdate();//真正发送sql2语句
            System.out.println("都更新成功了");
            root.commit();//提交事务
        } catch (SQLException e) {
            System.out.println("有一个更新失败了");
            root.rollback();//回滚事务
        }


        pst1.close();
        root.close();



    }

在这里插入图片描述
在这里插入图片描述

💡观察结果: 事务回滚成功

👉②更正之前写错的sql2语句,观察是否提交

	@Test
    //更正之前写错的sql2语句,观察是否提交;观察结果:发生了提交,数据已经更新
    public void test02() throws SQLException {
        //开始连接本机的MySQL服务端
        String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC";
        Connection root = DriverManager.getConnection(url, "root", "123456");

        root.setAutoCommit(false);//不开启这个,更新失败后不能回滚,

        String sql1="update t_department set description = 'xx' where did = 2";
        String sql2="update t_department set description = 'yy' where did = 3";
        PreparedStatement pst1 = root.prepareStatement(sql1);//把sql1语句装进PreparedStatement对象中,准备发送
        PreparedStatement pst2 = root.prepareStatement(sql2);//把sql2语句装进PreparedStatement对象中,准备发送
        try {
            pst1.executeUpdate();//真正发送sql1语句
            pst2.executeUpdate();//真正发送sql2语句
            System.out.println("都更新成功了");
            root.commit();//提交事务
        } catch (SQLException e) {
            System.out.println("有一个更新失败了");
            root.rollback();//回滚事务
        }


        pst1.close();
        pst2.close();

        //这里习惯上,在连接对象close之前,把连接重新设置为自动提交模式
        root.setAutoCommit(true);//虽然在本例中没有意义,但在数据库连接池中,连接会重复使用,在关闭(还给连接池时)需求恢复自动提交模式

        root.close();



    }

在这里插入图片描述
在这里插入图片描述

💡观察结果: 事务提交成功


评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌上少年,且听这风吟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值