类加载机制
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口;
JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dK2J4XCj-1596374172378)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200727\笔记\assets\1595848913192.png)]
加载
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
验证
这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的:
CONSTANT_Class_info
CONSTANT_Field_info
CONSTANT_Method_info
等类型的常量。
下面我们解释一下符号引用和直接引用的概念:
符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
初始化
初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。
初始化阶段是执行类构造器<client>方法的过程。<client>方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证<client>方法执行之前,父类的<client>方法已经执行完毕。ps: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成<client>()方法。
注意以下几种情况不会执行类初始化:
通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
定义对象数组,不会触发该类的初始化。
常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所 在的类。
通过类名获取Class对象,不会触发类的初始化。
通过Class.forName加载指定类时,如果指定参数initialize为false时,也不会触发类初始化,其实这个 参数是告诉虚拟机,是否要对类进行初始化。
通过ClassLoader默认的loadClass方法,也不会触发初始化动作
反射
java是一门静态语言,在操作类中的成分时都需要先获得对象,对于变量来说需要在编译期间确定类型,而且在运行期间不能修改变量类型,相比一些其他语言(javascript,ruby等)来说不具备动态性。但是Java提供了一种称之为反射的机制,能够让我们在运行期间动态的获取到类的成分(属性,方法,构造器,注解等信息)。
问题引入:
- 如何在不使用new关键字的情况下创建对象
- 如何在只知道实例方法名的情况下执行方法
User u = JSON.parseObject(json,User.class)
所谓反射,其实相当于类照镜子,是一种自省机制;可以使得程序在运行期间动态获取一个类中的成分;在类加载时,任何一个Java类都存在一个java.lang.Class的对象;反射即将类中的成分反射成为对应的类型(属性,方法,构造器,注解等)对象。
获取Class对象
需要使用java的反射机制,首先需要获取到类的Class对象,获取Class对象包含三种方式:
- Class.forName(类路径)
Class.forName("com.softeem.entity.User")
- 类名.class
User.class
- 对象.getClass()
u.getClass()
获取Class中的成分
获取类中的属性
- getField(String fieldName) 根据属性的名称获取公共的属性对象(Field)
- getDeclaredField(String fieldName) 根据属性名获取指定的属性对象(忽略访问修饰符)
- getFields() 获取所有公开的属性
- geDeclaredFields() 获取所有的属性(忽略访问修饰符)
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException {
//加载一个指定的类并获取该类的Class对象(唯一)
Class clz1 = Class.forName("com.softeem.lesson43.User");
//1.通过反射获取类中的成分-属性
//获取由public修饰所有字段(属性、全局变量)信息
// Field[] field = clz1.getFields();
//获取所有的字段信息(不考虑访问修饰的问题)
Field[] fields = clz1.getDeclaredFields();
for (Field f : fields) {
System.out.println(f.getModifiers()+"==="+f.getType()+"---"+f.getName());
}
//创建该类型的对象(等同于 new User())
Object obj = clz1.newInstance();
//获取类中指定的属性名的Field对象
Field field = clz1.getDeclaredField("score");
field.setAccessible(true);
field.set(obj, 2568); // score=2568
//获取属性对象的值
double d = field.getDouble(obj);
System.out.println("积分:"+d);
}
获取类中的方法
- Method getMethod(String methodName) 根据提供的方法名称获取公共的方法对象
- Method getDeclaredMethod(String methodName) 在不考虑访问修饰符的情况下获取方法对象
- Method[] getMethods() 获取所有公开的方法
- Method[] getDeclaredMethods() 在不考虑访问修饰符的情况下获取所有的方法对象
public static void main(String[] args) {
try {
Class clz = Class.forName("com.softeem.lesson43.User");
Object obj = clz.newInstance();
//获取类中的方法?
Method[] methods = clz.getDeclaredMethods();
for (Method m : methods) {
System.out.println(m.toString());
}
//根据方法名和方法中的参数类型,获取一个方法对象
Method m = clz.getMethod("showInfo", String.class,int.class);
//执行方法 u.showInfo("softeem",10)
Object returnVal = m.invoke(obj, "softeem",10);
//输出方法的返回值
System.out.println(returnVal);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
获取类中的构造器
通过反射除了可以获取类中的属性和方法之外,还能够获取构造器信息,相关的API跟属性和方法的获取方式一致,区别在于返回值类型是Constructor或者Constructor[].
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
//加载类(获取目标对象所属类的一面镜子)
Class clz = Dog.class;
//创建对象(等同 new Dog())
// Object obj = clz.newInstance();
//获取所有的public构造器
Constructor[] construtors = clz.getConstructors();
for (Constructor c : construtors) {
System.out.println(c);
}
//获取指定的构造器
Constructor c = clz.getConstructor(int.class);
//执行构造器的初始化方法,完成对象的创建
Object obj = c.newInstance(10);
System.out.println(obj);
}
获取注解信息
注解是java5之后新增的技术,用于在各种元素上通过声明注释来实现不同需求,注解可以定义到类,构造器,属性,方法。
注解类(Table):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Table {
String value();
String schema();
}
注解类(Mapper):
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE,
ElementType.METHOD,
ElementType.FIELD
})
public @interface Mapper {
}
实体类(Blog)
@Mapper
@Table(value="tb_blog",schema="test")
public class Blog {
@Mapper
private int bid;
private String title;
private String content;
private String[] tips;
private Timestamp time;
@Mapper
public void setBid(int bid) {
this.bid = bid;
}
public String[] getTips() {
return tips;
}
public void setTips(String[] tips) {
this.tips = tips;
}
}
反射读取注解信息
public class TestBlog {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, NoSuchFieldException {
Class<Blog> clz = Blog.class;
//获取类上的注解
Annotation[] annos = clz.getAnnotations();
for (Annotation a : annos) {
System.out.println(a);
}
//获取其中一个注解
Table table = clz.getAnnotation(Table.class);
System.out.println(table.value());
System.out.println(table.schema());
//获取指定的Method对象
Method m = clz.getMethod("setBid", int.class);
//获取方法上指定类型的注解
Mapper mapper = m.getAnnotation(Mapper.class);
//判断方法对象上是否存在指定类型的注解
System.out.println(m.isAnnotationPresent(Mapper.class));
//获取指定名称的属性对象(全局变量)
Field f = clz.getDeclaredField("bid");
//判断属性上是存在指定的注解
System.out.println(f.isAnnotationPresent(Mapper.class));
}
}
实例讲解
反射+注解模拟测试框架(Junit)
注解类(Test)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
测试类(UserTest)
public class UserTest {
@Test
public void testInsert() {
User user = null;
System.out.println(user.getUsername());
}
@Test
public void testQuery() {
Blog b = new Blog();
b.setTips(new String[] {"技术","java","多线程"});
String[] tips = b.getTips();
System.out.println(tips[3]);
}
@Test
public void divide() {
System.out.println(10/5);
}
@Test
public void testClassCast() {
String s = (String)new Object();
System.out.println(s);
}
}
反射实现单元测试
public class MyJunit {
public static void main(String[] args) {
try {
//记录方法总数
int methodCount = 0;
//记录错误方法总数
int expCount = 0;
//准备一个文件的输出流,用于记录程序执行过程中的异常信息
BufferedWriter bw = new BufferedWriter(new FileWriter("log.txt"));
//获取类的Class对象
Class clz = UserTest.class;
//创建目标类型的实例对象
Object obj = clz.newInstance();
//获取所有的方法对象
Method[] methods = clz.getMethods();
//统计总共有多少方法需要被测试
for (Method m : methods) {
if(m.isAnnotationPresent(Test.class)) {
methodCount++;
}
}
bw.write("测试方法总数:"+methodCount);
bw.newLine();
bw.write("====================================");
bw.newLine();
for (Method m : methods) {
try {
//如果方法上面包含了Test注解则作为测试方法进行测试
if(m.isAnnotationPresent(Test.class)) {
m.invoke(obj);
}
}catch (Exception e) {
//异常方法计数器递增
expCount++;
bw.write(m.getName()+"出现异常");
bw.newLine();
bw.write("类型:"+e.getCause().getClass());
bw.newLine();
bw.write("原因:"+e.getCause().getMessage());
bw.newLine();
bw.write("--------------------------------");
bw.newLine();
}
}
bw.write("==============================");
bw.newLine();
bw.write("已测试方法数:"+methodCount+",其中异常方法:"+expCount);
bw.flush();
bw.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
反射实现对象拷贝
/**
* 通过反射机制实现对任意对象的拷贝
*
* @author mrchai
*/
public class CopyDemo {
/**
* 实现任意对象的拷贝,返回Object对象
* @param source
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Object clone(Object source) {
Object target = null;
try {
// 获取原对象的Class对象
Class clz = source.getClass();
target = clz.newInstance();
// 获取目标对象对应类中的所有属性
Field[] fields = clz.getDeclaredFields();
// 遍历所有的属性
for (Field f : fields) {
// 获取每一个属性的名称
String fname = f.getName();
// 分别获取属性对应的setter/getter方法名称
String sname = "set" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
String gname = "get" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
//如果属性是boolean类型的,则get方法应该改成is
if("boolean".equals(f.getType().getCanonicalName())) {
gname = "is" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
}
//根据方法名以及属性类型获取方法对象setXXX getXXX
Method methodSet = clz.getMethod(sname, f.getType());
Method methodGet = clz.getMethod(gname);
//执行原对象的get方法获取到返回值 d.getXXX()
Object returnVal = methodGet.invoke(source);
//执行目标对象的set方法完成属性值的赋值 target.setXXX(v)
methodSet.invoke(target, returnVal);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return target;
}
/**
* 实现任意对象的拷贝,返回的具体类型的对象
* @param <T>
* @param source
* @param t
* @return
*/
public static <T> T clone(Object source,Class<T> t) {
T obj = null;
try {
//基于目标类型实例化对象
obj = t.newInstance();
Field[] fields = t.getDeclaredFields();
for (Field f : fields) {
String fname = f.getName();
// 分别获取属性对应的setter/getter方法名称
String sname = "set" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
String gname = "get" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
//如果属性是boolean类型的,则get方法应该改成is
if("boolean".equals(f.getType().getCanonicalName())) {
gname = "is" + fname.substring(0, 1).toUpperCase() + fname.substring(1);
}
//获取方法对象
Method methodSet = t.getMethod(sname, f.getType());
Method methodGet = t.getMethod(gname);
methodSet.invoke(obj, methodGet.invoke(source));
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return obj;
}
public static void main(String[] args) {
Dog d = new Dog();
d.setId(1);
d.setName("旺财");
d.setAge(5);
d.setType("二哈");
User u = new User(1001,"softeem","123");
// 拷贝
// Object d2 = CopyDemo.clone(u);
// u = (User)d2;
// System.out.println(u.getUsername());
Dog user = CopyDemo.clone(d, Dog.class);
System.out.println(user.getName());
}
}
Mysql数据库入门
数据库概述
数据
数据即用于描述一些客观事物的符号;比如说人具备:年龄,身高,体重,姓名,性别等信息,这些信息都称之为数据(在java中称之为属性);数据的分类:
- 文本
- 图形(图片)
- 图像(视频)
- 声音
- 文件
为什么需要数据库
回顾之前所学习到的所有存储机制:内存
>文件
;通过以上分析得知,文件存储可以长期有效存储数据,但是文件只提供了存储机制,如果需要操作文件:
例如,一个文本文档中存储了1000W条数据,如果需要打开改文件会非常耗时,另外如果需要从文件中找出一条符合要求数据,首先需要先读取文件中的所有内容,然后一条一条去分析。
由于文件系统存在以上的问题:在进行检索时效率低,数据的组织方式不够结构化,因此操作数据方面没有优势;因此需要一种能够跟结构化,在检索方面具备绝对优势的数据存储系统
什么是数据库
数据库由一批数据构成的有序集合,这些数据被分门别类地存放在一些结构化的数据表(table)里,而数据表之间又往往存在交叉引用的关系,这种关系使数据库又被称为关系型数据库
怎么理解数据库
- 数据库等同于档案柜(数据库==档案柜)
- 每一张数据表等同档案柜中的抽屉(数据表==抽屉)
- 抽屉中的每一个文件袋称之为一条数据(数据==文件袋)
名词解释
- Data 数据
- Database 数据库
- DBMS (DataBase Management System)数据库管理系统
- DBS(DataBase System)数据库系统
- DBA(Database Administrator)数据库管理员
- OCA 初级认证
- OCP 专家认证
- OCM 大师级认证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XukSnfSB-1596374172382)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595899480547.png)]
去O(Oracle)化
主流数据库产品
- Sybase SqlServer
- MSSqlServer(SqlServer)
- Oracle(先知/甲骨文)
- DB2/Informix (IBM)
- Mysql(瑞典Mysql AB)
- MariaDB
- PostgreSQL
- Access(微软)
- Sqlite3(应用于移动设备:手机,平板,pad)
- 达梦数据库(国产:政府,消防,军工以事业单位为主的客户)
- OceanBase(阿里巴巴+蚂蚁金服)
Mysql安装与卸载
卸载
卸载mysql数据库分为以下四个步骤
- 停止mysql服务
- 进入控制面板,卸载mysql相关的安装程序
- 进入mysql安装目录(默认c:/Program Files) 删除Mysql目录
- 进入mysql数据目录(默认c:/ProgramData)删除mysql目录(重要:如果不删除将会导致下一次重复安装失败)
安装
- 安装
- next
- 配置
- 编码设置(gbk/utf8)
- 密码设置
Mysql基本命令
mysql数据库组织结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gZHIWSTu-1596374172383)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595905308384.png)]
基本命令使用
-
连接mysql数据库
-
直接通过mysql命令行客户端
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lEmZIg2C-1596374172388)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595905462467.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CPIHnGrI-1596374172390)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595905499170.png)]
-
通过 cmd连接mysql(需要配置环境变量)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bUClOvj5-1596374172392)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595905560843.png)]
-h:主机地址
-u:用户名(默认root)
-p:连接密码
-
-
查看mysql数据库服务器中的所有实例
show databases;
-
创建mysql实例
create database mydb;
-
查看创建实例的命令
show create database mydb;
-
显示创建表的命令
show create table emp;
-
使用数据库实例
use mydb;
-
当输入了错误命令,需要取消时
show databaes xxx \c
\c表示取消当前的输入
-
显示当前数据库实例的编码信息
show variables like '%char%';
‘%char%’ :通配符,表示匹配包含了’char’字符的数据
-
查看帮助信息
\h
-
修改默认的分隔符
delimiter $
默认命令行结束为“;”,可以修改为指定符号结束
-
退出mysql命令行
#方式一 exit; #方式二 quit;
-
查看mysql服务器状态信息
\s
-
显示实例下的所有表(前提先use过了)
show tables;
SQL语句入门
SQL是Structured Query Language(结构化查询语言)的缩写;sql语句是专门为数据库管理提供的通用操作语言;语法类似于英语口语,在使用它时,只需要发出“做什么”的命令“怎么做”是不用使用者考虑的。sql语句分为以下几个大类:
-
DDL语句(数据定义语句)
数据定义语句主要应用于对数据表的结构操作:比如建表,删除表,修改表的结构等;DDL语句包含以下命令:
- create
- drop
- alter
- add
- modify
-
DML语句(数据操作语句)
数据操作语句一般用于对的语句数据库表中数据进行更新操作,比如添加,删除,修改数据等,DML语句包含以下命令:
- insert
- update
- delete
-
DQL语句(数据查询语句)
数据查询语句一般用于对数据库表中数据进行查询操作,命令主要包含:select
-
DCL语句(数据控制语句)
数据控制语句一般用于对于数据库用户的权限管理,事务管理,DCL包含以下命令:
- grant
- revoke
- commit
- rollback
DDL语句
-
创建一张数据库表
create table emp -- 创建表,名称叫emp ( eno int, --创建eno列,类型是int ename varchar(30), --创建ename列,类型是varchar长度是30字符 job varchar(30), sex char(2) default '1', --创建sex列,类型是char sal double, birth date );
关于mysql中字符长度问题:
如果是utf8编码下,默认的中文字符占3个字节;如果是gbk编码,默认的中文占2个字节
关于char类型和varchar类型:
- char类型是定长字符串,类似于java中String;长度一旦定义则无法改变,并且无论数据是否够指定长度,都会占满,不够的用空格填充;char类型一般应用于长度确定的字符串类型,比如:性别,手机号,身份证号等;
- varchar类型是可变长度字符串,类似java中StringBuffer;长度定义之后会根据实际填充的内容,选择占用多大空间,比较节省空间;varchar类型一般使用在长度不确定的字符串类型,比如:姓名,邮箱地址,评论信息等。
修改表结构
语法:
alter table 表名 [add/drop/modify/change] 列名称
-
新增一个列
alter table emp add hiredate date;
-
删除列
alter table emp drop column birth;
-
修改列的类型
alter table emp modify sal varchar(20);
-
修改列名称
alter table emp change job ejob varchar(30);
-
修改表名称
alter table emp rename to tbemp;
DML语句
-
显示表中所有的数据
select * from tbemp;
insert语句(插入)
-
向表中添加数据(所有列)
insert into tbemp values(1,'james','manager','1',8796,'2018-01-22',28);
-
向某一些列插入值
insert into tbemp(eno,ename,ejob,sal) values(4,'softeem','CEO',1500000);
-
同时插入多条数据
insert into tbemp(eno,ename) values(5,'来福'),(6,'狗蛋'),(7,'王二狗');
插入数据出现中文乱码时解决方案:
由于安装mysql使用的是UTF8编码,但是cmd命令行中默认为GBK编码,因此在命令行中
使用中文数据是会出现乱码;解决方式只需要在打开cmd命令行时输入以下命令:
- set names gbk;
然后再进行插入操作即可解决(但是以上修改只对一次会话生效;如果只会再次使用cmd需要重新设置)
update语句(更新)
-
修改表中的一条记录
update tbemp set hiredate='2006-03-01',age=45 where eno=4;
-
修改数据(将员工姓名叫旺财的人的月薪提高2000)
update tbemp set sal=sal+2000 where ename='旺财';
-
修改数据(将员工生日为null的人的生日全部设置为2020-01-01)
update tbemp set hiredate='2020-01-01' where hiredate is null;
注意事项:
数据库中是否为空(null)的判断不能使用“=”或“!=”应该使用 is null或者 is not null
delete语句(删除)
-
删除一行指定id的数据
delete from tbemp where eno=5;
-
删除所有月薪为null的员工
delete from tbemp where sal is null;
注意事项:
实际开发中,数据积累不容易,一般不会轻易使用物理删除;大多数时候会选择使用逻辑删除;所谓逻辑删除实际就是在表中增加一个标识列,通过该列的取值标记该列是否应该被查询到
因此针对删除需求,可以在表中新增一列用于标记该列是否被删除
alter table tbemp add isdel tinyint;
mysqldump与mysqladmin
mysqldump(备份)
- 备份指定实例到指定目录中
mysqldump -uroot -p123456 mydb > d:/mydb.sql
- 从指定的sql文件中恢复备份数据
source d:/mydb.sql
mysqladmin
-
使用mysqladmin创建一个数据库实例
mysqladmin -uroot -p123456 create mydb2
-
使用mysqladmin删除一个数据库实例
mysqladmin -uroot -p123456 drop mydb2
-
修改密码
mysqladmin -uroot -p password root
将root用的密码改为"root"
MySql数据类型
mysql数据库中支持的数据类型分为以下几个大类:
- 数值类型
- 整型
- 浮点型
- 字符类型
- 日期时间类型
数值类型
mysql中数值类型主要包含以下两大类
- 整型
- 浮点型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e71HM3Vt-1596374172393)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595923101038.png)]
取值范围:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YgIQWTll-1596374172394)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595924830552.png)]
常用数值类型主要包含以下几个:
- int
- double
- decimal
字符串类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ywJRaTRV-1596374172397)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595924866463.png)]
注意事项:
在使用中文字符串时,如果数据库编码是gbk,则每个中文字符占2个字节;如果是utf8编码,则每个中文字符占3个字节
关于取值返回:
char(m)
varchar(m)
其中m表示的字符个数
常见的字符串类型:
- char
- varchar
- text
blog和clob
blob(Binary Large object)二进制大对象(视频,图片,音频)
clob(Character Large Object)字符大对象(大文本)
enum类型
枚举类型,用于限定该列只能选择枚举中其中一个值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dpjgzhJe-1596374172401)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595925731146.png)]
日期时间类型
获取当前的系统时间
select now()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M5ioYEFs-1596374172402)(D:\带班资料\2020\j2003\线下\part2-mysql&jdbc\20200728\笔记\assets\1595925927087.png)]
常用日期类型:
date 用于表示日期 如:2020-01-01
datetime 用于表示日期时间 如:2020-01-01 10:11:12
timestamp 用于表示时间戳,格式等同datetime,支持默认值 CURRENT_TIMESTMP
另外该类型也支持自动更新(on update CURRENT_TIMESTAMP)当前行 数据更新时,该列也会自动更新为最新的时间
注意事项:
- 一张表中只能有一个字段的timestamp可以设置默认值
Mysql之SQL查询
Mysql图形界面工具使用
mysql的第三方图形界面工具非常多,比如:
- Sqlyog
- Navicat for Mysql
- Navicat Premium
- Mysql Front
- PhpMyAdmin
运算符
算术运算
运算符 | 操作方式 | 说明 |
---|---|---|
+ | a + b | 实现两数相加 |
- | a - b | 两数相减 |
- | -a | 一元减号,取负值 |
* | a * b | 两数相乘 |
/ | a / b | 两数相除 |
% | a % b | 两数取余 |
关系运算
运算符 | 操作方式 | 说明 |
---|---|---|
= | a=b | 如果两操作数相等则为true |
!=,<> | a != b,a<>b | 如果两操作数不相等则为true |
> | a > b | 如果a大于b,为true |
>= | a >= b | 如果a大于等于b,为true |
< | a < b | 如果a小于b,为true |
<= | a <= b | 如果a小于等于b,为true |
in | a in(b1,b2,b3…) | 判断a是否是b1,b2,b3…中的一个 |
between…and | a between b1 and b2 | 判断a是否在b1,b2之间 |
like | a like b | 如果a与b匹配,则为true |
not like | a not like b | 如果a与b不匹配,则为true |
is null | a is null | 如果操作数为null,则为true |
is not null | a is not null | 如果操作数不为null,则为true |
逻辑运算
运算符 | 操作房四海 | 说明 |
---|---|---|
and,&& | a and b,a && b | 逻辑与,两操作数都为true则为true |
or,|| | a or b,a||b | 逻辑或,两操作数任意一个为true,则为true |
not,! | not a,!a | 逻辑非,如果操作数为false,则结果为true |
运算数左右需为boolean结果
SQL查询
SQL查询在数据库中是非常重要的组成,因为未来开发场景下,大多数的功能都集中在查询上,而且查询可以简单,也可以复杂,复杂到很多表之间的联合查询。
SQL查询的语法:
select
distinct
查询列
from
表名称
where
查询条件
group by
分组依据
having
分组的查询条件
order by
排序字段
limit
结果限制
基础查询
-
查询所有列
select * from emp;
-
查询部分列信息(查询员工的姓名,职位,薪资)
select ename,job,sal from emp;
insert into emp(eno,ename,job,age) values(23,'卡牌大师','骨干员工',33);
-- 查询指定列
select ename,job,IFNULL(sal,0) from emp;
--查询时使用运算操作
select ename,ifnull(sal,0)-1000 from emp;
--显示所有的职位,不能重复
select distinct job from emp;
/*
mysql聚合函数查询
count 统计函数
sum 求和
avg 求平均值
max 求最大值
min 求最小值
*/
--查询表中一共有多少员工
select count(*) from emp;
--查询所有员工的总薪资
select sum(sal) from emp;
--查询所有员工的月薪平均值
select avg(sal) from emp;
--查询工资最低的员工薪资
select min(sal) from emp;
--查询工资最高的员工薪资
select max(sal) from emp;
条件查询
/* 条件查询 */
-- 查询年龄超过30的员工
select * from emp where age>=30;
-- 查询所有薪资超过3500 小于10000的员工信息
select * from emp where sal >= 3500 && sal < 10000;
select * from emp where sal between 3500 and 10000;
-- 查询所有在3,5,6三个部门的员工
select * from emp where dno=3 or dno=5 or dno=6;
select * from emp where dno=3 || dno=5 || dno=6;
select * from emp where dno in(3,5,6);
-- 查询所有不是经理的员工
select * from emp where job != '经理';
select * from emp where job <> '经理';
/* 模糊查询 */
-- 查询名字中带有“卡”的员工(模糊查询)
select * from emp where ename like '%卡%';
-- 查询姓“卡”的所有员工
select * from emp where ename like '卡%';
-- 查询只有三个字姓“卡”的员工
select * from emp where ename like '卡__';
/*
"%"和"_":都是占位符,%用于匹配多个字符,“_”用于匹配一个字符
*/
-- 查询名字只包含两个字的员工
select * from emp where ename like '__';
-- 查询所有员工中不是姓李的员工
select * from emp where ename not like '李%';
/*
分组查询:group by
查询目标必须是分组依据或者分组函数(聚合函数)
*/
-- 统计每一种职位的员工各有多少人?
select job,count(*) from emp group by job;
-- 统计每个部门分别有多少人
select dno,count(*) from emp group by dno;
-- 查询每个部门月薪超过3500员工有多少人?
select dno,count(*) from emp where sal > 3500 group by dno;
-- 查询每个部门月薪超过3500员工有多少人,要求显示部门号,人数以及平均薪资?
select dno,count(*) as '总人数',avg(sal) as '平均薪资' from emp where sal > 3500 group by dno;
-- 使用别名
select e.ename n,e.sal s,e.hiredate h from emp e;
/*
排序:order by
ASC 升序 (默认)
DESC 降序
*/
-- 查询所有员工信息,并且按照月薪从高到低排序显示
select * from emp order by sal desc
-- 查询每个部门的平均薪资,并且按照平均薪资从高到低排序(显示:部门号,平均薪资)
select dno,avg(sal) from emp group by dno order by avg(sal)
-- 在以上基础上要求显示平均薪资超过6000的部门号和平均薪资(having)
select dno,avg(sal) from emp group by dno having avg(sal)>=6000 order by avg(sal)
/*
分页:分页需求一般分为假分页(逻辑分页)和真分页(物理分页)
这里主要使用真分页,可以节省内存空间,直接在数据库里面对数据分页
limit
limit一般后面带两个整数参数
1:起始的查询位置
2:本次查询的数据行数
*/
-- 显示结果中的前五条数据
select * from emp limit 5;
-- 以上操作等同
select * from emp limit 20,5;
多表联合查询
/*
多表联合查询
1.等值连接 (查询条件数至少等于 表数-1)
2.内连接
3.左外连接
4.右外连接
5.自连接
*/
-- 等值连接
-- 为避免笛卡尔积出现,应该在查询时加入等值连接条件
-- 显示所有员工和所在部门的信息(emp,dept)
select * from emp,dept where emp.dno=dept.dno;
-- 查询所有员工的工号,姓名,职位,月薪和所在部门名称
select e.eno,e.ename,e.job,e.sal,d.dname from emp e,dept d where e.dno=d.dno;
-- 在以上基础上再显示员工的薪资等级
select
e.eno,e.ename,e.job,e.sal,d.dname,s.level
from
emp e,dept d,sallevel s
where
e.dno=d.dno and
e.sal between s.lowsal and s.hisal;
-- 查询所有T8等级薪资的员工来自哪些部门,显示部门名和员工姓名,薪资
select
e.ename,e.sal,d.dname
from
emp e,dept d,sallevel s
where
e.dno=d.dno and
e.sal between s.lowsal and s.hisal and
s.level='T8';
-- 内连接(根据连接条件获取相交的部分,与等值连接结果一致)
-- 显示所有员工的基本信息包含部门信息
select * from emp e inner join dept d on e.dno=d.dno;
-- 左外连接(左连接)
-- 左连接以左表为基准连接右表,不论左表是否存在与右表关联的数据,左表始终完全显示
-- 查询出所有员工信息包括部门信息,同时要求显示不属于任何部门的员工
select * from emp e LEFT JOIN dept d on e.dno = d.dno;
-- 查询出所有员工和部门的信息,要求显示没有员工的部门信息
select * from dept d LEFT JOIN emp e on e.dno = d.dno;
-- 右外连接(右连接)
-- 右连接以右表为基准连接左表,不论右表是否存在与左表关联的数据,右表始终完全显示
select * from emp e RIGHT JOIN dept d on e.dno = d.dno;
/*
子查询:将一个查询的查询结果当做另一个查询的条件使用
单行子查询
多行子查询
多列子查询(临时表)
*/
-- 查询与猪八戒同一个部门的其他员工信息(子查询)
select * from emp where dno=(select dno from emp where ename='猪八戒');
-- 自连接
select e2.* from emp e1,emp e2 where e1.dno = e2.dno and e1.ename='猪八戒';
-- 使用内连接实现以上需求?
select e2.* from emp e1 INNER JOIN emp e2 on e1.dno=e2.dno and e1.ename='猪八戒'
-- 查询在研发部和行政部的所有员工?
select * from emp where dno in
(select dno from dept where dname in('研发部','行政部'));
-- 查询与猪八戒同一个部门并且同一个职位的员工信息
select e.* from emp e,
(select dno,job from emp where ename='猪八戒') t
where e.dno=t.dno and e.job=t.job and e.ename != '猪八戒';
-- 查询行政部中比研发部中任何一个员工工资都高的员工信息
select e.* from emp e,dept d where e.sal >
(select max(e.sal) from emp e,dept d where e.dno=d.dno and d.dname='研发部')
and d.dname='行政部' and e.dno=d.dno;
-- 找出部门10中所有经理,部门20中所有普通员工以及既不是经理又不是普通员工
-- 但其薪金大于或等6000的所有雇员的详细资料。
select * from emp where dno=20 and job='普通员工'
UNION -- 联合其他查询结果
select * from emp where dno=10 and job='经理'
union
select * from emp where job not in ('普通员工','经理') and sal > 6000;
查询注意事项:
对于任何查询,明确几个目标:
- 查询列
- 查询目标表
- 查询条件
查询方式:
多表查询: 等值连接 内连接 自连接 外连接(左外连接,右外连接) 子查询 单行子查询 多行子查询 多列子查询(虚拟表)
对于同一个查询需求可以使用多种手段实现,但是需要考虑效率
查询语句优化:
- 尽量避免子查询
- 避免使用“*”
- 对查询结果尽量使用limit显示
维护数据完整性之约束
在数据库中维护数据完整性的解决方案有两种:
- 约束(constraint)
- 触发器(trigger)
约束(Constraint)
约束是通过对数据表中的字段使用以一些特殊用途的关键字进行限定,从而使得该列的数据不能随意填写,以此来保障数据的完整性;数据库中一共包含以下5种约束:
- 主键约束(primary key)
- 外键约束(foreign key)
- 唯一约束(unique)
- 检查约束(check) Mysql暂时不生效
- 不为空约束(not null)
主键约束(primary key)
主键约束一般用于一张表中的标识列(该列数据唯一且不为空);每一张表都应该存在一个主键,主键可以用于一个列,也可以应用于多个列
设置主键的方式有三种:
-
直接在建表时,在的字段后使用:
create table tbuser( id int primary key --设置主键 .... )
-
在建表时,所有字段的末尾使用
create table tbuser ( id int auto_increment, username varchar(20), password varchar(30), primary key(id) )
-
表结构已经创建完成,通过DDL语句设置
alter table tbuser add constraint pk_id primary key(id);
注意事项:
- 主键列一般用于标识列(不能重复,且不为空)
- 尽量避免使用联合主键(设置多个列同时为主键)
- 任何表都应该存在主键列
外键约束(foreign key)
外键约束一般用于对一个表与另一个表进行关联时的依据,通常会在表中使用foreign key建立外键;外键必然是另一张表的主键,而另一张就称之为主表,添加外键的表称之为从表。
设置外键的方式有两种:
-
在建表时设置:
create table tbuser ( id int primary key auto_increment, username varchar(30) unique not null, password varchar(64) not null default '123456', sex int check(sex in(0,1)), tid int, foreign key(tid) REFERENCES team(id) -- 外键设置 )
-
表已经创建完成之后,通过DDL设置
alter table tbuser add constraint fk_tid foreign key(tid) references team(id);
唯一约束(unique)
唯一约束用于设置表中指定列是唯一的(不可重复);常见于用于表中的用户名列,分类表中类别名列等,使用方式:
username varchar(30) unique not null,
不为空约束(not null)
设置表中指定列必须给定值,不允许为null
检查约束(check)
检查约束在mysql中还未生效,如果需要对字段进行检查约束,可以考虑使用enum类型。
Mysql数据库(三)
内置函数
聚合函数
函数名 | 说明 |
---|---|
count() | 统计数据行 |
sum() | 对指定列求和 |
avg() | 对指定列求平均值 |
max() | 获取指定列最大值 |
min() | 获取指定列最小值 |
--查询表中一共有多少员工
select count(*) from emp;
--查询所有员工的总薪资
select sum(sal) from emp;
--查询所有员工的月薪平均值
select avg(sal) from emp;
--查询工资最低的员工薪资
select min(sal) from emp;
--查询工资最高的员工薪资
select max(sal) from emp;
字符函数
函数名 | 说明 |
---|---|
concat (st2 [,… ]) | 连接多个字符串为一个字符串 |
length(s) | 获取字符串的字节长度 |
lcase/ucase | 字符串转小写/字符串转大写 |
ltrim(s)/rtrim(s) | 去除字符串左边空格/右边空格 |
substr(s,pos,len) | 将字符串s从pos开始截取len长 |
lpad/rpad | 左填充/右填充 |
-- 获取字符串使用的字符集
select charset('helloworld');
-- 将多个字符串连接为一个字符串
select concat('hello','world',',','softeem')
-- 应用
select * from emp where concat(ename,job,sex) like '%阿%';
-- 返回字符占据的字节数(中文字符占3字节(utf-8),英文字母占1个字节)
select length('hello');
select length('软帝softeem');
-- 将字母转小写(toLowerCase)
select LCASE('HELLO');
-- 去除字符串左边空格
select LTRIM(' world');
-- 左填充(对指定的字符串检测,长度如果未到达目标长度,则使用指定占位符从左边开始填充)
select LPAD('softeem',10,'*');
select LPAD(ename,10,"#") from emp;
select RPAD(ename,10,"#") from emp;
use mydb;
select * from tbemp;
-- 将所有人的名称首字符大写显示 substr 函数索引从1开始
select CONCAT(UCASE(SUBSTR(ename,1,1)),LCASE(SUBSTR(ename,2))) from tbemp;
数值函数
函数名 | 说明 |
---|---|
abs(n) | 取数值绝对值 |
bin(n) | 将十进制数转二进制 |
ceiling(n) | 向上取整 |
floor(n) | 向下取整 |
format(n,len) | 截取len位小数(四舍五入) |
random() | 获取随机数 |
/*
数值函数
*/
-- 绝对值
select abs(-100);
-- 将十进制数转二进制
select bin(10);
-- 向上、向下取整
select CEILING(3.14);
select FLOOR(3.54);
-- 保留指定位小数点
select FORMAT(3.1415926,2);
日期函数
函数名 | 说明 |
---|---|
now() | 获取当前时间 |
current_date() | 获取当前日期 |
current_time() | 获取当前时间(时分秒) |
current_timestamp() | 获取当前时间戳 |
date() | 获取时间的日期部分 |
day() | 获取日期中的天数部分 |
datediff(t1,t2) | 获取两个日期之差(天数) |
/*
时间日期函数
*/
-- 获取当前的系统时间
select now();
select CURRENT_DATE(); -- 获取当前日期
select CURRENT_TIME(); -- 获取当前时间
select CURRENT_TIMESTAMP(); -- 获取当前时间戳
select date(now());
-- 统计从出生到现在一共安全生活了多少天
select DATEDIFF(now(),'1998-07-29')
-- 获取日期中的day部分(天数)
select day(now());
create table temp(today date,msg varchar(30));
insert into temp values(CURRENT_DATE(),'nothing');
select * from temp;
-- 日报表中获取当天提交的日报信息
create table log(
id int primary key auto_increment,
content varchar(20000),
time timestamp default CURRENT_TIMESTAMP);
insert into log(content,time) values('n天前收获很多!!!','2020-03-29 10:11:11');
select * from log where date(time)=date(now());
-- 查询所有的在29号发布的日志
select * from log where day(time)=29;
加密函数
函数名 | 说明 |
---|---|
md5(s) | 对字符串使用md5算法加密 |
sha(s) | 对字符串使用sha加密 |
password(s) | 使用sha1对字符串加密 |
-- 加密函数(非对称加密)
select password('123456');
select MD5('123456');
select SHA('123456');
复制表与数据复制
-- 复制表(同时复制数据)
create table tbemp select * from emp;
-- 复制表结构(只需要结构不要数据)
create table tbemp select * from emp where 1=0;
-- 复制数据(蠕虫复制)
insert into tbemp(ename,job,hiredate,age,sex,sal,dno)
select ename,job,hiredate,age,sex,sal,dno from tbemp;
索引
当表中有大量记录时,若要对表进行查询,第一种搜索信息方式是全表搜索,是将所有记录一一取出,和查询条件进行一一对比,然后返回满足条件的记录,这样做会消耗大量数据库系统时间,并造成大量磁盘I/O操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。
索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得更快,可快速访问数据库表中的特定信息。
索引类似字典的目录,可通过索引快速查询到目标数据。
索引分类
1、普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一点
2、唯一性索引:索引列中的值必须是唯一的,但是允许为空值
3、主键索引(聚簇索引):即表中主键列
4、全文索引:全文索引,只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引。
5、组合索引:
在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。
索引创建语法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H5ZbCDo5-1596374172405)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596078377803.png)]
创建普通索引
create index index_ename on tbemp(ename);
创建唯一索引
create unique index index_username on tbuser(username);
唯一索引要求被添加索引的列值必须唯一
创建组合索引
create index index_emp on tbemp(ename,sal,age);
组合索引的使用一般在进行多条件查询时提升查询效率
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7HbGIrTc-1596374172408)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596079997391.png)]
注意事项:
任何一张数据库表都应该有一个主键列,默认数据系统对主键列增加了聚簇索引,因此针对主键列的查询速度比较快。
Mysql中的索引实现基于B+树(二叉树)
视图
视图实际上就是一张虚拟的表,视图是针对基表的一部分字段的缩影,一般用于对一些字段比较多的表,通过视图可以简化表结构;另外针对一些涉及到多表的查询操作时,建立视图可以简化查询语句;
创建语法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjQpFeJz-1596374172409)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596089489736.png)]
-- 创建视图
create view v$emp as select ename,job,hiredate,sal from emp;
select * from v$emp;
update v$emp set hiredate=now() where ename='孙悟空';
-- 复杂视图创建
create view v$details
as
select
e.eno,e.ename,e.job,e.hiredate,e.age,e.sal,
d.dno,d.dname,d.tel,
s.level
from emp e,dept d,sallevel s where e.dno=d.dno and
e.sal between s.lowsal and s.hisal;
select level from v$details where eno=6;
-- 查询研发部所有人的薪资等级和部门信息
select level,ename from v$details where dname='研发部'
注意事项:
视图不占据存储空间,只是一种逻辑存在(非物理存储);只有在使用视图时才通过视图的定义,加载对应的数据
对视图的操作会影响基表(物理表,实际开发中一般针对视图做查询,避免基于视图做修改
索引提高查询速度,视图简化了查询的方式
事务(Transaction)
概述
事务是实际开发中,比较常见业务逻辑操作中都会存在问题,比如转账的业务,需要将一个账户的钱转向到另一个账号,此时会涉及到两个修改操作:A账户钱减少,B账户钱增加;这两个操作要保证能同时成功或者同时失败,那么这一个业务需求(逻辑单元)就称之为一个事务。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pX4rmkYA-1596374172410)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596090904115.png)]
事务特性
数据库事务包含四大特征(ACID):
- 原子性:对于事务中的多次更新操作要么同时成功,要么同时失败
- 一致性:保证事务操作完成之后,所有的结果一致
- 隔离性:事物之间各自独立存在相互不影响
- 持久性:事务完成之后,确保所有的数据长期持久的存在
Mysql事务的使用
mysql中只有使用InnoDB引擎才支持事务;MyISAM引擎不支持事务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I9kWtX3X-1596374172411)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596093789302.png)]
参考代码
-- 开启事务(事务一旦开,后续的所有更新操作都在这个事务中,直到提交后才会对物理表产生影响)
start transaction;
-- 设置保存点(在当前位置设置保存点,通过rollback to 保存点,可以回滚到该位置)
savepoint p1;
-- 回滚保存点(回滚到指定的保存点:一旦回滚,则当前保存点会撤销)
rollback to p1;
-- 回滚到事务开始的位置
rollback;
-- 提交事务
commit;
事务隔离级别
由于实际的业务操作可能会涉及到很多事务的并发操作,因此在事务并发时可能会遇到以下问题:
- **丢失更新:**撤消一个事务时,把其它事务已提交的更新的数据覆盖了。
- 脏读:事务A读取了事务B更新的数据,而此时事务B并未提交,那么A读取到的数据是脏数据
- **不可重复读:**事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新
并提交,导致事务A多次读取同一数据时,结果 不一致。 - **幻读:**系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B
就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有
改过来,就好像发生了幻觉一样,这就叫幻读。
对于以上可能出现的问题,数据库中引入事务隔离级别的解决方案:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eh31MbTk-1596374172412)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596094489993.png)]
以上是解决事务并发问题的方案,其中隔离级别从低到高,对数据操作的效率影响从低到高;Mysql中默认的隔离级别是:repeatable-read
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S5fJKd1x-1596374172413)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596094769618.png)]
数据库可编程性介绍
概述
在之前学习到所有有关数据库操作几乎都是一行命令解决问题,这些命令大多都是sql标准语法;但是不同的数据库管理系统对sql语句都添加了扩展支持,允许通过一些常见数据类型,运算符,分支语句,循环语句等实现一些类似常规编程语言的操作,这个概念称之为数据库的可编程性;对于不同的数据库产品对于可编程的命名也存在差异:
- MSSQLServer 的可编程性称之为T-SQL
- Oracle中的可编程性称之为PL/SQL
数据库可编程性分类
- 存储过程(procedure)
- 自定义函数(function)
- 触发器(trigger)
存储过程(Procedure)
存储过程(Procedure),是数据库操作语言SQL的可编程性实现,传统的SQL语句通常是在数据库服务器中执行一条命令,命令在数据库引擎内部需要经过创建与分析的过程,因此在运行时间上开销较大;而存储过程是预编译的,在首次运行一个存储过程时,查询优化器对其进行分析、优化,并给出最终被存在系统表中的存储计划,这样,后期的使用只需通过命令调用即可,因此,在执行过程时便可节省此开销。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DfnkaaWu-1596374172415)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596104686639.png)]
-- 查询指定部门名的员工信息?如何使用存储过程实现
create procedure sp_emp1($dname varchar(30))
begin
-- 声明临时变量
declare $dno int;
-- 根据指定的部门名称查询到部门号并赋值到临时变量中
select dno into $dno from dept where dname=$dname;
select * from emp where dno=$dno;
end
call sp_emp1('研发部');
-- 如何利用存储过程实现一个分页操作:输入一个每页数据行数和页码数,显示该页数据
create procedure sp_page(pagesize int,pagenum int)
begin
-- 声明临时变量
declare startNum int;
-- 对变量赋值操作
set startNum = (pagenum - 1) * pagesize;
select * from emp limit startNum,pagesize;
end
-- 删除存储过程
drop procedure sp_page;
call sp_page(3,3)
-- 针对以上分页操作,如何能够获取总页码数并且返回?
create procedure sp_page2(in pagesize int,in pagenum int,out pagecount int)
begin
-- 声明临时变量:开始查询位置
declare startNum int;
-- 总记录行数
declare totalnum int;
-- 对变量赋值操作
set startNum = (pagenum - 1) * pagesize;
-- 查询总数据行数
select count(*) into totalnum from emp;
-- 计算并获取总页码数
set pagecount = CEILING(totalnum/pagesize);
select * from emp limit startNum,pagesize;
end
-- 调用包含输出参数的过程
call sp_page2(10,1,@pagecount);
select @pagecount;
存储过程优劣势:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4SFMNX8Q-1596374172416)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596104342361.png)]
自定义函数(Function)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-55ydMsuC-1596374172417)(D:\带班资料\2020\j2003\线下\part3-mysql&jdbc\20200730\笔记\assets\1596104767212.png)]
-- 根据提供了员工姓名返回该员工的月薪
create function sal($ename varchar(30))
returns double
begin
declare $sal double;
select sal into $sal from emp where ename=$ename;
return $sal;
end
-- 调用函数
select sal('孙悟空')
存储过程与函数区别:
1)一般来说,存储过程实现的功能要复杂一点,而函数的实现的功能针对性比较强。存储过程,功能强大,可以执行包括修改表等一系列数据库操作;用户定义函数不能用于执行一组修改全局数据库状态的操作。
2)对于存储过程来说可以返回参数,如记录集,而函数只能返回值或者表对象。函数只能返回一个变量;而存储过程可以返回多个。存储过程的参数可以有IN,OUT,INOUT三种类型,而函数只能有IN类,存储过程声明时不需要返回类型,而函数声明时需要描述返回类型,且函数体中必须包含一个有效的RETURNS语句。
3)存储过程,可以使用非确定函数,不允许在用户定义函数主体中内置非确定函数。
4)存储过程一般是作为一个独立的部分来执行( EXECUTE 语句执行),而函数可以作为查询语句的一个部分来调用(SELECT调用),由于函数可以返回一个表对象,因此它可以在查询语句中位于FROM关键字的后面。 SQL语句中不可用存储过程,而可以使用函数。