发布时间:2025-12-09 17:14:10 浏览次数:4
数据库:DB(DataBase)
概念:数据仓库,软件,安装在操作系统之上
作用:存储数据,管理数据
关系型数据库:SQL(Structured Query Language)
非关系型数据库:NoSQL(Not Only SQL)
DBMS(数据库管理系统)
所有的语句都要以分号结尾
show databases;--查看当前所有的数据库use 数据库名;--打开指定的数据库show tables;--查看所有的表describe/desc 表名;--显示表的信息create database 数据库名;--创建一个数据库exit--退出连接 ----单行注释#--单行注释/*...*/--多行注释1、创建数据库
CREATE DATABASE [IF NOT EXISTS] 数据库名;2、删除数据库
DROP DATABASE [if EXISTS] 数据库名;3、使用数据库
--如果表名或者字段名是特殊字符,则需要带``use 数据库名;4、查看数据库
SHOW DATABASES;数值
| tinyint | 十分小的数据 | 1个字节 |
| smallint | 较小的数据 | 2个字节 |
| mediumint | 中等大小的数据 | 3个字节 |
| int | 标准的整数 | 4个字节 |
| bigint | 较大的数据 | 8个字节 |
| float | 浮点数 | 4个字节 |
| double | 浮点数 | 8个字节 |
| decimal | 字符串形式的浮点数,一般用于金融计算 |
字符串
| char | 字符串固定大小 | 0~255 |
| varchar | 可变字符串 | 0~65535 |
| tinytext | 微型文本 | 2^8-1 |
| text | 文本串 | 2^16-1 |
时间日期
| date | 日期格式 | YYYY-MM-DD |
| time | 时间格式 | HH:mm:ss |
| datetime | 最常用的时间格式 | YYYY-MM-DD HH:mm:ss |
| timestamp | 时间戳,1970.1.1到现在的毫秒数 | |
| year | 年份表示 |
null
UnSigned
ZEROFILL
Auto_InCrement
通常理解为自增,自动在上一条记录的基础上默认+1
通常用来设计唯一的主键,必须是整数类型
可定义起始值和步长
NULL 和 NOT NULL
DEFAULT
拓展:每一个表,都必须存在以下五个字段:
| id | 主键 |
| version | 乐观锁 |
| is_delete | 伪删除 |
| gmt_create | 创建时间 |
| gmt_update | 修改时间 |
注意点:
表名和字段尽量使用``括起来
AUTO_INCREMENT 代表自增
所有的语句后面加逗号,最后一个不加
字符串使用单引号括起来
主键的声明一般放在最后,便于查看
不设置字符集编码的话,会使用MySQL默认的字符集编码Latin1,不支持中文,可以在my.ini里修改
格式:
CREATE TABLE IF NOT EXISTS `student`('字段名' 列类型 [属性] [索引] [注释],'字段名' 列类型 [属性] [索引] [注释],......'字段名' 列类型 [属性] [索引] [注释])[表的类型][字符集设置][注释]常用命令:
SHOW CREATE DATABASE 数据库名;-- 查看创建数据库的语句SHOW CREATE TABLE 表名;-- 查看表的定义语句DESC 表名;-- 显示表的具体结构INNODB
MYISAM
| 事务支持 | 不支持 | 支持 |
| 数据行锁定 | 不支持 | 支持 |
| 外键约束 | 不支持 | 支持 |
| 全文索引 | 支持 | 不支持 |
| 表空间大小 | 较小 | 较大,约为2倍 |
数据库文件存在的物理空间位置:
MySQL数据表以文件方式存放在磁盘中
MySQL在文件引擎上区别:
修改
修改表名 : ALTER TABLE 旧表名 RENAME AS 新表名
添加字段 : ALTER TABLE 表名 ADD字段名 列属性[属性]
修改字段 :
删除字段 : ALTER TABLE 表名 DROP 字段名
-- 修改表名-- ALTER TABLE 旧表名 RENAME AS 新表名ALTER TABLE teacher RENAME AS teachers;-- 增加表的字段-- ALTER TABLE 表名 ADD 字段名 列属性ALTER TABLE teachers ADD age INT(11);-- 修改表的字段(重命名,修改约束)-- ALTER TABLE 表名 MODIFY 字段名 [列属性];ALTER TABLE teachers MODIFY age VARCHAR(11);-- 修改约束-- ALTER TABLE 表名 CHANGE 旧名字 新名字 [列属性];ALTER TABLE teachers CHANGE age age1 INT(1);-- 字段重命名-- 删除表的字段-- ALTER TABLE 表名 DROP 字段名ALTER TABLE teachers DROP age1;删除
语法:DROP TABLE [IF EXISTS] 表名
所有的创建和删除尽量加上判断,以免报错~
外键概念
如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键。由此可见,外键表示了两个关系之间的相关联系。以另一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表。
在实际操作中,将一个表的值放入第二个表来表示关联,所使用的值是第一个表的主键值(在必要时可包括复合主键值)。此时,第二个表中保存这些值的属性称为外键(foreign key)。
外键作用:
保持数据一致性,完整性,主要目的是控制存储在外键表中的数据,约束。使两张表形成关联,外键只能引用外表中的列的值或使用空值。
目标:学生表(student)的gradeid字段 要去引用年级表(grade)的 gradeid字段
创建外键
方式一:在创建表的时候增加约束
/*1. 定义外键key2. 给外键添加约束(执行引用)references 引用*/CREATE TABLE IF NOT EXISTS `student`(`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学号',`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',`sex` VARCHAR(2)NOT NULL DEFAULT '女' COMMENT '性别',`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',`gradeid` INT(10) NOT NULL COMMENT '学生的年级',PRIMARY KEY (`id`),KEY `FK_gradeid` (`gradeid`),CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`))ENGINE=INNODB DEFAULT CHARSET=utf8-- 创建年级表CREATE TABLE `grade`(`gradeid` INT(10) NOT NULL COMMENT '年级id',`gradename` VARCHAR(50) NOT NULL COMMENT '年纪名称',PRIMARY KEY (`gradeid`))ENGINE=INNODB DEFAULT CHARSET=utf8删除有外键关系的表的时候,必须要先删除引用别人的表(从表),再删除被引用的表(主表)
方法二:创建表成功后,添加外键约束
/*1. 定义外键key2. 给外键添加约束(执行引用)references 引用*/CREATE TABLE IF NOT EXISTS `student`(`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学号',`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',`sex` VARCHAR(2)NOT NULL DEFAULT '女' COMMENT '性别',`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',`gradeid` INT(10) NOT NULL COMMENT '学生的年级',PRIMARY KEY (`id`))ENGINE=INNODB DEFAULT CHARSET=utf8-- 创建表的时候没有外键关系ALTER TABLE `student`ADD CONSTRAINT `FK_gradeid` FOREIGN KEY(`gradeid`) REFERENCES `grade`(`gradeid`);-- 创建年级表CREATE TABLE `grade`(`gradeid` INT(10) NOT NULL COMMENT '年级id',`gradename` VARCHAR(50) NOT NULL COMMENT '年纪名称',PRIMARY KEY (`gradeid`))ENGINE=INNODB DEFAULT CHARSET=utf8**实践
数据库的意义:数据存储,数据管理
Data Manipulation Luaguge:数据操作语言
语法:
INSERT INTO 表名([字段1,字段2..])VALUES('值1','值2'..),[('值1','值2'..)..];注意:
语法:
UPDATE 表名 SET 字段1=值1,[字段2=值2...] WHERE 条件[];关于WHERE条件语句:
| = | 等于 |
| <>或!= | 不等于 |
| > | 大于 |
| < | 小于 |
| <= | 小于等于 |
| >= | 大于等于 |
| BETWEEN…AND… | 闭合区间 |
| AND | 和 |
| OR | 或 |
语法:
DELETE FROM 表名 [WHERE 条件]关于DELETE删除的问题,重启数据库现象:
TRUNCATE
作用:完全清空一个数据库表,表的结构和索引约束不会变!
DELETE和TRUNCATE 的区别:
Data QueryLanguage 数据查询语言
SELECT [ALL | DISTINCT]{* | table.* | [table.field1[as alias1][,table.field2[as alias2]][,...]]}FROM table_name [as table_alias][left | right | inner join table_name2] -- 联合查询[WHERE ...] -- 指定结果需满足的条件[GROUP BY ...] -- 指定结果按照哪几个字段来分组[HAVING] -- 过滤分组的记录必须满足的次要条件[ORDER BY ...] -- 指定查询记录按一个或多个条件排序[LIMIT {[offset,]row_count | row_countOFFSET offset}]; -- 指定查询的记录从哪条至哪条前提配置:
-- 创建学校数据库CREATE DATABASE IF NOT EXISTS `school`;-- 用school数据库USE `school`;-- 创建年级表grade表CREATE TABLE `grade`(`GradeID` INT(11) NOT NULL AUTO_INCREMENT COMMENT '年级编号',`GradeName` VARCHAR(50) NOT NULL COMMENT '年纪名称',PRIMARY KEY(`GradeID`))ENGINE=INNODB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;-- 给grade表插入数据INSERT INTO `grade`(`GradeID`,`GradeName`) VALUES (1,'大一'),(2,'大二'),(3,'大三'),(4,'大四');-- 创建成绩result表CREATE TABLE `result`(`StudentNo` INT(4) NOT NULL COMMENT '学号',`SubjectNo` INT(4) NOT NULL COMMENT '考试编号',`ExamDate` DATETIME NOT NULL COMMENT '考试日期',`StudentResult` INT(4) NOT NULL COMMENT '考试成绩',KEY `SubjectNo` (`SubjectNo`))ENGINE=INNODB DEFAULT CHARSET=utf8;-- 给result表插入数据INSERT INTO `result`(`StudentNo`,`SubjectNo`,`ExamDate`,`StudentResult`) VALUES (1000,1,'2019-10-21 16:00:00',97),(1001,1,'2019-10-21 16:00:00',96),(1000,2,'2019-10-21 16:00:00',87),(1001,3,'2019-10-21 16:00:00',98);-- 创建学生表studentCREATE TABLE `student`(`StudentNo` INT(4) NOT NULL COMMENT '学号',`LoginPwd` VARCHAR(20) DEFAULT NULL,`StudentName` VARCHAR(20) DEFAULT NULL COMMENT '学生姓名',`Sex` TINYINT(1) DEFAULT NULL COMMENT '性别,取值0或1',`GradeID` INT(11) DEFAULT NULL COMMENT '年级编号',`Phone` VARCHAR(50) NOT NULL COMMENT '联系电话,允许为空,即可选输入',`Adress` VARCHAR(255) NOT NULL COMMENT '地址,允许为空,即可选输入',`BornDate` DATETIME DEFAULT NULL COMMENT '出生时间',`Email` VARCHAR(50) NOT NULL COMMENT '邮箱账号,允许为空,即可选输入',`IdentityCard` VARCHAR(18) DEFAULT NULL COMMENT '身份证号',PRIMARY KEY (`StudentNo`),UNIQUE KEY `IdentityCard` (`IdentityCard`),KEY `Email` (`Email`))ENGINE=MYISAM DEFAULT CHARSET=utf8;-- 给学生表插入数据INSERT INTO `student`(`StudentNo`,`LoginPwd`,`StudentName`,`Sex`,`GradeID`,`Phone`,`Adress`,`BornDate`,`Email`,`IdentityCard`) VALUES (1000,'1241','dsaf',1,2,'24357','unknow','2000-09-16 00:00:00','1231@qq.com','809809'),(1001,'1321','dfdj',0,2,'89900','unknow','2000-10-16 00:00:00','5971@qq.com','908697');-- 创建科目表CREATE TABLE `subject`(`SubjectNo` INT(11) NOT NULL AUTO_INCREMENT COMMENT '课程编号',`SubjectName` VARCHAR(50) DEFAULT NULL COMMENT '课程名称',`ClassHour` INT(4) DEFAULT NULL COMMENT '学时',`GradeID` INT(4) DEFAULT NULL COMMENT '年级编号',PRIMARY KEY (`SubjectNo`))ENGINE=INNODB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;-- 给科目表subject插入数据INSERT INTO `subject`(`SubjectNo`,`SubjectName`,`ClassHour`,`GradeID`) VALUES(1,'高数','96',2),(2,'大物','112',2),(3,'程序设计',64,3);SELECT 字段 FROM 表;语法:
SELECT 查询列表 FROM 表名;where 条件字句:检索数据中符合条件的值
语法:
select 查询列表 from 表名 where 筛选条件; -- 查询考试成绩在95~100之间的SELECT `StudentNo`,`StudentResult` FROM resultWHERE `StudentResult`>=95 AND `StudentResult`<=100;-- &&SELECT `StudentNo`,`StudentResult` FROM resultWHERE `StudentResult`>=95 && `StudentResult`<=100;-- BETWEEN ANDSELECT `StudentNo`,`StudentResult` FROM resultWHERE `StudentResult`BETWEEN 95 AND 100;-- 查询除了1000号以外的学生SELECT `StudentNo`,`StudentResult` FROM resultWHERE `StudentNo`!=1000;-- NOTSELECT `StudentNo`,`StudentResult` FROM resultWHERE NOT `StudentNo`=1000;-- 查询名字含d的同学SELECT `StudentNo`,`StudentName` FROM studentWHERE `StudentName` LIKE '%d%';-- 查询名字倒数第二个为d的同学SELECT `StudentNo`,`StudentName` FROM studentWHERE `StudentName` LIKE '%d_';-- 查询1000,1001学员SELECT `StudentNo`,`StudentName` FROM studentWHERE `StudentNo` IN (1000,1001);语法:
select 分组函数,分组后的字段from 表【where 筛选条件】group by 分组的字段【having 分组后的筛选】【order by 排序列表】区别:
| 分组前筛选 | where | 原始表 | group by的前面 |
| 分组后筛选 | having | 分组后的结果 | group by 的后面 |
自连接
自己的表和自己的表连接,核心:一张表拆为两张一样的表即可
-- 创建一个表CREATE TABLE `course` (`courseid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '课程id',`pid` INT(10) NOT NULL COMMENT '父课程id',`courseName` VARCHAR(50) NOT NULL COMMENT '课程名',PRIMARY KEY (`courseid`)) ENGINE=INNODB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8-- 插入数据INSERT INTO `course` (`courseid`, `pid`, `courseName`)VALUES('2','1','信息技术'),('3','1','软件开发'),('4','3','数据库'),('5','1','美术设计'),('6','3','web开发'),('7','5','ps技术'),('8','2','办公信息');得到下表:
将该表进行拆分:
| 1 | 2 | 信息技术 |
| 1 | 3 | 软件开发 |
| 1 | 5 | 美术设计 |
| 2 | 8 | 办公信息 |
| 3 | 4 | 数据库 |
| 3 | 6 | web开发 |
| 5 | 7 | ps技术 |
操作:查询父类对应的子类关系
| 信息技术 2 | 办公信息 4 |
| 软件开发 3 | 数据库 4、web开发 6 |
| 美术设计 5 | ps技术 7 |
排序
语法:
select 查询列表from 表where 筛选条件order by 排序列表 asc/desc| desc: | 降序 |
分页
语法:
select 查询列表from 表limit offset,pagesize;本质:在 where子句中嵌套一个子查询语句
-- 查询‘课程设计’的所有考试结果(学号,科目编号,成绩)降序排列-- 方式一:使用连接查询SELECT `StudentNo`,r.`SubjectNo`,`StudentResult`FROM result rINNER JOIN `subject` son r.StudentNo=s.SubjectNoWHERE SubjectName='课程设计'ORDER BY StudentResult DESC;-- 方式二:使用子查询(由里到外)SELECT StudentNo,SubjectNo,StudentResultfrom resultWHERE SubjectNo=(SELECT SubjectNo FROM `subject`WHERE SubjectName='课程设计')| max | 最大值 |
| min | 最小值 |
| sum | 和 |
| avg | 平均值 |
| count | 计算个数 |
MD5信息摘要算法(MD5 Message-Digest Algorithm)
要么都成功,要么都失败
SQL执行:A转账给BSQL执行:B收到A的钱将一组SQL放在一个批次中去执行
参考链接:https://blog.csdn.net/dengjili/article/details/82468576
| 原子性(Atomicity) | 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 |
| 一致性(Consistency) | 事务前后数据的完整性必须保持一致。 |
| 隔离性(Isolation) | 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。 |
| 持久性(Durability) | 事务一旦被提交则不可逆,被持久化到数据库中,接下来即使数据库发生故障也不应该对其有任何影响 |
隔离所导致的一些问题:
| 脏读 | 指一个事务读取了另外一个事务未提交的数据。 |
| 不可重复读 | 在一个事务内读取表中的某一行数据,多次读取结果不同。 |
| 虚读(幻读) | 是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。 |
在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别
读未提交:一个事务读取到其他事务未提交的数据;这种隔离级别下,查询不会加锁,一致性最差,会产生脏读、不可重复读、幻读的问题
读已提交:一个事务只能读取到其他事务已经提交的数据;该隔离级别避免了脏读问题的产生,但是不可重复读和幻读的问题仍然存在;
读提交事务隔离级别是大多数流行数据库的默认事务隔离级别,比如 Oracle,但是不是 MySQL 的默认隔离界别
可重复读:事务在执行过程中可以读取到其他事务已提交的新插入的数据,但是不能读取其他事务对数据的修改,也就是说多次读取同一记录的结果相同;该个里级别避免了脏读、不可重复度的问题,但是仍然无法避免幻读的问题
可重复读是MySQL默认的隔离级别
串行化:事务串行化执行,事务只能一个接着一个地执行,、,并且在执行过程中完全看不到其他事务对数据所做的更新;缺点是并发能力差,最严格的事务隔离,完全符合ACID原则,但是对性能影响比较大
| 读未提交(read-uncommitted) | 是 | 是 | 是 |
| 读已提交(read-committed) | 否 | 是 | 是 |
| 可重复读(repeatable-read) | 否 | 否 | 是 |
| 串行化(serializable) | 否 | 否 | 否 |
1️⃣ 关闭自动提交
SET autocommit=0;2️⃣ 事务开启
START TRANSACTION -- 标记一个事务的开始,从这个之后的sql都在同一个事务内3️⃣ 成功则提交,失败则回滚
-- 提交:持久化(成功)COMMIT -- 回滚:回到原来的样子(失败)ROLLBACK4️⃣ 事务结束
SET autocommit=1; -- 开启自动提交5️⃣ 其他操作
SAVEPOINT 保存点名; -- 设置一个事务的保存点ROLLBACK TO SAVEPOINT 保存点名; -- 回滚到保存点RELEASE SAVEPOINT 保存点名; -- 撤销保存点推荐阅读:MySQL索引背后的数据结构及算法原理
索引(Index)是帮助MySQL高效获取数据的数据结构。
唯一的标识,主键不可重复,只有一个列作为主键
默认的,快速定位特定数据
它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值
与主键索引的区别:主键索引只能有一个、唯一索引可以有多个
-- 直接创建唯一索引CREATE UNIQUE INDEX indexName ON tableName(columnName)-- 创建表的时候指定唯一索引CREATE TABLE tableName( ......UNIQUE INDEX [indexName] (columeName) ); -- 修改表结构添加唯一索引ALTER TABLE tableName ADD UNIQUE INDEX [indexName] (columnName)快速定位特定数据(百度搜索就是全文索引)
建表app_user:
CREATE TABLE `app_user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(50) DEFAULT '' COMMENT '用户昵称',`email` varchar(50) NOT NULL COMMENT '用户邮箱',`phone` varchar(20) DEFAULT '' COMMENT '手机号',`gender` tinyint(4) unsigned DEFAULT '0' COMMENT '性别(0:男;1:女)',`password` varchar(100) NOT NULL COMMENT '密码',`age` tinyint(4) DEFAULT '0' COMMENT '年龄',`create_time` datetime DEFAULT CURRENT_TIMESTAMP,`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATECURRENT_TIMESTAMP,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='app用户表'批量插入数据:100w
-- 1418错解决方案(创建函数前执行此语句)set global log_bin_trust_function_creators=true;-- 插入100万条数据DELIMITER $$-- 写函数之前要写的标志CREATE FUNCTION mock_data()-- 创建mock_data()函数RETURNS INTBEGINDECLARE num INT DEFAULT 1000000;DECLARE i INT DEFAULT 0;WHILE i < num DOINSERT INTO app_user(`name`, `email`, `phone`, `gender`, `password`, `age`)VALUES(CONCAT('用户', i), '24736743@qq.com', CONCAT('18', FLOOR(RAND()*(999999999-100000000)+100000000)),FLOOR(RAND()*2),UUID(), FLOOR(RAND()*100));SET i = i + 1;END WHILE;RETURN i;END;-- 执行函数SELECT mock_data(); -- 查询用户名为'用户9999'性能分析EXPLAIN SELECT * FROM app_user where name='用户99999'增加索引后测试
-- 给name列创建常规索引CREATE INDEX id_app_user_name ON app_user(`name`)-- 再测试EXPLAIN SELECT * FROM app_user where name='用户99999'用户信息存储在mysql数据库的user表中,对用户的管理本质上就是对这张表进行增删改查
-- 创建用户CREATE USER zsr IDENTIFIED BY '123456'-- 删除用户DROP USER zsr-- 修改当前用户密码SET PASSWORD = PASSWORD('200024')-- 修改指定用户密码SET PASSWORD FOR zsr = PASSWORD('200024')-- 重命名RENAME USER zsr to zsr2-- 用户授权(授予全部权限,除了给其他用户授权)GRANT ALLPRIVILEGES on *.* TO zsr2-- 查询权限SHOW GRANTS FOR zsr-- 查看root用户权限SHOW GRANTS FOR root@localhost-- 撤销权限REVOKE ALL PRIVILEGES ON *.* FROM zsr保证重要的数据不丢失、数据转义
方式一:直接拷贝物理文件,MySQL数据表以文件方式存放在磁盘中
包括表文件 , 数据文件 , 以及数据库的选项文件
位置 : Mysql安装目录\data\(目录名对应数据库名 , 该目录下文件名对应数据表)
方式二:可视化管理
Navicat打开要备份的数据库,然后点击新建备份
点击对象选择,这里可以自定义选择备份的表
选择完毕后,点击备份即可开始备份
等待备份完成,关闭,然后便可看到备份的文件
方式三:可视化管理
选中要导出的表,右键转储SQL文件
然就就可以得到.sql文件
方式四:命令mysqldump导出
# mysqldump -h主机 -u用户名 -p密码 数据库 [表1 表2 表3] >物理磁盘位置/文件名# 导出school数据库的cource grade student表到D:/school.sqlmysqldump -hlocalhost -uroot -p200024 school course grade student >D:/school.sql2️⃣ 导入mysql-connector-java
在项目目录下新建lib目录,放入jar包
3️⃣ 编写代码&测试
在src目录下新建JDBCDemo用来操作数据库
import java.sql.*;public class JDBCDemo {public static void main(String[] args) throws ClassNotFoundException, SQLException {//1.加载驱动Class.forName("com.mysql.cj.jdbc.Driver");//2.连接信息url,用户信息String url = "jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC";String username = "root";String password = "200024";//3.连接,获得数据库对象connectionConnection connection = DriverManager.getConnection(url, username, password);//4.获取执行sql的对象Statement statement = connection.createStatement();//5.执行sqlString sql = "select * from app_user where id<10";ResultSet resultSet = statement.executeQuery(sql);while (resultSet.next()) {System.out.println("id:" + resultSet.getObject("id") + "phone:" + resultSet.getObject("phone"));}//6.释放连接resultSet.close();statement.close();connection.close();}}DriverManager:驱动管理
//1.加载驱动Class.forName("com.mysql.cj.jdbc.Driver");本质上执行DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//3.连接,获得数据库对象connectionConnection connection = DriverManager.getConnection(url, username, password);connection代表数据库,因此可以设置事务自动提交,事务回滚等
Statement:执行sql的对象,用于向数据库发送SQL语句,想完成对数据库的增删改査,只需要通过这个对象向数据库发送增删改查语句即可
statement.executeQuery();//查询操作,返回结果statement.execute();//执行sqlstatement.executeUpdate();//用于增删改,返回受影响的行数
ResultSet:查询的结果集,封装了所有查询的结果
在src目录下新建db.properties,用于存放数据库配置信息
然后再src目录下新建JDBCUtils.java作为工具类
import java.io.InputStream;import java.sql.*;import java.util.Properties;public class JDBCUtils {private static String driver = null;private static String url = null;private static String username = null;private static String password = null;static {try {InputStream inputStream = JDBCDemo.class.getClassLoader().getResourceAsStream("db.properties");Properties properties = new Properties();properties.load(inputStream);driver = properties.getProperty("driver");url = properties.getProperty("url");username = properties.getProperty("username");password = properties.getProperty("password");//加载驱动Class.forName(driver);} catch (Exception e) {e.printStackTrace();}}//获取连接public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, username, password);}//释放连接资源public static void release(Connection connection, Statement statement, ResultSet resultSet) {if (resultSet != null) {try {resultSet.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if (statement != null) {try {statement.close();} catch (SQLException throwables) {throwables.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException throwables) {throwables.printStackTrace();}}}}修改JDBCDemo
import java.sql.*;public class JDBCDemo {public static void main(String[] args) throws SQLException {//获得数据库对象connectionConnection connection = JDBCUtils.getConnection();//获取sql执行对象statementStatement statement = connection.createStatement();//执行sqlString sql = "select * from app_user where id<10";ResultSet resultSet = statement.executeQuery(sql);while (resultSet.next()) {System.out.println("id:" + resultSet.getObject("id") + "phone:" + resultSet.getObject("phone"));}//释放连接JDBCUtils.release(connection, statement, resultSet);}}SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
sql注入案例:主函数中传入用户名,查找指定名字用户信息
import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;public class SQLInjection {public static void main(String[] args) throws SQLException {searchName("' or '1=1");}//查找指定名字用户信息public static void searchName(String username) throws SQLException {//获得数据库对象connectionConnection connection = JDBCUtils.getConnection();//获取sql执行对象statementStatement statement = connection.createStatement();//执行sqlString sql = "select * from app_user where name='" + username + "'";ResultSet resultSet = statement.executeQuery(sql);while (resultSet.next()) {System.out.println("id:" + resultSet.getObject("id") + "phone:" + resultSet.getObject("phone"));}//释放连接JDBCUtils.release(connection, statement, resultSet);}}结果:查询到了数据库中所有的数据
这里传入一个不是用户名,而是一个不合法字符串,却获取到了全部的数据,为什么呢?
拼接整条sql语句是select * from app_user where name=' ' or '1==1',其中1==1永远是真的,所以该sql语句相当于查询表中所有的数据;这就是sql注入,主要是字符串拼接引起的问题,十分危险!!
PreparedStatement是Statement的子类,与其相比,可以防止SQL注入,并且效率更高
同样测试sql注入案例
import java.sql.*;public class SQLInjection {public static void main(String[] args) throws SQLException {searchName("' 'or '1=1'");}//登录public static void searchName(String username) throws SQLException {//获得数据库对象connectionConnection connection = JDBCUtils.getConnection();//获取sql执行对象preparedStatement(预编译sql,先写不执行,参数用?表示)PreparedStatement preparedStatement = connection.prepareStatement("select * from app_user where name=?");//手动传参preparedStatement.setString(1, username);//执行sqlResultSet resultSet = preparedStatement.executeQuery();while (resultSet.next()) {System.out.println("id:" + resultSet.getObject("id") + "phone:" + resultSet.getObject("phone"));}//释放连接JDBCUtils.release(connection, preparedStatement, resultSet);}}
根据结果,PreparedStatement对象完美避免了sql注入问题
首先创建account表
CREATE TABLE account(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(40),money FLOAT);INSERT INTO account(name,money) VALUES('A',1000);INSERT INTO account(name,money) VALUES('B',1000);INSERT INTO account(name,money) VALUES('C',1000);
然后编写Java代码
运行结果:
如果两次更新之间加int x = 1 / 0;
则会报错,且事务执行失败,两条语句都不会执行成功