赵阳来软帝的第五周

day23 - 类加载与反射机制

Java内存模型

Java代码是运行在Java虚拟机(JVM)上的,Java虚拟机通过解释执行(解释器)或编译执行(编译器)来完成。Java内存模型分为5个部分:

方法区(Method Area),Java堆(Heap),Java栈(VM Stack),本地方法栈(Native Method Stack),程序计数器(PC 寄存器)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UyhNApQU-1596376192687)(\assets\1595848013154.png)]

虚拟机栈(Stack)

​ 线程私有的,与线程在同一时间创建。管理JAVA方法执行的内存模型。每个方法执行时都会创建一个桢栈来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出StackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutOfMemoryError.

堆(Heap)

​ 线程共享的,存放所有对象实例和数组。垃圾回收的主要区域。可以分为新生代(Eden)、老年代(tenured)、永久代(PermGen,Java8废弃,替换为Metaspace:本地内存)。新生代用于存放刚创建的对象以及年轻的对象,如果对象一直没有被回收,生存得足够长,老年对象就会被移入永久区。
​ 新生代又可进一步细分为eden、survivorSpace0(s0,from space)、survivorSpace1(s1,to space)。刚创建的对象都放入eden,s0和s1都至少经过一次GC并幸存。如果幸存对象经过一定时间仍存在,则进入老年代(tenure、、、、、、、、、、。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Av9m6jSg-1596376192693)(\assets\1595848168133.png)]

方法区(Method Area)

​ 线程共享的,用于存放被虚拟机加载的类的元数据信息:如常量、静态变量、即使编译器编译后的代码。也成为永久代。如果hotspot虚拟机确定一个类的定义信息不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的ClassLoader被回收

程序计数器(Program Count Register)

​ 多线程时,当线程数超过CPU数量或CPU内核数量,线程之间就要根据时间片轮询抢夺CPU时间资源。因此每个线程有要有一个独立的程序计数器,记录下一条要运行的指令。线程私有的内存区域。如果执行的是JAVA方法,计数器记录正在执行的java字节码地址,如果执行的是native方法,则计数器为空。

本地方法区(Native Method Area)

​ 本地方法栈和JVM栈非常相似,它们之间的区别不过是jvm栈是为执行java方法服务,而本地方法栈是为jvm使用到对的本地方法服务(由C/C++编写)。HotSpot虚拟机中直接把本地方法栈和JVM栈合二为一了。

垃圾回收机制(GC)

概述

​ Java GC(Garbage Collection,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。概括地说,该机制对JVM中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息的保证JVM中的内存空间,防止出现内存泄露和溢出问题。

​ 在真实工作中的项目中,时不时的会发生内存溢出、内存泄露的问题,这也是不可避免的Bug,这些潜在的Bug在某些时候会影响到项目的正常运行,如果你的项目没有合理的进行业务内存分配,将会直接影响到的项目的并发处理,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节,而了解GC实现机制则是我们一切监控和调节的前提。

回收算法

对于java堆中的垃圾对象,GC提供了几种回收策略(回收算法):

  • 标记清除法
  • 复制算法
  • 标记压缩法
  • 分代收集

标记清除法(Mark-Sweep)

​ 从根节点开始标记所有可达对象,其余没标记的即为垃圾对象,执行清除。但回收后的空间是不连续的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VDDLyAf-1596376192696)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200727\笔记\assets\1595848403517.png)]

​ 从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。

复制算法(Copying)

​ 为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nYtMh9ZX-1596376192705)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200727\笔记\assets\1595848571334.png)]

​ 这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

标记压缩法(Mark-Compact)

​ 适合用于老年代的算法(存活对象多于垃圾对象)。标记后不复制,而是将存活对象压缩到内存的一端,然后清理边界外的所有对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBE3pBjS-1596376192707)(D:\带班资料\2020\j2003\线下\part1-JavaSE\20200727\笔记\assets\1595848662303.png)]

标记压缩法缺点:效率慢。并且移动他的内存空间会对用户产生速度的影响。

分代收集法(Generational Collection)

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间(占8等份)和两块较小的Survivor空间(各占1等份),每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。

而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。

注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。(Java8中PermGen替换成Metaspace)

类加载机制

​ 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口;
​ JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IhrlLl7S-1596376192708)(0000\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提供了一种称之为反射的机制,能够让我们在运行期间动态的获取到类的成分(属性,方法,构造器,注解等信息)。

问题引入:

  1. 如何在不使用new关键字的情况下创建对象
  2. 如何在只知道实例方法名的情况下执行方法
User u = JSON.parseObject(json,User.class)

所谓反射,其实相当于类照镜子,是一种自省机制;可以使得程序在运行期间动态获取一个类中的成分;在类加载时,任何一个Java类都存在一个java.lang.Class的对象;反射即将类中的成分反射成为对应的类型(属性,方法,构造器,注解等)对象。

获取Class对象

需要使用java的反射机制,首先需要获取到类的Class对象,获取Class对象包含三种方式:

  1. Class.forName(类路径) Class.forName("com.softeem.entity.User")
  2. 类名.class User.class
  3. 对象.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());
    }

}

day1 - Mysql数据库入门

数据库概述

数据

数据即用于描述一些客观事物的符号;比如说人具备:年龄,身高,体重,姓名,性别等信息,这些信息都称之为数据(在java中称之为属性);数据的分类:

  • 文本
  • 图形(图片)
  • 图像(视频)
  • 声音
  • 文件

为什么需要数据库

​ 回顾之前所学习到的所有存储机制:内存>文件;通过以上分析得知,文件存储可以长期有效存储数据,但是文件只提供了存储机制,如果需要操作文件:

例如,一个文本文档中存储了1000W条数据,如果需要打开改文件会非常耗时,另外如果需要从文件中找出一条符合要求数据,首先需要先读取文件中的所有内容,然后一条一条去分析。

由于文件系统存在以上的问题:在进行检索时效率低,数据的组织方式不够结构化,因此操作数据方面没有优势;因此需要一种能够跟结构化,在检索方面具备绝对优势的数据存储系统

什么是数据库

​ 数据库由一批数据构成的有序集合,这些数据被分门别类地存放在一些结构化的数据表(table)里,而数据表之间又往往存在交叉引用的关系,这种关系使数据库又被称为关系型数据库

怎么理解数据库

  • 数据库等同于档案柜(数据库==档案柜)
  • 每一张数据表等同档案柜中的抽屉(数据表==抽屉)
  • 抽屉中的每一个文件袋称之为一条数据(数据==文件袋)

名词解释

  • Data 数据
  • Database 数据库
  • DBMS (DataBase Management System)数据库管理系统
  • DBS(DataBase System)数据库系统
  • DBA(Database Administrator)数据库管理员
    • OCA 初级认证
    • OCP 专家认证
    • OCM 大师级认证

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnvXpp5N-1596376220203)(assets\1595899480547.png)]

去O(Oracle)化

主流数据库产品

  • Sybase SqlServer
  • MSSqlServer(SqlServer)
  • Oracle(先知/甲骨文)
  • DB2/Informix (IBM)
  • Mysql(瑞典Mysql AB)
  • MariaDB
  • PostgreSQL
  • Access(微软)
  • Sqlite3(应用于移动设备:手机,平板,pad)
  • 达梦数据库(国产:政府,消防,军工以事业单位为主的客户)
  • OceanBase(阿里巴巴+蚂蚁金服)

Mysql安装与卸载

卸载

卸载mysql数据库分为以下四个步骤

  1. 停止mysql服务
  2. 进入控制面板,卸载mysql相关的安装程序
  3. 进入mysql安装目录(默认c:/Program Files) 删除Mysql目录
  4. 进入mysql数据目录(默认c:/ProgramData)删除mysql目录(重要:如果不删除将会导致下一次重复安装失败)

安装

  • 安装
    • next
  • 配置
    • 编码设置(gbk/utf8)
    • 密码设置

Mysql基本命令

mysql数据库组织结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1WkvpzvB-1596376220207)(assets\1595905308384.png)]

基本命令使用

  1. 连接mysql数据库

    1. 直接通过mysql命令行客户端

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pcJ7yszE-1596376220212)(assets\1595905462467.png)]

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z5HaQlBi-1596376220217)(assets\1595905499170.png)]

    2. 通过 cmd连接mysql(需要配置环境变量)

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4MRsXyhJ-1596376220219)(assets\1595905560843.png)]

      -h:主机地址

      -u:用户名(默认root)

      -p:连接密码

  2. 查看mysql数据库服务器中的所有实例

    show databases;
    
  3. 创建mysql实例

    create database mydb;
    
  4. 查看创建实例的命令

    show create database mydb;
    
  5. 显示创建表的命令

    show create table emp;
    
  6. 使用数据库实例

    use mydb;
    
  7. 当输入了错误命令,需要取消时

    show databaes xxx \c
    

    \c表示取消当前的输入

  8. 显示当前数据库实例的编码信息

    show variables like '%char%';
    

    ‘%char%’ :通配符,表示匹配包含了’char’字符的数据

  9. 查看帮助信息

    \h
    
  10. 修改默认的分隔符

    delimiter $
    
    

    默认命令行结束为“;”,可以修改为指定符号结束

  11. 退出mysql命令行

    #方式一
    exit;
    #方式二
    quit;
    
    
  12. 查看mysql服务器状态信息

    \s
    
    
  13. 显示实例下的所有表(前提先use过了)

    show tables;
    
    

SQL语句入门

SQL是Structured Query Language(结构化查询语言)的缩写;sql语句是专门为数据库管理提供的通用操作语言;语法类似于英语口语,在使用它时,只需要发出“做什么”的命令“怎么做”是不用使用者考虑的。sql语句分为以下几个大类:

  1. DDL语句(数据定义语句)

    数据定义语句主要应用于对数据表的结构操作:比如建表,删除表,修改表的结构等;DDL语句包含以下命令:

    • create
    • drop
    • alter
    • add
    • mofidy
  2. DML语句(数据操作语句)

    数据操作语句一般用于对的语句数据库表中数据进行更新操作,比如添加,删除,修改数据等,DML语句包含以下命令:

    • insert
    • update
    • delete
  3. DQL语句(数据查询语句)

    数据查询语句一般用于对数据库表中数据进行查询操作,命令主要包含:select

  4. DCL语句(数据控制语句)

    数据控制语句一般用于对于数据库用户的权限管理,事务管理,DCL包含以下命令:

    • grant
    • revoke
    • commit
    • rollback

DDL语句

  1. 创建一张数据库表

    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] 列名称

  1. 新增一个列

    alter table emp add hiredate date;
    
    
  2. 删除列

    alter table emp drop column birth;
    
    
  3. 修改列的类型

     alter table emp modify sal varchar(20);
    
    
  4. 修改列名称

    alter table emp change job  ejob varchar(30);
    
    
  5. 修改表名称

     alter table emp rename to tbemp;
    
    

DML语句

  1. 显示表中所有的数据

    select * from tbemp;
    
    

insert语句(插入)

  1. 向表中添加数据(所有列)

    insert into tbemp values(1,'james','manager','1',8796,'2018-01-22',28);
    
    
  2. 向某一些列插入值

    insert into tbemp(eno,ename,ejob,sal) values(4,'softeem','CEO',1500000);
    
    
  3. 同时插入多条数据

    insert into tbemp(eno,ename) values(5,'来福'),(6,'狗蛋'),(7,'王二狗');
    
    

    插入数据出现中文乱码时解决方案:

    由于安装mysql使用的是UTF8编码,但是cmd命令行中默认为GBK编码,因此在命令行中

    使用中文数据是会出现乱码;解决方式只需要在打开cmd命令行时输入以下命令:

    • set names gbk;

    然后再进行插入操作即可解决(但是以上修改只对一次会话生效;如果只会再次使用cmd需要重新设置)

update语句(更新)

  1. 修改表中的一条记录

    update tbemp  set hiredate='2006-03-01',age=45 where eno=4;
    
    
  2. 修改数据(将员工姓名叫旺财的人的月薪提高2000)

    update tbemp set sal=sal+2000 where ename='旺财';
    
    
  3. 修改数据(将员工生日为null的人的生日全部设置为2020-01-01)

     update tbemp set hiredate='2020-01-01' where hiredate is null;
    
    

    注意事项:

    数据库中是否为空(null)的判断不能使用“=”或“!=”应该使用 is null或者 is not null

delete语句(删除)

  1. 删除一行指定id的数据

     delete from tbemp where eno=5;
    
    
  2. 删除所有月薪为null的员工

    delete from tbemp where sal is null;
    
    

    注意事项:

    实际开发中,数据积累不容易,一般不会轻易使用物理删除;大多数时候会选择使用逻辑删除;所谓逻辑删除实际就是在表中增加一个标识列,通过该列的取值标记该列是否应该被查询到

    因此针对删除需求,可以在表中新增一列用于标记该列是否被删除

    alter table tbemp add isdel tinyint;
    
    

mysqldump与mysqladmin

mysqldump(备份)

  1. 备份指定实例到指定目录中
mysqldump -uroot -p123456 mydb > d:/mydb.sql

  1. 从指定的sql文件中恢复备份数据
source d:/mydb.sql

mysqladmin

  1. 使用mysqladmin创建一个数据库实例

    mysqladmin -uroot -p123456 create mydb2
    
    
  2. 使用mysqladmin删除一个数据库实例

    mysqladmin -uroot -p123456 drop mydb2
    
    
  3. 修改密码

    mysqladmin -uroot -p password root
    
    

    将root用的密码改为"root"

MySql数据类型

mysql数据库中支持的数据类型分为以下几个大类:

  • 数值类型
    • 整型
    • 浮点型
  • 字符类型
  • 日期时间类型

数值类型

mysql中数值类型主要包含以下两大类

  • 整型
  • 浮点型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Xk2Zrch-1596376220220)(\assets\1595923101038.png)]

取值范围:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AY9MM0Sd-1596376220222)(\assets\1595924830552.png)]

常用数值类型主要包含以下几个:

  1. int
  2. double
  3. decimal

字符串类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaoOOJVt-1596376220223)(\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-4LtUwfbu-1596376220224)(\assets\1595925731146.png)]

日期时间类型

获取当前的系统时间

select now()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtyYphW5-1596376220225)(\assets\1595925927087.png)]

常用日期类型:

  • date 用于表示日期 如:2020-01-01

  • datetime 用于表示日期时间 如:2020-01-01 10:11:12

  • timestamp 用于表示时间戳,格式等同datetime,支持默认值 CURRENT_TIMESTMP

​ 另外该类型也支持自动更新(on update CURRENT_TIMESTAMP)当前行 数据更新时,该列也会自动更新为最新的时间

注意事项:

  • 一张表中只能有一个字段的timestamp可以设置默认值

day2 - 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
ina in(b1,b2,b3…)判断a是否是b1,b2,b3…中的一个
between…anda between b1 and b2判断a是否在b1,b2之间
likea like b如果a与b匹配,则为true
not likea not like b如果a与b不匹配,则为true
is nulla is null如果操作数为null,则为true
is not nulla 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
	结果限制

基础查询

  1. 查询所有列

    select * from emp;
    
  2. 查询部分列信息(查询员工的姓名,职位,薪资)

    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;

查询注意事项:

  1. 对于任何查询,明确几个目标:

  2. 查询列

  3. 查询目标表

  4. 查询条件

  5. 查询方式:

多表查询:
等值连接
内连接
自连接
外连接(左外连接,右外连接)

子查询
单行子查询
多行子查询
多列子查询(虚拟表)
  1. 对于同一个查询需求可以使用多种手段实现,但是需要考虑效率

  2. 查询语句优化:

  3. 尽量避免子查询

  4. 避免使用“*”

  5. 对查询结果尽量使用limit显示

维护数据完整性之约束

在数据库中维护数据完整性的解决方案有两种:

  1. 约束(constraint)
  2. 触发器(trigger)

约束(Constraint)

约束是通过对数据表中的字段使用以一些特殊用途的关键字进行限定,从而使得该列的数据不能随意填写,以此来保障数据的完整性;数据库中一共包含以下5种约束:

  1. 主键约束(primary key)
  2. 外键约束(foreign key)
  3. 唯一约束(unique)
  4. 检查约束(check) Mysql暂时不生效
  5. 不为空约束(not null)

主键约束(primary key)

主键约束一般用于一张表中的标识列(该列数据唯一且不为空);每一张表都应该存在一个主键,主键可以用于一个列,也可以应用于多个列

设置主键的方式有三种:

  1. 直接在建表时,在的字段后使用:

    create table tbuser(
        id int primary key   --设置主键
        ....
    )
    
  2. 在建表时,所有字段的末尾使用

    create table tbuser
    (
    	id int auto_increment,
        username varchar(20),
        password varchar(30),
        primary key(id)
    )
    
    
  3. 表结构已经创建完成,通过DDL语句设置

    alter table tbuser add constraint pk_id primary key(id);
    
    

注意事项:

  1. 主键列一般用于标识列(不能重复,且不为空)
  2. 尽量避免使用联合主键(设置多个列同时为主键)
  3. 任何表都应该存在主键列

外键约束(foreign key)

外键约束一般用于对一个表与另一个表进行关联时的依据,通常会在表中使用foreign key建立外键;外键必然是另一张表的主键,而另一张就称之为主表,添加外键的表称之为从表。

设置外键的方式有两种:

  1. 在建表时设置:

    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) -- 外键设置
    )
    
    
  2. 表已经创建完成之后,通过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类型。

day4 - 数据库设计&JDBC入门

数据库可编程性-触发器

数据库优化

数据库设计

​ 目前所有的主流DBMS都是关系型数据库。通过二维表表示数据本身,另外表中存在一些关联列实现表和表之间的关系。

表之间关系

  • 一对一(人对应一个身份证)
  • 一对多/多对一(一个部门包含多个员工)
  • 多对多(学生选课,用户和商品)

一对一

实际开发中一对一的关系并不常见,大多数时候一对一的关系其实可以建立成为一张表;如果需要建立一对一的关系,实现方式有两种:

  1. 唯一外键关联(在其中一张表建立外键,同时设置外键列唯一unique)

  2. 主键关联(两个表的主键列一致)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUSAhGrd-1596376442648)(assets\1596164167153.png)]

一对多/多对一

一对多/多对一关系在开发中十分常见,大多数时候表之间的关系都是一对多/多对一的,比如:员工和部门,学生和班级,老师和学生,商品类别和商品,实现方式:

  • 在多的一方表中新增外键列,关联一的一方的主键列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfJ6Z04H-1596376442651)(assets\1596164769379.png)]

多对多

多对多的关系在开发中也是很常见的,比如说:学生和课程关系,用户和商品的,实现关联的方式如下:

  • 通过第三张表维护两个表的关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYOOpn2m-1596376442654)(assets\1596165443776.png)]

数据库设计范式

​ 在进行数据库设计的时候需要满足的一些规范形式,称之为数据库范式,满足范式的数据库设计是合理的,数据冗余小的,并且不会引起数据更新的异常。

​ 目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。

数据库设计三大范式

第一范式

要求建立的数据库表中所有的列是原子的,每一列不可再拆分;目前的关系型数据库默认都是满足第一范式(不可能创建出不满足第一范式的数据表)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EyHDlsEE-1596376442658)(assets\1596166392271.png)]

第一范式:列不可再分

观察以上表,满足第一范式,但是存在以下问题:

  1. 大量的数据冗余
  2. 进行插入操作时会出现插入的异常
  3. 在删除操作时会将一些不能删除列也一并删除(删除异常)
第二范式

​ 数据库表中不存在非关键字段(主键)对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字(联合主键)中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

  • 关键字段: 主键
  • 组合关键字:联合主键
  • 函数依赖:字段A->字段B,那么B依赖A,称之为函数依赖
  • 部分依赖:(字段A,字段B)->字段C ,字段C部分依赖A和B,称之为部分依赖
  • 完全依赖:要求非联合主键的字段完全依赖主键,或者字段完全依赖联合主键

第二范式:在满足第一范式的情况下,表中的非主键列对主键列必须完全依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPM8LdJY-1596376442661)(assets\1596167334552.png)]

经过第二范式的规范,数据冗余的问题解决了,但是:

  1. 进行插入操作时会出现插入的异常
  2. 在删除操作时会将一些不能删除列也一并删除(删除异常)

两个问题还是存在

第三范式

​ 在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如果存在"A → B → C"的决定关系,则C传递函数依赖于A

  • 传递函数依赖:表中的列存在A决定B,B决定C的关系

第三范式: 满足第二范式的基础上,要求表中列不能存在传递依赖关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zwll4Shf-1596376442663)(assets\1596167846781.png)]

范式总结

​ 在实际开发中,一般情况只要满足三大范式即可;另外,由于程序对查询的需求(处于便捷性考虑)可能会出现违背三大范式的情况;因此三大范式只是设计数据时候的一种参考,并不是定律。

范式的存在主要解决了:

  1. 数据冗余
  2. 更新(insert,delete)操作异常

数据库设计案例分析

概念模型设计(ER图)

实体(表),关系理解为表之间的联系;在数据库设计阶段,实体关系图的建立位于概念模型设计阶段,这一阶段主要用于进行实体之间的关系建立

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJ7ewfVM-1596376442663)(assets\1596177151438.png)]

物理模型设计(建表)

数据库设计一般使用一些专业设计工具,其中最常见以sybase(SAP)的数据库建模工具 PowerDesigner最为常见,还有一些其他数据库图形工具,比如navcat的模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oKHkXVEG-1596376442664)(assets\1596183813747.png)]

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页