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服务器能够接收和发送的最大数据包大小。
👉解决措施:
- 以
管理员身份运行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语句。
👉目的:
为了提高效率
👉批处理方法原理:
-
每次设置完 “?” 之后,先不执行,先攒着pst.addBatch();
把要执行sgL先添加到批处理队伍中,在缓存中攒着。 -
等所有的"?"设置完了之后,一口气执行
💡tips:
如果缓冲区溢满的时候,它也会自动执行一次。
如果缓冲区没有溢出,那么会最后一起执行。
❓怎么开启批处理?
MySQL服务器端,默认批处理功能没有开启。需要通过参数告知mysql服务器,开启批处理功能。
- 在url后面再加一个参数
rewriteBatchedStatements=true
//告知mysql服务器开启批处理功能
String url="jdbc:mysql://localhost:3306/0225db?serverTimezone=UTC&rewriteBatchedStatements=true";
-
PreparedStatement对象调用
A:addBatch()
B:executeBatch()
-
不要把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 问题演示
👉如何管理事务?
- mysql默认是自动提交事务,每执行一条语句成功后,自动提交。
需要开启手动提交模式。
Connection连接对象.setAutoCommit(false)
;
//取消自动提交模式,开始手动提交模式
- sql执行成功,别忘了提交事务
Connection连接对象.commit();
- 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();
}
💡观察结果: 事务提交成功