You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

63 KiB

title author top cover toc mathjax summary tags categories reprintPolicy abbrlink date coverImg img password
MySql TianZD true true true false 《MySQL》必知必会学习笔记 [数据库 MySql 学习笔记] [数据库] cc_by 6cac71de 2022-04-29 10:45:40 <nil> <nil> <nil>

[toc]

1、了解MySQL

数据库(DB,DataBase)

保存有组织的数据的容器(通常是一个文件或者一组文件)

注:数据库不同于数据库软件,数据库软件应成为DBMS(数据库管理系统)

数据库分类:

  • 关系型数据库SQL:通过表和表之间,行和列之间的关系进行数据的存储
    • MySQL
    • Oracle
    • Sql Server
    • DB2
    • SQLlite
  • 非关系型数据库NoSQL(Not Only SQL):对象存储,通过对象的自身的属性决定
    • Redis
    • MongDB

某种特定类型数据的结构化清单

列和数据类型

列:表中的一个字段。所有表都是由一个或多个列组成的。

数据类型(datatype) 所容许的数据的类型。每个表列都有相应的数据类型,它限制(或容许)该列中存储的数据。

行(row) 表中的一个记录。

主键

主键(primary key) 一列(或一组列),其值能够唯一区分表中每个行。

SQL

SQL简介

SQL(发音为字母S-Q-L或sequel)是结构化查询语言(Structured QueryLanguage)的缩写。 SQL是一种专门用来与数据库通信的语言

数据的所有存储、检索、管理和处理实际上是由数据库软件——DBMS( 数据库管理系统)完成的。 MySQL是一种DBMS,即它是一种数据库软件。

客户机-服务器软件

DBMS可分为两类:一类为基于共享文件系统的DBMS,另一类为基于客户机—服务器的DBMS。 前者(包括诸如Microsoft Access和FileMaker)用于桌面用途,通常不用于高端或更关键的应用。

MySQL、 Oracle以及Microsoft SQL Server等数据库是基于客户机—服务器的数据库。客户机—服务器应用分为两个不同的部分。 服务器部分是负责所有数据访问和处理的一个软件。这个软件运行在称为数据库服务器的计算机上。 与数据文件打交道的只有服务器软件。关于数据、数据添加、删除和数据更新的所有请求都由服务器软件完成。这些请求或更改来自运行客户机软件的计算机。 客户机是与用户打交道的软件。例如,如果你请求一个按字母顺序列出的产品表,则客户机软件通过网络提交该请求给服务器软件。服务器软件处理这个请求,根据需要过滤、丢弃和排序数据;然后把结果送回到你的客户机软件。

客户机和服务器软件可能安装在两台计算机或一台计算机上。不管它们在不在相同的计算机上,为进行所有数据库交互,客户机软件都要与服务器软件进行通信

MySQL

MySQL是一个关系型数据库管理系统,属于Oracle旗下产品,关系数据库将数据保存在不同的表中

  • 是最好的RDBMS(关系数据库管理系统)应用软件之一
  • 开源
  • 体积小、速度快,总体拥有成本低

2、使用MySQL

为了连接到MySQL,需要以下信息:

  • 主机名(计算机名)——如果连接到本地MySQL服务器, 为localhost;
  • 端口(如果使用默认端口3306之外的端口);
  • 一个合法的用户名;
  • 用户口令(如果需要)。
--使用root登录
mysql -u root -p;

--显示可用的数据库列表
SHOW databases;

--选择数据库
USE crashcourse;

--获得一个数据库内的表的列表
SHOW tables;

--显示数据库中所有表的信息
describe student;

--创建一个数据库
create databases school;

--显示表列
SHOW columns from customers;

--显示广泛的服务器状态信息
SHOW status;

--分别用来显示创建特定数据库或表的MySQL语句
SHOW create database;
SHOW create table;

--显示授予用户(所有用户或特定用户)的安全权限
SHOW Grants;

--显示服务器错误或警告消息
SHOW errors;
SHOW warnings;

--推出连接
exit;
--表示单行注释
/*
多行注释
*/

3、操作数据库

操作数据库——>操作数据库中的表——>操作数据库中表的数据

操作数据库

--增加数据库school1(在不存在时
CREATE DATABASES IF NOT EXISTS school1;	

--删除数据库
DROP DATABASE IF EXISTS school1;		

--使用数据库
USE school;		

--查看所有的数据库
SHOW DATABASES;

操作数据库表

创建数据库表

--创建数据库表
CREATE TABLE [IF NOT EXISTS] `表名`(
	`字段名` 列类型 [属性] [索引] [注释],
    `字段名` 列类型 [属性] [索引] [注释],
    `字段名` 列类型 [属性] [索引] [注释]
)[表类型] [字符集设置] [注释]
--实例
CREATE TABLE `student` (
  `id` int NOT NULL COMMENT '学员id',
  `name` varchar(100) NOT NULL COMMENT '学员姓名',
  `age` int NOT NULL COMMENT '学员年龄',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

--查看创建数据表table的语句
SHOW CREATE TABLE student;

--显示表的结构
DESC student;

ENGINE=InnoDB 表示采用的数据库引擎

数据库引擎主要有:

  • INNODB:安全性高,支持事务处理和多表多用户操作
  • MYISAM:节约空间,速度快
MYISAM INNODB
事务支持 不支持 支持
数据行锁定 不支持 支持
外键 不支持 支持
全文索引 支持 不支持
表空间大小 较小 较大,约为2倍

所有的数据库文件都储存在data目录下,本质还是文件的存储

修改删除表

--修改表名
ALTER TABLE teacher RENAME AS teacher1;

--增加表的字段  表名 ADD 字段名 列属性
ALTER TABLE teacher1 ADD age INT(11);

--修改表的字段,重命名,修改约束
ALTER TABLE teacher1 MODIFY age VARCHAR(11);	--修改约束:列属性
ALTER TABLE teacher1 CHANGE age age1 INT(1);	--修改约束:重命名

--删除表的字段
ALTER TABLE teacher1 DROP age1;

--删除表
DROP TABLE IF EXISTS teacher1;

MODIFY和CHANGE区别:

  • change用来字段重命名
  • modify不用来字段重命名,只能修改字段类型和约束

4、MySQL数据管理

外键

物理外键

数据库级别的外键,不建议使用,数据库之间有关联,容易导致混乱

  1. 创建表时添加外键
CREATE TABLE `student` (
  `id` int NOT NULL COMMENT '学员id',
  `name` varchar(100) NOT NULL COMMENT '学员姓名',
  `age` int NOT NULL COMMENT '学员年龄',
  `gradeid` int NOT NULL COMMENT `年级id`		--年级表的主键
  PRIMARY KEY (`id`),	--主键
  KEY `FK_gradeid` (`gradeid`),		--外键
  CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`)    --给外键添加约束
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3
  1. 创建后添加外键
ALTER TABLE `student`
ADD CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`)

最佳实践

  • 数据库就是单纯的表,只用来存储数据,只有行(数据)和列(字段)
  • 使用程序去实现外键功能

5、DML语言

数据库操作语言

  • insert添加
  • update修改
  • delete删除

insert添加

--插入语句
--insert into `表名` (`字段`) values('value1'),('value2')...
INSERT INTO `student`('name')
VALUES ('zhansan'),
('lisi');

INSERT INTO `student`('name','sex')
VALUES ('zhansan','男'),
('lisi','男');

字段可以省略

插入完整的行

INSERT INTO customers(cust_name,
    cust_contact,
    cust_email,
    cust_address,
    cust_city,
    cust_state,
    cust_zip,
    cust_country)
VALUES('Pep E.LaPew',
    NULL,
    NULL,
    '100 Main Street',
    'Los Angeles',
    'CA',
    '90046',
    'USA');

插入多个列

如果插入的顺序相同,那么可以在VALUES后再加一个括号,把另外的数据加上

插入检索出的数据

INSERT SELECT

--使用INSERT SELECT从custnew中将所有数据导入customers
INSERT INTO customers(cust_name,
                      cust_contact,
                      cust_email,
                      cust_address, 
                      cust_city,  
                      cust_state,    
                      cust_zip,    
                      cust_country)
SELECT cust_name,
		cust_contact,
        cust_email, 
        cust_address,  
        cust_city,    
        cust_state,  
        cust_zip,  
        cust_country
FROM custnew;

update修改

--修改单个属性
UPDATE `student` 		--修改的表
SET `name`='tianzhendong'	--修改的内容
WHERE id=1;		--修改的具体行,不指定条件会修改所有的行

--修改多个属性
UPDATE `student` 		
SET `name`='tianzhendong',`email`='xxxx@qq.com'		--修改的内容
WHERE id=1;	

--更新10005客户的邮件地址
UPDATE customers
SET cust_email='elmer@fudd.com'
WHERE cust_id = 10005;

--更新多个列
UPDATE customers
SET cust_email='120@fudd.com',
    cust_name = 'tian'
WHERE cust_id = 10005;

--删除某个列的值,可设置其为NULL
UPDATE customers
SET cust_email=NULL
WHERE cust_id = 10005;

删除

delete

--删除数据(避免这样写,会全部删除)
DELETE FROM `student`;

--删除指定数据
DELETE FROM `student`
WHERE id = 1;

truncate

可使用TRUNCATE TABLE语句,它完成相同的工作,但速度更快( TRUNCATE实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。

--清空student表,表的结构和索引约束不会变
TRUNCATE `student`;

区别

  • 相同点:都能删除数据,都不会删除表结构

  • 不同:

    • truncate重新设置自增列,计数器归零,delete不会影响自增
    • truncate不会影响事务

6、DQL查询数据

检索数据

--检索单个列
SELECT prod_name
FROM products;

--检索多个列
SELECT prod_id, prod_name, prod_price
FROM products;

--检索所有列
SELECT *
FROM products;

--只返回列中不同(唯一)的vend_id行
SELECT DISTINCT vend_id
FROM products;

--返回第一行或前几行
SELECT prod_name
FROM products
LIMIT 5;

--返回从行5开始的5行,检索出来的第一行为行0而不是行1。因此, LIMIT 1, 1将检索出第二行而不是第一行。如果没有足够的行只返回它能返回的那么多行。
SELECT prod_name
FROM products
LIMIT 5,5;

--使用完全限定的名字来引用列(同时使用表名和列字)
SELECT products.prod_name
FROM crashcourse.products;

多条SQL语句必须以分号(;)分隔。 MySQL如同多数DBMS一样,不需要在单条SQL语句后加分号。但特定的DBMS可能必须在单条SQL语句后加上分号。当然,如果愿意可以总是加上分号。事实上,即使不一定需要,但加上分号肯定没有坏处。如果你使用的是mysql命令行,必须加上分号来结束SQL语句。

SQL语句不区分大小写,因此SELECT与select是相同的。同样,写成Select也没有关系。 许多SQL开发人员喜欢对所有SQL关键字使用大写,而对所有列和表名使用小写,这样做使代码更易于阅读和调试。

在处理SQL语句时,其中所有空格都被忽略。 SQL语句可以在一行上给出,也可以分成许多行。多数SQL开发人 员认为将SQL语句分成多行更容易阅读和调试

排序检索数据

子句(clause) SQL语句由子句构成,有些子句是必需的,而有的是可选的。一个子句通常由一个关键字和所提供的数据组成。子句的例子有SELECT语句的FROM子句。

为了明确地排序用SELECT语句检索出的数据,可使用ORDER BY子句。 ORDER BY子句取一个或多个列的名字,据此对输出进行排序。

--指示MySQL对prod_name列以字母顺序排序数据
SELECT prod_name
FROM products
ORDER BY prod_name;

--检索3个列并按其中两个列对结果进行排序——首先按价格然后再按名称排序
SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price, prod_name;--仅在多个行具有相同的prod_price值时才对产品按prod_name进行排序

--按价格以降序排序产品最贵的排在最前面
SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price DESC;

--DESC关键字只应用到直接位于其前面的列名
SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price DESC, prod_name;
--在上例中只对prod_price列指定DESC对prod_name列不指定因此prod_price列以降序排序而prod_name列在每个价格内仍然按标准的升序排序

--使用ORDER BY和LIMIT的组合能够找出一个列中最高或最低的值
SELECT prod_id, prod_price, prod_name
FROM products
ORDER BY prod_price DESC
LIMIT 1;

与DESC相反的关键字是ASC(ASCENDING), 在升序排序时可以指定它。 但实际上, ASC没有多大用处,因为升序是默认的(如果既不指定ASC也不指定DESC,则假定为ASC)

在字典( dictionary)排序顺序中,A被视为与a相同,这是MySQL(和大多数数据库管理系统)的默认行为。但是,许多数据库管理员能够在需要时改变这种行为(如果你的数据库包含大量外语字符,可能必须这样做)。

过滤数据

在同时使用ORDER BY和WHERE子句时,应该让ORDER BY位于WHERE之后, 否则将会产生错误

--在SELECT语句中数据根据WHERE子句中指定的搜索条件进行过滤
SELECT prod_price, prod_name
FROM products
WHERE prod_price = 2.50;

--在SELECT语句中数据根据WHERE子句中指定的搜索条件进行过滤
SELECT prod_price, prod_name
FROM products
WHERE prod_price != 2.50;--等同于WHERE prod_price <> 2.50;

--检索价格在5美元和10美元之间的所有产品
SELECT prod_price, prod_name
FROM products
WHERE prod_price BETWEEN 5 AND 10;

--返回没有价格空prod_price字段不是价格为0的所有产品
SELECT prod_price, prod_name
FROM products
WHERE prod_price BETWEEN IS NULL;

组合WHERE子句

--AND OR IN
--检索由供应商1003制造且价格小于等于10美元的所有产品的名称和价格
SELECT prod_id, prod_price, prod_name
FROM products
WHERE vend_id = 1003 AND prod_id <=10;

--检索由任一个指定供应商制造的所有产品的产品名和价格
SELECT prod_id, prod_price, prod_name
FROM products
WHERE vend_id = 1003 OR vend_id = 1002;

--IN和OR具有相同的功能
--检索供应商1002和1003制造的所有产品
SELECT prod_id, prod_price, prod_name
FROM products
WHERE vend_id in(1002,1003)
ORDER BY prod_name;

-- SQL像多数语言一样在处理OR操作符前优先处理AND操作符组合使用时应使用圆括号明确地分组相应的操作符
SELECT prod_name, prod_price
FROM products
WHERE (vend_id = 1002 OR vend_id = 1003) AND prod_price>=10;

--NOT NOT否定跟在它之后的条件
--   1002  1003      的vend_id
--!=  <>好像没有区别只是not适用于复杂的语句
SELECT prod_id, prod_price, prod_name
FROM products
WHERE vend_id NOT IN (1002,1003)
ORDER BY prod_name;

用通配符进行过滤

前面介绍的所有操作符都是针对已知值进行过滤的。不管是匹配一个还是多个值,测试大于还是小于已知值,或者检查某个范围的值,共同点是过滤中使用的值都是已知的。但是,这种过滤方法并不是任何时候都好用。例如,怎样搜索产品名中包含文本anvil的所有产品?用简单的比较操作符肯定不行,必须使用通配符。利用通配符可创建比较特定数据的搜索模式。在这个例子中,如果你想找出名称包含anvil的所有产品,可构造一个通配符搜索模式,找出产品名中任何位置出现anvil的产品。

通配符:(wildcard) 用来匹配值的一部分的特殊字符。

搜索模式(search pattern)由字面值、通配符或两者组合构成的搜索条件。

为在搜索子句中使用通配符,必须使用LIKE操作符。 LIKE指示MySQL,后跟的搜索模式利用通配符匹配而不是直接相等匹配进行比较。

谓词 操作符何时不是操作符?答案是在它作为谓词( predicate)时。从技术上说, LIKE是谓词而不是操作符。

--百分号 %通配符
--在搜索串中 %表示任何字符出现任意次数
--检索任意以jet起头的词 %告诉MySQL接受jet之后的任意字符不管它有多少字符
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE 'jet%';

--匹配任何位置包含文本anvil的值而不论它之前或之后出现什么字符
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE '%anvil%';

--找出以s起头以e结尾的所有产品
SELECT prod_id, prod_name
FROM products
WHERE prod_name LIKE 's%e';

--下划线 _通配符
--下划线的用途与%一样但下划线只匹配单个字符而不是多个字符_总是匹配一个字符不能多也不能少

注意:通配 符搜索的处理一般要比前面讨论的其他搜索所花时间更长。不要过度使用通配符。如果其他操作符能达到相同的目的,应该使用其他操作符。

  • 在确实需要使用通配符时,除非绝对有必要,否则不要把它们用在搜索模式的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
  • 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

正则表达式

MySQL中的正则表达式匹配(自版本3.23.4后)不区分大小写(即,大写和小写都匹配)。为区分大 小写,可使用BINARY关键字,如WHERE prod_name REGEXPBINARY 'JetPack .000'。

LIKE匹配整个列。如果被匹配的文本在列值中出现, LIKE将不会找到它,相应的行也不被返回(除非使用 通配符)。而REGEXP在列值内进行匹配,如果被匹配的文本在列值中出现, REGEXP将会找到它,相应的行将被返回。这是一个非常重要的差别。

--REGEXP
--检索列prod_name包含文本1000的所有行
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP '1000';

--检索列prod_name包含文本000的所有行, .是正则表达式语言中一个特殊的字符它表示匹配任意一个字符因此 1000和2000都匹配
且返回
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP '.000';

--搜索两个串之一或者为这个串或者为另一个串),使用|
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP '1000|2000';

--匹配特定的字符
--正则表达式[123] Ton为[1|2|3] Ton的缩写也可以使用后者但是需要用[]来定义OR语句查找什么
-- /使用了正则表达式[123] Ton [123]定义一组字符它的意思是匹配1或2或3因此 1 ton和2 ton都匹配且返回没有3 ton
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP '[123] Ton';

--集合可用来定义要匹配的一个或多个字符
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP '[1-3] Ton';

--为了匹配特殊字符必须用\\为前导 \\-表示查找- \\.表示查找.
SELECT prod_id, prod_name
FROM products
WHERE prod_name REGEXP '\\.';

匹配字符类:

说明
[:alnum:] 任意字母和数字(同[a-zA-Z0-9])
[:alpha:] 任意字符(同[a-zA-Z])
[:blank:] 空格和制表(同[\t])
[:cntrl:] ASCII控制字符( ASCII 0到31和127)
[:digit:] 任意数字(同[0-9])
[:graph:] 与[:print:]相同,但不包括空格
[:lower:] 任意小写字母(同[a-z])
[:print:] 任意可打印字符
[:punct:] 既不在[:alnum:]又不在[:cntrl:]中的任意字符
[:space:] 包括空格在内的任意空白字符(同[\f\n\r\t\v])
[:upper:] 任意大写字母(同[A-Z])
[:xdigit:] 任意十六进制数字(同[a-fA-F0-9])

匹配多个示例

可以用正则表达式重复元字符来完成

元字符 说明
* 0个或多个匹配
+ 1个或多个匹配(等于{1,})
? 0个或1个匹配(等于{0,1})
{n} 指定数目的匹配
{n,} 不少于指定数目的匹配
{n,m} 匹配数目的范围( m不超过255)
SELECT prod_name
FROM products
WHERE prod_name REGEXP '\\([0-9] sticks?\\)';
-- \\(\\)使用\\匹配特殊字符
--sticks后跟?,使的s可有可无可以出现0次或者1次因此stick也会被匹配出来

--匹配连在一起的4位数字
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[[:digit:]]{4}';
--[:digit:]匹配任意数字因而它为数字的一个集合 {4}确切地要求它前面的字符任意数字出现4次所以[[:digit:]]{4}匹配连在一起的任意4位数字
--等同于下面
SELECT prod_name
FROM products
WHERE prod_name REGEXP '[0-9][0-9][0-9][0-9]';

定位符

特定位置的文本需要使用定位符

元 字 符 说 明
^ 文本的开始
$ 文本的结尾
:<: 词的开始
:>: 词的结尾

LIKE和REGEXP的不同在于, LIKE匹配整个串而REGEXP匹配子串。利用定位符,通过用^开始每个表达式,用$结束每个表达式,可以使REGEXP的作用与LIKE一样。

--找出以一个数包括以小数点开始的数开始的所有产品
SELECT prod_name
FROM products
WHERE prod_name REGEXP '^[0-9\\.]';
--^匹配串的开始因此 ^[0-9\\.]只在.或任意数字为串中第一个字符时才匹配它们

创建计算字段

存储在表中的数据都不是应用程序所需要的。我们需要直接从数据库中检索出转换、计算或格式化过的数据;而不是检索出数据,然后再在客户机应用程序或报告程序中重新格式化。

字段(field) 基本上与列( column) 的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。

拼接字段

拼接(concatenate) 将值联结到一起构成单个值。在MySQL的SELECT语句中,可使用Concat()函数来拼接两个列。

多数DBMS使用+或||来实现拼接,MySQL则使用Concat()函数来实现。当把SQL语句转换成MySQL语句时一定要把这个区别铭记在心。

--Concat()拼接串即把多个串连接起来形成一个较长的串Concat()需要一个或多个指定的串各个串之间用逗号分隔
SELECT Concat(vend_name, '(', vend_country, ')')
FROM vendors
ORDER BY vend_name;

输出:

image-20210726210622468

--删除数据右侧多余的空格来整理数据这可以使用MySQL的RTrim()函数来完成
SELECT Concat(Rtrim(vend_name), '(', Rtrim(vend_country), ')')
FROM vendors
ORDER BY vend_name;

Trim函数 MySQL除了支持RTrim()(正如刚才所见,它去掉串右边的空格),还支持LTrim()(去掉串左边的空格)以及Trim()(去掉串左右两边的空格)

别名

上述拼接过后实际上它没有名字,它只是一个值。如果仅在SQL查询工具中查看一下结果,这样没有什么不好。但是,一个未命名的列不能用于客户机应用中,因为客户机没有办法引用它。

为了解决这个问题, SQL支持列别名。 别名( alias) 是一个字段或值的替换名。别名用AS关键字赋予。

SELECT Concat(Rtrim(vend_name), '(', Rtrim(vend_country), ')') AS vend_title
FROM vendors
ORDER BY vend_name;

执行算数计算

--汇总物品的价格单价乘以订购数量
SELECT prod_id, quantity, item_price, quantity*item_price AS expanded_price
FROM orderitems
where order_num = 20005;

输出:

image-20210726210649949

使用数据处理函数

与其他大多数计算机语言一样, SQL支持利用函数来处理数据。函数一般是在数据上执行的,它给数据的转换和处理提供了方便。

使用函数

大多数SQL实现支持以下类型的函数。

  • 用于处理文本串(如删除或填充值,转换值为大写或小写)的文本函数。
  • 用于在数值数据上进行算术操作(如返回绝对值,进行代数运算)的数值函数。
  • 用于处理日期和时间值并从这些值中提取特定成分(例如,返回两个日期之差,检查日期有效性等)的日期和时间函数。
  • 返回DBMS正使用的特殊信息(如返回用户登录信息,检查版本细节)的系统函数。

文本处理函数

函 数 说 明
Left() 返回串左边的字符
Length() 返回串的长度
Locate() 找出串的一个子串
Lower() 将串转换为小写
LTrim() 去掉串左边的空格
Right() 返回串右边的字符
RTrim(a) 去掉串右边的空格
Soundex() 返回串的SOUNDEX值
SubString() 返回子串的字符
Upper(a) 将串转换为大写

SOUNDEX是一个将任何文本串转换为描述其语音表示的字母数字模式的算法。 SOUNDEX考虑了类似的发音字符和音节,使得能对串进行发音比较而不是字母比较。

日期和时间处理函数

日期和时间采用相应的数据类型和特殊的格式存储,以便能快速和有效地排序或过滤,并且节省物理存储空间。

一般,应用程序不使用用来存储日期和时间的格式,因此日期和时间函数总是被用来读取、统计和处理这些值。

函 数 说 明
AddDate() 增加一个日期(天、周等)
AddTime() 增加一个时间(时、分等)
CurDate() 返回当前日期
CurTime() 返回当前时间
Date() 返回日期时间的日期部分
DateDiff() 计算两个日期之差
Date_Add() 高度灵活的日期运算函数
Date_Format() 返回一个格式化的日期或时间串
Day() 返回一个日期的天数部分
DayOfWeek() 对于一个日期,返回对应的星期几
Hour() 返回一个时间的小时部分
Minute() 返回一个时间的分钟部分
Month() 返回一个日期的月份部分
Now() 返回当前日期和时间
Second() 返回一个时间的秒部分
Time() 返回一个日期时间的时间部分
Year() 返回一个日期的年份部分

无论你什么时候指定一个日期,不管是插入或更新表值还是用WHERE子句进行过滤,日期必须为格式yyyy-mm-dd。因此, 2005年9月1日,给出为2005-09-01。

SELECT cust_id ,order_num 
FROM orders 
WHERE DATE(order_date) ='2005-09-01';

--检索出2005年9月下的所有订单
--方法1
SELECT cust_id ,order_num 
FROM orders 
WHERE DATE(order_date) BETWEEN '2005-09-01' AND '2005-09-30';
--方法2
SELECT cust_id ,order_num 
FROM orders 
WHERE Year(order_date)=2005 AND MONTH(order_date)=9;

数值处理函数

数值处理函数仅处理数值数据。这些函数一般主要用于代数、三角或几何运算,因此没有串或日期—时间处理函数的使用那么频繁。

函 数 说 明
Abs() 返回一个数的绝对值
Cos() 返回一个角度的余弦
Exp() 返回一个数的指数值
Mod() 返回除操作的余数
Pi() 返回圆周率
Rand() 返回一个随机数
Sin() 返回一个角度的正弦
Sqrt() 返回一个数的平方根
Tan() 返回一个角度的正切

汇总数据

聚集函数

  • 确定表中行数(或者满足某个条件或包含某个特定值的行数)。
  • 获得表中行组的和。
  • 找出表列(或所有行或某些特定的行)的最大值、最小值和平均值

聚集函数( aggregate function) 运行在行组上,计算和返回单个值的函数。

函 数 说 明
AVG() 返回某列的平均值
COUNT() 返回某列的行数
MAX() 返回某列的最大值
MIN() 返回某列的最小值
SUM() 返回某列值之和

AVG()

AVG()只能用来确定特定数值列的平均值,而且列名必须作为函数参数给出。为了获得多个列的平均值,必须使用多个AVG()函数。

--AVG()通过对表中行数计数并计算特定列值之和求得该列的平均值 AVG()可用来返回所有列的平均值也可以用来返回特定列或行的平均值
--返回products表中所有产品的平均价格
SELECT AVG(prod_price) AS avg_price
FROM products;
--确定特定列或行的平均值 下面的例子返回特定供应商所提供产品的平均价格
SELECT AVG(prod_price) AS avg_price
FROM products
WHERE vend_id = 1003;

COUNT()

COUNT()函数进行计数。 可利用COUNT()确定表中行的数目或符合特定条件的行的数目。

COUNT()函数有两种使用方式。

  • 使用COUNT(*)对表中行的数目进行计数, 不管表列中包含的是空值( NULL)还是非空值。
  • 使用COUNT(column)对特定列中具有值的行进行计数,忽略NULL值

如果指定列名,则指定列的值为空的行被COUNT()函数忽略,但如果COUNT()函数中用的是星号( *),则不忽 略。

--返回customers表中客户的总数
SELECT COUNT(*) AS num_cust
FROM customers;

--对具有电子邮件地址的客户计数
SELECT COUNT(cust_email) AS num_cust
FROM customers;

MAX()函数\MIN()函数

MAX()返回指定列中的最大值。 MAX()要求指定列名

MIN()的功能正好与MAX()功能相反,它返回指定列的最小值

对非数值数据使用MAX()函数\MIN()函数,虽然MAX()函数\MIN()函数一般用来找出最大最小的数值或日期值,但MySQL允许将它用来返回任意列中的最大最小值,包括返回文本列中的最大最小值。在用于文本数据时,如果数据按相应的列排序,则MAX()函数\MIN()函数返回最后一行\第一行。

MAX()函数\MIN()函数忽略列值为NULL的行。

--返回products表中最贵的物品的价格
SELECT MAX(prod_price) AS max_price
FROM products;

--返回products表中最便宜物品的价格
SELECT MIN(prod_price) AS min_price
FROM products;

SUM()函数

SUM()用来返回指定列值的和(总计)。\

--检索所订购物品的总数所有quantity值之和
SELECT SUM(quantity) AS items_ordered
FROM orderitems
WHERE order_num = 20005;

--返回订单中所有物品价钱之和
SELECT SUM(quantity*item_price) AS total_price
FROM orderitems
WHERE order_num = 20005;

聚集不同的值

以上5个聚集函数都可以如下使用:

  • 对所有的行执行计算,指定ALL参数或不给参数(因为ALL是默认行为);
  • 只包含不同的值,指定DISTINCT参数

如果指定列名,则DISTINCT只能用于COUNT()。DISTINCT不能用于COUNT(*),因此不允许使用COUNT( DISTINCT),否则会产生错误。类似地, DISTINCT必须使用列名,不能用于计算或表达式

--返回特定供应商1003提供的产品的平均价格(相同的产品只会计算一次)
SELECT AVG( DISTINCT prod_price) AS avg_price
FROM products
WHERE vend_id = 1003;

组合聚集函数

目前为止的所有聚集函数例子都只涉及单个函数。但实际上SELECT语句可根据需要包含多个聚集函数。

SELECT COUNT(*) AS num_items,
    MIN(prod_price) AS price_min,
    Max(prod_price) AS price_max,
    AVG(prod_price) AS price_avg,
    SUM(prod_price) AS price_sum
FROM products;

分组数据

--返回vend_id=1005的数量
SELECT COUNT(*) AS num_prods 
FROM products
WHERE vend_id = 1005;

--创建数据分组,GROUP BY子句指示MySQL按vend_id排序并分组数据
SELECT vend_id, COUNT(*) AS num_prods 
FROM products
GROUP BY vend_id;

--过滤数量大于等于3的
--where只能过滤行不能过滤分组
--HAVING 用于过滤分组
SELECT vend_id, COUNT(*) AS num_prods 
FROM products
GROUP BY vend_id
HAVING COUNT(*)>=3;

--分组和排序
--GROUP BY子句用来按订单号order_num列分组数据以便SUM(*)函数能够返回总计订单价格 HAVING子句过滤数据使得只返回总计订单价格大于等于50的订单最后 用ORDERBY子句排序输出
SELECT order_num, SUM(quantity*item_price) AS ordertotal
FROM orderitems
GROUP BY order_num
HAVING SUM(quantity*item_price)>=50
ORDER BY ordertotal;

使用子查询

子查询

查询(query) 任何SQL语句都是查询。但此术语一般指SELECT语句

SELECT语句是SQL的查询。迄今为止我们所看到的所有SELECT语句都是简单查询,即从单个数据库表中检索数据的单条语句。

SQL还允许创建子查询( subquery) ,即嵌套在其他查询中的查询。

利用子查询进行过滤

--检索包含物品TNT2的所有订单的编号
SELECT order_num
FROM orderitems
WHERE prod_id = 'TNT2';
--检索具有前一步骤列出的订单编号的所有客户的ID
SELECT cust_id
FROM orders
WHERE order_num IN(20005,20007);

--把第一个查询返回订单号的那一个变为子查询组合两个查询
SELECT cust_id
FROM orders
WHERE order_num IN(SELECT order_num
                    FROM orderitems
                    WHERE prod_id = 'TNT2');

联结表

外键(foreign key):外键为某个表中的一列,它包含另一个表的主键值,定义了两个表之间的关系。

可伸缩性(scale) 能够适应不断增加的工作量而不失败。设计良好的数据库或应用程序称之为可伸缩性好( scale well) 。

联结是一种机制,用来在一条SELECT语句中关联表,因此称之为联结。使用特殊的语法,可以联结多个表返 回一组输出,联结在运行时关联表中正确的行。

创建联结

联结的创建非常简单,规定要联结的所有表以及它们如何关联即可。

--使用where联结
SELECT vend_name , prod_name, prod_price
FROM vendors, products
WHERE vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;

--使用INNER JOIN联结推荐
SELECT vend_name , prod_name, prod_price
FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id
ORDER BY vend_name, prod_name;

联结多个表

SQL对一条SELECT语句中可以联结的表的数目没有限制。创建联结的基本规则也相同。首先列出所有表,然后定义表之间的关系

SELECT prod_name, vend_name , prod_price, quantity
FROM orderitems,products,vendors, 
WHERE products.vend_id=vendors.vend_id 
AND orderitems.prod_id = products.prod_id 
AND order_num =20005
ORDER BY vend_name, prod_name;

创建高级联结

使用别名

-- 使用别名
SELECT cust_name, cust_contact
FROM customers AS c,orders AS o, orderitems AS oi
WHERE c.cust_id = o.cust_id
    AND oi.order_num = o.order_num
    AND prod_id = 'TNT2';

--自联结
--在同一个表中搜索
SELECT p1.prod_id, p1.prod_name
FROM products AS p1, products AS p2
WHERE p1.vend_id = p2.vend_id
    AND p2.prod_id = 'DTNTR';

组合查询

SELECT vend_id, prod_id ,prod_price
FROM products
WHERE prod_price<=5
UNION 
SELECT vend_id, prod_id ,prod_price
FROM products
WHERE vend_id IN(1001,1002);

--等同于
SELECT vend_id, prod_id ,prod_price
FROM products
WHERE prod_price<=5 OR vend_id IN (1001,1002);

UNION从查询结果集中自动去除了重复的行(换句话说,它的行为与单条SELECT语句中使用多个WHERE子句条件一样)。

使用UNION ALL, MySQL不取消重复的行。

在用UNION组合查询时,只能使用一条ORDER BY子句,它必须出现在最后一条SELECT语句之后。

全文本搜索

启用全文本搜索支持

一般在创建表时启用全文本搜索。 CREATE TABLE语句(第21章中介绍)接受FULLTEXT子句,它给出被索引列的一个逗号分隔的列表。

进行全文本搜索

使用两个函数Match()和Against()执行全文本搜索,其中Match()指定被搜索的列, Against()指定要使用的搜索表达式。

SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('rabbit');

--等同于
SELECT note_text
FROM productnotes
WHERE note_text LIKE '%rabbit%';

SELECT note_text
FROM productnotes
WHERE note_text REGEXP 'rabbit';

区别:

全文本搜索会根据匹配程度进行自动排序

使用查询扩展

找出所有提到anvils的注释。只有一个注释包含词anvils,但你还想找出可能与你的搜索有关的所有其他行,即使它们不包含词anvils。

在使用查询扩展时, MySQL对数据和索引进行两遍扫描来完成搜索:

SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('rabbit' WITH QUERY EXPANSION);

布尔文本搜索

--查询有heavy但没有rope的行
SELECT note_text
FROM productnotes
WHERE Match(note_text) Against('heavy -rope*' IN BOOLEAN MODE);

7、事务

要么都成功,要么都失败

A给B转账,步骤1:A钱减少500,步骤2:B钱增加500

ACID

参考:https://blog.csdn.net/dengjili/article/details/82468576

ACID,数据库事务正确执行的四个基本要素的缩写,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:

  • 原子性(atomicity,或称不可分割性):两个步骤1和2要么都成功,要么都失败,
  • 一致性(consistency):最终一致性,事务前后的数据完整性要保证一致,转账前后A和B最终总钱数不变
  • 隔离性(isolation,又称独立性):针对多个用户同时操作,排除其他事务对本次事务的影响
    • 同时C在给B转账,之间不会有影响
  • 持久性(durability):事务一旦提交则不可逆,被持久化到数据库中,事务结束后的数据不随着外界原因导致数据丢失
    • 转账事务提交前断电,重启后,恢复到原状,数据状态为转账前的
    • 转账事务提交后断电,重启后,数据状态为转账后的

隔离性导致的问题:事务的隔离级别:

  • **脏读:**一个事务读取了另外一个事务未提交的数据
  • **不可重复读:**在一个事务内读取表中的某一行数据,多次读取结果不同。(这个不一定是错误,只是某些场合不对)
  • **幻读(虚读):**是指在一个事务内读取到了别的事务插入的数据,导致前后读取数量总量不一致。(一般是行影响,如下图所示:多了一行)

执行事务

mysql是默认开启事务自动提交的

--mysql是默认开启事务自动提交的
SET autocommit =0;	--关闭
SET autocommit =1;	--开启

事务的完整过程:

--1.关闭自动提交
SET autocommit =0;	--关闭自动提交

--2.标记一个事务的开始,从这个开始后的sql都在同一个事务内
START TRANSACTION;
INSERT XX

--3.1提交:持久化(成功!)
COMMIT;

--3.2回滚:回到原来的样子(失败)
ROLLBACK;

--4.事务结束
SET autocommit =1;	--开启自动提交

--了解
SAVEPOINT 保存点名	--设置一个事务的保存点
ROLLBACK TO SAVEPOINT 保存点名	--回滚到保存点
RELEASE SAVEPOINT 保存点名		--撤销保存点

8、索引

索引index是帮助MySQL高效获取数据的数据结构

提取句子主干,就可以得到索引的本质:索引是数据结构,MySQL中常用的索引结构(索引底层的数据结构)有:B-TREE ,B+TREE ,HASH 等

优点

  • 索引大大减小了服务器需要扫描的数据量

  • 索引可以帮助服务器避免排序和临时表

  • 索引可以将随机IO变成顺序IO

  • 索引对于InnoDB(对索引支持行级锁)非常重要,因为它可以让查询锁更少的元组。在MySQL5.1和更新的版本中,InnoDB可以在服务器端过滤掉行后就释放锁,但在早期的MySQL版本中,InnoDB直到事务提交时才会解锁。对不需要的元组的加锁,会增加锁的开销,降低并发性。 InnoDB仅对需要访问的元组加锁,而索引能够减少InnoDB访问的元组数。但是只有在存储引擎层过滤掉那些不需要的数据才能达到这种目的。一旦索引不允许InnoDB那样做(即索引达不到过滤的目的),MySQL服务器只能对InnoDB返回的数据进行WHERE操作,此时,已经无法避免对那些元组加锁了。如果查询不能使用索引,MySQL会进行全表扫描,并锁住每一个元组,不管是否真正需要。

    • 关于InnoDB、索引和锁:InnoDB在二级索引上使用共享锁(读锁),但访问主键索引需要排他锁(写锁)

缺点

  • 虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存索引文件。
  • 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快。
  • 如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
  • 对于非常小的表,大部分情况下简单的全表扫描更高效;

索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

因此应该只为最经常查询和最经常排序的数据列建立索引。

MySQL里同一个数据表里的索引总数限制为16个。

索引的分类

--显示所有索引信息
SHOW INDEX FROM student;
  • 主键索引PRIMARY KEY

唯一的标识,一张表只能有一个主键索引,不允许重复、不允许为 NULL;

 ALTER TABLE TableName ADD PRIMARY KEY(column_list);
  • 唯一索引*UNIQUE KEY

避免重复的列出现,允许为 NULL 值,一张表可有多个唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一

ALTER TABLE TableName ADD UNIQUE (column_list); 
  • 常规索引KEY/INDEX

一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入;

ALTER TABLE TableName ADD INDEX IndexName(`字段名`(length));
  • 全文索引FullText
    • 在特定的数据库引擎下才有,MylSAIM
    • 快速定位数据,它查找的是文本中的关键词,主要用于全文检索。

索引原则

  • 索引不是越多越好
  • 不要对进程变动数据加索引
  • 小数据量的表不需要加索引
  • 索引一般加在常用来查询的字段上

9、权限管理

用户管理

--创建用户
CREATE USER username IDENTIFIED BY 'password';

--修改用户密码
USE mysql	--使用mysql数据库
ALTER USER 'test'@'localhost' IDENTIFIED WITH MYSQL_NATIVE_PASSWORD BY '新密码';	--%代替localhost表示所有ip

--用户名重命名
RENAME USER oldname TO newname;

--用户授权
GRANT ALL PRIVILEGES ON *.* TO username;--全部的表,所有权限,除了给别人授权

--查看指定用户权限
SHOW GRANTS FOR username;

--撤销权限  revoke哪些权限, 在哪个表, 给谁
REVOKE ALL PRIVILEGES ON *.* FROM username;

--删除用户
DROP USER username;

10、数据库备份

  • 直接拷贝物理文件
  • 在可视化工具中导出
  • 使用命令行mysqludmp

导出

mysqldump -h 主机 -u 用户名 -p 密码 数据库 表1 表2 表3 >物理磁盘位置/文件名
mysqldump -hlocalhost -uroot -p123456 school student >D:/a.sql

导入

--首先登陆数据库
source 物理磁盘位置/文件名.sql

11、规范数据库设计

为什么需要设计

当数据库比较复杂时,需要进行数据库设计

糟糕的设计:

  • 数据冗余,浪费空间
  • 数据增删麻烦,会产生异常
  • 程序性能差

良好的设计:

  • 节省内存空间
  • 保证数据库的完整性
  • 方便开发系统
  • 避免使用物理外键

软件开发中,关于数据库的设计:

  • 分析需求:分析业务和需要处理的数据库需求
  • 概要设计:设计关系图E-R图

设计数据库步骤(个人博客):

  1. 收集信息,分析需求
    1. 用户表(用户登陆注销、个人信息,写博客,创建分类)
    2. 分类表(文章分类,谁创建的)
    3. 文章表(文章的信息)
    4. 评论表(评论信息)
    5. 友链表(友情链接信息)
    6. 自定义表(可以不需要)(系统信息,某个关键的字,或者一些主题)key:value
  2. 标识实体(把需求落实到每个字段)
  3. 标识实体之间的关系
    1. 写博客:user->blog
    2. 创建分类:user->category
    3. 关注:user->user
    4. 评论:user->user->blog

三大范式

为什么需要数据规范化:

  • 信息重复
  • 更新异常
  • 插入异常
    • 无法正常显示信息
  • 删除异常
    • 丢失有效的信息

**三大范式:**规范数据库的设计

  • 第一范式(1NF)
    • 要求数据库表中的每一列都是不可分割的原子数据项
  • 第二范式(2NF)
    • 前提:满足第一范式
    • 每张表只描述一件事情
  • 第三范式(3NF)
    • 前提:满足第一范式和第二范式
    • 确保数据表中的每一列数据都和主键直接相关,而不能间接相关

规范性和性能的问题

  • 关联查询的表不能超过三个
  • 考虑商业化的需求和目标,数据库的性能更加重要
  • 在规范性能的问题的时候,需要适当的考虑一下规范性
  • 故意给某些表增加一些冗余字段(从多表查询变为单表查询)
  • 故意增加一些计算列(从大数据量降低为小数据量的查询)

12、JDBC

概述

数据库驱动

应用程序通过数据库驱动和数据库链接、打交道。不同的数据库有不同的驱动

应用程序——>MySQL驱动、Oracle驱动——>数据库

JDBC

架构中,没有什么是加一层解决不了的

SUN公司为了简化开发人员的(对数据库的统一)操作,提供了一个java操作数据库的规范,即JDBC

这些规范的实现由具体的厂商去做,对于开发人员,只需要掌握JDBC接口的操作即可

应用程序——>JDBC——>MySQL驱动、Oracle驱动——>数据库

第一个JDBC程序

  1. maven引入jdbc驱动,配置pom.xml

数据库版本为8.0.26

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
  1. 编写java程序
    1. 加载驱动
    2. 连接数据库Drivermanager
    3. 获得执行sql对象Statement
    4. 获得返回的结果集ResultSet
    5. 释放连接
public class JdbcTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        //1.加载驱动    固定写法
        Class.forName("com.mysql.cj.jdbc.Driver");

        //2.用户信息和url
        /*school:连接的数据库
        useUnicode=true&characterEncoding=utf8&useSSL=true:支持中文编码&字符集utf8&使用安全连接*/
        String url = "jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=true";
        String username = "root";
        String password = "123456";

        //3.连接成功,数据库对象Connection代表数据库对象
        Connection connection = DriverManager.getConnection(url, username, password);

        //4.执行sql对象   statement执行sql的对象
        Statement statement = connection.createStatement();

        //5.执行sql对象去执行sql,可能存在结果,查看连接结果
        //sql语句
        String sql = "SELECT * FROM student";
        //返回的结果集,结果集中封装了查询出来的全部结果
        ResultSet resultSet = statement.executeQuery(sql);
        //输出结果
        while (resultSet.next()) {
            System.out.println("id = " + resultSet.getObject("id"));
            System.out.println("name = " + resultSet.getObject("name"));
            System.out.println("age = " + resultSet.getObject("age"));
            System.out.println("==============");
        }

        //6.释放连接
        resultSet.close();
        statement.close();
        connection.close();
    }
}

JDBC对象解释

Connection

//3.连接成功,数据库对象Connection代表数据库对象
Connection connection = DriverManager.getConnection(url, username, password);
connection.rollback();  //事务回滚
connection.commit();    //事务提交
connection.setAutoCommit(true);   //设置事务自动提交

Statement

执行sql的对象的类

Statement statement = connection.createStatement();		//获取statement对象
ResultSet resultSet = statement.executeQuery(sql);  //查询操作,返回ResultSet结果集
boolean execute = statement.execute(sql);//执行任何sql
int i = statement.executeUpdate(sql);//更新、插入、删除,返回一个受影响的行数

ResultSet

查询的结果集

ResultSet resultSet = statement.executeQuery(sql);  //查询操作,返回ResultSet结果集

resultSet.getObject();  //返回结果,在不知道列的类型的情况下使用
resultSet.getString();
resultSet.getInt();
....

遍历操作

resultSet.beforeFirst();    //移动到最前面
resultSet.afterLast();      //移动到最后面
resultSet.next();           //移动到下一个
resultSet.previous();       //移动到前一个
resultSet.absolute(row);    //移动到指定行

释放资源

//6.释放连接
resultSet.close();
statement.close();
connection.close();

采用配置文件方式

新增db.properties文件

driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=utf8&useSSL=true
username = root
password = 123456

jdbc配置类:

package com.jdbc;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * @author TianZhendong
 * @date 2021/8/5
 */

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{
         //db.properties"为配置文件名
         InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
         Properties properties = new Properties();
         properties.load(in);
         driver=properties.getProperty("driver");
         url=properties.getProperty("url");
         username=properties.getProperty("username");
         password=properties.getProperty("password");
         //驱动加载
         Class.forName(driver);
      } catch (IOException | ClassNotFoundException e) {
         e.printStackTrace();
      }
   }

   /**
   * @Description: 获取连接
   * @Author: TianZD
   * @Date: 2021/8/5 19:15
   * @Param: []
   * @Return: java.sql.Connection
   */
   public static Connection getConnection() throws SQLException {
      return DriverManager.getConnection(url, username, password);
   }

   /**
   * @Description: 释放资源
   * @Author: TianZD
   * @Date: 2021/8/5 19:14
   * @Param: [conn, st, rs]
   * @Return: void
   */
   public static void release(Connection conn, Statement st, ResultSet rs) throws SQLException {
      if(rs!=null){
         rs.close();
      }
      if(st!=null){
         st.close();
      }
      if(conn!=null){
         conn.close();
      }
   }
}

java:

package com.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.jdbc.JdbcUtils;

/**
 * @program: JDBCDemo
 * @description: test
 * @author: TianZD
 * @create: 2021-08-05 19:22
 **/
public class InsertTest {
   public static void main(String[] args) {
      Connection connection = null;
      Statement statement = null;
      ResultSet resultSet = null;
      try {
         //获取数据库连接
         connection = JdbcUtils.getConnection();
         //获得sql的执行对象
         statement = connection.createStatement();
         String sql = "INSERT INTO student(id, name, age)" +
               " VALUES(3, 'stu3', '25')";
         int i = statement.executeUpdate(sql);
         if (i > 0) {
            System.out.println("插入成功!");
         }
      } catch (SQLException throwable) {
         throwable.printStackTrace();
      } finally {
         JdbcUtils.release(connection,statement, null);
      }

   }
}

SQL注入

SQL 注入(SQL Injection)是发生在 Web 程序中数据库层的安全漏洞,是网站存在最多也是最简单的漏洞。主要原因是程序对用户输入数据的合法性没有判断和处理,导致攻击者可以在 Web 应用程序中事先定义好的 SQL 语句中添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步获取到数据信息。

简而言之,SQL 注入就是在用户输入的字符串中加入 SQL 语句,如果在设计不良的程序中忽略了检查,那么这些注入进去的 SQL 语句就会被数据库服务器误认为是正常的 SQL 语句而运行,攻击者就可以执行计划外的命令或访问未被授权的数据。

PreparedStatement对象

可以防止SQL注入

本质是:把传递进来的参数当作字符

具体实现见DBCP部分代码

数据库连接池

数据库连接——执行完毕——释放

连接——释放很耗费资源

池化技术:准备一些预先的资源,过来就连接预先准备好的

  • 最小连接数:根据常用连接数确定

  • 最大连接数:业务最高承载上限

  • 排队等待

  • 等待超时

编写连接池,实现一个接口DataSource

开源数据源实现

  • DBCP
  • C3P0
  • Druid阿里巴巴

使用这些数据库连接池后,在项目开发中就不需要编写连接数据库代码了

DBCP

maven引入依赖:

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.26</version>
    </dependency>
    <dependency>
        <groupId>commons-dbcp</groupId>
        <artifactId>commons-dbcp</artifactId>
        <version>1.4</version>
    </dependency>
</dependencies>

编写dbcp.properties

#连接设置  该部分格式不能变
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/school
username=root
password=123456

#下面的都可以不要
#<!-- 初始化连接 -->
initialSize=10

#最大连接数量
maxActive=50

#<!-- 最大空闲连接 -->
maxIdle=20

#<!-- 最小空闲连接 -->
minIdle=5

#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=60000


#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

编写配置类

package com.jdbc;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 * @program: JDBCDemo
 * @description: test
 * @author: TianZD
 * @create: 2021-08-05 23:17
 **/
public class JdbcUtils_Dbcp {
   private static DataSource dataSource = null;
   static{
      try{
         //dbcp.properties为配置文件名
         InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcp.properties");
         Properties properties = new Properties();
         properties.load(in);

         //创建数据源  工厂模式
         dataSource = BasicDataSourceFactory.createDataSource(properties);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /**
    * @Description: 从数据源中获取连接
    * @Author: TianZD
    * @Date: 2021/8/5 19:15
    * @Param: []
    * @Return: java.sql.Connection
    */
   public static Connection getConnection() throws SQLException {
      return dataSource.getConnection();
   }

   /**
    * @Description: 释放资源
    * @Author: TianZD
    * @Date: 2021/8/5 19:14
    * @Param: [conn, st, rs]
    * @Return: void
    */
   public static void release(Connection conn, Statement st, ResultSet rs) {
      if(rs!=null){
         try {
            rs.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
      if(st!=null){
         try {
            st.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
      if(conn!=null){
         try {
            conn.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
   }
}

测试

package com.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @program: JDBCDemo
 * @description:
 * @author: TianZD
 * @create: 2021-08-05 23:23
 **/
public class TestDbcp {
   public static void main(String[] args) {
      Connection connection = null;
      PreparedStatement preparedStatement = null;
      try {
         connection = JdbcUtils_Dbcp.getConnection();
         //sql语句,其中value值先不赋予

         String sql = "insert into student(id, `name`, `age`) values(?,?,?)";

         //预先编译sql,先写sql,但不执行
         preparedStatement = connection.prepareStatement(sql);

         //手动value赋值
         preparedStatement.setInt(1, 3);
         preparedStatement.setString(2, "tian");
         preparedStatement.setInt(3, 25);

         //执行
         int i = preparedStatement.executeUpdate();
         if (i > 0) {
            System.out.println("插入成功!");
         }
      } catch (SQLException throwables) {
         throwables.printStackTrace();
      } finally {
         JdbcUtils_Dbcp.release(connection, preparedStatement, null);
      }
   }
}

C3P0

maven引入依赖

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
    </dependencies>

配置文件,名字必须为c3p0-config

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!--默认配置-->
    <default-config>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
    </default-config>

    <!--配置连接池mysql-->
    <named-config name="MySQL">
        <property name="driverClass">com.mysql.cj.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/school</property>
        <property name="user">root</property>
        <property name="password">123456</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
    </named-config>

</c3p0-config>

配置类

package com.jdbc;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * @program: JDBCDemo
 * @description:
 * @author: TianZD
 * @create: 2021-08-05 23:41
 **/
public class JdbcUtils_C3P0 {
   private static DataSource dataSource = null;
   static{
      try{
         //创建数据源  工厂模式
         dataSource = new ComboPooledDataSource("MySQL");
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /**
    * @Description: 从数据源中获取连接
    * @Author: TianZD
    * @Date: 2021/8/5 19:15
    * @Param: []
    * @Return: java.sql.Connection
    */
   public static Connection getConnection() throws SQLException {
      return dataSource.getConnection();
   }

   /**
    * @Description: 释放资源
    * @Author: TianZD
    * @Date: 2021/8/5 19:14
    * @Param: [conn, st, rs]
    * @Return: void
    */
   public static void release(Connection conn, Statement st, ResultSet rs) {
      if(rs!=null){
         try {
            rs.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
      if(st!=null){
         try {
            st.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
      if(conn!=null){
         try {
            conn.close();
         } catch (SQLException e) {
            e.printStackTrace();
         }
      }
   }
}

测试

package com.jdbc;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * @program: JDBCDemo
 * @description:
 * @author: TianZD
 * @create: 2021-08-05 23:45
 **/
public class TestC3P0 {
   public static void main(String[] args) {
      Connection connection = null;
      PreparedStatement preparedStatement = null;
      try {
         connection = JdbcUtils_C3P0.getConnection();
         //sql语句,其中value值先不赋予

         String sql = "insert into student(id, `name`, `age`) values(?,?,?)";

         //预先编译sql,先写sql,但不执行
         preparedStatement = connection.prepareStatement(sql);

         //手动value赋值
         preparedStatement.setInt(1, 4);
         preparedStatement.setString(2, "tianzhendong");
         preparedStatement.setInt(3, 28);

         //执行
         int i = preparedStatement.executeUpdate();
         if (i > 0) {
            System.out.println("插入成功!");
         }
      } catch (SQLException throwables) {
         throwables.printStackTrace();
      } finally {
         JdbcUtils_C3P0.release(connection, preparedStatement, null);
      }
   }
}