MySQL基础

1、初识MySQL

javaEE:企业级java开发 Web

前端(页面:展示,数据!)

后台(连接点:连接数据库JDBC,连接前端(控制,控制视图调转,和前端传递数据))

数据库(存数据,Txt,Excel,word)

只会写代码,学好数据库,

操作系统,数据结构与算法!当一个不错的程序员!

离散数学,数字电路,体系结构,编译原理。+实战经验,高级程序员

1.1、为什么学习数据库

1、岗位需求

2、现在的世界,大数据时代,得数据库者得天下

3、被迫需求:存数据 去IOE

4、数据库是所有软件体系中最核心的存在 DBA

1.2、什么是数据库

数据库(DB,DataBase)

概念:数据仓库,软件、安装在操作系统(window,linux,mac,……)之上!SQL,可以存储大量的数据。500万一下顺便存,500万以上要做SQL查询的优化。

作用:存储数据、管理数据

1.3、数据库分类

关系型数据库:(SQL)

  • MySQL、Oracle、Sql Server、DB2、SQLlite
  • 通过表和表之间,行和列之间的关系进行数据的存储, 学员信息表,考勤表,……

非关系型数据库:(NoSQL) Not Only

  • Redis,MongDB
  • 非关系型数据库,对象存储,通过对象的自身的属性来决定。

DBMS(数据库管理系统)

  • 数据库的管理软件,科学有效的管理我们的数据。维护和获取数据;
  • MySQL,数据库管理系统!

1.4、MySQL简介

MySQL是一个关系型数据库管理系统

前世:瑞典MySQL AB公司

今生:属于Oracle旗下产品

MySQL是最好的RDBMS(Realational Database Management System,关系型数据库管理系统)应用软件之一

开源的数据库软件

体积小、速度快、总体拥有成本低、找人成本比较低、所有人必须会

中小型网站、或者大型网站、集群!

官网:https://www.mysql.com

安装建议:

1、尽量不要使用exe,注册表

2、尽可能使用压缩包安装

1.5、安装MySQL

教程:https://www.cnblogs.com/godles/p/12202175.html

  1. 解压

  2. 把这个包放到自己环境的目录下

  3. 配置环境变量,把bin目录的绝对路径拷贝到环境变量path中

  4. 新建mysql配置文件my.ini

    注意:目录一定要换成自己的

    [mysqld]
    basedir=D:\Environment\mysql-5.7.19\
    datadir=D:\Environment\mysql-5.7.19\data\
    port=3306
    skip-grant-tables
    
  5. 启动管理员模式下的CMD,切换到bin目录下,运行所有命令

  6. 安装MySQL服务

    • mysqld -install
  7. 初始化数据库文件(可能需要等一会)

    • mysqld --initialize-insecure --user=mysql
  8. 启动mysql

    • net start mysql
  9. 通过命令进入mysql

    • mysql -u root -p
  10. 修改root密码

    • update mysql.user set authentication_string=password('111111') where user='root' and Host='localhost';
  11. 刷新权限

    • flush privileges;
  12. 修改my.ini文件,删除或注释最后一句skip-grant-tables

  13. 重启mysql后即可正常使用,先执行exit命令退出mysql后再执行以下命令

    • net stop mysql
    • net start mysql
  14. 连接测试,如果连接成功就ok了

    • mysql -u root -p回车后输入密码
    • mysql -u root -p111111密码紧跟其后按回车也行

安装可能出现的问题:

  1. 缺少组件.dll
  2. 命令输错

解决方法:

  1. sc delete mysql(清空服务)
  2. 然后再次重新安装

1.6、安装Navicat Premium 15

1、无脑安装

2、注册

3、打开

4、新建一个数据库school

​ 以上选择防止中文乱码

​ select version() 查看数据库版本

5、新建一张表student

​ 字段:id,name,age

6、查看表

7、添加多条记录

1.7、基本命令

mysql -u root -p11111 --连接数据库

select version();  --查看数据库版本

update mysql.user set authentication_string=password('111111') where user='root' and Host='localhost'; --修改用户密码

flush privileges; --刷新权限
-----------------------------------------------
--所有的语句都要使用;结尾
show databases; --查看所有数据库
use school; --切换数据库  use 数据库名
show tables; --查看数据库中所有的表
describe student; --显示数据库中表的结构
create database mydb; --创建一个数据库
exit; --退出连接
-- 单行注释
/*
多行
注释
*/

数据库xxx语言 CRUD 增删改查

DDL 定义

DML 操作

DQL 查询

DCL 控制

2、操作数据库

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

mysql关键字不区分大小写

2.1、操作数据库(了解)

  1. 创建数据库

    create database [if not exists] test;
    
  2. 删除数据库

    drop database [if exists] test;
    
  3. 使用数据库

    -- 如果你的表名或者字段名是一个特殊字符,就需要带``(tab键上面的)
    use `school`;
    
  4. 查看数据库

    show databases;  --查看所有的数据库
    

2.2、数据库的列类型

  • 数值

    • tinyint 十分小的数据 1个字节
    • smallint 较小的数据 2个字节
    • mediumint 中等大小的数据 3个字节
    • int 标准的整数 4个字节 (常用的,对应java的数据类型为int)
    • bigint 较大的数据 8个字节
    • float 浮点数 4个字节
    • double 浮点数 8个字节 (精度问题!)
    • decimal 字符串形式的浮点数 金融计算的时候,一般使用这个
  • 字符串

    • char 字符串固定大小 0~255
    • varchar 可变字符串 0~65535 (常用的,对应java的数据类型为String)
    • 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

    • 没有值,未知
    • 注意,不要使用NULL进行运算,结果为NULL

2.3、数据库的字段属性(重点)

  • Unsigned
    • 无符号的整数
    • 声明了该列能声明为负数
  • zerofill
    • 0填充的
    • 不足的位数,使用0来填充,int(3),5--->005
  • 自增
    • 通常理解为自增,自动在上一条记录的基础上+1(默认)
    • 通常用来设计唯一的主键index,必须是整数类型
    • 可以自定义设计主键自增的起始值和步长
  • 非空 not null
    • 假设设置为not null,如果不给他赋值,就会报错!
    • null,如果不填写值,默认就是null!
  • 默认
    • 设置默认的值!

拓展

每一个表,都必须存在以下五个字段!未来做项目用的,表示一个记录存在的意义

  • id:主键
  • version:乐观锁
  • is_delete:伪删除
  • gmt_create:创建时间
  • gmt_update:修改时间

2.4、创建数据库表(重点)

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 '邮箱',
	PRIMARY KEY(`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

格式

CREATE TABLE [IF NOT EXISTS] `表名` (
	`字段名` 列类型 [属性] [索引] [注释],
	`字段名` 列类型 [属性] [索引] [注释],
    ......
    `字段名` 列类型 [属性] [索引] [注释],
) [表类型][字符集设置][注释]

常用命令

SHOW CREATE DATABASE school  --查看创建数据库的语句
SHOW CREATE TABLE student  --查看student数据表的定义语句
DESC student  --显示表的结构

2.5、数据表的类型

  • 关于数据库引擎

    • INNODB:默认使用

    • MYISAM:早些年使用的

      MYISAM INNODB
      事务支持 不支持 支持
      数据行锁定 不支持 支持
      外键约束 不支持 支持
      全文搜索 支持 不支持
      表空间的大小 较小 较大,约为2倍

    常规使用操作:

    • MYISAM 节约空间,速度较快
    • INNODB 安全性高,事务的处理,多表多用户操作
  • 在物理空间存在的位置

    所有的数据库文件都存在data目录下,一个文件夹就对应一个数据库

    本质还是文件的存储

    MySQL引擎在物理文件上的区别

    • INNODB 在数据库表中只有一个*.frm文件,以及上级目录下的ibdata1文件

    • MYISAM 对应文件

      • *.frm 表结构的定义文件
      • *.MYD 数据文件(data)
      • *.MYI 索引文件(index)
  • 设置数据库表的字符集彪马

    CHARSET=utf8
    

    不设置的话,回是mysql默认的字符集编码(不支持中!)

    MySQL的默认编码是Latin1,不支持中文

    在my.ini中配置默认的编码

    character-set-server=uft8
    

2.6、修改和删除表

  • 修改

    --修改表名 ALTER TABLE 旧表名 RENAME AS 新表名
    ALTER TABLE teacher RENAME AS teacher1;
    --增加表字段 ALTER TABLE 表名 ADD 字段名 列属性
    ALTER TABLE teacher1 ADD age INT(3)
    --修改表字段
    ALTER TABLE teacher1 MODIFY age VARCHAR(3); --修改约束
    ALTER TABLE teacher1 CHANGE age age1 INT(3); --字段重命名
    --删除表字段
    ALTER TABLE teacher1 DROP age1;
    
  • 删除

    --删除表(如果存在再删除)
    DROP TABLE IF EXISTS teacher1;
    

注意点:

  1. 使用``符号包裹字段名
  2. sql关键字大小写不敏感,建议大家写小写
  3. 多有的符号全部用英文

3、MySQL的数据管理

3.1、外键(了解)

  • 方式一:在创建表的时候,增加约束(麻烦,比较复杂)

    CREATE TABLE `grade`(
    	`gradeid` INT(10) NOT NULL AUTO_INCREMENT COMMENT '年级id',
        `gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
        PRIMARY KEY (`gradeid`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    
    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 '出生日期',
        `gradeid` INT(10) NOT NULL COMMENT '学生的年级',
    	`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
    	`email` VARCHAR(50) DEFAULT 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 AUTO_INCREMENT COMMENT '年级id',
        `gradename` VARCHAR(50) NOT NULL COMMENT '年级名称',
        PRIMARY KEY (`gradeid`)
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    
    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 '出生日期',
        `gradeid` INT(10) NOT NULL COMMENT '学生的年级',
    	`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
    	`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
    	PRIMARY KEY(`id`),
    ) ENGINE=INNODB DEFAULT CHARSET=utf8
    
    ALTER TABLE `student` ADD CONSTRAINT `FK_gradeid` FOREIGN KEY (`gradeid`) REFERENCES `grade`(`gradeid`);
    

以上的操作都是物理外键,数据库级别的外键,我们不建议使用!(避免数据库过多造成困扰,这里了解即可~)

  • 最佳实践

    • 数据库就是单纯的表,只用来存数据,只有行(数据)和列(字段)
    • 我们想使用多张表的数据,想使用外键,用程序去实现

3.2、DML语言(全部记住)

数据库的意义:数据存储,数据管理

DML语言:数据操作语言

  • insert
  • update
  • delete

3.3、添加

--insert into 表名([字段1,字段2,字段3,...]) values('值1','值2','值3',...)
INSERT INTO `grade`(`gradename`) VALUES('大一');
INSERT INTO `student`(`name`,`pwd`,`sex`) VALUES('张三','aaaaa','男');
--一次插入多条记录
INSERT INTO `grade`(`gradename`) VALUES('大一'),('大二'),('大三');
INSERT INTO `student`(`name`,`pwd`,`sex`) VALUES('张三','aaaaa','男'),('李四','bbbbbb','男');

语法:insert into 表名([字段1, 字段2, 字段3,...]) values('值1', '值2', '值3'),('值1', '值2', '值3'),('值1', '值2', '值3'),...

注意事项:

  1. 字段和字段之间使用英文逗号隔开
  2. 字段是可以省略的,但后面的值必须要一一对应,不能少
  3. 可以同时插入多条数据,values后面的值,需要使用英文逗号隔开

3.4、修改

UPDATE `student` SET `name` = '王五' 	WHERE `id` = 1;
--不指定条件,会修改整个表的数据
UPDATE `student` SET `name` = '孙六';

where条件操作符(操作符会返回布尔值):

操作符 含义
= 等于
<> 或 != 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于
between ... and ... 在两者单位之间,[2, 5]
and &&
or ||

语法:update 表名 set 字段名1 = 字段值1, 字段名2 = 字段值2, ... [where 条件字段 = 条件值]

注意事项:

  1. where 条件不带,则会修改所有的列
  2. 字段值是一个具体的值,也可以是一个变量(一般时间能用,CURRENT_TIME)
  3. 多个设置的值之间要用英文逗号隔开

3.5、删除

DELETE FROM `student`;
DELETE FROM `student` WHERE `id` = 1;

--清空表,完全清空一个表,表结构和约束都不会变
TRUNCATE `student`;

语法:delete from 表名 [where 条件]TRUNCATE 表名

delete和truncate

  • 相同点:
    • 都能删除数据,都不会删除表结构
  • 不同点:
    • truncate会重置自增列,计数器会归零
    • truncate不会影响事务

了解:delete删除的问题,重启数据库现象

  • InnoDB 自增列会从1开始(存在内存当中的,断电即失)
  • MyISAM 继续从上一个自增量开始(存在文件当中的,不会丢失)

4、DQL查询数据(最重点)

4.1、DQL

Data Query Language:数据库查询语言

  • 所有的查询操作都用它 select
  • 简单的查询,复杂的查询它都能做
  • 数据库最核心的语言,最重要的语句
  • 使用频率最高的语句

select完整的语法:

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_count offset offset}] --指定查询的记录从哪条到哪条

注意:[]括号代表可选的,{}括号代表必选的

4.2、指定查询字段

-- 查询所有字段
select * from student;
-- 查询指定字段
select `StudentNo`, `StudentName` from `student`;
-- 可以给字段、表起别名
select `StudentNo` as 学号, `StudentName` as 学生姓名 from `student` as s;
-- 函数concat(a, b)
select concat('姓名:', StudentNo) as 新名字 from `student`;

语法:select 字段,... from 表名

  • 去重distinct

    作用:去除select查询出来的结果中重复的数据,重复的数据只显示一条

    select distinct `StudentNo` from result
    
  • 数据库的列(表达式)

    select version()  --查询系统版本
    select 100*3-1 as 计算结果 --用来计算
    select @@auto_increment_increment --查询自增的步长
    -- 学员考试成绩+1分查看
    select `StudentNo`, `StudentResult` + 1 as '提分后' from result
    

    数据库中的表达式:文本值、列、Null、函数、计算表达式、系统变量……

    select 表达式 from 表

4.3、where条件子句

作用:检索数据库中符合条件的数据

搜索的条件由一个或者多个表达式组成,结果为布尔值

  • 逻辑运算符

    运算符 语法 描述
    and(&&) a and b、a && b 逻辑与,两个都为真,结果为真
    or(||) a or b、a || b 逻辑或,其中一个为真,则结果为真
    not(!) not a、!a 逻辑非,真为假,假为真

    尽量使用英文字母

    select StudentNo, StudentResult from result where StudentResult >= 95 and StudentResult <= 100;
    select StudentNo, StudentResult from result where StudentResult >= 95 && StudentResult <= 100;
    select StudentNo, StudentResult from result where StudentResult between 95 and 100;
    select StudentNo, StudentResult from result where StudentNo != 1000;
    select StudentNo, StudentResult from result where not StudentNo = 1000;
    
  • 比较运算符(模糊查询)

    运算符 语法 描述
    is null a is null 如果操作符为null,则结果为真
    is not null a is not null 如果操作符不为null,则结果为真
    between a between b and c 如果a在b和c之间,则结果为真
    like a like b SQL匹配,如果a匹配b,则结果为真
    in a in (a1, a2, a3, ...) 如果a在a1,a2,a3...其中的某一个值,则结果为真
    -- 查询姓刘的同学
    select StudentNo, StudentName from student where StudentName like '刘%';
    -- 查询姓刘的同学,名字后面只有一个字的
    select StudentNo, StudentName from student where StudentName like '刘_';
    -- 查询姓刘的同学,名字后面有两个字的
    select StudentNo, StudentName from student where StudentName like '刘__';
    -- 查询名字中间有‘嘉’的同学
    select StudentNo, StudentName from student where StudentName like '%嘉%';
    -- 查询1001,1002,1003号的学员
    select StudentNo, StudentName from student where StudentNo in (1001, 1002, 1003);
    -- 查询地址为空的学生
    select StudentNo, StudentName from student where address = '' or address is null;
    

4.4、联表查询

  • 7种join理论

    1. 内连接

      select * from A inner join B where A.key = B.key;
      
    2. 左连接

      select * from A left join B on A.key = B.key where B.key is null;
      
    3. 右连接

      select * from A right join B on A.key = B.key where A.key is null;
      
    4. 左外连接

      select * from A left join B on A.key = B.key;
      
    5. 右外连接

      select * from A right join B on A.key = B.key;
      
    6. 全外连接

      select * from A left join B where A.key = B.key 
      union 
      select * from A right join B where A.key = B.key;
      
    7. 两表独有的数据集

      select * from A left join B on A.key = B.key where B.key is null 
      union 
      select * from A right join B on A.key = B.key where A.key is null;
      
  • 自连接

    把一张表有层级的表(category),看为两个一摸一样的表

    category_id pid categoryName
    1 0 信息技术
    2 0 软件开发
    3 0 美术设计
    4 1 办公信息
    5 2 数据库
    6 2 web开发
    7 3 ps技术

    所需查询结果:

    父栏目 子栏目
    信息技术 办公信息
    软件开发 数据库
    软件开发 web开发
    美术设计 ps技术
    select a.categoryName as 父栏目, b.categoryName as 子栏目
    from category as a, category as b
    where a.category_id = b.pid
    

4.5、分页和排序

  • 分页

    目的:缓解数据库压力,给人的体验更好

    语法:limit 起始下标, pageSize

    以下以每页5条为例:

    select * from A where ... limit 0, 5;  --第一页
    select * from A where ... limit 5, 5;  --第二页
    select * from A where ... limit 10, 5; --第三页
    -- 第一页  limit 0,5
    -- 第二页  limit 5,5
    -- 第三页  limit 10,5
    -- 第n页   limit (n-1)*5
    -- 【pageSize:页面大小】
    -- 【(n-1)*pageSize:起始值】
    -- 【n:当前页】
    -- 【总页数:数据总数/页面大小】
    
  • 排序

    • 升序:asc

    • 降序:desc

    select id, name, ... from A where ... order by asc(desc)
    

4.6、子查询

本质:在where语句中嵌套一个子查询语句

select * from A where id = (select a_id from B where ...)
select * from A where id in (select a_id from B where ...)
...

where子查询比联合查询速度更快一些

4.7、分组和过滤

查询不同课程的平均分、最高分、最低分、平均分大于80

select SubjectName, avg(StudentResult) as 平均分, max(StudentResult) as 最高分, min(StudentResult) as 最低分
from result r inner join subject s
on r.SubjectNo = s.SubjectNo
group by r.SubjectNo
having 平均分 >= 80;

5、MySQL函数

5.1、常用函数

-- 数学运算
select abs(-8); -- 8,绝对值
select ceiling(9.4); -- 10,向上取整
select floor(9.4); -- 9,向下取整
select rand(); -- 返回一个0~1之间的随机数
select sign(-10); -- 判断一个数的符号,负数返回-1,正数返回1,0返回0

-- 字符串函数
select char_length('爱你一万年'); -- 字符串长度
select concat('我', '爱', '你们'); -- 拼接字符串
select insert('abcdefg', 1, 2, '111'); -- 111cdefg,在指定位置插入字符串
select lower('Hello World'); -- hello world,转小写字母
select upper('Hello World'); -- HELLO WORLD,转大写字母
select instr('abcdefg', 'c'); -- 3,返回第一次出现的字符串的索引
select replace('坚持就能成功', '坚持', '努力'); -- 替换出现的指定字符串
select substr('坚持就能成功', 5, 2); -- 成功,截取字符串
select reverse('坚持就能成功'); -- 反转

-- 时间和日期函数
select current_date(); -- 获取当前日期
select curdate(); -- 获取当前日期
select now(); -- 获取当前的时间
select localtime();  -- 获取本地时间
select sysdate();  -- 获取系统时间
select year(now()); -- 获取当前年
select month(now()); -- 获取当前月
select day(now()); -- 获取当前日
select hour(now()); -- 获取当前时
select minute(now()); -- 获取当前分
select second(now()); -- 获取当前秒

-- 系统
select system_user(); -- 获取系统用户
select user();
select version(); -- 获取版本号

5.2、聚合函数(常用)

函数名称 描述
count() 计数
sum() 求和
avg() 求平均值
max() 取最大值
min() 取最小值
-- 都能统计表中的数据(想查询一个表中有多少条记录就使用count())
select count(字段) from tablename; -- 会忽略所有的null值
select count(*) from tablename; -- 不会忽略null值,本质计算行数
select count(1) from tablename; -- 同上

select sum(StudentResult) as 总和 from result;
select avg(StudentResult) as 平均分 from result;
select max(StudentResult) as 最高分 from result;
select min(StudentResult) as 最低分 from result;

5.3、数据库级别的MD5加密(扩展)

什么是MD5?

主要是增强算法复杂度和不可逆性。

MD5不可逆,具体的值的md5是一样的

-- 插入的时候给密码加密
insert into user(userName, pwd) values('张三', md5('11111'));
-- 验证
select * from user where username = '张三' and pwd = md5('11111');

6、事务

6.1、什么是事务

将一组SQL放在一个批次中去执行

事务原则:ACID原则,原子性、一致性、隔离性、持久性 (脏读、幻读)

  • 原子性(Atomicity)

    要么都成功,要么都失败

  • 一致性(Consistency)

    事务前后的数据完整性要保证一致

  • 隔离性(Isolation)

    多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作的数据所干扰,事务之间要相互隔离

  • 持久性(Durability)

    事务一旦提交则不可逆,被持久化到数据库中

隔离性会导致一些问题

  1. 脏读:指一个事务读取了另一个事务未提交的数据。
  2. 不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同(这个不一定是错误,只是某些场合不对)。
  3. 虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

6.2、执行事务

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

-- 手动处理事务
set autocommit = 0; -- 关闭自动提交
start transaction; --事务开启,标记一个事务的开始,从这之后的sql都在同一个事务内
insert xxx...
insert xxx...
commit; --提交:持久化(成功)
rollback; --回滚:回到原来的样子(失败)
set autocommit = 1; -- 事务结束,开启自动提交

-- 了解
savepoint 保存点名; -- 设置一个事务的保存点
rollback to savepoint; -- 回滚到保存点
release savepoint 保存点名; -- 撤销保存点

6.3、模拟场景

-- 转账
create database shop character set utf8 collate utf8_general_ci;
use shop;
create table account(
	id int(10) not null auto_increment,
    name varchar(30) not null,
    money decimal(9,2) not null,
    primary key(id)
)engine=innodb default charset=utf8;
insert into account(name, money) values('A', 2000.00), ('B', 1000.00);
-- 模拟转账:事务
set autocommit = 0; -- 关闭自动提交
start transaction; -- 开启一个事务
update account set money = money - 500 where name = 'A'; -- A减500
update account set money = money + 500 where name = 'B'; -- B加500
commit; -- 成功,提交事务
rollback; -- 失败,回滚
set autocommit = 1; -- 开启自动提交,恢复默认值

7、索引

MySQL官方对索引的定义为:索引(index)是帮助MySQL高效获取数据结构。

提取句子主干,就可以得到索引的本质:索引是数据结构。

7.1、索引的分类

在一个表中,主键索引只能有一个,唯一索引可以有多个

  • 主键索引(primary key)
    • 唯一标识,主键不可重复,只能有一个列作为主键
  • 唯一索引(unique key)
    • 避免出现重复的列,唯一索引可以重复,多个列都可以标识为唯一索引
  • 常规索引(key/index)
    • 默认的,index、key关键字来设置
  • 全文索引(FullText)
    • 在特定的数据库引擎下才有,MyISAM
    • 快速定位数据

基础语法

-- 索引的使用
-- 1、在创建表的时候给字段增加索引
-- 2、创建完毕后,增加索引

-- 显示所有的索引信息
show index from student;

-- 增加一个全文索引(索引名) 列名
alter table school.student add fulltext index `studentNameIndex`(studentName);

-- explain 分析sql执行的状况
explain select * from student; -- 非全文索引
explain select * from student where match(studentName) against('刘');

7.2、测试索引

-- 创建一个表
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(1) UNSIGNED DEFAULT '0' COMMENT '性别(0:男;1:女)',
	password VARCHAR(100) NOT NULL COMMENT '密码',
	age TINYINT(3) DEFAULT '0' COMMENT '年龄',
	create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
	update_time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
	PRIMARY KEY(ID)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='app用户表';

-- 插入100万条数据
DELIMITER $$   
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
	DECLARE num INT DEFAULT 1000000;
	DECLARE i INT DEFAULT 0;
	WHILE i < num DO
		INSERT INTO app_user(name, email, phone, gender, password, age) 
		VALUES(CONCAT('用户', i), '815899463@qq.com', CONCAT('1',FLOOR(RAND() * 9999999999)), FLOOR(RAND() * 2), UUID(), FLOOR(RAND() * 100));
		SET i = i + 1;
	END WHILE;
	RETURN i;
END;

SELECT mock_data();

SELECT * FROM app_user WHERE name = '用户9999'; -- 0.784s
SELECT * FROM app_user WHERE name = '用户9999'; -- 0.834s

EXPLAIN SELECT * FROM app_user WHERE name = '用户9999'; -- 发现查找的行数为992262

-- 添加索引
-- create index 索引名(id_表名_字段名) on 表(字段)
CREATE INDEX id_app_user_name ON app_user(name);
SELECT * FROM app_user WHERE name = '用户9999'; -- 0.019s
EXPLAIN SELECT * FROM app_user WHERE name = '用户9999'; -- 发现查找的行数为1,直接定位了

索引在小数据量的时候,用处不大,但是在大数据量的时候,区别十分明显。

7.3、索引的原则

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

索引的数据结构

  • Hash
  • Btree(innoDB的默认数据结构)

阅读:http://blog.codinglabs.org/articles/theory-of-mysql-index.html

8、权限管理和备份

8.1、用户管理

用户表:mysql.user

本质:对这张表进行增删改查

-- 创建用户 create user 用户名 identified by '密码'
create user test identified by '111111';

-- 修改当前用户密码
set password = password('123456');

-- 修改指定用户密码
set password for test = password('123456');

-- 重命名 rename user 原来的名字 to 新的名字
rename user test to test2;

-- 用户授权 all privileges 全部权限
-- all privileges除了给别人授权,其他都能干
grant all privileges on *.* to test2;

-- 查询权限
show grants for test2; -- 查看指定用户的权限
show grants for root@localhost  -- 查看root用户的权限

-- 撤销权限
revoke all privileges on *.* from test2;

-- 删除用户
drop user test2;

8.2、MySQL备份

  • 为什么要备份

    • 保证重要的数据不丢失
    • 数据转移
  • MySQL数据备份的方式

    • 直接拷贝物理文件

    • 在可视化工具中手动导出

    • 使用命令行导出 mysqldump 命令

      导出

      # 导出一张表
      # mysqldump -h主机 -u用户名 -p密码 数据库 表名 > 物理磁盘位置/文件名
      mysqldump -hlocalhost -uroot -p123456 school student > D:/a.sql
      
      # 导出多张表
      # mysqldump -h主机 -u用户名 -p密码 数据库 表1 表2 表3 > 物理磁盘位置/文件名
      mysqldump -hlocalhost -uroot -p123456 school student result > D:/b.sql
      
      # 导出整个库
      # mysqldump -h主机 -u用户名 -p密码 数据库 > 物理磁盘位置/文件名
      mysqldump -hlocalhost -uroot -p123456 school > D:/c.sql
      

      导入

      # 登录的情况下,切换到指定的数据库
      # source 备份文件
      source d:/a.sql;
      
      # 非登录情况
      # mysql -u用户名 -p密码 库名 < 备份文件
      mysql -uroot -p111111 school < d:/c.sql;
      

9、规范数据库设计

9.1、为什么需要设计

当数据库比较复杂的时候,我们就需要设计了

糟糕的数据库设计

  • 数据冗余,浪费空间
  • 数据库插入和删除都会麻烦,异常(屏蔽使用物理外键)
  • 程序的性能差

良好的数据库设计

  • 节省内存空间
  • 保证数据库的完整性
  • 方便我们开发系统

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

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

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

  • 收集信息,分析需求
    • 用户表(用户登录注销,用户的个人信息,写博客,创建分类)
    • 分类表(文章分类,谁创建的)
    • 文章表(文章的信息)
    • 评论表(评论信息)
    • 友链表(友链信息)
    • 自定义表(系统信息,某个关键的字,或者一些主字段)
    • 说说表(发表心情……)
  • 标识实体(把需求落地到每个字段)
  • 标识实体之间的关系
    • 写博客:user --> blog
    • 创建分类:user --> category
    • 关注:user -->user
    • 友链:links
    • 评论:user-->user-->blog

9.2、三大范式

为什么需要数据规范化?

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

三大范式

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

规范性和性能的问题

关联查询的表不得超过三张表

  • 考虑商业化的需求和目标,(成本,用户体验!)数据库的性能更加重要
  • 在规范性能的问题的时候,需要适当考虑一下规范性
  • 有时需要故意给某些表增加一些冗余的字段。(从多表查询中变为单表查询)
  • 故意增加一些计算列(从大数量降低为小数据量的查询:索引)

10、JDBC(重点)

10.1、数据库驱动

驱动:声卡、显卡、数据库

我们的程序会通过数据库驱动和数据库打交道!

10.2、JDBC

SUN公司为了简化开发人员的(对数据库的统一)操作,提供了一个(Java操作数据库的)规范,俗称JDBC,这些规范的实现由具体的厂商去做。

对于开发人员来说,我们只需要掌握JDBC接口的操作即可!

java.sql

javax.sql

还需要导入一个数据库驱动包

10.3、第一个JDBC程序

创建测试数据库

CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;
USE jdbcStudy;
CREATE TABLE users(
	id INT PRIMARY KEY,
	name VARCHAR(40),
	password VARCHAR(40),
	email VARCHAR(60),
	birthday DATE
);
INSERT INTO users(id, name, password, email, birthday)
VALUES(1, '张三', '111111', 'zhangsan@sina.com', '1980-12-04'),
(2, '李四', '123456', 'lisi@sina.com', '1981-12-04'),
(3, '王五', '222222', 'wangwu@sina.com', '1979-11-02');
  1. 创建一个普通项目

  2. 导入数据库驱动

  3. 编写测试代码

    import java.sql.*;
    
    public class JdbcFirstDemo {
        public static void main(String[] args) throws ClassNotFoundException, SQLException {
            //1.加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.url和用户信息
            String url = "jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
            String username = "root";
            String password = "111111";
            //3.连接成功,数据库连接对象
            Connection connection = DriverManager.getConnection(url, username, password);
            //4.执行sql的对象
            Statement statement = connection.createStatement();
            //5.用对象去执行sql
            String sql = "select * from users";
            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("pwd=" + resultSet.getObject("password"));
                System.out.println("email=" + resultSet.getObject("email"));
                System.out.println("birth=" + resultSet.getObject("birthday"));
                System.out.println("======================================");
            }
            //6.释放连接
            resultSet.close();
            statement.close();
            connection.close();
        }
    }
    

    步骤总结:

    1. 加载驱动
    2. 链接数据库DriverManager
    3. 获得执行sql的对象Statement
    4. 获得返回的结果集
    5. 释放连接

DriverManager

// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(url, username, password);

//connection代表数据库
//数据库设置自动提交
connection.setAutoCommit();
//事务提交
connection.commit();
//事务回滚
connection.rollback();

URL

String url = "jdbc:mysql://localhost:3306/jdbcStudy?useUnicode=true&characterEncoding=utf8&useSSL=true";

//mysql --3306
// 协议://主机地址:端口号/数据库名?参数1&参数2&参数3

//oracle --1521
// jdbc:oracle:thin@localhost:1521:sid

Statement执行SQL的对象 (PrepareStatement)

Statement statement = connection.createStatement();
statement.executeQuery(); //查询操作返回的数据集
statement.execute(); //执行任何sql
statement.executeUpdate(); //更新、插入、删除都是用这个,返回一个受影响的行数

ResultSet查询的结果集:封装了所有的查询结果

获得指定的数据类型

ResultSet resultSet = statement.executeQuery(sql);
resultSet.getString();
resultSet.getInt();
resultSet.getFloat();
resultSet.getDate();
resultSet.getObject();

遍历,指针

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

释放资源

resultSet.close();
statement.close();
connection.close();

10.4、Statement对象

Jdbc中的Statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查的语句即可。

Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sql语句,executeUpdate执行完后,将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。

Statement.executeQuery方法用于向数据库发送查询语句,executeQuery方法返回代表查询结果的ResultSet对象。

  • CRUD操作-create

    使用executeUpdate(String sql)方法完成数据添加操作,示例代码:

    Statement st = conn.createStatement();
    String sql = "insert into user(...) values(...)";
    int num = st.executeUpdate(sql);
    if(num > 0) {
        System.out.println("插入成功!")
    }
    
  • CRUD操作-delete

    使用executeUpdate(String sql)方法完成数据删除操作,示例代码:

    Statement st = conn.createStatement();
    String sql = "delete from user where id = 1";
    int num = st.executeUpdate(sql);
    if(num > 0) {
        System.out.println("删除成功!")
    }
    
  • CRUD操作-update

    使用executeUpdate(String sql)方法完成数据修改操作,示例代码:

    Statement st = conn.createStatement();
    String sql = "update user set name = '' where name = ''";
    int num = st.executeUpdate(sql);
    if(num > 0) {
        System.out.println("修改成功!")
    }
    
  • CRUD操作-read

    使用executeQuery(String sql)方法完成数据查询操作,示例代码:

    Statement st = conn.createStatement();
    String sql = "select * from user where id = 1";
    ResultSet rs = st.executeQuery(sql);
    while(rs.next()) {
        //根据获取列的数据类型,分别调用rs的相应方法映射到java的对象中
    }
    

具体代码实现

  1. 提取工具类

    db.properties,放在根目录下

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

    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 {
            InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
            Properties properties = new Properties();
            try {
                properties.load(in);
                driver = properties.getProperty("driver");
                url = properties.getProperty("url");
                username = properties.getProperty("username");
                password = properties.getProperty("password");
                //1.驱动只用加载一次
                Class.forName(driver);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 获取连接
         *
         * @return
         * @throws SQLException
         */
        public static Connection getConnection() throws SQLException {
            return DriverManager.getConnection(url, username, password);
        }
    
        /**
         * 释放连接资源
         *
         * @param conn
         * @param st
         * @param rs
         */
        public static void release(Connection conn, Statement st, ResultSet rs) {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    
  2. 编写增删改的方法,executeUpdate

    TestInsert.java

    import demo02.utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestInsert {
        public static void main(String[] args) {
            Connection conn = null;
            Statement st = null;
    
            try {
                conn = JdbcUtils.getConnection();
                st = conn.createStatement();
                String sql = "insert into app_user(name, email, phone, gender, password, age) " +
                        "values('张三', '815899463@qq.com', 15628744621, 0, md5('111111'), 23)";
                int i = st.executeUpdate(sql);
                if (i > 0) {
                    System.out.println("插入成功!");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, st, null);
            }
        }
    }
    

    TestDelete.java

    import demo02.utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestDelete {
        public static void main(String[] args) {
            Connection conn = null;
            Statement st = null;
    
            try {
                conn = JdbcUtils.getConnection();
                st = conn.createStatement();
                String sql = "delete from app_user where id = 1000001";
                int i = st.executeUpdate(sql);
                if (i > 0) {
                    System.out.println("删除成功!");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, st, null);
            }
        }
    }
    

    TestUpdate.java

    import demo02.utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestUpdate {
        public static void main(String[] args) {
            Connection conn = null;
            Statement st = null;
    
            try {
                conn = JdbcUtils.getConnection();
                st = conn.createStatement();
                String sql = "update app_user set name = '张三' where id = 1";
                int i = st.executeUpdate(sql);
                if (i > 0) {
                    System.out.println("更新成功!");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, st, null);
            }
        }
    }
    
  3. 编写查询的方法

    TestSelect.java

    import demo02.utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestSelect {
        public static void main(String[] args) {
            Connection conn = null;
            Statement st = null;
            ResultSet rs = null;
    
            try {
                conn = JdbcUtils.getConnection();
                st = conn.createStatement();
                String sql = "select name, email, phone, gender, age from app_user where id = 1";
                rs = st.executeQuery(sql);
                while (rs.next()) {
                    System.out.println("name:" + rs.getString("name"));
                    System.out.println("email:" + rs.getString("email"));
                    System.out.println("phone:" + rs.getString("phone"));
                    System.out.println("gender:" + rs.getInt("gender"));
                    System.out.println("age:" + rs.getInt("age"));
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, st, rs);
            }
        }
    }
    

SQL注入的问题

sql存在漏洞,会被攻击导致数据泄露,SQL会被拼接or

import demo02.utils.JdbcUtils;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestSqlInjection {
    public static void main(String[] args) {
//        login("用户999999", "0ca530fa-e3eb-11eb-8225-f832e48908f3");
        login("' or '1 = 1", "' or '1 = 1"); //sql拼接技巧
    }

    /**
     * 登录
     */
    private static void login(String username, String password) {
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;

        try {
            conn = JdbcUtils.getConnection();
            st = conn.createStatement();
            String sql = "select name, email, phone, gender, age " +
                    "from app_user where name = '" + username + "' and password = '" + password + "'";
            rs = st.executeQuery(sql);
            while (rs.next()) {
                System.out.println("name:" + rs.getString("name"));
                System.out.println("email:" + rs.getString("email"));
                System.out.println("phone:" + rs.getString("phone"));
                System.out.println("gender:" + rs.getInt("gender"));
                System.out.println("age:" + rs.getInt("age"));
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JdbcUtils.release(conn, st, rs);
        }
    }
}

10.5、PrepareStatement对象

PrepareStatement可以防止SQL注入,并且效率更高!

  1. 新增

    import utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class TestInsert {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement pst = null;
    
            try {
                conn = JdbcUtils.getConnection();
                //使用占位符替代参数
                String sql = "insert into app_user(name, email, phone, gender, password, age) " +
                        "values(?, ?, ?, ?, md5(?), ?)";
                //预编译sql,先写sql
                pst = conn.prepareStatement(sql);
                //手动给参数赋值
                pst.setString(1, "张三");
                pst.setString(2, "815899463@qq.com");
                pst.setString(3, "15628744621");
                pst.setInt(4, 0);
                pst.setString(5, "111111");
                pst.setInt(6, 23);
                int i = pst.executeUpdate();
                if (i > 0) {
                    System.out.println("插入成功!");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, pst, null);
            }
        }
    }
    
  2. 删除

    import utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class TestDelete {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement pst = null;
    
            try {
                conn = JdbcUtils.getConnection();
                //使用占位符替代参数
                String sql = "delete from app_user where id = ?";
                //预编译sql,先写sql
                pst = conn.prepareStatement(sql);
                //手动给参数赋值
                pst.setLong(1, 1000001);
                int i = pst.executeUpdate();
                if (i > 0) {
                    System.out.println("删除成功!");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, pst, null);
            }
        }
    }
    
  3. 修改

    import utils.JdbcUtils;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    
    public class TestUpdate {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement pst = null;
    
            try {
                conn = JdbcUtils.getConnection();
                //使用占位符替代参数
                String sql = "update app_user set name = ? where id = ?";
                //预编译sql,先写sql
                pst = conn.prepareStatement(sql);
                //手动给参数赋值
                pst.setString(1, "李四");
                pst.setLong(2, 1000002);
                int i = pst.executeUpdate();
                if (i > 0) {
                    System.out.println("修改成功!");
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, pst, null);
            }
        }
    }
    
  4. 查询

    import utils.JdbcUtils;
    
    import java.sql.*;
    
    public class TestSelect {
        public static void main(String[] args) {
            Connection conn = null;
            PreparedStatement pst = null;
            ResultSet rs = null;
    
            try {
                conn = JdbcUtils.getConnection();
                String sql = "select name, email, phone, gender, age from app_user where id = ?";
                pst = conn.prepareStatement(sql);
                pst.setLong(1, 1);
                rs = pst.executeQuery();
                while (rs.next()) {
                    System.out.println("name:" + rs.getString("name"));
                    System.out.println("email:" + rs.getString("email"));
                    System.out.println("phone:" + rs.getString("phone"));
                    System.out.println("gender:" + rs.getInt("gender"));
                    System.out.println("age:" + rs.getInt("age"));
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, pst, rs);
            }
        }
    }
    
  5. 防止SQL注入

    import utils.JdbcUtils;
    
    import java.sql.*;
    
    public class TestSqlInjection {
        public static void main(String[] args) {
    //        login("用户999999", "0ca530fa-e3eb-11eb-8225-f832e48908f3");
            login("'' or 1 = 1", "'' or 1 = 1");
        }
    
        private static void login(String username, String password) {
            Connection conn = null;
            PreparedStatement pst = null;
            ResultSet rs = null;
    
            try {
                conn = JdbcUtils.getConnection();
                //PreparedStatement防止SQL注入的本质是把传递进来的参数当作字符
                //如果其中存在转义字符,会被直接转义
                String sql = "select name, email, phone, gender, age " +
                        "from app_user where name = ? and password = ?";
                pst = conn.prepareStatement(sql);
                pst.setString(1, username);
                pst.setString(2, password);
                rs = pst.executeQuery();
                while (rs.next()) {
                    System.out.println("name:" + rs.getString("name"));
                    System.out.println("email:" + rs.getString("email"));
                    System.out.println("phone:" + rs.getString("phone"));
                    System.out.println("gender:" + rs.getInt("gender"));
                    System.out.println("age:" + rs.getInt("age"));
                }
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            } finally {
                JdbcUtils.release(conn, pst, rs);
            }
        }
    }
    

10.6、使用IDEA连接数据库

sql控制台

更新数据

10.7、JDBC操作事务

  1. 开启事务conn.setAutoCommit(false);
  2. 一组业务执行完,提交事务
  3. 可以在catch语句中显示定义回滚语句,但默认失败就会回滚

代码实现:

import utils.JdbcUtils;

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

public class TestTransaction {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;

        try {
            conn = JdbcUtils.getConnection();
            conn.setAutoCommit(false); //开启事务
            String sql1 = "update account set money = money - 500 where name = 'A'";
            pst = conn.prepareStatement(sql1);
            pst.executeUpdate();

//            int x = 1/0;  //报错

            String sql2 = "update account set money = money + 500 where name = 'B'";
            pst = conn.prepareStatement(sql2);
            pst.executeUpdate();

            conn.commit();  //业务完毕,提交事务
            System.out.println("转账成功!");
        } catch (SQLException throwables) {
            //如果失败,则默认回滚,下面的代码可以不写
//            try {
//                if (conn != null) {
//                    conn.rollback();
//                }
//            } catch (SQLException e) {
//                e.printStackTrace();
//            }
            throwables.printStackTrace();
        } finally {
            JdbcUtils.release(conn, pst, null);
        }
    }
}

10.8、数据库连接池

数据库连接 ---> 执行完毕 ---> 释放

连接 --- 释放,十分浪费系统资源

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

最小连接数:10

最大连接数:15

等待超时:100ms

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

开源数据源实现(拿来即用)

  • DBCP
  • C3P0
  • Druid:阿里巴巴

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

DBCP

需要导入commons-dbcp-1.4.jarcommons-pool-1.6.jar,可从apache官网下载

dbcp.properties

#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=111111

#!-- 初始化连接 --
initialSize=10

#最大连接数量
maxActive=50

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

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

#!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
#注意:user 与 password 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8

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

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

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

JdbcUtils_DBCP.java

package utils;

import org.apache.commons.dbcp.BasicDataSourceFactory;

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

public class JdbcUtils_DBCP {
    private static DataSource dataSource = null;

    static {
        InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcp.properties");
        Properties properties = new Properties();
        try {
            properties.load(in);
            //创建数据源  工厂模式--创建
            dataSource = BasicDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     *
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    /**
     * 释放连接资源
     *
     * @param conn
     * @param st
     * @param rs
     */
    public static void release(Connection conn, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

TestDBCP.java

import utils.JdbcUtils;
import utils.JdbcUtils_DBCP;

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

public class TestDBCP {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;

        try {
            conn = JdbcUtils_DBCP.getConnection();
            //使用占位符替代参数
            String sql = "insert into users(name, password, email, birthday) values(?, ?, ?, ?)";
            //预编译sql,先写sql
            pst = conn.prepareStatement(sql);
            //手动给参数赋值
            pst.setString(1, "张三");
            pst.setString(2, "111111");
            pst.setString(3, "815899463@qq.com");
            pst.setDate(4, new java.sql.Date(new Date().getTime()));
            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JdbcUtils.release(conn, pst, null);
        }
    }
}

C3P0

需要导入c3p0-0.9.5.5.jarmchange-commons-java-0.2.19.jar,可从https://sourceforge.net/projects/c3p0/这里下载

c3p0-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <!--
    c3p0的缺省(默认)配置
    如果在代码中ComboPooledDataSource ds=new ComboPooledDataSource();这样写就表示使用的是c3p0的缺省(默认)
    -->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <property name="acquiredIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </default-config>

    <!--
    c3p0的命名配置
    如果在代码中ComboPooledDataSource ds=new ComboPooledDataSource(MySQL);这样写就表示使用的是mysql的缺省(默认)
    -->
    <named-config name="MySQL">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=true</property>
        <property name="user">root</property>
        <property name="password">111111</property>
        <property name="acquiredIncrement">5</property>
        <property name="initialPoolSize">10</property>
        <property name="minPoolSize">5</property>
        <property name="maxPoolSize">20</property>
    </named-config>
</c3p0-config>

JdbcUtils_C3P0.java

package utils;

import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class JdbcUtils_C3P0 {
    private static ComboPooledDataSource dataSource = null;

    static {
        try {
            //代码版配置
            //dataSource = new ComboPooledDataSource();
            //dataSource.setDriverClass();
            //dataSource.setUser();
            //dataSource.setPassword();
            //dataSource.setJdbcUrl();

            //dataSource.setMaxPoolSize();
            //dataSource.setMinPoolSize();

            //配置文件写法
            dataSource = new ComboPooledDataSource("MySQL");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     *
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }

    /**
     * 释放连接资源
     *
     * @param conn
     * @param st
     * @param rs
     */
    public static void release(Connection conn, Statement st, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

TestC3P0.java

import utils.JdbcUtils;
import utils.JdbcUtils_C3P0;

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

public class TestC3P0 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pst = null;

        try {
            conn = JdbcUtils_C3P0.getConnection();
            //使用占位符替代参数
            String sql = "insert into users(name, password, email, birthday) values(?, ?, ?, ?)";
            //预编译sql,先写sql
            pst = conn.prepareStatement(sql);
            //手动给参数赋值
            pst.setString(1, "张三");
            pst.setString(2, "111111");
            pst.setString(3, "815899463@qq.com");
            pst.setDate(4, new java.sql.Date(new Date().getTime()));
            int i = pst.executeUpdate();
            if (i > 0) {
                System.out.println("插入成功!");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            JdbcUtils.release(conn, pst, null);
        }
    }
}

结论

无论使用什么数据源,本质还是一样的,DataSource接口不会变,方法就不会变

posted @ 2021-07-18 00:18  蓝色空间号  阅读(73)  评论(0)    收藏  举报