java知识点总结

1.Java

1.1 基础部分

1)switch-case支持数据类型(整形常量)

  • 支持数据类型(整形常量)
1)只能是如下的六种数据类型之一:`byte`、`short`、`char`、`int`、`枚举类型`(JDK5.0)、`String类型`(JDK7.0)
2)不能是:longfloatdoubleboolean
  • case穿透
switch语句中,如果case的后面不写break,将出现穿透现象,也就是不会在判断下一个case的值,直接向后运 行,直到遇到break,或者整体switch结束。

2)成员变量&局部变量&静态变量*

成员变量局部变量静态变量
定义方法外,类中方法内,形参,代码块中方法外,类中
调用方式对象调用局部范围内(函数,语句内)对象,类型调用
生命周期与对象共存亡与方法共存亡与类共存亡
存储位置堆内存栈内存方法区
初始值有默认初始化值没有,先赋值后使用有默认初始化值
别名实例变量类变量
修饰符可以使用不能使用

遵循的原则为:就近原则

首先在局部范围找,有就使用;接着在成员位置找。

3)类&对象*

对象
定义一类事物的描述,是抽象的是一类事物的实例,是具体的
区别类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。对象是一个动态的概念。每一个对象都存在着有别于其它对象的属于自己的独特的属性和行为。对象的属性可以随着它自己的行为而发生改变。

具体的:类是对象的模板,对象是类的实例。类只有通过对象才可以使用,而在开发之中应该先产生类,之后再产生对象。类不能直接使用,对象是可以直接使用的。

4)值传递*

值传递引用传递
定义在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
区别会创建副本。不能改变原始对象不会创建副本。可以改变原始对象

注意:

1.Java都是值传递

​ 1)当基本数据类型时,是将变量值拷贝一份给方法的形参,不会改变原有变量的值。

​ 2)当引用数据类型时,是将对象引用的地址值拷贝一份给形参,对对应地址值上的数据操作,原来的也会改变。

5)封装继承多态**

①封装
  • 高内聚&低耦合
1.高内聚:类的内部数据操作细节自己完成,不允许外部干涉
2.低耦合:仅对外暴露少量的方法用于使用
  • 介绍
	将类的某些信息隐藏在类的内部,不允许外部程序直接访问,并通过该类提供的方法来实现对隐藏信息的操作和访问。(简单的说就是隐藏对象的信息,留出访问的接口)。
  特点:
  		1.只能通过规定的方法访问数据;
  		2.隐藏类的实例细节,方便修改和实现。
  • 四种权限修饰符
内部类同一个包不同包的子类同一个工程
privateyes
缺省yesyes
protectyesyesyes
publicyesyesyesyes
②继承
  • 继承性的好处
	多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
	①# 减少了代码的冗余,提高了代码的复用性; # 便于功能的扩展;  # 为之后多态性的使用,提供了前提。
  • 体现
①一旦子类 A 继承父类以后,子类 A 中就获取了父类 B 中声明的结构:属性、方法
	特别的,父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。
	只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
②子类继承父类以后,还可以声明自己特有的属性或方法,实现功能的拓展。
  	子类和父类的关系:不同于子集与集合的关系。
  	extends:延展、扩展
③规定
   	1.一个类可以被多个类继承
  	2.Java 中类的单继承性:一个类只能有一个父类
  	3.子父类是相对的概念。
  	4.子类直接继承的父类,称为:直接父类。间接继承的父类,称为,间接父类。
  	5.子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法。
    6.如果我们没有显式的声明一个类的父类的话,则此类继承于 java.lang.Object
③多态
  • 父类引用指向子类对象
Father father=new Son()Son son=(Son)father
相同成员变量父类子类
相同static成员变量父类子类
重写方法子类子类
static方法父类子类
只有父类有父类父类
  • 解释
    • 因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特, 定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能又可以抽取父类的共性

    • 所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的;

    • 同时,父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用

    • 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。也可以叫做动态绑定。

6)重写&重载

重写(overriding)重载(overloading)
多态体现父类与子类之间多态性的表现一个类中多态性的表现
定义子类中定义某方法与其父类有相同的名称和参数同名方法,不同参数个数或有不同的参数类型
  • 重写规定
1)子类与父类重写的方法名,方法形参列表相同
2)子类重写方法的修饰权限不小于父类被重写的方法(向上转型时,调用方法矛盾)
	子类不能重写父类修饰符为private的方法
3)返回值类型:
	父类方法返回值类型void,则子类一定为void
	父类方法返回值A类,则子类一定为A类或者A类的子类(例如,intdouble不能被重写)
4)子类重写的方法抛出异常不大于父类被重写的方法抛出的异常类型
5)要非static修饰都一样,要么都是static修饰(此时不是方法的重写,不能被覆盖)
6)重写以后,当创建子类对象以后,通过子类对象去调用子父类中同名同参数方法时,执行的是子类重写父类的方法。即在程序执行时,子类的方法将覆盖父类的方法。
7static修饰的方法不能被重写

7)static&final

staticfinal
修饰内容属性、方法、代码块、内部类类、变量、方法
定义创建多个对象,多个对象共享个静态变量或方法最终的、不能被修改。被修改的类不能被继承
使用① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用。
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一次。存在方法区的静态域中。
①修饰属性,可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
⑤修饰局部变量,尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。 一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
用途工具类(Math,Arrays、Collections)、共享的数据常量、不可改变的类(String、System、StringBuffer)

常量: static + final

  • 单例模式(饿汉式&懒汉式)
//饿汉式
class Single{
    private static Single single = new Single();
    private Single(){
    }
    public static Single getSingle(){
        return single;
    }
}
//懒汉式--线程安全
class Single{
    private static Single single = null;
    private Single(){
    }
    public static Single getSingle(){
        if (single == null){
            synchronized(Single.class){
                if (single == null){
                    single =  new Single();
                }
            }
        }
        return single;
    }
}

8)代码块

静态代码块非静态代码块
作用初始化类初始化对象
创建类的加载而执行(执行一次)对象的创建而执行
顺序优于非静态代码块优于构造器
调用只能调用静态的属性、方法可以调用静态的,也可以调用非静态的

执行:

#先父后子、静态先行
	1.随着类的加载,先执行静态代码块(只执行一次)
	2.随着对象的创建,先执行父类的代码块、构造器,后执行子类的

9)抽象类&接口

区别点抽象类接口
1定义既可以有抽象方法,也可以有普通方法只能定义方法,不能实现
2组成构造方法、抽象方法、普通方法、常量、变量常量、抽象方法、(jdk8.0:默认方法、静态方法)
3使用子类继承抽象类(extends)子类实现接口(implements)
4构造器有构造器,不能实例化没有构造器,不能实例化
5常见设计模式模板方法简单工厂、工厂方法、代理模式
6访问权限publicprotected缺省public
7局限抽象类有单继承的局限接口没有此局限
8优点可以有方法的实现,便于扩展可以同时被多个类实现,

说明

​ 也就是说在层次结构中,Java 接口在最上面,然后紧跟着抽象类,这下两个的最大优点都能发挥到极至了。这个模式就是缺省适配模式

A extends AbstractB implements interfaceC,那么A可以选择实现(@Override)接口interfaceC中的方法,也可以选择不实现;A可以选择实现(@Override)抽象类AbstractB中的方法,也可以选择不实现

  • 静态代理
public class Test {
    public static void main(String[] args) {
        ProxyServer proxyServer = new ProxyServer(new Server());
        proxyServer.show();
    }
}
interface Work{
    void show();
}
class Server implements Work{
    @Override
    public void show(){
        System.out.println("真实的服务器");
    }
}
class ProxyServer implements Work{
    private Work work;
    public ProxyServer(Work work){
        this.work = work;
    }
    @Override
    public void show() {
        System.out.println("准备中。。。。");
        work.show();
        System.out.println("销毁中。。。。");
    }
}

1.2 java高级

1)多线程

a.概念
1.程序:一组指令的集合(静态代码)
2.进程:正在运行的程序,或者程序的一次执行过程(动态过程)
3.线程:进程执行的一条路径

1.并行:多个cpu同时执行多个任务(多人做多事)
2.并发:一个cpu同时执行多个任务(多人做一事)
b.创建线程的四种方式
①继承Thread类
public class Test extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("a" + i);
        }
    }
    public static void main(String[] args) {
        Test test = new Test();
        //线程test
        test.start();
        //线程main
        for (int i = 0; i < 100; i++) {
            System.out.println("b" + i);
        }
    }
}
  • 相关的方法
 * 1.start():启动当前线程,执行当前线程的run()
 * 2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
 * 3.currentThread(): 静态方法,返回当前代码执行的线程
 * 4.getName():获取当前线程的名字
 * 5.setName():设置当前线程的名字
 * 6.yield():释放当前CPU的执行权
 * 7.join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才
结束阻塞状态。
 * 8.stop():已过时。当执行此方法时,强制结束当前线程。
 * 9.sleep(long millitime):让当前线程“睡眠”指定时间的millitime毫秒)。在指定的millitime毫秒时间内,当前线程是阻塞状态的。
 * 10.isAlive():返回boolean,判断线程是否还活着
     
 * - 线程的优先级等级
 *   - MAX_PRIORITY:10
 *   - MIN _PRIORITY:1
 *   - NORM_PRIORITY:5 --->默认优先级
 * - 涉及的方法
 *   - getPriority() :返回线程优先值
 *   - setPriority(intnewPriority) :改变线程的优先级
②实现Runable接口
public class Test implements Runnable{
    public static void main(String[] args) {
        Thread a = new Thread(new Test());
        a.start();
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}
③实现Callable接口
public class Work implements Callable<Integer> {
    private int ticket = 500;
    public static void main(String[] args) {
        FutureTask<Integer> task = new FutureTask<>(new Work());
        Thread thread1 = new Thread(task, "线程1:");
        thread1.start();
        try {
            System.out.println(task.get());		//获取返回值
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Integer call() throws Exception {
        while (true){
            Thread.sleep(10);
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + ticket--);
            }else {
                System.out.println("没票了!!");
                return 0;
            }
        }
    }
}

④创建线程池
1)优势
描述
(1)降低资源消耗通过重复利用已创建的线程降低线程创建销毁造成的消耗
(2)提高响应速度重复利用线程池中线程,不需要每次都创建
(3)提高线程的可管理性线程池可以进行统一分配,调优和监控
2)使用

线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {}
 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler) {}
//最终都调用构造器 
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

​ 参数介绍:

参数名说明
corePoolSize(必需)核心线程数。默认情况下,核心线程会一直存活。
但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
maxnumPoolSize(必需)线程池所能容纳的最大线程数。
当活跃线程数达到该数值后,后续的新任务将会阻塞。
keppAliveTime(必需)线程闲置超时时长。如果超过该时长,非核心线程就会被回收。
如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
unit(必需)指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
workQueue(必需)任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
threadFactory(可选)线程工厂。用于指定为线程池创建新线程的方式。
handler(可选)拒绝策略。当达到最大线程数时需要执行的饱和策略。

任务队列(workQueue):

参数说明
ArrayBlockingQueue一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)
LinkedBlockingQueue一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE
PriorityBlockingQueue一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
DelayQueue类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
SynchronousQueue一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
LinkedBlockingDeque使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)
LinkedTransferQueue它是ConcurrentLinkedQueueLinkedBlockingQueueSynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

拒绝策略(handler):

拒绝策略(handler)描述
AbortPolicy(默认)丢弃任务并抛出 RejectedExecutionException 异常
CallerRunsPolicy由调用线程处理该任务
DiscardPolicy丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式
DiscardOldestPolicy丢弃队列最早的未处理任务,然后重新尝试执行任务
3)4 种常见的功能线程池

4)总结

Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

其实 Executors 的 4 个功能线程有如下弊端:

  • FixedThreadPoolSingleThreadExecutor:主要问题是堆积的请求处理队列均采用 LinkedBlockingQueue,可能会耗费非常大的内存,甚至 OOM
  • CachedThreadPoolScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM
c.生命周期
1.新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2.就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3.运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
4.阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
5.死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

d.线程同步
①同步代码块
synchronized (同步监视器){
	同步代码
}

**说明**:
​	1.操作**共享数据**的代码(同步代码块)
​			共享数据:多个线程**共同**操作的变量

​	2.**同步监视器**,俗称锁。任何一个类的的对象,都可以充当一个锁。
​			要求:多个线程必须公用一个锁
    		1)this当前对象 2)对象.class 3)Object
    3.操作共享数据的代码,即为需要被同步的代码 --->不能包含代码多了,也不能包含代码少了
    4.实现Runable接口,可以用this,而继承Thread谨慎使用this
②同步方法
1.如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
 *  1)同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
 *  2)非静态的同步方法,同步监视器是:this
 *     静态的同步方法,同步监视器是:当前类本身
③lock锁(JDK5.0)
1.死锁:
	不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
public class Work extends Thread implements Runnable{
    private static int ticket = 100;
    //1.实例化lock,参数true表示按顺序轮流执行线程
    private ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while (true){
            lock.lock();//2.枷锁
            try {		//必须放在try-finally里面
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName() + ticket--);
                }else {
                    break;
                }
            }finally {
                lock.unlock();//3.释放锁
            }
        }
    }
    public static void main(String[] args) {
        Work work = new Work();
        Thread a = new Thread(work, "a: ");
        Thread b = new Thread(work, "b: ");
        Thread c = new Thread(work, "c: ");
        a.start();
        b.start();
        c.start();
    }
}
e.线程的通信
  • sleep()和wait()的异同
sleep()wait()
声明位置Thread类中声明Object类中声明
调用要求任何需要的场景下同步代码块或同步方法中
否释放同步监视器不会释放锁会释放锁
  • wait(),notify(),notifyAll()
wait()notify()notifyAll()
执行结果进入阻塞状态,并释放同步监视器会唤醒被wait的一个线程唤醒所有被wait的线程
说明
1.使用位置必须使用在同步代码块同步方法
2.调用者要求调用者必须是同步代码块或同步方法中的同步监视器
3.定义位置定义在java.lang.Object类中

2)String、StringBuffer和StringBuilder

①String

public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
    private int hash;
}

1.Serializable 序列化接口,表示对象可以被序列化。
2.Comparable 可比较接口,提供了一个compareTo()方法,用来比较两个对象的大小。
3.CharSequence 字符序列接口,提供了几个对字符序列进行只读访问的方法,比如:length()charAt()subSequence()toString()方法等。
4.value 用来存储字符串中的字符。因此,String类的底层实现是把字符存储在一个char类型的数组中。value数组被 final 所修饰,所以String对象创建之后就不能被修改了。
5.hash 用来缓存计算之后的哈希值。这样就不用每次都重新计算哈希值了。
②StringBuffer
  • 定义
public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
    private transient char[] toStringCache;
    //继承AbstractStringBuilder的属性
	char[] value;
    int count;
}
1.Serializable 序列化接口,表示对象可以被序列化。
2.CharSequence 字符序列接口,提供了几个对字符序列进行只读访问的方法,比如:length()charAt()subSequence()toString()方法等。
3.toStringCache 用来缓存toString()方法返回的最近一次的value数组中的字符。当修改StringBuffer对象时会被清除。
4.value 用来存储字符序列中的字符。value是一个动态的数组,当存储容量不足时,会对它进行扩容。
5.count 表示value数组中已存储的字符数。
  • append源码分析
public synchronized StringBuffer append(String var1) {
        this.toStringCache = null;		//清除上一次toString,保存下的字符数组(字符串值)
        super.append(var1);		//拼接
        return this;
}
//扩容方式(append)--- 初始容量16
1.ensureCapacityInternal(拼接之后的总长度);	//判断容量是否需要扩容this.value = Arrays.copyOf(原字符数组, this.newCapacity(需拼接的字符串))//如果容量不足,数组容量扩大
  		③newCapacity ——> (this.value.length << 1) + 2; //容量扩大为原来的2倍+2
2.getChars(首索引位置,末索引位置,目标数组,目标数组开始偏移量)		//将新字符串拼接System.arraycopy(原数组, 原数组起始位置,新数组,新数组起始位置,复制多长的原数组)	//
3.this.count += var2	//字符数组长度,增加
③StringBuilder

类似Stringbuffer

  • 注意:StringBuilder没有成员变量toStringCache
1.存储的是value的一个副本,只要value值发生变化,toStringCache就会置为空。
	使得每次调用toString,产生一个新的toStringCache数组副本,保证引用旧的toStringCache字符串对象不会改变
2.优点:
	1)调用toString时,共享一个toStringCache数据,提高了创建String对象的速度
	2)连续调用不会,创建内容一样的String多个对象
④区别与联系
StringBufferStringBuilder
线程安全是,速度慢否,速度块
继承关系都是继承AbstractStringBuilder
toStringCache有。解决线程安全问题没有

3)Comparable和Comparator

ComparableComparator
实现类String、包装类
重写的方法compareTo(obj)compare(Object o1,Object o2)
规则如果当前对象this大于形参对象obj,则返回正整数,
如果当前对象this小于形参对象obj,则返回负整数
如果当前对象this等于形参对象obj,则返回零。
如果方法返回正整数,则表示o1大于o2
如果返回0,表示相等
返回负整数,表示o1小于o2
对比一旦一定,按这种排序临时性的比较
使用原因1、当元素的类型没有实现java.lang.Comparable接口而又不方便修改代码
2、一个对象实现了Comparable接口,但是开发者认为compareTo方法中的比较方式并不是自己想要的那种比较方式
优点个性化比较、解耦
CollectionsCollections.sort(T[])Collections.sort(T[], Comparator<? super T>)

java8新特性对象排序:

先按照age倒序排,然后按照name正序排
//①集合方法排序(改变原list)				    	        list.sort(Comparator.comparing(Man::getAge).reversed().thenComparing(Man::getName))   
    
//②stream排序 (不改变list,生产新的)   list.stream().sorted(Comparator.comparing(Man::getAge).reversed().thenComparing(Man::getName)).forEach(System.out::println);

4)注解

①介绍
1.定义新的Annotation类型使用@interface关键字
2.自定义注解自动继承了java.lang.annotation.Annotation接口
3.Annotation的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
4.可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用**default**关键字
5.如果只有一个参数成员,建议使用参数名为value
6.如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名=参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
7.没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据Annotation
注意:自定义注解必须配上注解的信息处理流程才有意义
②四个元注解
  • @Retention—>生命周期
@Retention: 只能用于修饰一个Annotation定义, 用于指定该Annotation 的生命周期, @Rentention包含一个RetentionPolicy类型的成员变量, 使用@Rentention时必须为该value 成员变量指定值:

	1RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
	2RetentionPolicy.CLASS:class文件中有效(即class保留),当运行Java 程序时, JVM 不会保留注解。这是默认值
	3RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。

  • @Target—>修饰范围
用于修饰Annotation 定义, 用于指定被修饰的Annotation 能用于修饰哪些程序元素。@Target 也包含一个名为value 的成员变量。
@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR)  //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包  
  • @Documented—>是否在.class文件包含
用于指定被该元Annotation 修饰的Annotation 类将被javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。
  • @Inherited—>子类会继承这个注解
被它修饰的Annotation 将具有继承性。如果某个类使用了被@Inherited 修饰的Annotation, 则其子类将自动具有该注解。
③Java8新特性
  • 可重复注解
@Repeatable(MyAnnotations.class)

@MyAnnotation(value = "hi")
@MyAnnotation(value = "abc")
//jdk 8之前的写法:
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hi")})
  • 类型注解
ElementType.TYPE_PARAMETER表示该注解能写在类型变量的声明语句中(如:泛型声明)。
ElementType.TYPE_USE表示该注解能写在使用类型的任何语句中。
④反射获取注解信息(需补)

5)集合

①介绍
1)继承关系

2)Collection接口
增删改查等等

小知识点(Arrays.asList):

public static <T> List<T> asList(T... var0) {
    return new Arrays.ArrayList(var0);
}
//可以将一个变长参数或者数组转换成List

/**
*1.可变参数为基本数据类型数组时,由于基本数据类型不能泛型化,会当作一个数组对象传进去,size为1
*2.。。。。。包装类数组。。。。。。,可以泛型化,会当作多个参数传进去,size就是多个
*3.地层实现Arrays下的内部类ArrayList,没有重写add,remove,而且以final修饰变量。因此不能改变,会报异常。
*4.底层数组作为其物理实现,只要执行操作修改这个list就会修改原来的数组。要想不改变原来数组,就要在另一个容器中创建一个副本,写法如下:new ArrayList<String>(Arrays.asList(test));
3)Iterator迭代器接口
1.Collection接口继承了java.lang.Iterable接口,该接口有一个iterator()方法,那么所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象。
2.Iterator 仅用于遍历集合,Iterator本身并不提供承装对象的能力。如果需要创建Iterator 对象,则必须有一个被迭代的集合。
3.集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。

  • 错误写法
//错误方式一:
Iterator iterator = coll.iterator();
while(iterator.next() != null){
    System.out.println(iterator.next());
}

//错误方式二:
//集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
while(coll.iterator().hasNext()){
    System.out.println(coll.iterator().next());
}

//如果还未调用next()或在上一次调用next方法之后已经调用了remove方法,再调用remove都会报IllegalStateException。
②List
1. List接口框架
|----Collection接口:单列集合,用来存储一个一个的对象
  |----List接口:存储有序的、可重复的数据。  -->“动态”数组,替换原有的数组
   |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[]elementData存储 
   |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
   |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[]elementData存储
1)ArrayList

public class ArrayList<E> 
    //让*通用*的方法在继承的抽象类AbstractList中实现
    extends AbstractList<E>
    	//通用抽象方法
        implements List<E>, 
					//支持快速(通常是固定时间)随机访问
					RandomAccess, 
					//可以使用Object.Clone()方法
					Cloneable, 
					//(序列化)从类变成字节流传输,然后还能从字节流变成原来的类。
					java.io.Serializable{
	//1.序列化id
	private static final long serialVersionUID = 8683452581122892189L;
    //2.默认的初始化容量                    
    private static final int DEFAULT_CAPACITY = 10;
    //3.指定该ArrayList容量为0时,返回该空数组。
    private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    //4.当调用无参构造方法,返回的是该数组。刚创建一个ArrayList 时,其内数据量为0。
 	//它与EMPTY_ELEMENTDATA的区别就是:该数组是默认返回的,而EMPTY_ELEMENTDATA是在用户指定容量为0时返回。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
    // 5.保存添加到ArrayList中的元素。 
	// ArrayList的容量就是该数组的长度。 
	// 该值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时,当第一次添加元素进入ArrayList中时,数组将扩容值DEFAULT_CAPACITY。 
	// 被标记为transient,在对象被序列化的时候不会被序列化。                   
    transient Object[] elementData;
    //6.实际大小(数组包含的元素个数/实际数据的数量)默认为0              
    private int size;
    //7.分派给arrays的最大容量(Integer.MAX_VALUE - 8)	, 为什么要减去8呢?
 	//因为某些VM会在数组中保留一些头字,尝试分配这个最大存储容量,可能会导致array容量大于VM的limit,最终导致OutOfMemoryError。                    
    private static final int MAX_ARRAY_SIZE = 2147483639;
                    
    //1.指定容量
    public ArrayList(int initialCapacity)    
    //2.空List(DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    public ArrayList(int initialCapacity)
    //3.将collection对象转换成数组,然后将数组的地址的赋给elementData
    public ArrayList(Collection<? extends E> var1)                   
}
  • add()方法
public boolean add(E var1) {
    //确保容量是否足够
    this.ensureCapacityInternal(this.size + 1);
    //添加元素,长度size自增
    this.elementData[this.size++] = var1;
    return true;
}

【扩容方式】
this.ensureExplicitCapacity(
    calculateCapacity(this.elementData, var1))    
/*
*1. elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA ? Math.max(10, size+1) : size+1
*【计算所需容量】若elementData空,默认10容量;第二次及之后扩容,所需容量size+1

*2. ensureExplicitCapacity(第一步 计算出来所需的容量)
*【扩容】++this.modCount,修改次数自增;grow(第一步 计算出来所需的容量),扩容

*第一步 计算出来所需的容量 - this.elementData.length > 0  容量不足
*3. this.grow(第一步 计算出来所需的容量)
*【扩容方式】var2 + (var2 >> 1)先扩容至1.5倍;
				如果不够,直接扩容到所需容量
				如果超出最大容量,只能扩容到最大容量
				Arrays.copyOf(this.elementData, 所需容量)
2) LinkedList

public class LinkedList<E> 
    extends AbstractSequentialList<E> 
    implements List<E>, 
				//可以作为一个双端队列
                Deque<E>, 
                Cloneable, 
                Serializable {
    
    //元素实际个数                
    transient int size;
    //头节点                
    transient LinkedList.Node<E> first;
    //尾节点                
    transient LinkedList.Node<E> last;
                    
    //内部类Node                
    private static class Node<E> {
    E item;// node存储的元素
    Node<E> next;// 前驱
    Node<E> prev;// 后驱
    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
        
    //无参构造函数
    public LinkedList() {this.size = 0;}
    //带参构造函数    
    public LinkedList(Collection<? extends E> var1) {
       this();
       this.addAll(var1);
    }    
}
                
  • 指定索引添加元素add(int index, E element)
public void add(int var1, E var2) {
    this.checkPositionIndex(var1);
    if (var1 == this.size) {
        this.linkLast(var2);	//尾节点插入
    } else {
        //其他节点插入(node()方法:会判断在前一半,还是后一半去遍历)
        this.linkBefore(var2, this.node(var1));
    }
}

void linkLast(E var1) {
    //当前最后一个节点
    LinkedList.Node var2 = this.last;
    //创建节点,并使得其前驱节点指向原尾节点
    LinkedList.Node var3 = new LinkedList.Node(var2, var1, (LinkedList.Node)null);
    //新创建的尾节点
    this.last = var3;
    //如果空链表,添加元素,即首节点也是尾节点
    if (var2 == null) {
        this.first = var3;
    //否则,原尾节点的next指向新尾节点
    } else {
        var2.next = var3;
    }

    ++this.size;
    ++this.modCount;
}

void linkBefore(E var1, LinkedList.Node<E> var2) {
    LinkedList.Node var3 = var2.prev;
    //插入节点指定前驱和后继
    LinkedList.Node var4 = new LinkedList.Node(var3, var1, var2);
    var2.prev = var4;
    //判断是否空链表插入
    if (var3 == null) {
        this.first = var4;
    } else {
        var3.next = var4;
    }

    ++this.size;
    ++this.modCount;
}
3)Vector
1.Vector是线程安全的, ArrayList不是线程安全的, 这是最主要的
2.ArrayList不可以设置扩展的容量, 默认1.5; Vector可以设置, 默认23.ArrayList无参构造函数中初始量为0; Vector的无参构造函数初始容量为10
  • Vector与Collections.synchronizedList
1.SynchronizedList有很好的扩展和兼容功能, 可以将所有的List子类转成线程安全的类
2.使用SynchronizedList在遍历的时候要手动进行同步处理
3.SynchronizedList可以指定锁对象
4)总结
ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储
③Set(建议先看Map)
Set接口的框架:
|----Collection接口:单列集合,用来存储一个一个的对象
         |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
            |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null|----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
                                    对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
            |----TreeSet:可以按照添加对象的指定属性,进行排序。
1)HashSet
public class HashSet<E> 
    extends AbstractSet<E> 
    implements Set<E>, Cloneable, Serializable {
    
    //hashSet基于HashMap实现,使用HashMap存储数据
    private transient HashMap<E, Object> map;
    //用于充当map的value,用于判断删除是否成功
    private static final Object PRESENT = new Object();
    
    //无参构造函数
    public HashSet() {this.map = new HashMap();}
	//指定初始值,
    public HashSet(Collection<? extends E> var1) {
        this.map = new HashMap(Math.max((int)((float)var1.size() / 0.75F) + 1, 16));
        this.addAll(var1);
    }
	//指定初始化容量,加载因子
    public HashSet(int initialCapacity, float loadFactor) {
        this.map = new HashMap(initialCapacity, loadFactor);
    }
	//指定容量
    public HashSet(int initialCapacity) {
        this.map = new HashMap(initialCapacity);
    }
2)LinkedHashSet
  • 使用双向链表维护元素的次序,这使得元素看起来是以插入顺序保存
3)TreeSet
  • 自然排序和定制排序
④Map
 * 一、Map的实现类的结构:
 *  |----Map:双列数据,存储key-value对的数据   ---类似于高中的函数:y = f(x)
 *      |----HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value
 *           |----LinkedHashMap:保证在遍历map元素时,可以按照添加的顺序实现遍历。
 *                  原因:在原有的HashMap底层结构基础上,添加了一对指针,指向前一个和后一个元素。
 *                      对于频繁的遍历操作,此类执行效率高于HashMap*      |----TreeMap:保证按照添加的key-value对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序,底层使用红黑树
 *      |----Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
 *           |----Properties:常用来处理配置文件。key和value都是String类型
 *
 *
 * HashMap的底层:数组+链表  (jdk7及之前)
 *                    数组+链表+红黑树 (jdk 8

1)HashMap
  • JDK7实现原理

  • JDK8实现原理

  • 实现原理
1.HashMap的内部存储结构其实是【数组+链表+红黑树】的结合

2.当实例化一个HashMap时,会初始化initialCapacity【初始容量16】和loadFactor【加载因子默认0.753.在put第一对映射关系时,系统会创建一个长度为initialCapacity【初始容量16】的Node数组,这个长度在哈希表中被称为容量(Capacity),在这个数组中可以存放元素的位置我们称之为“桶”(bucket),每个bucket都有自己的索引,系统可以根据索引快速的查找bucket中的元素

4.每个bucket中存储一个元素,即一个Node对象,但每一个Node对象可以带一个引用变量next,用于指向下一个元素,因此,在一个桶中,就有可能生成一个Node链。也可能是一个一个TreeNode对象,每一个TreeNode对象可以有两个叶子结点left和right,因此,在一个桶中,就有可能生成一个TreeNode树。而新添加的元素作为链表的last,或树的叶子结点。

5.那么HashMap什么时候进行扩容和树形化呢?

当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值(DEFAULT_LOAD_FACTOR)0.75,这是一个折中的取值。也就是说,默认情况下,数组大小(DEFAULT_INITIAL_CAPACITY)16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

当HashMap中的其中一个链的对象个数如果达到了8个,此时如果capacity没有达到64,那么HashMap会先扩容解决,如果已经达到了64,那么这个链会变成红黑树,结点类型由Node变成TreeNode类型。当然,如果当映射关系被移除后,下次resize方法时判断树的结点个数低于6个,也会把红黑树再转为链表。
    
注意:关于映射关系的key是否可以修改?answer:不要修改
    映射关系存储到HashMap中会存储key的hash值,这样就不用在每次查找时重新计算每一个EntryNodeTreeNode)的hash值了,因此如果已经put到Map中的映射关系,再修改key的属性,而这个属性又参与hashcode值的计算,那么会导致匹配不上。
  • 成员变量及构造器
public class HashMap<K, V> 
    extends AbstractMap<K, V> 
    implements Map<K, V>, Cloneable, Serializable {

    //HashMap的默认容量,16
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //HashMap的默认加载因子:0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75F;
    //Bucket中链表长度大于该默认值,转化为红黑树:8
    static final int TREEIFY_THRESHOLD = 8;
    //Bucket中红黑树节点小于该默认值,转化为链表:6
    static final int UNTREEIFY_THRESHOLD = 6;
    //桶中的Node被树化时最小的hash表容量:64
    static final int MIN_TREEIFY_CAPACITY = 64;
    //Node数组
    transient HashMap.Node<K, V>[] table;
    //所有数据
    transient Set<Entry<K, V>> entrySet;
    transient int size;
    transient int modCount;
    //扩容的临界值,= 容量*填充因子:16 * 0.75 => 12
    int threshold;
    //实际加载因子
    final float loadFactor;
  • 遍历3种方式
//方式 1.Map.Entry
Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
//【1.1 for】
for (Map.Entry<Integer, Integer> entry : entries) {
    System.out.println(entry.getKey() + " : " + entry.getValue());
}
//【1.2 itertor】
Iterator<Map.Entry<Integer, Integer>> iterator = entries.iterator();
while (iterator.hasNext()){
    Map.Entry<Integer, Integer> next = iterator.next();
    System.out.println(next.getKey() + " : " + next.getValue());
}


//方式 2.map.keySet()
Set<Integer> keySet = map.keySet();
//【2.1 for】
for (Integer key : keySet) {
    System.out.println(key + " : " + map.get(key));
}
//【2.2 itertor】
Iterator<Integer> iterator1 = keySet.iterator();
while (iterator1.hasNext()) {
    Integer next = iterator1.next();
    System.out.println(next + " : " + map.get(next));
}


//方式 3.map.values()
Collection<Integer> values = map.values();
Iterator<Integer> iterator2 = values.iterator();
while (iterator2.hasNext()) {
    System.out.println(iterator2.next());
}
2)LinkedMap
  • HashMap存储结构的基础上,使用了一对双向链表来记录添加元素的顺序
  • HashMap中的内部类:Node
static class Node<K, V> implements Map.Entry<K, V> {
    final int hash;
    final K key;
    V value;
    HashMap.Node<K, V> next;
  • LinkedHashMap中的内部类:Entry
static class Entry<K, V> extends HashMap.Node<K, V> {
    LinkedHashMap.Entry<K, V> before;
    LinkedHashMap.Entry<K, V> after;
3)TreeMap
  • TreeMap存储Key-Value 对时,需要根据key-value对进行排序。TreeMap可以保证所有的Key-Value 对处于有序状态。

  • TreeSet底层使用红黑树结构存储数据

  • TreeMapKey的排序:

    • 自然排序:TreeMap的所有的Key 必须实现Comparable接口,而且所有的Key应该是同一个类的对象,否则将会抛出ClasssCastException
    • 定制排序:创建TreeMap时,传入一个Comparator 对象,该对象负责对TreeMap中的所有key 进行排序。此时不需要Map 的Key实现Comparable 接口
  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0

4)HashTable
  • Hashtable是线程安全的。
  • 不允许使用null 作为keyvalue
⑤补充:cloneable接口
//cloneable其实就是一个【标记接口】,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。Object中clone方法:
protected native Object clone() throws CloneNotSupportedException;

	//1.调用jvm中的实现体时进行判断的,调用的是本地在jvm中编写的C的接口
	//2.clone先分配对象,再将值填充到对象中。和原对象相同,但是【地址值不同】。
  • 浅拷贝&深拷贝
//实现Cloneable接口,重写clone()方法
@Override
protected School clone() throws CloneNotSupportedException {
    School clone = (School) super.clone();			//浅拷贝,只拷贝一层
    clone.student = student.clone();			//深拷贝,拷贝学校类下的学生类
    return clone;
}
//注意:
	浅拷贝,拷贝的对象只是引用他的地址,改变其值,拷贝对象的值也随之改变
⑥红黑树
1)红黑树的5个性质
定义:通过对任何一条从根到叶子的简单路径上各个节点的颜色进行约束,确保没有一条路径会比其他路径长2倍,因而是近似平衡的。所以相对于严格要求平衡的AVL树来说,它的旋转保持平衡次数较少。用于搜索时,插入删除次数多的情况下我们就用红黑树来取代AVL

1 每个结点要么是红的要么是黑的。  
2 根结点是黑的。  
3 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。  
4 如果一个结点是红的,那么它的两个儿子都是黑的。  
5 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。

(注:上述第3、5点性质中所说的NULL结点,包括wikipedia.算法导论上所认为的叶子结点即为树尾端的NIL指针,或者说NULL结点。然百度百科以及网上一些其它博文直接说的叶结点,则易引起误会,因,此叶结点非子结点)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OMZ9dxM0-1680846789817)(C:\Users\monkey\AppData\Roaming\Typora\typora-user-images\image-20220331105710362.png)]

hashCode和equals

所有处理的根本目的,都是为了提高 存储key-value的数组下标位置 的随机性 & 分布均匀性,尽量避免出现hash值冲突。即:对于不同key,存储的数组下标位置要尽可能不一样

6)反射

/**
* 关于java.lang.Class类的理解
* 1.类的加载过程:
* 	程序经过Javac.exe命令后,会生成一个或多个字节码文件(.class结尾)。
* 	接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
* 	【加载到内存中】。此过程就称为【类的加载】。加载到内存中的类,我们就称为【运行时类】,此
* 	运行时类,就作为Class的一个实例。
*
* 2.换句话说,Class的实例就对应着一个运行时类。
*/

①获取Class实例的四种方式
//1. 类名.class
Class<Person> clazz1 = Person.class;

//2.运行时对象.getClass()
Person per = new Person();
Class clazz2 = per.getClass();

//3.Class静态方法 forName(String classPath)
Class clazz3 = Class.forName("com.example.servlet.Person");

//4.使用类的加载器:ClassLoader
ClassLoader classLoader = this.getClass().getClassLoader(); //获取当前的类加载器
Class<?> clazz4 = classLoader.loadClass("com.example.servlet.Person"); //获取Person实例
②常见使用
一、获取属性数组:        
		//1.getFields() 获取当前【运行时类及其父类】中声明为【public】访问权限的属性
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        //2.getDeclaredFields() 获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
            //权限修饰符+变量类型+变量名
            System.out.println(Modifier.toString(declaredField.getModifiers())+" "+
                    declaredField.getType()+" "+
                    declaredField.getName());
        }
二、获取方法数组:
        //3.getMethods() 获取当前【运行时类及其所有父类】中声明为【public】权限的方法
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        //4.getDeclaredMethods() 获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
        Method[] decMethods = clazz.getDeclaredMethods();
        for (Method decMethod : decMethods) {
            System.out.println(decMethod);
            //注解
            System.out.println(Arrays.toString(decMethod.getAnnotations()));
            //权限修饰符+返回值类型+方法名+形参列表
            System.out.println(Modifier.toString(decMethod.getModifiers())+" "+
                    decMethod.getReturnType()+" "+
                    decMethod.getName()+" "+
                    Arrays.toString(decMethod.getParameterTypes()));
        }
三、获取构造器数组
        //5.获取当前运行时类中声明为public的构造器
        System.out.println(Arrays.toString(clazz.getConstructors()));
        //6.获取当前运行时类中声明的所有的构造器
        System.out.println(Arrays.toString(clazz.getDeclaredConstructors()));
四、获取父类运行时类
        //7.获取运行时类的父类
        System.out.println(clazz.getSuperclass());
        //8.获取运行时类的带泛型的父类
        System.out.println(clazz.getGenericSuperclass());
        //9.获取运行时类的父类实现的接口
        System.out.println(Arrays.toString(clazz.getSuperclass().getInterfaces()));
        //10.获取运行时类所在的包
        System.out.println(clazz.getPackage());
五、创建对象,读写属性
        //11.创建对象
        Person person = clazz.newInstance();
        //方式一:获取属性,读写属性
        Field id = clazz.getField("id");
        id.set(person, 111);    //设值
        System.out.println(id.get(person)); //取值
        //方式二:获取属性,读写属性(常用)
        Field id1 = clazz.getDeclaredField("id");
        id1.setAccessible(true);    //确保可以访问
        id1.set(person, 222);
        System.out.println(id1.get(person));
六、获取指定方法及构造器
        //12.指定的方法
        Method show = clazz.getDeclaredMethod("show", String.class);
        show.setAccessible(true);
        show.invoke(person, "SHOW!!");
        //13.指定构造器
        Constructor<Person> constructor = clazz.getDeclaredConstructor(String.class);
        System.out.println(constructor.newInstance("wwwwwww"));
③动态代理
public class Test {
    public static void main(String[] args) throws Exception {
        ProxyServer proxyServer = new ProxyServer();
        Works works = (Works) proxyServer.getInstance(new Server());
        works.show();
    }
}
interface Works{
    void show();
}
class Server implements Works{
    @Override
    public void show() {
        System.out.println("真实服务器执行。。。");
    }
}
class ProxyServer {
    //根据加载到内存中的被代理类,动态的创建一个代理类及其对象
    public Object getInstance(Object obj){
        Handler handler = new Handler(obj);
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
    }
}
class Handler implements InvocationHandler{
    //代理类对象进行赋值
    private Object obj;
    public Handler(Object obj){
        this.obj = obj;
    }
    //调用方法时,自动执行下面方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理类实现业务
        return method.invoke(obj, args);
    }
}

7)java8新特性

①Lamdba表达式
直接访问
	1)标记了【final】的外层【局部变量】
	2)实例的字段
	3)静态变量
②Optional(验空)
//1.获取Optional对象
//        Optional<Object> o1 = Optional.of(null);    //报错:NullPointerException
Optional<Object> o2 = Optional.ofNullable(null); //允许为空
Optional<Object> o3 = Optional.empty();     //与ofNullable传null效果一致

//2.访问 Optional 对象的值
//        Object getVal = o2.get();   //Optional对象不能为空(报错!!)
String getStr = Optional.of("Java").get();  //得到Java字符串

//3.判断是否为空
boolean isEmpty = o2.isPresent();   //空返回false,非空返回true
o2.ifPresent(System.out::println);  //为空,后面不执行
Optional.of("not is empty!").ifPresent(System.out::println);    //不为空,执行lamdba表达式

//4.对象为空时,返回指定默认值
Object orElse1 = Optional.ofNullable(null).orElse("执行了orElse");//空值执行orElse操作
Object orElseGet2 = Optional.ofNullable(null).orElseGet(()->"执行了orElse");//空值执行orElse操作
//区别:对象不为空时,orElseGet() 方法不创建 User 对象,影响性能
Object orElse3 = Optional.of("null").orElse(show());//有值返回,后面操作【会执行】
Object orElseGet4 = Optional.of("null").orElseGet(()->show());//有值返回,后面操作【不会执行】

//5.map(): 获取用户姓名,没有返回【"姓名为空!!"】
String name = Optional.of(new Person("小明", 7)).map(t -> t.getName()).orElse("姓名为空!!");
Object faltMap = Optional.of(new Person("小明", 7)).flatMap((t) -> Optional.of(t.getName() + ",你好!")).orElse("姓名为空!!");	//输出:【小明,你好!】

//6.过滤得到, 年龄大于六的人
Optional<Person> person = Optional.of(new Person("小明", 7)).filter(t -> t.getAge() > 6);
③Stream
Stream 自己不会存储元素。Stream关注的是对数据的运算,与CPU打交道
			 集合关注的是数据的存储,与内存打交道
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
  • 创建
//Stream创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();  //创建顺序流
Stream<String> parallel = stream.parallel();    //顺序流转化为并行流
Stream<String> parallelStream = list.parallelStream();  //创建并行流
IntStream arrStream = Arrays.stream(new int[]{1, 2, 3}); //创建数组流
//静态方法
Stream<Integer> intStream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> iterateStream = Stream.iterate(10, t -> t * t).limit(3);//输出:10 100 10000
Stream<Double> genStream = Stream.generate(Math::random).limit(3);//获取三个[0,1)随机数
  • 遍历/匹配(foreach/find/match)
List<Integer> num = Arrays.asList(7, 6, 9, 3, 8, 2, 1);
num.stream().forEach(System.out::println);  //foreach遍历
Integer integer = num.stream().findFirst().get();   //获取第一个元素
Integer integer1 = num.stream().parallel().findAny().get();    //随机获取一个(适用于并行)
boolean isHave = num.stream().anyMatch(t -> t > 5);  //是否包含符合特定条件的元素
  • 过滤filter
Stream<Integer> filterNum = num.stream().filter(t -> t > 5);//数字大于5的
  • 聚合(max/min/count)
List<String> strList = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");
String maxStr = strList.stream().max(Comparator.comparing(String::length)).get();//得到:"weoujgsd"
Integer maxInt = num.stream().max(Comparator.comparingInt(t -> t)).get();   //int比较
long count = strList.stream().count();  //获取个数
  • 映射(map/flatMap)
//map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
//flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
Stream<String> strStream = Arrays.stream(strArr).map(String::toUpperCase);//字符串转大写
//将两个字符数组合并成一个新的字符数组
List<String> list1 = Arrays.asList("m,k,l,a", "1,3,5,7");
Stream<String> stringStream = list1.stream().flatMap(t -> Stream.of(t.split(",")));//[m, k, l, a, 1, 3, 5, 7]
  • 归约(reduce)
//归约,也称缩减,顾名思义,是把一个流缩【减成】一个值,能实现对集合【求和】【求乘积和求】【最值】操作。
    
List<Integer> list2 = Arrays.asList(1, 3, 2, 8, 11, 4);
Integer sum1 = list2.stream().reduce((t1, t2) -> t1 + t2).get();//1.求和方式1
Integer sum2 = list2.stream().reduce(Integer::sum).get();//2.求和方式2
Integer sum3 = list2.stream().reduce(10, Integer::sum);//3.求和方式3【设置初始值10】
Integer mul = list2.stream().reduce((t1, t2) -> t1 * t2).get();//求乘积和
Integer max1 = list2.stream().reduce((t1, t2) -> t1 > t2 ? t1 : t2).get();//最大值方式1
Integer max2 = list2.stream().reduce(Integer::max).get();//求最大值方式2
  • 收集collect
//collect从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
//collect主要依赖java.util.stream.Collectors类内置的静态方法。
  
1. 归集(toList/toSet/toMap)
List<Integer> list3 = Arrays.asList(1, 2, 3, 3, 5, 2);
List<Integer> toList = list3.stream().collect(Collectors.toList());//数组输出
Set<Integer> toSet = list3.stream().collect(Collectors.toSet());//去重set
Map<String, Person> toMap = person.stream().collect(Collectors.toMap(Person::getName, t -> t));//map输出

2. 统计(count/averaging)
    计数:count
    平均值:averagingInt、averagingLong、averagingDouble
    最值:maxBy、minBy
    求和:summingInt、summingLong、summingDouble
    统计以上所有:summarizingInt、summarizingLong、summarizingDouble
//统计数量
Long counts = person.stream().collect(Collectors.counting());
//平均工资
Double avgSalary = person.stream().collect(Collectors.averagingDouble(Person::getSalary));
//最低工资
Integer minSalary = person.stream().map(Person::getSalary).collect(Collectors.minBy(Integer::compare)).get();
//工资总和
Integer sumSalary = person.stream().collect(Collectors.summingInt(Person::getSalary));
//统计所有工资,输出IntSummaryStatistics{count=6, sum=49300, min=7000, average=8216.666667, max=9500}
IntSummaryStatistics allSalary = person.stream().collect(Collectors.summarizingInt(Person::getSalary)); 

3. 分组(partitioningBy/groupingBy)
    分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。
	分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。
//工资高于7000分组【只能2组,是、否】
Map<Boolean, List<Person>> partition = person.stream().collect(Collectors.partitioningBy(t -> t.getSalary() > 7000));
//性别分组【多组】
Map<String, List<Person>> group = person.stream().collect(Collectors.groupingBy(Person::getSex));
//将员工先按性别分组,再按地区分组
Map<String, Map<String, List<Person>>> groups = person.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));

4.接合(joining)
List<String> list4 = Arrays.asList("A", "B", "C");
        String join = list4.stream().collect(Collectors.joining(","));//输出: A,B,C

5.归约(reducing)
//年龄求和加上1000
Integer sumAge = person.stream().collect(Collectors.reducing(1000, Person::getAge, Integer::sum));
  • 排序sorted
// 先按工资升序再按年龄降序排序
List<String> newList3 = personList.stream()		.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).reversed().map(Person::getName).collect(Collectors.toList());
  • 提取组合
String[] arr1 = { "a", "b", "c", "d" };
String[] arr2 = { "d", "e", "f", "g" };
//合并去重
Stream<String> distinct = Stream.concat(Arrays.stream(arr1), Arrays.stream(arr2)).distinct();
//获取1~6元素
Stream<Integer> limit = Stream.iterate(1, t -> t + 1).skip(1).limit(6);//输出:2~7

1.3 JavaWeb

1)Tomcat

  • 1.真正管理Servlet的容器是Context容器
一个Context【对应】一个Web工程;新建一个servlet时【就会新建】一个context,同时【加载】它需要的config,容器的配置属性由应用的web.xml指定;
  • 2.ServletConfig、ServletContext、ServletRequest和ServletResponse通过容器传递给Servlet
a.启动Tomcat时,Servlet容器被创建,每个web应用都对应于一个context容器;

b.客户端发起一次请求时,请求根据url地址指定的ip和端口号就能找到tomcat服务器,再根据工程名找到对应的web服务;

c.此时创建一个线程,根据ServletRequest发起请求,Servlet作为控制器根据页面的请求内容查找相应的服务,并将结果通过ServletResponse返回给客户端。

//注意:每个请求(而非每个用户)对应一个线程,Servlet一般只会存在一个实例

2)servlet

①介绍
ServletServer Applet),全称Java Servlet,未有中文译文。是【用Java编写的服务器端程序】。其主要功能在于【交互式地浏览和修改数据】,【生成动态Web】内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

Servlet API 包含以下4个Java包:

1.javax.servlet   其中包含定义servlet和servlet容器之间契约的类和接口。

2.javax.servlet.http   其中包含定义HTTP ServletServlet容器之间的关系。

3.javax.servlet.annotation   其中包含标注servlet,Filter,Listener的标注。它还为被标注元件定义元数据。

4.javax.servlet.descriptor,其中包含提供程序化登录Web应用程序的配置信息的类型。
②执行流程

Servlet类加载 -> 实例化 -> 服务 -> 销毁

1.Web Client向Servlet容器(Tomcat)发出Http请求;
2.Servlet容器接收Web Client的请求;
3.Servlet容器创建一个HttpRequest对象,将Web Client请求的信息封装到这个对象中;
4.Servlet容器创建一个HttpResponse对象;
5.Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给HttpServlet对象;
6.HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息;
7.HttpServlet调用HttpResponse对象的有关方法,生成相应数据;
8.Servlet容器把HttpServlet的响应结果传给Web Client。
③javax.servlet包内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iccphkML-1680846789819)(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fp-blog.csdn.net%2Fimages%2Fp_blog_csdn_net%2FLhyUp%2FEntryImages%2F20091226%2Fservlet.jpg&refer=http%3A%2F%2Fp-blog.csdn.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1651981906&t=74a6baf1c966130779e7d17ee8835a18)]

1.Servlet 接口
public interface Servlet {
    void init(ServletConfig var1) throws ServletException;
	//这个方法会返回由Servlet容器传给init( )方法的ServletConfig对象
    ServletConfig getServletConfig();

    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
	//这个方法会返回Servlet的一段描述,可以返回一段字符串。
    String getServletInfo();

    void destroy();
}

生命周期:

1.init( )	//初始化信息,只执行一次Servlet第一次被请求时,Servlet容器就会开始调用这个方法来初始化一个Servlet对象出来,但是这个方法在后续请求中不会在被Servlet容器调用,就像人只能“出生”一次一样。我们可以利用init( )方法来执行相应的初始化工作。调用这个方法时,Servlet容器会传入一个ServletConfig对象进来从而对Servlet对象进行初始化。

2.service( )	
	每当请求Servlet时,Servlet容器就会调用这个方法。就像人一样,需要不停的接受老板的指令并且“工作”。第一次请求时,Servlet容器会先调用init( )方法初始化一个Servlet对象出来,然后会调用它的service( )方法进行工作,但在后续的请求中,Servlet容器只会调用service方法了。

3.destory
	当要销毁Servlet时,Servlet容器就会调用这个方法,就如人一样,到时期了就得死亡。在卸载应用程序或者关闭Servlet容器时,就会发生这种情况,一般在这个方法中会写一些清除代码。
2.ServletRequset接口

说明:Servlet容器对于接受到的每一个Http请求,都会创建一个ServletRequest对象,并把这个对象传递给Servlet的Sevice( )方法

public interface ServletRequest {
    //返回请求主体的字节数
    int getContentLength();
    //返回主体的MIME类型
    String getContentType();
	//返回请求参数的值【最常用的方法,可用于获取查询字符串的值】
    String getParameter(String var1);
3.ServletResponse接口

说明: javax.servlet.ServletResponse接口表示一个Servlet响应,在调用Servlet的Service( )方法前,Servlet容器会先创建一个ServletResponse对象,并把它作为第二个参数传给Service( )方法。ServletResponse隐藏了向浏览器发送响应的复杂过程。

public interface ServletResponse {
    String getCharacterEncoding();

    String getContentType();
	//发送二进制数据
    ServletOutputStream getOutputStream() throws IOException;
	//发送文本PrintWriter对象,使用ISO-8859-1编码(该编码在输入中文时会发生乱码)
    PrintWriter getWriter() throws IOException;

    void setCharacterEncoding(String var1);

    void setContentLength(int var1);

    void setContentLengthLong(long var1);
	//在发送任何HTML之前,应该先调用setContentType()方法,设置响应的内容类型,并将“text/html”作为一个参数传入,这是在告诉浏览器响应的内容类型为HTML,需要以HTML的方法解释响应内容而不是普通的文本,或者也可以加上“charset=UTF-8”改变响应的编码方式以防止发生中文乱码现象。
    void setContentType(String var1);

    void setBufferSize(int var1);

    int getBufferSize();

    void flushBuffer() throws IOException;

    void resetBuffer();

    boolean isCommitted();

    void reset();

    void setLocale(Locale var1);

    Locale getLocale();
}
4.ServletConfig接口

说明:当Servlet容器初始化Servlet时,Servlet容器会给Servlet的init( )方式传入一个ServletConfig对象

public interface ServletConfig {
    String getServletName();//获取servlet在web.xml中配置的name值	

    ServletContext getServletContext();//获取servlet上下文配置

    String getInitParameter(String var1);//获取servlet初始化参数

    Enumeration<String> getInitParameterNames();//获取servlet所有初始化参数的name
}
//例如:
String paramVal = this.config.getInitParameter("name");//获取指定的初始化参数
response.getWriter().print(paramVal);	//页面输出:name对应的value
5.ServletContext对象

说明:

​ WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用
 ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象

public interface ServletContext {
    Object getAttribute(String var1);

    Enumeration<String> getAttributeNames();

    void setAttribute(String var1, Object var2);

    void removeAttribute(String var1);
6.GenericServlet抽象类

好处:

1.为Servlet接口中的所有方法【提供了默认】的实现,则程序员需要什么就直接改什么,不再需要把所有的方法都自己实现了。

2.提供方法,包围ServletConfig对象中的方法。
 

3.将init( )方法中的ServletConfig参数赋给了一个内部的ServletConfig引用从而来保存ServletConfig对象,【不需要】程序员自己去【维护】ServletConfig了。
④javax.servlet.http包内容
1.HttpServlet抽象类

public abstract class HttpServlet extends GenericServlet {
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        HttpServletRequest request;
        HttpServletResponse response;
        try {
            request = (HttpServletRequest)req;
            response = (HttpServletResponse)res;
        } catch (ClassCastException var6) {
            throw new ServletException(lStrings.getString("http.non_http"));
        }

        this.service(request, response);
    }
2.HttpServletRequest接口

  • 1获取请求行
//获取请求方式
String method = request.getMethod(); 	//【GET】
//获取请求资源相关
String uri = request.getRequestURI();	// 【/WEBpro/line】
StringBuffer url = request.getRequestURL(); //【http://localhost:8080/WEBpro/line】
//获取web应用名称
String name = request.getContextPath();		//【/WEBpro】
//获取地址后参数
String parms = request.getQueryString();	//【null】
//获得客户机IP地址
String ip = request.getRemoteAddr();		//【127.0.0.1】
  • 2获取请求头
//获取指定的头
String user = request.getHeader("User-Agent");//【Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36】
System.out.println(user);
//获得所有头的名字
Enumeration<String> em =  request.getHeaderNames();//【host、cookie 等等】

  • 3获取请求体
//获得单个参数的值
String par = request.getParameter("username");
//获得多个参数的值
String[] hobby = request.getParameterValues("hobby");
//获得所有请求参数名称
Enumeration<String> en = request.getParameterNames();
//获取所有参数,参数封装到MAP<String,String[]>
Map<String, String[]> map = request.getParameterMap();
  • 乱码问题
//此为post与get通用方式
String username = request.getParameter("username");
username = new String(username.getBytes("iso8859-1"),"UTF-8");

//Post可以直接这么写
request.setCharacterEncoding("UTF-8");
  • 请求转发

request.getRequestDispatcher("login.jsp").forward(request, response);
3.HttpServletReaponse接口

  • 响应数据
// 字符输出流
PrintWriter writer = response.getWriter();
writer.write("<h2>Hello</h2>");

// 字节输出流
ServletOutputStream out = response.getOutputStream();
out.write("<h2>Hello</h2>".getBytes());
//乱码问题
	1.getWriter()
    // 设置服务端的编码
    response.setCharacterEncoding("UTF-8");
    // 设置客户端的响应类型及编码
    response.setHeader("content-type","text/html;charset=UTF-8");

	2.getOutputStream()
    response.setHeader("content-type","text/html;charset=UTF-8");
//同时指定服务器和客户端【简单】【替换上面】
	response.setContentType("text/html;charset=UTF-8");
  • 重定向

// 重定向跳转到index.jsp
response.sendRedirect("index.jsp");

3)Cookies&Session

  • Cookies

//Cookies获取
Cookie[] cookies = req.getCookies();
//Cookie值的修改
方案一(直接写新的盖上去)
    Cookie cookie = new Cookie("key1","newValue1");
    resp.addCookie(cookie);
方案二(找到要改的,再用Cookie对象的方法改)
    Cookie[] cookies = request.getCookies();
    for (Cookie cookie: cookies){
        if (name.equals(cookie.getName())){
            myCookie.setValue("NewValue");
        }
    }
//生命周期
	cookie.setMaxAge(10*60);	//单位秒
	Cookie在生成时就会被指定一个Expire值,这就是Cookie的生存周期,在这个周期内Cookie有效,超出周期Cookie就会被清除。
    *正数:超出失效
    *负数:关闭浏览器失效
    *0:立即失效【删除已经存在客户端的此Cookie(我们可以通过此方法删除某个Cookie)】
    *不设置:一次会话范围内
1)会话级Cookie
    服务器端并【没有】明确指定Cookie的存在时间
    在浏览器端,Cookie数据存在于【内存】中
    只要浏览器还开着,Cookie数据就一直都在
    【浏览器关闭】,内存中的Cookie数据就会被【释放】
    
2)持久化Cookie
    服务器端明确【设置】了Cookie的存在时间
    在浏览器端,Cookie数据会被保存到【硬盘】上
    Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,【不受浏览器关闭的影响】
    持久化Cookie到达了预设的时间会被释放
  • Session
session是服务器端的技术。服务器为每一个浏览器开辟一块内存空间,其本质就是要给大的Map。一般情况下,服务器会在一定时间内(默认30分钟)保存这个 Session,过了时间限制,就会销毁这个Session。在销毁之前,程序员可以将用户的一些数据以Key和Value的形式暂时存放在这个 Session中。
// 1.调用request对象的方法尝试获取HttpSession对象
HttpSession session = request.getSession();

// 2.调用HttpSession对象的isNew()方法
boolean wetherNew = session.isNew();

// 3.打印HttpSession对象是否为新对象
System.out.println("wetherNew = " + (wetherNew?"HttpSession对象是新的":"HttpSession对象是旧的"));

// 4.调用HttpSession对象的getId()方法
String id = session.getId();

// 5.打印JSESSIONID的值
System.out.println("JSESSIONID = " + id);
  • 区别
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。

2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗
   考虑到安全应当使用session。

3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
   考虑到减轻服务器性能方面,应当使用COOKIE。

4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

5、所以个人建议:
   将登陆信息等重要信息存放为SESSION
   其他信息如果需要保留,可以放在COOKIE中

2.Spring

2.1 IOC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tsK6RQ35-1680846789821)(./.assets/spring一.jpg)]

1)面试题

1.聊聊spring
2.bean生命周期
3.循环依赖
4.三级缓存
5.FactoryBean和BeanFactory
6.ApplicationContext和BeanFactory
7.设计模式
①beanFactoryPostProcessor接口详解

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

  • 扩展介绍

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

//扩展【占位符替换】---实现BeanFactoryPostProcessor接口
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Object properties = beanFactory.getBean("properties");
        System.out.println(properties);//Properties{url='aaa', ip=123}

        BeanDefinition properties1 = beanFactory.getBeanDefinition("properties");
        //Generic bean: class [com.monkey.model.Properties]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in class path resource [spring-config.xml]
        System.out.println(properties1);
        

        properties1.setInitMethodName("init");//扩展
    }
}
  • 补充【springboot自动装配】
|--BeanFactoryPostProcessor
    |--ConfigurationClassPostprocessor//实现子类
    
    |--BeanDefinitionRegistryPostProcessor
    	|--void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
			。。。。
			|--ConfigurationClassParser
                |--doProcessConfigurationClass()
【1// Process any @Import annotations
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
②beanFactoryProcessor接口详解
|--BeanPostProcessor
	|--AbstractAutoProxyCreator//子类实现
    	|--postProcessAfterInitialization()
    		|--return wrapIfNecessary(bean, beanName, cacheKey);
				|--Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
					|--return proxyFactory.getProxy(getProxyClassLoader());
						|--return createAopProxy().getProxy(classLoader);
							|--CglibAopProxy
                            |--JdkDynamicAopProxy
③BeanFactory&FactoryBean

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

  • 实现FactoryBean接口【mybatis、feign使用】
//自定义创建Bean
public class MyFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        //自定义创建Bean,想咋实现就咋实现,不需要遵循标准流程
        return new Person();
    }

    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }
}
  • 执行位置
|--DefaultlistableBeanFactory
	|--preInstantiateSingletons()1if (isFactoryBean(beanName)) {
		Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);    
  			。。。
④执行流程图

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

①实例化&初始化

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

1.实例化
	堆中开辟内存
2.初始化
	属性赋值
		1)填充
            <property name="name" value="小刘" />
            <property name="age" value="1" />
		2)调用初始化方法
			<bean id="person" class="com.monkey.model.Person" init-method="getAge" />
②BeanFactoryPostProcessor(增强器/后置处理器)

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

//增强器、后置处理器【扩展--springboot、springcloud】
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Object person = beanFactory.getBean("person");
    }
}

//实现类
PlaceholderConfigurerSupport【用于读取配置】
    //例子
   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
     <property name="driverClassName" value="${driver}"/>
     <property name="url" value="jdbc:${dbname}"/>
   </bean>
③BeanPostProcessor(Aop)
image-20220420225423944
BeanPostProcessor【接口】
    |-1-AbstractAutoProxyCreator【实现类】
    	|-2-createProxy()【方法】
    		|-3-proxyFactory.getProxy(getProxyClassLoader())【方法调用】
    			|-4-getAopProxyFactory().createAopProxy(this)
					|-5-createAopProxy(AdvisedSupport config)
                        |-5-createAopProxy(AdvisedSupport config)【实现类对应方法】


//动态代理-6-    
//createAopProxy(AdvisedSupport config)
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
        Class<?> targetClass = config.getTargetClass();
        if (targetClass == null) {
            throw new AopConfigException("TargetSource cannot determine target class: " +
                                         "Either an interface or a target is required for proxy creation.");
        }
        if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);	//【JDK动态代理】
        }
        return new ObjenesisCglibAopProxy(config);	//【cglib动态代理】
    }
    else {
        return new JdkDynamicAopProxy(config);
    }
}
④Environment

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

//用于获取系统属性、环境变量
StandardEnvironment
    |--customizePropertySources
    	|--getSystemProperties()return (Map) System.getProperties();|--getSystemEnvironment()return (Map) System.getenv();
⑤refresh【重:执行流程】
  • 问题引出

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

AbstractApplicationContext
	|——refresh()

  public void refresh() throws BeansException, IllegalStateException {
    // synchronized块锁(monitorenter --monitorexit),不然 refresh() 还没结束,又来个启动或销毁容器的操作
    synchronized (this.startupShutdownMonitor) {
      //1、【准备刷新】【Did four things】
      prepareRefresh();
      // 2、【获得新的bean工厂】关键步骤
      //1、关闭旧的 BeanFactory
      //2、创建新的 BeanFactory(DefaluListbaleBeanFactory)
      //3、解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(未初始化)
      //4、返回全新的工厂
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      //3、【准备bean工厂】为BeanFactory配置容器特性,例如类加载器、表达式解析器、注册默认环境、后置管理器
      prepareBeanFactory(beanFactory);
      try {
        // 4、【后置处理器Bean工厂】此处为空方法,子类的实现--springboot
        postProcessBeanFactory(beanFactory);

        //5、【调用bean工厂后置处理器】,开始调用我们自己实现的接口
        //目标:
        //调用顺序一:先bean定义注册后置处理器
        //调用顺序二:后bean工厂后置处理器
        invokeBeanFactoryPostProcessors(beanFactory);

        //6、【注册bean后置处理器】只是注册,但是不会反射调用
        //逻辑:找出所有实现BeanPostProcessor接口的类,分类、排序、注册
        registerBeanPostProcessors(beanFactory);
        // Initialize message source for this context.
        //7、【初始化消息源】国际化问题i18n,参照https://nacos.io/
        initMessageSource();

        // Initialize event multicaster for this context.
        //8、【初始化应用程序事件多路广播】初始化自定义的事件监听多路广播器,(观察者)S
        initApplicationEventMulticaster();


        // 9、【刷新】具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
        onRefresh();


        //10、【注册所有监听器】,监听器需要实现 ApplicationListener 接口。
        registerListeners();


        //11、 【完成bean工厂初始化操作】负责初始化所有的 singleton beans,反射生成对象/填充
        //此处开始调用Bean的前置处理器和后置处理器

        //目标:找到xxxx.newInstance!!!!!
        finishBeanFactoryInitialization(beanFactory);

        // 12、发布事件与清除上下文环境
        finishRefresh();


      } catch (BeansException ex) {
        if (logger.isWarnEnabled()) {
          logger.warn("Exception encountered during context initialization - " +
              "cancelling refresh attempt: " + ex);
        }

        // Destroy already created singletons to avoid dangling resources.
        //13、销毁已创建的Bean、以免有些 bean 会一直占用资源
        destroyBeans();

        // Reset 'active' flag.
        //14、取消refresh操作,重置容器的同步标识。
        cancelRefresh(ex);

        // 把异常往外抛
        throw ex;
      } finally {
        // Reset common introspection caches in Spring's core, since we
        // might not ever need metadata for singleton beans anymore...
        //15、重设公共缓存
        resetCommonCaches();
      }
    }
  }
1.调用ApplicationContext对象构造器
//1.new 
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

//2.构造函数
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);	//执行带参的构造方法
}

//3.带参构造
public ClassPathXmlApplicationContext(
    String[] configLocations, boolean refresh, @Nullable ApplicationContext parent){

    super(parent);	//调用父类构造器,获取需要的环境变量,属性
    setConfigLocations(configLocations);	//设置保存 配置xml路径,便于后面读取
    if (refresh) {
        refresh();				//执行refresh()方法【重点】
    }
}
2.准备工作(初始化属性、设置系统环境等)
//方法一:prepareRefresh();
//Prepare this context for refreshing【前期工作】

protected void prepareRefresh() {
    this.startupDate = System.currentTimeMillis();	//设置启动时间
    this.closed.set(false);		//设置容器关闭为false
    this.active.set(true);		//设置容器活跃

    initPropertySources(); 	//设置初始化属性资源(空:需要子类实现)

    getEnvironment().validateRequiredProperties();	//创建环境对象(StandardEnvironment)

    // 创建一些Set集合
    if (this.earlyApplicationListeners == null) {
        this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
    }
    else {
        this.applicationListeners.clear();
        this.applicationListeners.addAll(this.earlyApplicationListeners);
    }
    this.earlyApplicationEvents = new LinkedHashSet<>();
}
3.创建BeanFactory、读取xml《1》
//方法二:ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//创建最新的BeanFactory
	|--refreshBeanFactory();
		|--AbstractRefreshableApplicationContextrefreshBeanFactory();
            
protected final void refreshBeanFactory() throws BeansException {
    if (hasBeanFactory()) {		//如果存在,先销毁
        destroyBeans();
        closeBeanFactory();
    }
    try {
        //创建BeanFactory【DefaultListableBeanFactory重点】
        DefaultListableBeanFactory beanFactory = createBeanFactory();
        beanFactory.setSerializationId(getId());	//设置序列化id
        customizeBeanFactory(beanFactory);		//设置一些属性
        //加载xml信息,保存在BeanDefinition对象------【见下方截图】------
        loadBeanDefinitions(beanFactory);		
        synchronized (this.beanFactoryMonitor) {
            this.beanFactory = beanFactory;
        }
    }
}


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

4.给Beanfactory填充一些默认属性值
//方法三:prepareBeanFactory(beanFactory);
5.增强器、后置处理器【扩展springboot】《2》
//方法四:postProcessBeanFactory(beanFactory);
默认空方法,子类实现【springboot做扩展】
6.调用增强器
//方法五:invokeBeanFactoryPostProcessors(beanFactory);
例如:替换属性,修改BeanDefinition对象
7.注册BeanPostProcessors相关信息
//方法六:registerBeanPostProcessors(beanFactory);

//注册BeanPostProcessors
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
    PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}
8.国际化【例:浏览器的中英切换】
//方法七:initMessageSource();
9.初始化应用事件多播器【方便发布监听器】
//方法八:initApplicationEventMulticaster();
10.初始化特定上下文子类信息【子类实现】
//方法九:onRefresh();
空,子类来实现
11.注册监听器
//方法十:registerListeners();
12.实例化Bean《3》
//方法十一:finishBeanFactoryInitialization(beanFactory);
实例化所有非懒加载的单例对象

//1.实例化
finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)
    |--// Instantiate all remaining (non-lazy-init) singletons.【最后一行】
		beanFactory.preInstantiateSingletons();
			|--getBean(beanName);	//获取bean
				|--doGetBean(name, null, null, false); //实际干活:带有do
					|--return createBean(beanName, mbd, args); //比较复杂,点进方法,打断点,直接跳转
						|--Object beanInstance = doCreateBean(beanName, mbdToUse, args);
							|--instanceWrapper = createBeanInstance(beanName, mbd, args);//创建实例化
	|--Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);//创构造器
	|--return instantiateBean(beanName, mbd); //实例化
		|--beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
			|--return BeanUtils.instantiateClass(constructorToUse);
				|--return ctor.newInstance(argsWithDefaultValues); //【反射 实例化】
//2.填充属性、Aop、调用init方法【doGetBean方法内】
AbstractAutowireCapableBeanFactory
    |--populateBean(beanName, mbd, instanceWrapper);//填充属性
	|--exposedObject = initializeBean(beanName, exposedObject, mbd);//aware方法,before【aop】
		|--invokeAwareMethods(beanName, bean);//执行aware
		|--wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//before
			|--Object current = processor.postProcessBeforeInitialization(result, beanName);
		|--invokeInitMethods(beanName, wrappedBean, mbd); //执行init方法
		|--wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);//after
			|--Object current = processor.postProcessAfterInitialization(result, beanName);
13.发布
//方法十二:finishRefresh();
14.销毁
//方法十三:destroyBeans();
15.重置“活动”标志
//方法十四:cancelRefresh(ex);

3)知识点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1ifcvbj-1680846789824)(.assets\Spring循环依赖.jpg)]

①面试题
1.Spring是如何解决循环依赖问题的
2.为什么要使用三级缓存
3.只保留二级缓存行不行
②自定义注解实现对象注入
//1.需要注入的对象
public class PersonService {
    @MyAnation
    private Person person;
    public Person getPerson() {
        return person;
    }
}

//2.自定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyAnation {
}

//3.通过反射注入person对象
public static void main(String[] args) {
    PersonService service = new PersonService();
    Class<? extends PersonService> clazz = service.getClass();

    try {
        //反射获取属性
        Field field = clazz.getDeclaredField("person");
        field.setAccessible(true);
        MyAnation annotation = field.getAnnotation(MyAnation.class);
        if (annotation != null){
            try {
                //创建对象
                Object per = field.getType().newInstance();
                //对象注入
                field.set(service, per);
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
	//输出对象
    System.out.println(service.getPerson());
}
③FactroyBean

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0jtaRox-1680846789825)(C:\Users\monkey\AppData\Roaming\Typora\typora-user-images\image-20220426224351641.png)]

4)三级缓存

三大循环依赖场景
  • 构造器注入【无法解决】
@Service
public class A {
    public A(B b) {
    }
}
@Service
public class B {
    public B(A a) {
    }
}

//结果:项目启动失败抛出异常BeanCurrentlyInCreationException
//根本原因:Spring解决循环依赖依靠的是Bean的“中间态”这个概念,【已经实例化,没初始化的状态】。
			//而构造器是没有这个中间状态,所以构造器的循环依赖无法解决
  • setter注入【解决】
@Service
public class A {
    @Autowired
    private B b;
}
 
@Service
public class B {
    @Autowired
    private A a;
}

//结果:成功
  • prototype模式field属性注入【了解、未解决】
流程图

①循环依赖的对象
//1.对象A
public class A {
    private B b;

    public B getB() { 		return b;		}

    public void setB(B b) {    this.b = b;    }
}

//2.对象B
public class B {
    private A a;

    public A getA() {    return a;   	}

    public void setA(A a) {    this.a = a;    }
}
②类DefaultSingletonBeanRegistry
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

   /** Cache of singleton objects: bean name to bean instance. 【一级缓存】*/
   private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

   /** Cache of singleton factories: bean name to ObjectFactory. 【三级级缓存】*/
   private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

   /** Cache of early singleton objects: bean name to bean instance. 【二级缓存】*/
   private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
③源码分析
  • 流程简介

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


  • 1.源码分析
//1.遍历bean对象
refresh();
	|--finishBeanFactoryInitialization(beanFactory);
		|--beanFactory.preInstantiateSingletons();1for (String beanName : beanNames) {		//以及遍历bean对象
    RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);//获取bean定义信息【例:类型、属性等等】
    if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {//非抽象、单例、懒加载的
        if (isFactoryBean(beanName)) {	//特殊的bean(spingcloud、mybatis有)【true】
            。。。。。。
                getBean(beanName);	//获取bean【没有就创建】

//2.缓存中 获取bean对象
 	|--doGetBean(name, null, null, false)
        |--getSingleton(beanName);	//获取缓存信息【DefaultSingletonResgitry】
            |--getSingleton(beanName, true);//重载【获取对象】       2Object singletonObject = this.singletonObjects.get(beanName);	//是否一级缓存【首次false】
        //是否空、是否在创建中【实例化未初始化,就是创建中,首次false】    
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			。。。。。。
		}
	|--return singletonObject;	//返回
            
//3.获取不到,创建对象
    |--sharedInstance = getSingleton(beanName, () -> {	createBean(beanName, mbd, args);
        |--Object singletonObject = this.singletonObjects.get(beanName);//先从以及缓存取【没有】
        |--singletonObject = singletonFactory.getObject();//执行上两行,匿名内部接口
            |--createBean(beanName, mbd, args)
                |--Object beanInstance = doCreateBean(beanName, mbdToUse, args);
					|--instanceWrapper = createBeanInstance(beanName, mbd, args);//通过反射实例化
                   	|--addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));   	
//判断以及缓存是否有【没有】,添加到三级缓存                                                      
if (!this.singletonObjects.containsKey(beanName)) {//首次,一级缓存里面没有
	this.singletonFactories.put(beanName, singletonFactory);   //先添加到三级缓存
    
					|--populateBean(beanName, mbd, instanceWrapper); //填充属性
|--applyPropertyValues(beanName, mbd, bw, pvs);//处理属性值【关键】
    |--String propertyName = pv.getName();//获取名称
	|--Object originalValue = pv.getValue();//获取属性值【为null,存在RunTimeBeanReference】
    |--Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);//处理有必要的属性
     	|--return resolveReference(argName, ref);	//处理引用
    		|--bean = this.beanFactory.getBean(resolvedName);//再次创建B,然后填充
    			。。。。。。。
    |--bw.setPropertyValues(new MutablePropertyValues(deepCopy));//填充属性
    			
④面试题
1.一级缓存为啥不行?
    不能。如果只有一级缓存,一个map只能保存一种状态【完全状态、创建中状态】,无法同时对两种状态进行获取,因此需要二级缓存来保存这种中间状态。
    
2.只有二级缓存行不行?
    如果在A中配置Aop,则需要创建代理对象。当我们向三级缓存中放置【匿名内部类】的时候,可以【在获取】的时候在决定到底是放简单对象,还是代理对象。

2.2 AOP

1)基本概念

1Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice//切点和通知的组合,一个完成的过程2Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。//3Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。//4Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
        ①before advice,在join point【前被执行】的.但是不能够阻止join point的执行, 除非发生异常
		②after return advice, 在一个 join point 【正常返回】后执行的advice
		③after throwing advice, 当一个 join point 【抛出异常】后执行的advice
		④after(final) advice, 无论一个 join point 是正常退出还是发生了异常【都会被执行】
		⑤around advice, 在 join point 前和 joint point 退出后都执行的 advice【常用】
		⑥introduction,introduction可以为原有的对象【增加新的属性和方法】
	
	【5Target(目标对象):织入 Advice 的目标对象.//被代理类6Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程//连接创建
  • 使用场景
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging  调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence  持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
  • 属性说明
1execution(* com.mutest.controller..*.*(..)))
	[返回值类型] [包名].[方法名](参数)
    //*【任意一个类型、路径】	..【任意多个参数、子包】2@Pointcut("@annotation(org.springframework.web.bind.annotation.PostMapping)")
    //注解切点  

3.SpringMVC

3.1 基本使用

3.1.1 基础介绍

1)Mvc介绍
  • M Model 模型层 DAO封装>>>Mybatis
  • V Veiw 视图层 html css js jsp
  • C Controller 控制层 Servlet封装>>>SpringMvc
2) 搭建原生Servlet
1.设置自己的Maven
2.右键新建一个 【model --> Maven】模块
3.选中web骨架【creat from archetype --> maven-archetype-webapp】
4.输入基本信息,点击完成
5.补全目录	src
    		|--main
    			|--java
    			|--resources
    			|--webapps
    		|--test
    			|--java
6.替换web.xml【原创建的版本过低】
    <?xml version="1.0" encoding="UTF-8"?>
	<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
	</web-app>
7.运行web【如下】  
<!--①pom文件导入依赖【地址:https://mvnrepository.com/】-->
<!--    servlet jsp依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>
//②编写contrller
@WebServlet("/create")
public class CreateProjectController extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //请求转发
        req.getRequestDispatcher("create.jsp").forward(req, resp);
    }
}
③配置tomcat
	选择tomcat,部署项目
    
④运行
    http://localhost:8080/xxx【Tomcat路径】/create
    输出: 页面跳转【create.jsp内容】
3)创建mvc
【1】添加依赖
<!--核心包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.5</version>
</dependency>
<!--切面包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.5</version>
</dependency>
<!--aop联盟包-->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
<!--德鲁伊连接池-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.22</version>
</dependency>
<!--springJDBC包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.5</version>
</dependency>
<!--spring事务包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.3.5</version>
</dependency>
<!--spring orm 映射依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>5.3.5</version>
</dependency>
<!--Apache Commons 日志包-->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
<!--log4j2 日志-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.14.0</version>
    <scope>test</scope>
</dependency>
<!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>
<!--spring test 测试支持包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.3.5</version>
    <scope>test</scope>
</dependency>
<!--junit5 单元测试-->
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.7.0</version>
    <scope>test</scope>
</dependency>
<!--spring web支持包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.5</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.5</version>
</dependency>
<!--jsp 和 servlet 可选-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>
【2】配置 WEB-INF 下的 web.xml
<servlet>
    <servlet-name>myweb</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--自定义springmvc读取配置文件的位置-->
    <init-param>
       <!--springmvc的配置文件的位置的属性【非自定义】--> 
        <param-name>contextConfigLocation</param-name>
        <!--指定自定义文件的位置 【resources根路径下资源】-->
        <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!--
    在tomcat启动后,创建Servlet对象
    load-on-startup:表示tomcat启动后创建对象的顺序。它的值是整数(>=0)
    值越小,tomcat创建对象的时间越早
    -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <!--与【<servlet-name>的值】一致--> 
    <servlet-name>myweb</servlet-name>
    <!--
    在使用框架的时候url-pattern有两种值可以使用
      1)使用拓展名的方式,语法: *.xxx,xxx是自定义拓展名。常用的方式有:*.do、*.action、*.mvc等
         http://localhost:8080/myweb/some.do
         http://localhost:8080/myweb/some.do
      2)使用"/"【除Jsp之外所有路径】
	  3)使用"/"【所有】
    -->
    <url-pattern>/</url-pattern>
</servlet-mapping>
【3】在 resources 下创建 springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    <!-- 扫描controller的bean注解 -->
    <context:component-scan base-package="com.monkey.controller"/>
    
    <!-- 配置视图解析器 -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
</beans>
【4】controller代码
@Controller
public MyController{
    /**
     * @return 【String】返回要跳转的jsp路径
     */
    @RequestMapping("/first")
    public String firstController(){
        //【/】表示相对路径;视图解析器默认拼接前缀【/WEB-INF/jsp/】和后缀【.jsp】
        return "first";
    } 
}
【5】启动、访问
访问:http://localhost:8080/xxx【上下文路径】/first

结果:页面跳转,显示【first!!!】
细节问题
  • 1. 在不指定springmvc.xml配置路径, 那么DispatcherServlet会自定在 /WEB-INF 路径下找-servlet.xml【即 myweb-servlet.xml
  • 2. 视图解析器。自动拼接路径下的前缀、后缀

3.1.2 执行流程

1)执行流程图

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuaWSpIZ-1680846789825)(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg-blog.csdnimg.cn%2F20201113165007864.png%3Fx-oss-process%3Dimage%2Fwatermark%2Ctype_ZmFuZ3poZW5naGVpdGk%2Cshadow_10%2Ctext_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjExODk4MQ%3D%3D%2Csize_16%2Ccolor_FFFFFF%2Ct_70%23pic_center&refer=http%3A%2F%2Fimg-blog.csdnimg.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1654930123&t=416a704d706ad2000f6c169f69c4f88f)]

1.DispatcherServlet前端控制器。【C】
	用户请求到达前端控制器,整个流程的控制中心,由他调用其他组件处理用户请求,降低了组件之间的耦合度

2.HandlerMapping处理映射器
	负责根据用户请求找到Handler处理器【controller】,方式:【配置文件、注解、实现接口】三种方式

3.Handler处理器【处理自己定义的controller】
	前端控制器把用户请求转发给Handler,处理返回ModelAndView

4.HandlerAdapter处理适配器
	适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行【不同参数、不同返回值等等】

5.ViewResolver视图解析器
	处理结果生成View视图【将逻辑视图名解析为物理视图名,即物理地址,在生成View视图】
	
6.View视图【Jsp】
    渲染结果,展示在页面上    
  • 备注
1.<mvc:annotation-driven>说明
    //会自动加载处理映射器、处理适配器
	 <!-- 配置处理映射器 -->
    <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>-->
    <!-- 配置处理适配器 -->
    <!--<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>-->

    <!-- 会自定配置处理映射器、处理适配器 -->
    <mvc:annotation-driven />
        
2.放行静态资源
    <!-- location:对应路径下本地资源; mapping:url中的路径 -->
   	<mvc:resources location="/css/" mapping="/css/**" />
    <mvc:resources location="/js/" mapping="/js/**" />
    <mvc:resources location="/img/" mapping="/img/**" />

3.2 源码解析

1)

2)

3)

4.SpringBoot

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

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

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

https://github.com/bjmashibing/InternetArchitect/blob/master/15%E4%BA%8C%E6%9C%9Fspringboot/%E7%AC%AC%E4%BA%94%E5%85%AD%E8%8A%82%E8%AF%BE/springboot%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90(%E4%B8%80)%EF%BC%9A%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.md
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration	//就是@configuration
@EnableAutoConfiguration	//自动配置
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })//包扫描
public @interface SpringBootApplication {
	//排出不想加载的配置类
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	//排出不想被加载的类名称
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	//指定入口的扫描包【默认启动类同级、子类所有的包】
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	//指定扫描的类
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
	Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;

	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;
}

4.1 执行流程

1)基本流程

【1】启动类
@SpringBootApplication
public class StartupApplication {
    public static void main(String[] args) {
        //参数1,后面会获取启动类的注解
        SpringApplication.run(StartupApplication.class, args);
    }
}
【2】创建SpringApplication对象
|--run(Class<?> primarySource, String... args)
    |--return run(new Class[]{primarySource}, args);
		//【1】创建SpringApplication对象,【2】执行run方法
		|--(new SpringApplication(primarySources)).run(args)
            |--this((ResourceLoader)null, primarySources);
//【1】SpringApplication构造方法
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
    //推断web应用类型【①NONE无,②SERVLET默认,③REACTIVE响应式】
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    //【1.1】加载spring.foctories文件
    this.setInitializers(this.getSpringFactoriesInstances(
        ApplicationContextInitializer.class));
    //【1.2】设置监视器
    this.setListeners(this.getSpringFactoriesInstances(
        ApplicationListener.class));
    //【1.3】处理main方法.异常处理
    this.mainApplicationClass = this.deduceMainApplicationClass();
}

//【1.1】this.getSpringFactoriesInstances(ApplicationContextInitializer.class)
|--return this.getSpringFactoriesInstances(type, new Class[0]);
	|--SpringFactoriesLoader.loadFactoryNames(type, classLoader)
        |--loadSpringFactories(classLoader)
			//读取META-INF/spring.factories
        	|--Enumeration<URL> urls = classLoader != null ? 
        		classLoader.getResources("META-INF/spring.factories") : 
				ClassLoader.getSystemResources("META-INF/spring.factories");
			|--cache.put(classLoader, result);//【缓存】二次加载直接使用缓存        

【3】执行run方法
SpringApplication---run(Class<?>[] primarySources, String[] args)
    |--(new SpringApplication(primarySources)).run(args)//【2】run方法
    
//【2】执行run方法    
public ConfigurableApplicationContext run(String... args) {
    //记录启动时间
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
    this.configureHeadlessProperty();
    SpringApplicationRunListeners listeners = this.getRunListeners(args);
    listeners.starting();

    Collection exceptionReporters;
    try {
        ApplicationArguments applicationArguments = 
            new DefaultApplicationArguments(args);
        //获取系统环境
        ConfigurableEnvironment environment = 
            this.prepareEnvironment(listeners, applicationArguments);
        this.configureIgnoreBeanInfo(environment);
        //打印banner
        Banner printedBanner = this.printBanner(environment);
        context = this.createApplicationContext();
        exceptionReporters = this.getSpringFactoriesInstances(
            SpringBootExceptionReporter.class, new Class[]
            {ConfigurableApplicationContext.class}, context);
        //【2.1重】识别启动类信息,方便后期解析【启动类注解】
        this.prepareContext(
            context, environment, listeners, applicationArguments, printedBanner);
        //【2.2】spring 加载原理【13个方法】
        this.refreshContext(context);//refresh()方法--invokeBeanFactoryPostProcessors
        this.afterRefresh(context, applicationArguments);
        stopWatch.stop();

}

//【2.1】prepareContext(context, environment, listeners, applicationArguments, printedBanner)
|--this.load(context, sources.toArray(new Object[0]));//加载启动类
	|-- loader.load()
        |--this.load(source);
			|--return this.load((Class)source)
                |--this.isComponent(source)
                	//找到@Component注解
                	|--if (AnnotationUtils.findAnnotation(type, Component.class)
                           != null)
                //启动类的【注解】做标识,【不会解析】--【2.2.3-3解析时用】
                |--this.annotatedReader.register(new Class[]{source});
                        
//【2.2】refresh()方法
    //【从spring.factories文件中得到对应的Bean对象,再由spring容器来统一管理】
|--invokeBeanFactoryPostProcessors(beanFactory) //增强器【springboot】
    |--PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(...
        //寻找该类型【BeanDefinitionRegistryPostProcessor】的beanNames【2.2.1】
    	|--beanFactory.getBeanNamesForType(
            BeanDefinitionRegistryPostProcessor.class, true, false);
        //执行启动类信息                                                                
        |--invokeBeanDefinitionRegistryPostProcessors(
            currentRegistryProcessors, registry);    
            |--postProcessor.postProcessBeanDefinitionRegistry(registry);
                |--processConfigBeanDefinitions(registry); //启动类执行【2.2.2】                                                                               
                                                                         
//【2.2.1】得到org.springframework.context.annotation.                                                  internalConfigurationAnnotationProcessor;
|--AnnotationConfigUtils//【搜internalConfigurationAnnotationProcessor】
   if (!registry.containsBeanDefinition
        ("org.springframework.context.annotation.
          	internalConfigurationAnnotationProcessor")) {
      //internalConfigurationAnnotationProcessor放ConfigurationClassPostProcesso
      def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.
                       annotation.internalConfigurationAnnotationProcessor"));
      }                                                                      
                                                                     
//【2.2.2】执行启动类
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
	//1.当为启动类,添加
    configCandidates.add(new BeanDefinitionHolder(beanDef, beanName)); 
    if (!configCandidates.isEmpty()) {//configCandidates就是启动类
        //2.【创建】解析所有@Configuration修饰的类【这里指的是启动类】
    	ConfigurationClassParser parser = 
            new ConfigurationClassParser(this.metadataReaderFactory, 
                                         this.problemReporter, this.environment, 
                                         this.resourceLoader, 
                                         this.componentScanBeanNameGenerator,
                                         registry);
        //3.【解析】当前解析器来解析启动类
        parser.parse(candidates);//【3】注解实现
    }                                                                      
【4】解析启动类parser.parse(candidates)
//【3】注解实现@Import【2.2.3-3实现】
|--parser.parse(candidates)
    |--if (bd instanceof AnnotatedBeanDefinition) {//【3】- 48行,设置的
    //1.解析启动类注解信息
    |--parse(((AnnotatedBeanDefinition)bd).getMetadata(), holder.getBeanName());
    	|--this.processConfigurationClass(new C。。。
            //循环递归启动类注解信息                             
    		|--do {
                //判断不同注解存在与否,执行相对应的操作【3.1】
                sourceClass = this.doProcessConfigurationClass(
                    configClass, sourceClass, filter);
            } while(sourceClass != null);
     //2.自动配置【延迟导入选择器处理程序】                                     
     |--this.deferredImportSelectorHandler.process();                                     	  
         |--handler.processGroupImports();
           |--grouping.getImports().。。。//处理得到一个AutoConfigurationImportSelector
              |--this.group.process(deferredImport.get。。。)
                 //getAutoConfigurationEntry(get..)【3.2】获取对应的自动配置的对象
                 |--AutoConfigurationEntry autoConfigurationEntry = 
                                          ((AutoConfigurationImportSelector) deferredImportSelector)
                      .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);             //3.实例化操作
                                    
    			
//【3.1】判断不同注解存在与否,执行相对应的操作 @Import注解                                   
protected final ConfigurationClassParser.SourceClass 									doProcessConfigurationClass(ConfigurationClass 。。。{
    //1.@Component注解解析
    if (configClass.getMetadata().isAnnotated(Component.class.getName())) {。。。}
    //2.@PropertySources,@PropertySource注解处理    
    AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), 
                                                  PropertySources.class, 
                                                  PropertySource.class).iterator();
    //3.@ComponentScans,@ComponentScan注解解析
    AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), 
                                                  ComponentScans.class, 
                                                  ComponentScan.class);
    //4.【重】@Import注解解析
    this.processImports(configClass, sourceClass, //【3.1.2】解析@Import注解
                        this.getImports(sourceClass), //【3.1.1】获取@Import注解
                        filter, true);
    
    //5.@ImportResource注解解析
    AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), 
                                        ImportResource.class);
    //6.@Bean注解解析
    Set<MethodMetadata> beanMethods = this.retrieveBeanMethodMetadata(sourceClass);
    
    //7.接口默认方法解析
    this.processInterfaces(configClass, sourceClass);
    
    //8.子类解析
    if (sourceClass.getMetadata().hasSuperClass()) {。。。}
}                                          
                                          
                                          
//【3.1.1】 获取@Import注解--getImports(sourceClass)
|--getImports(ConfigurationClassParser.SourceClass sourceClass)
   |--collectImports(sourceClass, imports, visited)
   |--return imports//得到两个数据
        {|--@SpringBootApplication
            |--@EnableAutoConfiguration
               |--@Import({AutoConfigurationImportSelector.class})//【1】
               |--@AutoConfigurationPackage
                  |--@Import({Registrar.class}) //【2】}                                            
//递归获取启动类所有的@Import注解
if (visited.add(sourceClass)) {//添加对应的@Import
    Iterator var4 = sourceClass.getAnnotations().iterator();
	while(var4.hasNext()) {
        ConfigurationClassParser.SourceClass annotation =
            (ConfigurationClassParser.SourceClass)var4.next();
        String annName = annotation.getMetadata().getClassName();
        //判断是否为@Import注解
        if (!annName.equals(Import.class.getName())) {
            //否,递归调用
            this.collectImports(annotation, imports, visited);
         }
    }
    //添加所有
    imports.addAll(sourceClass.getAnnotationAttributes(
           Import.class.getName(), "value"));
}                                              
                        
//【3.1.2】解析@Import注解--processImports(...)
processImports(configClass...){
    //进行实例化操作。。。
}
               
//【3.2】getAutoConfigurationEntry(get..)获取对应的自动配置的对象
getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata){
    //1.获取所有的自动配置对象【一百多】
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    	|--SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
			//cache.get(classLoader)缓存中获取@EnableAutoConfiguration
    		|--loadSpringFactories(classLoader)
                //获取所有spring.factories中的对象【一百多个】
                .getOrDefault(factoryTypeName, Collections.emptyList())
     //2.去除不用的【还有23个】
     Set<String> exclusions = getExclusions(annotationMetadata, attributes); 
     configurations.removeAll(exclusions);
	 configurations = filter(configurations, autoConfigurationMetadata);
    
}

2)注意点:

1. Context 上下文理解
【相当于一些资源,属性配置项,环境对象等等】
	当前操作中,有一个【容器空间】
	在这个操作步骤的【前面或者后面】,【设置】了一些最基本的【属性值】,将其放在上下文对象中
	方便后期,在上下文对象中【直接获取】,而【不用每次去加载】
2. 自动装配实现的原理
1、当启动springboot应用程序的时候,会先创建SpringApplication的对象,在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及初始化器和监听器,在这个过程中会加载整个应用程序中的spring.factories文件,将文件的内容放到缓存对象中,方便后续获取。

2SpringApplication对象创建完成之后,开始执行run方法,来完成整个启动,启动过程中最主要的有两个方法,第一个叫做prepareContext,第二个叫做refreshContext,在这两个关键步骤中完整了自动装配的核心功能,前面的处理逻辑包含了上下文对象的创建,banner的打印,异常报告期的准备等各个准备工作,方便后续来进行调用。

3、在prepareContext方法中主要完成的是对上下文对象的初始化操作,包括了属性值的设置,比如环境对象,在整个过程中有一个非常重要的方法,叫做load,load主要完成一件事,将当前启动类做为一个beanDefinition注册到registry中,方便后续在进行BeanFactoryPostProcessor调用执行的时候,找到对应的主类,来完成@SpringBootApplicaiton,@EnableAutoConfiguration等注解的解析工作
    
4、在refreshContext方法中会进行整个容器刷新过程,会调用中spring中的refresh方法,refresh中有13个非常关键的方法,来完成整个spring应用程序的启动,在自动装配过程中,会调用invokeBeanFactoryPostProcessor方法,在此方法中主要是对ConfigurationClassPostProcessor类的处理,这次是BFPP的子类也是BDRPP的子类,在调用的时候会先调用BDRPP中的postProcessBeanDefinitionRegistry方法,然后调用postProcessBeanFactory方法,在执行postProcesseanDefinitionRegistry的时候回解析处理各种注解,包含@PropertySource,@ComponentScan,@ComponentScans,@Bean,@lmport等注解,最主要的是@Import注解的解析
    
5、在解析@Import注解的时候,会有一个getimports的方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到,然后在processlmport方法中对Import的类进行分类,此处主要识别的时候AutoConfigurationlmportSelect归属于ImportSelect的子类,在后续过程中会调用deferredImportSelectorHandler中的process方法,来完整EnableAutoConfiguration的加载。

5 Mybatis

5.1 前序

1)JDBC连接数据库流程

public static void main(String[] args) {
    Connection con = null;
    Statement statement = null;
    ResultSet res = null;
    try {
        //1.加载驱动到JVM
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获取连接【静态代码块的loadInitialDrivers()方法获取驱动对象】
        con = DriverManager.getConnection(
            "jdbc:mysql://139.9.189.49:3306/monkey?" +
            "characterEncoding=utf8&autoReconnect=true&" +
            "allowMultiQueries=true&useSSl=false&serverTimezone=GMT%2B8",
            "root",
            "mysql1234");
        //3.创建Statement对象
        statement = con.createStatement();
        //4.执行sql
        res = statement.executeQuery("select * from person");
        //5.操作结果集
        while (res.next()){
            System.out.println(res.getString("id")+ "," +
                               res.getString("name")+ "," +
                               res.getString("age"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        //6.关闭连接【依次从后向前关闭】
        if (res != null){//关闭结果集
            try {
                res.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (statement != null){//关闭对象
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (con != null){//关闭连接
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

2)手写代理实现mapper

//【1】dao接口
public interface PersonMapper{

    @Select("select * from person where id = #{id} and name = #{name}")
    List<Person> selectPersonById(int id, String name);
}
//【2】代理实现
class SqlProxy{
    public static void main(String[] args) {
        //【2.1】动态代理
        PersonMapper person =
                (PersonMapper) Proxy.newProxyInstance(SqlProxy.class.getClassLoader(),
                        new Class[]{PersonMapper.class},
                        new InvocationHandler() {
                            @Override
                            public Object invoke(Object proxy, Method method, Object[] args)  {
                                //获取所有参数的map集合
                                Map<String, Object> paramMap = getParameters(method, args);
                                System.out.println("所有的参数map:" + paramMap);
                                //获取原始sql
                                Select annotation = method.getAnnotation(Select.class);
                                if (annotation != null){
                                    String[] value = annotation.value();
                                    System.out.println("初始sql:" + value[0]);
                                    //#{}替换,得到最终sql
                                    String sql = replacePlaceholder(value[0], paramMap);
                                    System.out.println("最终sql:" + sql);
                                    //获取结果
                                    List<Person> result = getResult(sql);
                                    System.out.println(result);
                                }

                                return null;
                            }
                        });
        person.selectPersonById(1, "小红");
    }

    //【2.2】获取所有参数的map集合
    public static Map<String, Object> getParameters(Method method, Object[] args){
        Map<String, Object> map = new HashMap<>();
        Parameter[] parameters = method.getParameters();
        int index = 0;
        for (Parameter parameter : parameters) {
            map.put(parameter.getName(), args[index++]);
        }
        return map;
    }
    //【2.3】替换占位符
    public static String replacePlaceholder(String sql, Map<String, Object> map){
        int start = 0;
        int end;
        while (sql.contains("#{")){
            start = sql.indexOf("#{");
            end = sql.indexOf("}");

            String param = sql.substring(start + 2, end);
            String placeholder = sql.substring(start, end+1);

            Object o = map.get(param);
            if (o != null){
                sql = sql.replace(placeholder, "'"+o+"'");
            }
        }
        return sql;
    }
    //【2.4】获取结果集
    public static List<Person> getResult(String sql){
        //1.创建连接池
        PooledDataSource dataSource = new PooledDataSource("com.mysql.cj.jdbc.Driver",
                "jdbc:mysql://139.9.189.49:3306/monkey?" +
                        "characterEncoding=utf8&autoReconnect=true&" +
                        "allowMultiQueries=true&useSSl=false&serverTimezone=GMT%2B8",
                "root",
                "mysql1234");
        //2.创建事务
        JdbcTransactionFactory transaction = new JdbcTransactionFactory();
        //3.创建环境
        Environment environment = new Environment("dev", transaction, dataSource);
        //4.创建配置
        Configuration configuration = new Configuration(environment);
        configuration.setMapUnderscoreToCamelCase(true);
        //5.加入资源 mapper接口
        configuration.addMapper(PersonMapper.class);
        //6.构建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        //7.相当于Connection对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //8.查询结果
//        List<Person> person = sqlSession.selectList(
//            "com.monkey.dao.PersonMapper.selectPersonById", new Person(1,"小红"));
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        List<Person> person = mapper.selectPersonById(1, "小红");
        //9.提交事务
        sqlSession.commit();
        //10.事务回滚
//        sqlSession.rollback();
        return person;
    }
输出:	
    所有的参数map:{name=小红, id=1}
    初始sql:select * from person where id = #{id} and name = #{name}
    最终sql:select * from person where id = 1 and name = 小红
    [Person(id=1, name=小红, age=18)]

5.2 源码解析

1)spring+mybatis整合

  • dao以及xml
public interface PersonMapper{
    List<Person> selectPersonById(int id, String name);
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.monkey.dao.PersonMapper">
    <select id="selectPersonById" resultType="com.monkey.model.Person">
        select * from person
    </select>
</mapper>
        
  • spring-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://139.9.189.49:3306/monkey
                    ?characterEncoding=utf8&amp;autoReconnect=true&amp;
                    allowMultiQueries=true&amp;useSSl=false&amp;serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="mysql1234"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/monkey/mapper/Personmapping.xml"/>
    </mappers>
</configuration>
  • test
public class PersonController {
    public static void main(String[] args) {
        try {
            InputStream stream = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(stream);
            SqlSession sqlSession = build.openSession();
            List<Person> person = sqlSession.selectList("com.monkey.dao.PersonMapper.selectPersonById");
            person.forEach(System.out::println);
            sqlSession.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 注意:
<!-- xml在java目录下,不能被加载到【默认加载resource下】,需要手动在pom.xml配置-->
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
        </resource>
    </resources>
</build>

2)build(Reader reader)方法

|--build((Reader)reader, (String)null, (Properties)null)
   //解析xml,返回结果Document树【1】
   |--XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
   //【2】创建DefaultSqlSessionFactory,
		//全局配置、映射配置封装Configuration,
		//crud封装到MapperStatement
   |--var5 = this.build(parser.parse());
         
//【1】解析xml
|--new XMLConfigBuilder(reader, environment, properties)
   |--new XPathParser(reader, true, props, new XMLMapperEntityResolver())
      //创建xpath实例
      |--this.commonConstructor(validation, variables, entityResolver);
      //创建Document树
      |--this.document = this.createDocument(new InputSource(reader));


//【2】parser.parse()方法
|--this.parseConfiguration(this.parser.evalNode("/configuration"));//解析mybatis.xml文件
	//解析对应标签【2.1】
	|--this.propertiesElement(root.evalNode("properties"));
	|--this.environmentsElement(root.evalNode("environments"));
    |--this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    |--this.typeHandlerElement(root.evalNode("typeHandlers"));
	。。。。。。
        
//【2.1】解析<properties resource="db.properties" url=""/>两个属性不能同时存在
|--if (resource != null) {
    	//1.存放在Properties对象中,他继承HashTable
    	defaults.putAll(Resources.getResourceAsProperties(resource));
	} else if (url != null) {
    	defaults.putAll(Resources.getUrlAsProperties(url));
	}
	//2.将存放在Properties对象中值,在存放放到configuration对象中variables中
|--this.configuration.setVariables(defaults);

3)openSession()方法

//1.创建一个DefaultSsqlSession对象
//2.根据指定类型创建Executor 执行器,做部分缓存和插件的增强
|--openSessionFromDataSource(this.configuration.getDefaultExecutorType(), 
                             (TransactionIsolationLevel)null, false)
    //【1】执行器     SIMPLE【不可复用】
    //			REUSE【复用statement】
    //			BATCH【批处理】
    |--configuration.newExecutor(tx, execType);	//执行器

4)sqlSession.selectList()方法

//MappedStatement存放的sql的所有信息
|--MappedStatement ms = this.configuration.getMappedStatement(statement);
//【1】先在二级缓存中找,没有找一级缓存,最后去数据库查
|--executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
	|--queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);//数据库查询
		|--doQuery(ms, parameter, rowBounds, resultHandler, boundSql);//SimpleExecutor
            |--Configuration configuration = ms.getConfiguration();
            |--StatementHandler handler = configuration.newStatementHandler(
                this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
			//【1.1】 获取Statement对象
            |--Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
            //【1.2】 查询处理
			|--List var9 = handler.query(stmt, resultHandler);


补充

1)

2)addMapper()方法
|--configuration.addMapper(PersonMapper.class);
   //通过代理将mapper接口,放在当前MapperResgistry类的knownMappers的map集合中(以便getMapper方法获取接口)
   |--this.knownMappers.put(type, new MapperProxyFactory(type));
   	  |--
  • 补充:不写@Mapper如何注入的
@Data
public class ConfigMapper {
    //配置文件配置的mapper路径
    private String mapperLocation;
    //扫描mapper接口
    public List<Class<?>> scanMapper(){
        return new ArrayList<>();
    }
    public static void main(String[] args) {
        String mapperLocation = "class:mapper/*.xml";
        ConfigMapper scanMapper = new ConfigMapper();
        //设置mapper路径
        scanMapper.setMapperLocation(mapperLocation);
        List<Class<?>> classes = scanMapper.scanMapper();
        //遍历添加
        Configuration configuration = new Configuration();
        classes.forEach(t ->{
            configuration.addMapper(t);
        });
    }
}

6.Mysql

1)基础补充

1.sql分类

DDL【数据定义语言】# create | alter | dorp | rename | truncate

DML【数据操作语言】# insert | delete | update | select

DCL【数据控制语言】# commit | rollback | savepoint | grant | revoke

2.Union

SELECT column,... FROM table1
UNION [ALL]
SELECT column,... FROM table
	执行UNION ALL语句时所需要的资源比UNION语句少。如果明确知道合并数据后的结果数据
不存在重复数据,或者不需要去除重复的数据,
	则【尽量使用UNION ALL语句】,以提高数据查询的效率。

3.sql99新特性

自然连接

SELECT employee_id,last_name,department_name
FROM employees e NATURAL JOIN departments d;

#自动查询两张连接表中 所有相同的字段 ,然后进行等值连接

using连接

SELECT employee_id,last_name,department_name
FROM employees e JOIN departments d
USING (department_id);

#与自然连接 NATURAL JOIN 不同的是,USING 指定了具体的相同的字段名称,你需要在 USING的括号 () 中填入要指定的同名字段
#等同于:
SELECT employee_id,last_name,department_name
FROM employees e ,departments d
WHERE e.department_id = d.department_id

4.函数补充

  • 时间函数
#日期加减
SELECT DATE_ADD(NOW(), INTERVAL 1 DAY),			#加一天
    DATE_ADD('2021-10-21 23:32:12',INTERVAL 1 SECOND), #加一秒
    ADDDATE('2021-10-21 23:32:12',INTERVAL 1 SECOND),	#加一秒
    DATE_ADD('2021-10-21 23:32:12',INTERVAL '1_1' MINUTE_SECOND) ,	#加一分&一秒
    DATE_ADD(NOW(), INTERVAL -1 YEAR) AS col5, #可以是负数 加一年
    DATE_ADD(NOW(), INTERVAL '1_1' YEAR_MONTH) AS col6 #需要单引号
FROM DUAL;

#日期的格式化【日期-->字符串】
SELECT DATE_FORMAT(NOW(), '%H:%i:%s');
#解析【字符串-->日期】
SELECT STR_TO_DATE('09/01/2009','%m/%d/%Y') #2009-09-01
FROM DUAL;
SELECT STR_TO_DATE('20140422154706','%Y%m%d%H%i%s')#2014-04-22 15:47:06
FROM DUAL;
SELECT STR_TO_DATE('2014-04-22 15:47:06','%Y-%m-%d %H:%i:%s')#2014-04-22 15:47:06
FROM DUAL
  • 流程控制函数
1)IF(1 > 0,'正确','错误')
2)IFNULL(null,'Hello Word')
3)CASE WHEN salary > 15000 THEN '高薪'		#类似if..else if..else...
		WHEN salary > 10000 THEN '中薪'
		WHEN salary > 8000 THEN '一般'
		ELSE '低薪' END
3)ASE `status` WHEN 1 THEN '未付款'		#类似switch..case
				WHEN 2 THEN '已付款'
				WHEN 3 THEN '已发货'
				WHEN 4 THEN '确认收货'
				ELSE '无效订单' EN
  • 聚合函数
1)count(字段)			#计算字段出现的个数【注意:不包括null】

5.分组group by

注意事项:
#1)select中出现的非聚合函数,必须出现在gruop by中
	而group by中出现的字段,不一定都在select中
	
#2)where中包含聚合函数【报错】,此时,使用having来代替

#3)有聚合函数,放在having中
	没有聚合函数,放在where中【效率高】
	
#4)执行顺序
	SELECT ...,....,...				【2】
	
	FROM ... 					
	JOIN ... ON 多表的连接条件		  
	JOIN ... ON ...
	WHERE 不包含组函数的过滤条件		 【1】
    AND/OR 不包含组函数的过滤条件
    GROUP BY ...,...
    HAVING 包含组函数的过滤条件
    
    ORDER BY ... ASC/DESC			 【3】
    LIMIT ...,..

6.子查询

 #1)单行子查询	=  !=  >  <  <=  >=
 注意:后面不能跟多行子查询结果
 
 #2)多行子查询  in(包含某个值)  any(至少有一个匹配)  all(所有匹配)  some(类似any)
 注意:聚合函数不能嵌套使用,不能有子查询
 #也可分为 【关联子查询】和【不相关子查询】 
 
 #3)结论:在
 			①group by
 			⑥limit
 			③聚合函数
 		中不能写子查询
 #4)exist & not exist
    1、如果在子查询中不存在满足条件的行:
    	条件返回 FALSE
    	继续在子查询中查找
    2、如果在子查询中存在满足条件的行:
    	不在子查询中继续查找
    	条件返回 TRUE
    3、NOT EXISTS关键字表示如果不存在某种条件,则返回TRUE,否则返回FALSE。

7.约束

#1)根据约束起的作用
	NOT NULL 非空约束,规定某个字段不能为空
    UNIQUE 唯一约束,规定某个字段在整个表中是唯一的
    PRIMARY KEY 主键(非空且唯一)约束
    FOREIGN KEY 外键约束
    CHECK 检查约束
    DEFAULT 默认值约束

#2)

8.视图

#1)定义
	视图是一种【虚拟表】,本身是【不具有数据】的,占用很少的内存空间,它是 SQL 中的一个重要概念。
	视图建立在【已有表】的基础上, 视图赖以建立的这些表称为基表。
	
#2)理解
	本质:可以看作是存储的select语句
	对视图做DML操作,影响对应基表的数据(反之亦然)
	视图本身的删除,不会导致主表数据的删除
	有点:简化查询;数据访问权限的控制

#3)创建视图
	1.完整版
        CREATE [OR REPLACE(存在替换)]
        [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
        VIEW 视图名称 [(字段列表)]
        AS 查询语句
        [WITH [CASCADED|LOCAL] CHECK OPTION] 
    2.简化版
    	CREATE VIEW 视图名称
		AS 查询语句

#4)查看	
	SHOW TABLES;	##查看数据库的表对象、视图对象
	DESC / DESCRIBE 视图名称;	#查看视图的结构
	SHOW TABLE STATUS LIKE '视图名称';	# 查看视图信息(显示数据表的存储引擎、版本、数据行数和数据大小等)
	SHOW CREATE VIEW 视图名称;	#查看视图的详细定义信息
	
#5)修改 
	失败情况:
		1.不能更新基表中【没有】的数据
		2.在定义视图的SELECT语句中使用了 【JOIN联合查询】
        3.在定义视图的SELECT语句后的字段列表中使用了 【数学表达式】 或 【子查询】
        4.在定义视图的SELECT语句后的字段列表中使用【DISTINCT、聚合函数、GROUP BY、HAVING、UNION】 等

9.存储过程&存储函数

1 创建
#存储过程:预先存储在 MySQL 服务器上,需要执行的时候,客户端只需要向服务器端发出调用
		 存储过程的命令,服务器端就可以把预先存储好的这一系列 SQL 语句全部执行。
	1)参数
		IN :当前参数为输入参数,也就是表示入参;
			存储过程只是读取这个参数的值。如果没有定义参数种类, 默认就是 IN ,表示输入参数。
		OUT :当前参数为输出参数,也就是表示出参;
			执行完成之后,调用这个存储过程的客户端或者应用程序就可以读取这个参数返回的值了。
		INOUT :当前参数既可以为输入参数,也可以为输出参数。
	
    2)创建
    	delimiter $			#避免与存储过程中SQL语句结束符;相冲突

        create procedure get_name (in fid int, out fname varchar(20))
        begin
            select `name` into fname from person where id = fid;
        end $

        delimiter ;
    3)调用
    	1.in
    		CALL sp1('值');
    	2.out
    		SET @name;
            CALL sp1(@name);
            SELECT @name;
		3.inout
			SET @name=值;
			CALL sp1(@name);
			SELECT @name;
#存储函数:
	1)创建
		delimiter //

        create function get_age(fid int)
        returns int
        begin
            return (select age from person where id = fid);
        end //

        delimiter ;
     2)调用
     	select get_age(1)
2 对比
存储过程存储函数
关键字procedurefunction
调用call 过程名()selelct 函数名()
返回值0个或多个只能一个
应用场景一般用于更新一般用于查询且返回一个值
3 查看 修改 删除
#查看
	1.SHOW CREATE {PROCEDURE | FUNCTION} 存储过程名或函数名
	2.SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
	3.SELECT * FROM information_schema.Routines
		WHERE ROUTINE_NAME='存储过程或函数的名' [AND ROUTINE_TYPE = {'PROCEDURE|FUNCTION'}]

#修改
	修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性。使用ALTER语句实现。
	ALTER {PROCEDURE | FUNCTION} 存储过程或函数的名 [characteristic ...]

#删除
	DROP {PROCEDURE | FUNCTION} [IF EXISTS] 存储过程或函数的名
4 优缺点
#优点
	1、存储过程可以一次编译多次使用。存储过程只在创建时进行编译,之后的使用都不需要重新编译,这就提升了 SQL 的执行效率。
	2、可以减少开发工作量。将代码 封装 成模块,实际上是编程的核心思想之一,这样可以把复杂的问题拆解成不同的模块,然后模块之间可以 重复使用 ,在减少开发工作量的同时,还能保证代码的结构清晰。
    3、存储过程的安全性强。我们在设定存储过程的时候可以 设置对用户的使用权限 ,这样就和视图一样具有较强的安全性。
    4、可以减少网络传输量。因为代码封装到存储过程中,每次使用只需要调用存储过程即可,这样就减少了网络传输量。
    5、良好的封装性。在进行相对复杂的数据库操作时,原本需要使用一条一条的 SQL 语句,可能要连接多次数据库才能完成的操作,现在变成了一次存储过程,只需要 连接一次即可 。
    
#缺点
	1、可移植性差。存储过程不能跨数据库移植,比如在 MySQL、Oracle 和 SQL Server 里编写的存储过程,在换成其他数据库时都需要重新编写。
	2、调试困难。只有少数 DBMS 支持存储过程的调试。对于复杂的存储过程来说,开发和维护都不容易。虽然也有一些第三方工具可以对存储过程进行调试,但要收费。
	3、存储过程的版本管理很困难。比如数据表索引发生变化了,可能会导致存储过程失效。我们在开发软件的时候往往需要进行版本管理,但是存储过程本身没有版本控制,版本迭代更新的时候很麻烦。
	4、它不适合高并发的场景。高并发的场景需要减少数据库的压力,有时数据库会采用分库分表的方式,而且对可扩展性要求很高,在这种情况下,存储过程会变得难以维护, 增加数据库的压力 ,显然就不适用了。
10.触发器
#1)定义
	触发器是由 【事件来触发】 某个操作,这些事件包括 INSERT 、 UPDATE 、 DELETE 事件。所谓事件就是指用户的动作或者触发某项行为。如果定义了触发程序,当数据库执行这些语句时候,就相当于事件发生了,就会 【自动】 激发触发器执行相应的操作。
	
#2)创建
	CREATE TRIGGER 触发器名称
    {BEFORE|AFTER} {INSERT|UPDATE|DELETE} ON 表名
    FOR EACH ROW
    触发器执行的语句块;
    
#3)查看
	1.SHOW TRIGGERS
	2.SHOW CREATE TRIGGER 触发器名
	3.SELECT * FROM information_schema.TRIGGERS

#4)删除
	DROP TRIGGER IF EXISTS 触发器名称

2)高级部分

1.文件目录

1.1 安装文件目录
#1)查找文件
	find / -name mysql

#2)数据目录文件【类似windows下data数据】(数据库启动时,会到文件系统目录下加载一些文件,运行过程中产生的数据也会存储该目录)
	cd /var/lib/mysql
	
#3)存放数据库命令(例mysql,musqladmin)
	cd /usr/bin/ 或 cd /usr/sbin/
	
#4)配置文件目录
	cd /usr/share/mysql-5.7		【命令及配置文件】
	cd /etc/mysql				【my.cnf】(类似windows配置my.ini)
1.2 默认数据库
#1)mysql
	用户账户及权限信息
	存储过程、事件的定义信息
	日志信息
	时区信息

#2)information_schema
	维护所有其他数据库信息【例如表、视图、触发器、列、索引等。不是真实数据,是一些描述信息】
	
#3)performance_schema
	运行过程的一些状态信息,用来监控mysql服务的各类性能指标
	
#4)sys
	通过视图的形式把【2】和【3】结合起来,监控mysql的技术性能
1.3 Innodb和myIsam数据库表文件
#1)innodb
	cd /var/lib/mysql/数据库名
        .opt #字符集、比较规则【8.0直接放在每个具体的表中】
        .frm #表结构
        .ibd #表数据--独立表空间【8.0可以存放在 上一层的ibdatal下(又叫系统表空间)】
		
#2)myIsam
	cd /var/lib/mysql/数据库名
        .opt #字符集、比较规则【8.0直接放在每个具体的表中】
        .frm #表结构
        .MYD #表数据
        .MYI #表索引
1.4 配置文件my.cnf(windows下my.ini)
[mysqld]	#服务端
port = 3306
socket = /apps/mysql/lock/mysql.sock	#为mysql客户端程序和服务器之间的本地通讯指定一个套接字文件
basedir = /apps/mysql	#安装路径
datadir = /apps/mysql/data	#数据存放路径
log-error = /apps/mysql/logs/mysql-error.log #日志路径
user = mysql	#启动用户,默认root
skip_name_resolve = 1 #跳过主机名解析

#作用所有的【客户端】程序
[client]

#作用所有的【服务端】程序
[server]


#两种格式
	1》option1			# boolean类型
	2》option2 = value	# key - value

2.逻辑架构

2.1 处理请求流程

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

1)连接层
#1.校验【用户名密码】
#2.校验【读写权限】
#3.连接管理
	TCP连接池【与客户端交互】
	线程池【权限 认证处理】
2)服务层
#1.SQL Interface:sql接口
	接收sql指令,返回查询结果【例如 select...from..】
	
#2.Parer:解析器
	生成解析树
	
#3.Optimizer:优化器
	生成一个执行计划(使用那个索引、and 连接执行顺序)

#4.Caches & Buffers:查询缓存
	不同客户端共享(命中率低)
3)存储引擎层
#1.MYIsam、InnoDB、Memory
2.2 执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RgNkHEFe-1680846789827)(https://gimg2.baidu.com/image_search/src=http%3A%2F%2Finotgo.com%2FimagesLocal%2F202107%2F31%2F20210731174313148R_0.png.jpg&refer=http%3A%2F%2Finotgo.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1658416787&t=da07eec41dffcf190b9f87a8f24599db)]

2.1 查询缓存
#1)条件
	服务器查询缓存中【有sql语句】,直接【返回客户端】;
	服务器查询缓存中【没有sql语句】,【进入解析器】,进行语法、语义解析
	
#2)8.0舍弃,效率低
	1、查询缓存,不是缓存查询计划。
		意味着查询sql必须和缓存中的sql【一模一样】,例如空格、大小写、注释,都会导致缓存不命中。
	2、查询请求中包含某些【系统函数、自定义变量及函数、一些系统表(mysql、information_schema等)】,都不会被缓存。
	3、mysql缓存系统会监测每张表,当表的【数据或者结构被修改】,所有相关的查询缓存会失效,被删除,对于更新压力大的数据库,命中率会非常低。
2.2 解析器

#1)语义(词法)解析
	就是把一个完整的sql语句打碎成一个个的单词
	
#2)语法解析
	语法分析器(比如:Bison)会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。
	如果SQL语句正确,则会【生成一个语法树】。
	
#3)预处理器
	检查生成的解析树
		1检查【表名】是否错误(存在)
		2检查【列名】是否错误(存在)
		3检查【别名】是否错误(保证没有歧义)
2.3 优化器
#1)说明
	确定 SQL 语句的执行路径
	
#2)优化
	1、【逻辑转换】,包括否定消除、等值传递和常量传递、常量表达式求值、外连接转换为内连接、子查询转换、视图合并等;
	2、【基于成本优化】,包括访问方法和连接顺序的选择等;
	3、【执行计划改进】,例如表条件下推、访问方法调整、排序避免以及索引条件下推。判断是全表扫描,还是索引扫描
2.4 执行器
#1)执行之前需要判断该用户是否具备权限
	如果没有,返回权限错误信息
	如果有,执行sql返回结果
	#8.0以下,如果设置查询缓存,会将结果进行缓存
2.3 缓存池

#1)缓存原则【位置*频次】
	1.位置
		位置决定效率。缓存池目的是为了在内存中可以直接访问数据
	2.频次
		频次决定优先次序。缓存池大小有限(例如1G),会优先对使用频次高的热数据进行加载
		
#2)如何读取数据
	缓存池管理器会将经常使用的数据保存起来:
		1、如果【有】,直接读取
		2、如果【没有】,会通过内存或者磁盘将页面【放进缓存池】中,在进行【读取】

#3)sql操作
	1、查看 show variables like 'innodb_buffer_pool_size';
	2、设置 set global innodb_buffer_pool_size = 268435456;
		#配置文件my.cnf
		[server] 
		innodb_buffer_pool_size = 268435456
	3、查看个数	show variables like 'innodb_buffer_pool_instances';

3. 存储引擎

1. sql命令
#1)查看所有
	show engines;
	
#2)查看默认
	show variables like '%storage_engine%'; 
	#或
	SELECT @@default_storage_engine;
	
#3)修改默认【需重启mysql】
	SET DEFAULT_STORAGE_ENGINE=MyISAM;
	#或配置文件my.cnf
	default-storage-engine=MyISAM 
	
#4)指定表存储引擎操作
	1、创建
		CREATE TABLE 表名( 建表语句; ) ENGINE = 存储引擎名称;
	2、修改
		ALTER TABLE 表名 ENGINE = 存储引擎名称;
2. 存储引擎介绍
2.1 InnoDB & MyISAM
InnoDBMyISAM
外键、事务支持不支持
行级锁只锁某一行,不对其它行有影响,适合高并发的操作即使操作一条记录也会锁住整个表不适合高并发的操作
缓存不仅缓存索引还要缓存真实数据
对内存要求较高,而且内存大小对性能有决定性的影响
只缓存索引,不缓存真实数据
关注点事务:并发写、事务、更大资源性能:节省资源、消耗少、简单业务
2.2 其他存储引擎
#1)Memory 引擎:置于内存的表

#2)Archive 引擎:用于数据存档

#3)Blackhole 引擎:丢弃写操作,读操作会返回空内容

#4)CSV 引擎:存储数据时,以逗号分隔各个数据项

...
3. InnoDB数据存储结构(页,区,端,表空间)

3.1 说明
#行
	【行格式Compact简介】
	1、【record_type】记录类型。0-普通记录;1-目录项记录;2-最小记录;3-最大记录
	2、【next_record】下一条记录地址。相对本条记录的地址偏移量
	3、各个列对应的数据
	4、其他信息。包括其他隐藏列的值以及记录的额外信息

#页
	1、默认大小为16KB
	2、【页和页】之间通过【双向链表】连接的,他的物理结构上不相邻的
	3、【页内行】之间通过【单向列表】连接的,所有记录按照从小到大排序的
	4、每个页都会生成一个【页目录】,查找记录时,会使用【二分法】快速定位对应的槽
	
#区
	1、一个区由一个或多个页组成
	2、在InnoDB引擎中,一个区会分配【64个连续的页】,即区的大小【64*16kb = 1M】

#段
	1、一个段由一个或多个区组成,但【不要求】段中的区时相邻的
	2、段是数据库的【分配单位】,不同类型对象以不同的段形式存在
		例如:创建表---->表段
			 创建索引--->索引段

#表空间
	1、是逻辑容器,【一个表空间】对应【一个或多个段】,
				但【一个段】只能属于【一个表空间】
	2、从管理上划分:
		系统表空间
		用户表空间
		撤销表空间
		临时表空间
3.1 页

  • 结构
#1)File Header(文件头部)----页的编号、其上一页、下一页是谁等

#2)File Trailer(文件尾部)
	InnoDB存储引擎以【页为单位】把数据【加载】到内存中处理,如果该页中的数据在内存中【被修改】了,那么在修改后的【某个时间】需要把数据【同步】到磁盘中。但是在同步了一半的时候【断电】了,造成了该页传输的不完整。
	
	为了检测一个页是否完整(也就是在同步的时候有没有发生只同步一半的尴尬情况),这时可以通过【文件尾】的校验和(checksum 值)与【文件头】的校验和做比对:
	如果两个值【不相等】则证明页的传输有问题,需要【重新】进行传输,否则认为页的传输已经【完成】。

#3)空闲空间、用户记录和最小最大记录

#4)页目录和页面头部
	1、为啥有页目录?
		页内记录,以【单链表】的形式进行存储,【插入和删除】效率【高】,但【检索】效率【低】,
		设计页目录,自增的数组,用【二分法】来提高效率
	2、页面头部
		本页中已经存储了多少条记录,第一条记录的地址是什么,页目录中存储了多少个槽等等

3.2 行格式(默认Dynamic)
CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称

ALTER TABLE 表名 ROW_FORMAT=行格式名称

#1)变长字段长度列表
	比如VARCHAR(M)、VARBINARY(M)、TEXT类型,BLOB类型都是【变长字段】
	在Compact格式中,会把所有变长字段的【真实数据】占用的字节长度【记录】起来,形成一个【变长字段列表】
	
#2)NULL值列表
	将所有【可以为null】的列统一管理起来,存放在【null值列表】中;没有则不存
	原因:
		1、【没有标记null值】的位置,查询数据时会【出现混乱】
		2、如果用【特定的符号标记】,会【浪费空间】

#3)记录真实的数据
	除了包含数据,还有
		1、行ID【没有手动指定,系统默认提供主键ID】
		2、事物ID
		3、回滚指针

#4)Dynamic和Compressed
	1、Dynamic(默认)
		一个页16kb,就是16348字节,而Varchar类型最多可以存放65533个字节,就会存在【一个页】放不下【一条记录】,称【行溢出】
		分页存储,当一条数据存不下,其余部分放在另一页存放
		
	2、Compressed
		在Dynamic基础上。可以进行压缩处理
3.3 区
#1)为什么有?
	由于【页与页】之间【双向链表】连接,这就导致【相邻页】在磁盘上的【物理位置】可能离的比较【远】
	B+树在使用【范围查询】时,需要定位最大与最小记录,可能需要加【载多个页到内存】中,
	即需要多次【磁盘随机IO】,且磁盘速度相对于内存相差好几个量级
	
#2)分配原则
	一个区就是连续的64个页(1M),
	当【数据量比较大】时,索引分配空间的时候按【照区为单位分配】,
	甚至在【数据特别多】时,可以一次分配多个【连续的区】,
	虽然可能造成【空间的浪费】,当从性能角度来看,消除很多随机IO,【功大于过】!
3.4 段
#1)为什么有?
	在进行【范围查询】时,B+树【数据只存储在叶子节点】,从而要【区分】是否非叶子节点,导致效率变低,
	所以InnnoDB对B+树的叶子节点和非叶子节点做了区分,分段存储,
	即【一个索引】会生成【两个段】,【叶子节点段】和【非叶子节点段]
	
	其他段:数据段、索引段、回滚段
	
#2)分配原则
	对段的管理都是由【引擎自身所完成】,DBA【不能也没有必要】对其进行控制
3.5 碎片区
#1)为什么有?
	默认情况下,【一个索引】生成【2个段】,而段是以【区】(默认1M)申请存储空间的,
	所以,即使【很小的数据】,也【需要2M】的存储空间吗?
	
	在一个碎片区中,并不是所有的页都是为了存储同一个段的数据而存在的,而是碎片区中的页可以用于不同的目的
	比如有些页面【用于段A】,有些页面【用于段B】,有些页甚至哪个段【都不属于】。
	碎片区【直属于表空间】,并不属于任何一个段。
	
#2)分配原则
	1、在【一开始插入】数据时,段是从某个碎片区【以单个页面】为【单位】来分配存储空间的
	2、当某个段占用【超过32个碎片区】页面之后,就会申请【以完整的区】为【单位】来分配存储空间
	
#3)直属于表空间
3.6 表空间
#InnoDB表空间
	1、在5.6版本之前,【数据和索引】默认存放.ibdata1中。						  -----系统表空间【只有一个】
	2、在5.6版本之后,会多产生一个.ibd文件用来【单独存放】每个表对应的【数据和索引】	-----独立表空间【每个表对应一个】

#说明;
	1、【独立表空间】有利于数据库之间的迁移
	2、【系统表空间】记录系统信息。例如:表属于那个表空间、有多少列、列类型、有多少索引、索引字段、外键等等

4. 索引

#定义:帮助MySQL高效获取数据的数据结构
	
#本质:简单理解为“排好序的快速查找数据结构”,满足特定查找算法
1. 索引数据结构(B+树)
1.1 优缺点
#1)优点
	1、提高检索效率,降低数据库IO成本。类似书的目录【主要】
	2、唯一索引,可以保证每行数据的唯一性【主键索引=唯一索引+非空】
	3、加速表和表之间的连接【外键索引】
	4、在【分组和排序】数据查询时,显著减少查询事件,降低cpu消耗
	
#2)缺点
	1、索引的【创建和维护】要消耗时间。数据量增加,耗费的时间随之增加。
	2、索引【占用磁盘空间】。除了占据数据空间之外,还要占据物理空间存储在磁盘上。
	3、会【降低表的更新速度】。在对表进行增、删、改操作时,索引也要动态的维护。
1.2 索引的推演过程
  • 没有索引
#1)页内查找【页目录:数组】
	1、以主键查找【顺序】----使用二分法查找
	2、以其他列查找【无序】----全表遍历(效率低)	

#2)页与页之间查找【双向链表】
	1、遍历查找记录所在的页
	2、在进行页内查找
  • 设计索引
mysql> CREATE TABLE index_demo(
    -> c1 INT,
    -> c2 INT,
    -> c3 CHAR(1),
    -> PRIMARY KEY(c1)
    -> ) ROW_FORMAT = Compact;
#行格式Compact简介
	1、【record_type】记录类型。0-普通记录;1-目录项记录;2-最小记录;3-最大记录
	2、【next_record】下一条记录地址。相对本条记录的地址偏移量
	3、各个列对应的数据
	4、其他信息。包括其他隐藏列的值以及记录的额外信息

  • 索引方案

① 迭代一次【单个目录项的页】

② 迭代二次【多个目录项的页】

③ 迭代三次【目录项记录页的目录页】

1.3 常见索引

①聚簇索引

# 说明
	聚簇索引【叶子节点】存放完整的【用户记录数据】

②二级(辅助、非聚簇)索引

# 回表操作
	因为叶子节点【没有】存放真实数据,
	通过列找到对应的【主键】,
	【根据主键】到聚簇索引找到对应的【数据】

③联合索引

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

#1)说明
	先对a1列排序,在对a2列排序。监理a1、a2联合索引
1.4 B+树注意事项
  • b+树生成过程
#1)创建【非聚簇索引】,在一个页中【插入】用户记录(此页一直作为根节点)

#2)当页中【空间用完】时,将根节点的页,【复制一份】
		【原来的页】充当【目录项的页】
		【复制的页】充当【叶子节点的页】

#3)以此类推,生成三层、四层的B+树。
		注意:最上层根节点一直固定不变【方便使用】
  • 目录项记录的唯一性

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

  • 一个页最少存储2条数据
#需要形成二叉树,一条数据没有意义
1.5 MyISAM索引

索引对比:

InnoDBMyISAM
聚簇索引有,
聚簇索引一次找到记录
没有,
相当于二级索引
文件类型数据即索引(.idb)数据(.MYD)和索引(.MYI)分离
叶子节点主键值(二级索引)数据地址值
查找速度
直接根据地址取数据

获取主键,在去聚簇索引中取数据
主键一定有可以没有
1.6 hash、AVL、B、B+对比
  • Hash结构
#1)定义
	输入相同,hash运算输出也相同
	时间复杂度为O(1)
	
#2)缺点
	1、仅能满足【等值查询】(例= != in),范围查询,时间复杂度退化为O(n)
	2、【无序】的,排序效率低
	3、【联合索引】进行计算,【无法区分】
	4、如果重复值过多,效率会降低(例性别)。由于hash冲突过多,导致遍历行指针去比较

#3)自适应Hash索引
	InnoDB【本身不支持】索引,但提供了【自适应Hash索引】---默认开启
		当某些数据【经常被访问】,在满足一定条件下,会将这些数据页的地址【存放在Hash表】中,
		方便下次查询的时候,【直接找到】该页的位置

  • 二叉搜索树
#1)定义
	1、所有节点【最多只有】两个子节点
	2、所有节点的【左子节点<该节点,右子节点>=该节点】
	
#2)缺点
	1、存在特殊情况,时间复杂度退化o(n)
  • AVL树(平衡二叉树)
#1)定义
	1、满足二叉搜索树条件
	2、空树、或者所有节点的【左右子树高度差】的绝对值【不超过1】
	
#2)缺点
	1、数据量大,对应树【高度高】,意味着【磁盘IO】操作次数【多】,会影响效率
  • B树(多路平衡查找树)

#1)定义
	1、在AVL树基础上,由【二叉】变为【多叉】
	
#2)缺点
	1、叶子节点和非叶子节点【都存放数据】,导致【范围查询】效率变【慢】
  • B+树
#1)定义
	相对B树,只有叶子节点存数据
	

对比(B和B+树):

中间节点不存放数据【好处】
#1)【查询效率更稳定】。B+树IO次数一定,只有叶子节点放数据;B树可能一次就找到,也可能多次。

#2)【查询效率更高】。由于B+树目录页不存放数据,即可以存放更多的数据,IO次数也会减少

#3)【范围查询效率更高】。由于B树非叶子节点也放数据,范围查找时,会返回上一次,效率会贬低
2. 索引创建与设计原则
2.1 索引分类
#1)功能逻辑
	普通索引
	唯一索引
	主键索引
	全文索引

#2)物理实现
	聚簇索引【主键索引】
	非聚簇索引【非主键索引】

#3)作用字段个数
	单列索引
	联合索引

2.2 创建索引
#创建表时,创建索引
CREATE TABLE table_name [col_name data_type] 
    [UNIQUE | FULLTEXT | SPATIAL] 
    [INDEX | KEY] [index_name] 
    (col_name [length]) 
    [ASC | DESC]

#说明
	unique【唯一索引】,fulltext【全文索引】,spatial【空间索引】
	index、key作用相同,用来【指定索引】
	index_name【索引名称】。如果【不指定】默认col_name作为索引名
	col_name创建【索引的列】,必须从数据库表中的列选择
	length【索引长度】,只有字符串类型才能指定
	ASC或DESC表示【升序或降序】存储索引值

  • 普通索引
CREATE TABLE book( 
    book_id INT , 
    year_publication YEAR, 
    KEY(year_publication)		#也可以使用INDEX(year_publication)
);
  • 唯一索引
CREATE TABLE test( 
    id INT NOT NULL, 
    name varchar(30) NOT NULL, 
    UNIQUE INDEX unique_id(id) 
);
  • 主键索引
CREATE TABLE student ( 
    id INT(10) UNSIGNED AUTO_INCREMENT, 
    student_no VARCHAR(200),
    student_name VARCHAR(200), 
    PRIMARY KEY(id) 
);

#删除【有问题:只有一个列自增时,必须要有key】
ALTER TABLE student DROP PRIMARY KEY ;
  • 单列索引
CREATE TABLE `test1` (
  `id` int(11) NOT NULL,
  `name` char(50) DEFAULT NULL,
  KEY `index_name` (`name`(20))
)
  • 联合索引
CREATE TABLE `test3` (
  `id` int(11) NOT NULL,
  `name` char(30) NOT NULL,
  `age` int(11) NOT NULL,
  `info` varchar(255) DEFAULT NULL,
  KEY `multi_index` (`id`,`name`,`age`)
)
  • 全文索引
CREATE TABLE `papers` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `title` varchar(200) DEFAULT NULL,
  `content` text,
  PRIMARY KEY (`id`),
  FULLTEXT KEY `title` (`title`,`content`)
)

#查询
	数据:1	123456789	abcdefg
	sql:
		SELECT * FROM papers WHERE MATCH(title,content) AGAINST ('123456789');
		SELECT * FROM papers WHERE MATCH(title,content) AGAINST ('abcdefg');
  • 空间索引
CREATE TABLE `test5` (
  `geo` geometry NOT NULL,
  SPATIAL KEY `spa_idx_geo` (`geo`)
)
  • 已有表添加、删除索引
#添加
ALTER TABLE table_name 
ADD [UNIQUE | FULLTEXT | SPATIAL] 
	[INDEX | KEY] [index_name] (col_name[length],...) [ASC | DESC]

#删除
ALTER TABLE table_name DROP INDEX index_name;
  • 8.0索引新特性
#1)支持降序索引
CREATE TABLE ts1(a int,
                 b int,
                 index idx_a_b(a,b desc));
                 
#2)隐藏索引
	1、定义:将索引设置为隐藏索引,使【查询优化器】不再使用这个索引,
		确定设为隐藏索引后系统【不受影响】,就可以【彻底删除】索引,
	2、创建
		#1)表创建时
    	CREATE TABLE tablename( 
            propname1 type1[CONSTRAINT1], 
            propname2 type2[CONSTRAINT2], 
            ……
            propnamen typen, 
            INDEX [indexname](propname1 [(length)]) INVISIBLE 
        );
        #2)已有表设置
        ALTER TABLE tablename 
		ADD INDEX indexname (propname [(length)]) INVISIBLE;
		#3)切换
		ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; #切换成隐藏索引 
		ALTER TABLE tablename ALTER INDEX index_name VISIBLE; #切换成非隐藏索引
2.3 索引设计原则
  • 适合创建索引
#1)字段唯一的,创建唯一索引
	【Alibaba】唯一性的字段,即使是组合字段,也要建成唯一索引

#2) 频繁作为 WHERE 查询条件的字段【√】

#3)经常 GROUP BY 和 ORDER BY 的列【√】
	分组和排序都是【按照某种顺序】检索或者存储的,提过效率。
	如果【排序有多个】,可以建立【组合索引】

#4) UPDATE、DELETE 的 WHERE 条件列
	因为更新、删除操作,需要【先检索】出来对应的数据,在执行
	如果【更新】的是【非索引字段】也会提高效率,因为【不需要对索引维护】

#5)DISTINCT 字段需要创建索引

#6)多表 JOIN 连接操作时,创建索引注意事项【√】
	1、连接表的数量尽量不要超过3张,每增加一张表就相当于增加了一次嵌套的循环,数量级增长非常快
	2、对 WHERE 条件创建索引
	3、对用于连接的字段创建索引,并且该字段在多张表中的类型必须一致。

#8)使用列的类型小的创建索引【表示的数据范围的大小】
	1、数据类型越小,在查询时进行的【比较操作越快】
	2、数据类型越小,索引【占用的存储空间就越少】,在一个数据页内就可以【放下更多的记录】,
	从而减少磁盘I/O带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。

#8)使用字符串前缀创建索引
	1、区分度计算公式:
		count(distinct left(列名, 索引长度))/count(*)
	2、【Alibaba】在 varchar 字段上建立索引时,必须指定索引长度,
	没必要对全字段建立索引,根据实际文本区分度决定索引长度。
	#说明:
	索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上。

#9) 区分度高(散列性高)的列适合作为索引
	1、列的基数指的是某一列中不重复数据的个数,比方说某个列包含值2,5,8,2,5,8,2,5,8,虽然有【9条记录】,但该列的【基数却是3】。
	2、也就是说,在记录行数一定的情况下,列的【基数越大】,该列中的值【越分散】;列的【基数越小】,该列中的值【越集中】。这个列的基数指标非常重要,直接影响我们是否能有效的利用索引。最好为列的基数大的列建立索引,为基数太小的列建立索引效果可能不好。
	
	可以使用公式【select count(distinct a)/count(*) from t1】计算区分度,【越接近1越好】,一般【超过33%】就算是比较【高效】的索引了。

#10)使用最频繁的列放到联合索引的左侧

#11)在多个字段都要创建索引的情况下,联合索引优于单值索引

##【限制】单张表的索引不超过6个
	1、每个索引都需要【占用磁盘空间】
	2、索引会【影响INSERT、DELETE、UPDATE】等语句的性能,
		因为表中的数据更改的同时,索引也会进行调整和更新
	3、会增加MySQL优化器生成执行计划时间,需判断使用那个索引
	
  • 不适合创建索引
#1)在where中使用不到的字段,不要设置索引

#2)数据量小的表最好不要使用索引

#3)有大量重复数据的列上不要建立索引

#2)避免对经常更新的表创建过多的索引

#4) 不建议用无序的值作为索引
	例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)、MD5、HASH、无序长字符串等

#5)删除不再使用或者很少使用的索引

#6)不要定义冗余或重复的索引
2.4 性能分析工具
  • 优化步骤

解释:
#1)首先在S1部分,我们需要观察服务器的状态是否存在周期性的波动。如果存在周期性波动,有可能是周期性节点的原因,比如双十一、促销活动等。这样的话,我们可以通过A1这一步骤解决,也就是加缓存,或者更改缓存失效策略。

#2)如果缓存策略没有解决,或者不是周期性波动的原因,我们就需要进一步分析查询延迟和卡顿的原因。接下来进入S2这一-步,我们需要开启慢查询。慢查询可以帮我们定位执行慢的SQL语句。我们可以通过设置long_query_time参数定义“慢”的阈值,如果SQL执行时间超过了long_query_time,则会认为是慢查询。当收集上来这些慢查询之后,我们就可以通过分析工具对慢查询日志进行分析。

#3)在S3这一步骤中,我们就知道了执行慢的SQL,这样就可以针对性地用EXPLAIN查看对应SQL语句的执行计划,或者使用show profile查看SQL中每一个步骤的时间成本。这样我们就可以了解SQL查询慢是因为执行时间长,还是等待时间长。

#4)如果是SQL等待时间长,我们进入A2步骤。在这一步骤中,我们可以调优服务器的参数,比如适当增加数据库缓冲池等。如果是SQL执行时间长,就进入A3步骤,这一步中我们需要考虑是索引设计的问题?还是查询关联的数据表过多?还是因为数据表的字段设计问题导致了这一现象。然后在这些维度上进行对应的调整。

#5)如果A2和A3都不能解决问题,我们需要考虑数据库自身的SQL查询性能是否已经达到了瓶颈,如果确认没有达到性能瓶颈,就需要重新检查,重复以上的步骤。如果已经达到了性能瓶颈,进入A4阶段,需要考虑增加服务器,采用读写分离的架构,或者考虑对数据库进行分库分表,比如垂直分库、垂直分表和水平分表等。
  • sql查询成本(last_query_cost)
#1)定义
	sql查询在【执行前】需确定查询【执行计划】,【选择成本最小】的一条记录作为最终的执行计划

#2)使用
	执行sql之后,通过【查看last_query_cost变量】得到查询的成本,对应sql所【需要读取的页的数量】

#3)创建【十万数据】
	CREATE TABLE `student_info` (
        `id` INT(11) NOT NULL AUTO_INCREMENT,
        `student_id` INT NOT NULL ,
        `name` VARCHAR(20) DEFAULT NULL, `course_id` INT NOT NULL ,
        `class_id` INT(11) DEFAULT NULL,
        `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        PRIMARY KEY (`id`)
     );
     
#4)主键等值查询
	SELECT student_id, class_id, NAME, create_time 
     	FROM student_info WHERE id = 900001 ;
	SHOW STATUS LIKE 'last_query_cost';		#结果last_query_cost:1
	
	#为什么只需要读取一个页面?
		主键等职查询只需要一个页

#5)主键范围查询
	SELECT student_id, class_id, NAME, create_time FROM student_info
		WHERE id BETWEEN 900001 AND 900100;
	SHOW STATUS LIKE 'last_query_cost';		#结果last_query_cost:42.237653
	
	#为什么效率不低?
		因为页与页之间采用【顺序存储】的方式【一次性把页加载到缓存池】中,
		磁盘io次数没有增加很多,速度并没有慢多少。

#6)结论
	1、【位置决定效率】。如果页就在数据库缓冲池中,那么效率是最高的,否则还需要从磁盘中进行读取,当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。
	2、【批量决定效率】。如果我们从磁盘中对单一页进行随机读,那么效率是很低的(差不多10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。
  • 慢查询
#1)定义
	用来记录mysql中【响应时间超过阈值】的语句,用【long_query_time】来设置,【默认10s】,超出时间就记录
	【默认不开启】,如果不是调优,一般不建议启动参数。因为会对性能有影响。

#2)开启
	show status like '%last_query_cost%';		#默认关闭off
	set GLOBAL slow_query_log = on;

#3)修改long_query_time阈值
	show variables like '%long_query_time%'		#默认值10s
	set long_query_time = 5;

#4) 查看慢查询数目
	SHOW GLOBAL STATUS LIKE '%Slow_queries%';	#显示个数

#5)慢查询日志分析工具:mysqldumpslow【linux操作】
	1、参数 mysqldumpslow --help
		-a: 不将数字抽象成N,字符串抽象成S
        -s: 是表示按照何种方式排序:
            c: 访问次数
            l: 锁定时间
            r: 返回记录
            t: 查询时间
            al:平均锁定时间
            ar:平均返回记录数
            at:平均查询时间 (默认方式)
            ac:平均查询次数
        -t: 即为返回前面多少条的数据;
        -g: 后边搭配一个正则匹配模式,大小写不敏感的

	2、常用参考
        #得到返回【记录集最多】的10个SQL
        mysqldumpslow -s r -t 10 /var/lib/mysql/4c331f1af8d7-slow.log

        #得到【访问次数最多】的10个SQL
        mysqldumpslow -s c -t 10 /var/lib/mysql/4c331f1af8d7-slow.log

        #得到按照【时间排序】的前10条里面含【有左连接】的查询语句
        mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/4c331f1af8d7-slow.log

        #另外建议在使用这些命令时【结合 | 和more 使用】 ,否则有可能出现爆屏情况
        mysqldumpslow -s r -t 10 /var/lib/mysql/4c331f1af8d7-slow.log


#6)关闭
	SET GLOBAL slow_query_log=off;

#7)删除慢查询日志【linux下】
	mysqladmin -uroot -p flush-logs slow;
2.5 分析查询语句:EXPLAIN
#作用:
	定位了查询慢的SQL之后,我们就可以使用EXPLAIN或DESCRIBE工具做针对性的分析查询语句

#语法:
	explain select * from 表名
	# 或者
	describe select * from 表名
  • 属性说明
列名描述
id1、2、3每个select对应一个或多个 唯一id
select_typeSIMPLE、PRIMARY、SUBQUERY对应的查询类型
table表名、<union1,2>表名
partitions分区信息
typesystem、const、ref针对单表的访问方法(重要)
possible_keys可能用到的索引
key实际上使用的索引
key_len实际使用到的索引长度(主要联合索引)
ref当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows预估的需要读取的记录条数
filtered某个表经过搜索条件过滤后剩余记录条数的百分比
Extra一些额外的信息
  • 创建表
#表1
CREATE TABLE s1 (
	id INT AUTO_INCREMENT,
	key1 VARCHAR(100), 
	key2 INT, 
	key3 VARCHAR(100), 
	key_part1 VARCHAR(100),
	key_part2 VARCHAR(100),
	key_part3 VARCHAR(100),
	common_field VARCHAR(100),
	PRIMARY KEY (id),
	INDEX idx_key1 (key1),
	UNIQUE INDEX idx_key2 (key2),
	INDEX idx_key3 (key3),
	INDEX idx_key_part(key_part1, key_part2, key_part3)
) 

#表2
CREATE TABLE s2 (
    id INT AUTO_INCREMENT,
    key1 VARCHAR(100),
    key2 INT,
    key3 VARCHAR(100),
    key_part1 VARCHAR(100),
    key_part2 VARCHAR(100),
    key_part3 VARCHAR(100),
    common_field VARCHAR(100),
    PRIMARY KEY (id),
    INDEX idx_key1 (key1),
    UNIQUE INDEX idx_key2 (key2),
    INDEX idx_key3 (key3),
	INDEX idx_key_part(key_part1, key_part2, key_part3)
) 
①table
EXPLAIN SELECT * FROM s1 INNER JOIN s2 ;

②id
#1)使用
	1、查询中【每一个select】关键字,mysql都会为他【分配一个唯一的id值】
		查询优化器可能对涉及子查询的查询语句进行【重写】,从而【转换】为【连接查询】
	2、在【连接查询】执行过程中,【每个表】对应【一条记录】,且这些记录的【id都是相同】的
		靠前---》驱动表
		靠后---》被驱动表
		

#2)例子
	#【包含子查询】,2行且id都为1
	EXPLAIN SELECT * FROM s1 WHERE key1 IN (SELECT key3 FROM s2);
	#【union语句】,3行,id分别为1、2、null(union去重时虚拟表)
	EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

#3)结论
	1、【id值相同】,认为一组,【从上向下】顺序执行
	2、id值【越大】,优先级【越高】,【越先执行】
	3、id【每一个】号码,都代表【一次】独立的查询,sql的查询次数【越少越好】

EXPLAIN SELECT * FROM s1 UNION SELECT * FROM s2;

③select_type
名称sql(EXPAIN)描述
SIMPLE①SELECT * FROM s1;
②SELECT * FROM s1 INNER JOIN s2;
简单查询(不使用union或子查询)
PRIMARY①SELECT * FROM s1 UNION SELECT * FROM s2;包含【UNIONUNION ALL或者子查询】的大查询来说,记录的【第一行】(最外侧查询)
UNION对应上一行除primary之外的union查询
UNION RESTUL同上UNION查询的【去重】,临时表
SUBQUERY①SELECT * FROM s1 WHERE key1 = (SELECT max(key1) FROM s1)不相关子查询
DEPENDENT SUNQUERY①SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s2) OR key3 = ‘a’;相关子查询
DEPENDENT UNION①SELECT * FROM s1 WHERE key1 IN (SELECT key1 FROM s1 UNION SELECT key1 FROM s2)相关union
DERIVED①SELECT * FROM (SELECT key1, count(*) as c FROM s1 GROUP BY key1) AS a;派生表,from后面子查询
④partitions【略】
⑤type⭐
名称sql描述
system①CREATE TABLE t(i int) Engine=MyISAM;
②INSERT INTO t VALUES(1);<③EXPLAIN SELECT * FROM t;
当表中只有一条记录并且该表使用的存储引擎的统计数据是精确的,例如memory、MyISAM
const①SELECT * FROM s1 WHERE id = 10005;根据主键、唯一二级索引、常量进行等值匹配
eq_refSELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id连接查询时,被驱动表通过主键或唯一二级索引等值匹配
ref①SELECT * FROM s1 WHERE key1 = ‘a’;通过普通的二级索引列与常量进行等值匹配
fulltext全文索引
ref_or_null①SELECT * FROM s1 WHERE key1 = ‘a’ OR key1 IS NULL;普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值
index_merge①EXPLAIN SELECT * FROM s1 WHERE key1 = ‘a’ OR key3 = ‘a’;使用多个普通索引合并
unique_subquery①SELECT * FROM s1 WHERE key2 IN (SELECT id FROM s2 where s1.key1 = s2.key1) OR key3 = ‘a’;类似于两表连接中被驱动表的eq_ref访问方法,unique_subquery是针对在一些包含IN子查询的查询语句中,如果查询优化器决定将IN子查询转换EXISTS子查询,而且子查询可以使用到主键进行等值匹配
index_subquery①SELECT * FROM s1 WHERE common_field IN (SELECT key3 FROM s2 where s1.key1 = s2.key1) OR key3 = ‘a’;index_subqueryunique_subquery类似,只不过访问子查询中的表时使用的是普通的索引
range①SELECT * FROM s1 WHERE key1 IN (‘a’, ‘b’, ‘c’);
②SELECT * FROM s1 WHERE key1 > ‘a’ AND key1 < ‘b’;
使用索引获取某些范围区间的记录
index①SELECT key_part2 FROM s1 WHERE key_part3 = ‘a’;使用索引覆盖,但需要扫描全部的索引记录
All①SELECT * FROM s1;全表扫描
#小结
	结果值从最好到最坏依次是(至少range级别):
	system > const > eq_ref > ref >
	fulltext > ref_or_null > index_merge > unique_subquery > 
	index_subquery > range > index > ALL
⑥possible_keys
#说明
	1、执行单表查询时【可能用到】的索引有哪些。
	2、一般查询涉及到的字段上若【存在索引】,则该索引将被【列出】,但不一定被查询使用	

#sql
	EXPLAIN SELECT * FROM s1 WHERE key1 > 'z' AND key3 = 'a';

⑦key
#说明
	1、表示【实际用到】的索引有哪些,
	2、如果为NULL,则没有使用索引
⑧key_len⭐
#说明
	1、【实际】使用到的【索引长度】(即:字节数)
	2、越小索引效果越好
	3、但是在【联合索引】里面,命中一次key_len加一次长度。【越长代表精度越高】,效果越好
	
#sql
	1、EXPLAIN SELECT * FROM s1 WHERE id = 10005;#4【int类型占四字节】
	2、EXPLAIN SELECT * FROM s1 WHERE key2 = 10126;#5【空值需要保存1字节】
	#303【utf8一个字符3字节,非空占1字节,变长字符占3字节,即100*3+1+2 = 303】
	#或403【utf8mb4一个字符4字节,即100*4+1+2 = 403】
	3、EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';
	#utf8:每个索引303,用到2个,即606
	#utf8mb4:每个索引403,用到2个,即806
	4、EXPLAIN SELECT * FROM s1 WHERE key_part1 = 'a' AND key_part2 = 'b';
	
⑨ref
#说明
	1、显示索引的【哪一列被使用】了,如果可能的话,是一个【常数】。哪些列或常量被用于查找索引列上的值。
	
#sql
	1、EXPLAIN SELECT * FROM s1 WHERE key1 = 'a';#const --> 'a'
	2、EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.id = s2.id;#列s1.id
⑩rows ⭐
#说明
	1、预估的需要读取的记录条数
	2、值越小,代表数据越有可能在一个页里面,这样IO就会更小。
	3、通常与filtered一起使用	
①①filtered
#说明
	1、越大越好!
	2、指【返回结果的行】占需要【读到的行】(rows 列的值)的百分比
	3、对于【单表查询】来说,这个filtered列的值【没什么意义】,
	我们更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了【被驱动表】要执行的【次数】(即:rows * filtered)
	
#sql
	EXPLAIN SELECT * FROM s1 INNER JOIN s2 ON s1.key1 = s2.key1 WHERE
s1.common_field = 'a';

①②Extra⭐
#说明
	1、一些【额外信息】的,包含不适合在其他列中显示但十分重要的额外信息
	
#补充(索引下推)
	SELECT * FROM s1 WHERE key1 > 'z' AND key1 LIKE '%a';
	原:索引找符合第一个条件记录 --> 回表 --> 找符合第二个条件记录
	现:索引找符合第一个条件记录 --> 找符合第二个条件记录 --> 回表
	【原因】回表操作其实是一个随机IO,比较耗时
	
名称描述sql
No tables used没使用表fromSELECT 1;
Impossible WHEREWHERE子句永远为FALSESELECT * FROM s1 WHERE 1 != 1;
Using wherewhere包含非索引字段①SELECT * FROM s1 WHERE common_field = ‘a’;
②SELECT * FROM s1 WHERE key1 = ‘a’ AND common_field = ‘a’;
No matching min/max rowMINMAX聚集函数,无记录SELECT * FROM s1 WHERE key1 = ‘a’ AND common_field = ‘a’;
Using index使用索引覆盖SELECT key1 FROM s1 WHERE key1 = ‘a’;
Using index condition出现了索引列,但不能使用到索引【索引条件下推SELECT * FROM s1 WHERE key1 > ‘z’ AND key1 LIKE ‘%a’;
Using join buffer (Block Nested Loop)被驱动表无索引,分配内存块SELECT * FROM s1 INNER JOIN s2 ON s1.common_field = s2.common_field;
Not exists被驱动表不为nullSELECT * FROM s1 LEFT JOIN s2 ON s1.key1 = s2.key1 WHERE s2.id IS NULL;
Using intersect(…) 、 Using union(…) 、 Using sort_union(…)索引合并、使用Union索引合并、使用Sort-Union索引合并SELECT * FROM s1 WHERE key1 = ‘a’ OR key3 = ‘a’;
Zero limitlimit 0没有数据SELECT * FROM s1 LIMIT 0;
Using filesort记录进行排序SELECT * FROM s1 ORDER BY key1 LIMIT 10;
Using temporary包含DISTINCTGROUP BYUNION等子句SELECT DISTINCT common_field FROM s1;
explain几种格式
#1)传统格式
	EXPLAIN ....
	 
#2)JSON格式
	EXPLAIN FORMAT=JSON SELECT ....
	
#3)TREE格式【8.0.16版本】
	主要根据【查询的各个部分之间的关系】和【各部分的执行顺序】来描述如何查询

#4)可视化输出
	通过MySQL Workbench可视化查看MySQL的执行计划。通过点击Workbench的放大镜图标,即可生成可视化的查询计划。

SHOW WARNINGS使用
#说明
	1、在explain之后,执行
	2、可以看到查询优化器【真正执行的语句】
Sys schema视图使用场景
#1)索引
    #1. 查询冗余索引
    select * from sys.schema_redundant_indexes;
    #2. 查询未使用过的索引
    select * from sys.schema_unused_indexes;
    #3. 查询索引的使用情况
    select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted
    from sys.schema_index_statistics where table_schema='dbname' ;

#2)表
	# 1. 查询表的访问量
    select table_schema,table_name,sum(io_read_requests+io_write_requests) as io from
    sys.schema_table_statistics group by table_schema,table_name order by io desc;
    # 2. 查询占用bufferpool较多的表
    select object_schema,object_name,allocated,data
    from sys.innodb_buffer_stats_by_table order by allocated limit 10;
    # 3. 查看表的全表扫描情况
    select * from sys.statements_with_full_table_scans where db='dbname';

#3)语句相关
	#1. 监控SQL执行的频率
    select db,exec_count,query from sys.statement_analysis
    order by exec_count desc;
    #2. 监控使用了排序的SQL
    select db,exec_count,first_seen,last_seen,query
    from sys.statements_with_sorting limit 1;
    #3. 监控使用了临时表或者磁盘临时表的SQL
    select db,exec_count,tmp_tables,tmp_disk_tables,query
    from sys.statement_analysis where tmp_tables>0 or tmp_disk_tables >0
    order by (tmp_tables+tmp_disk_tables) desc;

#4)IO
    #1. 查看消耗磁盘IO的文件
    select file,avg_read,avg_write,avg_read+avg_write as avg_io
    from sys.io_global_by_file_by_bytes order by avg_read limit 10;
    
#5)InnoDB相关
	#1. 行锁阻塞情况
	select * from sys.innodb_lock_waits;
3. 优化(上)
3.1 建表
# class表
CREATE TABLE `class` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`className` VARCHAR(30) DEFAULT NULL,
`address` VARCHAR(40) DEFAULT NULL,
`monitor` INT NULL ,
PRIMARY KEY (`id`)
) 
# student表
CREATE TABLE `student` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`stuno` INT NOT NULL ,
`name` VARCHAR(20) DEFAULT NULL,
`age` INT(3) DEFAULT NULL,
`classId` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`)
#CONSTRAINT `fk_class_id` FOREIGN KEY (`classId`) REFERENCES `t_class` (`id`)
)
3.2 索引失效案例
#1)全值匹配我最爱
	select * from student WHERE age = 1 and name = 'a' and classId = 1;#时间: 0.503s

   	CREATE INDEX idx_age ON student(age ) ;	#单个索引
    select * from student WHERE age = 1 and name = 'a' and classId = 1;#时间: 0.136s

    CREATE INDEX idx_age_classid ON student( age , classId);#两个联合索引
    select * from student WHERE age = 1 and name = 'a' and classId = 1;#时间: 0.037s

    CREATE INDEX idx_age_classid_name ON student( age , classId , name) ;#三个联合索引
    select * from student WHERE age = 1 and name = 'a' and classId = 1;#时间: 0.037s
	
#2)最左匹配原则【索引顺序age、classId、name】
	EXPLAIN SELECT * FROM student WHERE age=30 AND name = 'abcd';# 走idx_age索引

    EXPLAIN SELECT * FROM student WHERE classid=1 AND name = 'abcd' ;# ALL

	# 走idx_age_classid_name索引
    EXPLAIN SELECT * FROM student WHERE classid=4 and age=30 AND name = 'abcd' ;
    
    说明:【过滤条件】使用的索引必须【按照索引创建的顺序】,依次满足
    	一旦【跳过】那个索引字段,【后面的索引】字段都会【失效】
	
#3)计算、函数、类型转换(自动或手动)导致索引失效
	CREATE INDEX idx_name ON student (NAME) ;
    #使用函数【无法确定返回值,即没法确定使用那个索引】
    EXPLAIN SELECT * FROM student WHERE LEFT(student.name,3) = 'abc';

    CREATE INDEX idx_sno ON student(stuno);
    EXPLAIN SELECT SQL_NO_CACHE id, stuno, NAME FROM student WHERE stuno+1 = 900001;#做计算

    # name字符串类型,需要隐式函数转换
    EXPLAIN SELECT * FROM student WHERE name=123;
    
#4)范围查询,右侧索引列失效【联合索引】
	#范围查询。第三个索引失效
    EXPLAIN SELECT * FROM student
    WHERE student.age=30 AND student.classId>20 AND student.name = 'abc' ;

#5)不等于(!= 或者<>)索引失效

#6)is null可以使用索引,is not null无法使用索引

#7)like以通配符%开头索引失效

#8)OR 前后存在非索引的列,索引失效
	原因:取并集,由于最少有一个全表扫描,索引就没有意义
	EXPLAIN SELECT * FROM student WHERE age = 10 OR classid = 108;

3.3 关联查询优化
  • 创建表
#分类
CREATE TABLE IF NOT EXISTS `type`(
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY ( `id` )
);

#图书
CREATE TABLE IF NOT EXISTS `book`(
	`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
    `card`INT(10) UNSIGNED NOT NULL,
	PRIMARY KEY (`bookid`)
);
  • 连接查询
#1)左外连接
	EXPLAIN SELECT * FROM `type` a LEFT JOIN book b ON a.card = b.card;

    ALTER TABLE book ADD INDEX Y(card); #【被驱动表】,可以避免全表扫描
    EXPLAIN SELECT * FROM `type` a LEFT JOIN book b ON a.card = b.card;

    ALTER TABLE `type` ADD INDEX X (card); #【驱动表】,无法避免全表扫描
	EXPLAIN SELECT * FROM `type` a LEFT JOIN book b ON a.card = b.card;

	#如果只能添加一边的索引,,那就给【被驱动表】添加上索引。
	
#2)内连接
	1、内连接的【主被驱动表】是由优化器决定的。优化器认为哪个【成本比较小】,就采用哪种作为【驱动表】。
    2、如果两张表只有一个有索引,那有索引的表作为被驱动表。
    	原因:驱动表要全查出来。有没有索引你都得全查出来。
    3、两个索引都存在的情况下, 数据量大的作为被驱动表(小表驱动大表)
    	原因:驱动表要全部查出来,而大表可以通过索引加快查找
  • join连接原理
#1)本质
	join方式连接多个表,本质就是各个表之间数据的【循环匹配】

#2)内连接&&外连接
	内连接:
		优化器会根据你查询语句做【优化】,决定先查哪张表
	外连接:
		通常【主表】是【驱动表】,【连接表】是【被驱动表】
		#注意:有时候会【优化为内连接】

①Simple Nested-Loop Join(简单嵌套循环连接)

#从表A中取出一条数据1,遍历表B,将匹配到的数据放到result…以此类推,驱动表A中的每一条记录与被驱动表B的记录进行判断

②Index Nested-Loop Join(索引嵌套循环连接)

#优化的思路主要是为了【减少内层表数据的匹配次数】,所以要求【被驱动表】上必须【有索引】才行。通过外层表匹配条件直接与内层表索引进行匹配,避免和内层表的每条记录去进行比较,这样极大的减少了对内层表的匹配次数。

③Block Nested-Loop Join(块嵌套循环连接)

在没有索引优化:
#1)原:
	每次访问被驱动表,加载到内存-->匹配——>清除-->再次访问,加载到内容。。。。
	# 大大增加了IO的次数

#2)现:
	一块数据-->join buffer-->批量匹配-->下一块。。。。
	#一块一块获取,引入【join buffer缓冲区】

④Hash Join(8.0)

#从MysQL的8.0.20版本开始将废弃BNLJ,因为从MySQL8.0.18版本开始就加入了hash join默认都会使用hash join

3.4 子查询优化
#说明
	子查询是MySQL的一项重要的功能,可以帮助我们通过一个SQL语句实现比较【复杂的查询】。
	但是,子查询的执行【效率不高】。
	
#原因:
	1、执行子查询时MySQL需要为内层查询语句的查询结果【建立】一个【临时表】,然后外层查询语句从临时表中查询记录。查询完毕后,再【撤销这些临时表】。这样会消耗过多的CPU和IO资源,产生大量的慢查询。
	2、子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都【不会存在索引】,所以查询性能会受到一定的影响。
	3、对于返回【结果集比较大】的子查询,其对查询性能的影响也就越大。
	
#替代
	1、在MySQL中,可以使用【连接(JOIN)查询来替代子查询】。
	连接查询【不需要建立临时表】,其速度比子查询要快,如果查询中【使用索引】的话,性能就会更好。
	
	2、尽量【不要】使用NOT IN或者NOT EXISTS,用LEFT JOIN Xxx ON xx WHERE xx IS NULL【替代】
3.5 排序优化
#1)两种排序方式【fileSort && index排序】
	1、index:添加【索引】字段后,数据【有序】性,不需要排序,【效率高】
	2、fileSort:一般在【内存中排序】,占用CPU比较多。
		如果【数据过大】时,会产生【临时文件IO】到【磁盘进行排序】的情况,效率较低
		
#2)优化建议
	1、SQL中,可以在【WHERE子句和ORDER BY】子句中【使用索引】,目的是在WHERE子句中【避免全表扫描】,在ORDER BY子句【避免使用FileSort排序】。当然,某些情况下全表扫描,或者FileSort排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。
	2、尽量使用Index完成ORDER BY排序。如果WHERE和ORDER BY后面是【相同的列】就使用【单索引列】;
		如果不同就使用【联合索引】。
	3、无法使用Index时,需要对FileSort方式进行调优。
	
#3)例子【索引:(age,classid, NAME)】
	#1、不限制条数,索引失效【需要回表获取所有数据,优化器局的会耗费大量时间】
	EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid ;#不走
	EXPLAIN SELECT SQL_NO_CACHE * FROM student ORDER BY age,classid limit 10;#走索引
	#2、order by时顺序错误,索引失效
	EXPLAIN SELECT* FROM student ORDER BY classid LIMIT 10;#不走
	#3、order by时规则不一致,索引失效(顺序错,不索引; 方向反,不索引)
	EXPLAIN SELECT * FROM student ORDER BY age DESC, classid ASC LIMIT 10;#不走
	EXPLAIN SELECT * FROM student ORDER BY age DESC, classid DESC LIMIT 10; #走
	#4、无过滤,不索引
	EXPLAIN SELECT * FROM student WHERE age=45 ORDER BY classid, name;#走【优化器优化】
	EXPLAIN SELECT * FROM student wHERE classid=45 ORDER BY age;#不走
	EXPLAIN SELECT* FROM student WHERE classid=45 ORDER BY age LIMIT 10;#走
	
#4)小结
	INDEX a_b_c( a, b,c)

    order by 能使用索引最左前缀
    - ORDER BY a
    - ORDER BY a, b
    - ORDER BY a , b, c
    - ORDER BY a DESC, b DESC,c DESC


    # 如果WHERE使用索引的最左前缀定义为常量,则order by 能使用索引
    - WHERE a = const ORDER BY b, c
    - WHERE a = const AND b = const ORDER BY c
    - WHERE a = const ORDER BY b, c
    - WHERE a = const AND b > const ORDER BY b , c

    # 不能使用索引进行排序
    - ORDER BY a ASC, b DESC, c DESC/*排序不一致*/
    - WHERE g = const ORDER BY b,c/*丢失a索引*/
    - WHERE a = const ORDER BY c/*丢失b索引*/
    - WHERE a = const ORDER BY a, d /*d不是索引的一部分*/
    - WHERE a in (...) ORDER BY b,c /*对于排序来说,多个相等条件也是范围查询*/

3.6 group by优化
1、group by使用索引的原则【几乎跟order by一致】,group by即使没有过滤条件用到索引,也可以直接使用索引。.
2、group by【先排序再分组】,遵照索引建的【最佳左前缀法则】
3、当无法使用索引列,增大max_length_for_sort_data和sort_buffer_size参数的设置
4、where效率【高于】having,能写在where限定的条件就不要写在having中了
5、【减少使用】order by,和业务沟通能不排序就不排序,或将排序放到程序端去做
6、Order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。
7、包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请【保持在1000行以内】,否则SQL会很慢。
3.7 分页查询优化
#1)查询排序代价非常大
	EXPLAIN SELECT * FROM student LIMIT 2000000,10;

#2)优化1
	EXPLAIN SELECT * FROM 
		student t, ( SELECT id FROM student ORDER BY id LIMIT 2000000,10) a 
	WHERE t.id = a.id;

#3)优化2(几乎没法用)
	EXPLAIN SELECT * FROM student WHERE id > 2000000 LIMIT 10;
4. 优化(下)
4.1 覆盖索引
#1)定义
	1、二级索引【查询结果】满足了【所有字段】在【联合索引】字段中
		即:【索引列+主键】包含【select中所有的字段】	
	
#2)例子
	1、EXPLAIN SELECT id, age , NAME FROM student WHERE age <> 28;	#使用索引【所有结果列是联合索引字段】
	2、EXPLAIN SELECT id, age ,NAME FROM student WHERE NAME LIKE '%abc';	#使用索引
	3、EXPLAIN SELECT id, age ,NAME,classid FROM student WHERE NAME LIKE '%abc ';#失效【数据少】,优化器决定
	
#3)利弊
	1、避免二次查询(回表查询)
	2、随机IO变为顺序IO加快查询效率
	3、数据在索引里面数据量少更紧凑
4.2 字符串加索引
#1)sql
	alter table teacher add index index1(email);
	#或
	alter table teacher add index index2(email(6))

#2)分析
	1、index1:
		二分法查找记录 --> 回表 --> 结果
	2、index2:
		查找符合前缀第一条数据 --> 回表 --> 得到一条结果 --> 查询符合第二条数据 --> .....
    
#3)结论
	1、前缀索引,定义好长度,就可以做到【既节省空间】,又【不用额外】增加太多的【查询成本】
	2、【使用前缀索引】就【用不上覆盖索引】对查询性能的优化了,这也是你在选择是否使用前缀索引时需要考虑的一个因素。

4.3 索引下推
#1)定义
	1、ICP是5.6的新特性,是一种存储引擎层使用【索引过滤】的一种优化方式
	2、不使用:
		查找索引记录 --> 回表 --> 得到结果
	3、使用:
		查找索引记录 --> 根据其他索引过滤部分数据 --> 回表【数据变少】 --> 得到结果
	4、好处:可以【减少】存储引擎访问基表【次数】,但效率取决于【过滤】掉的数据【比例】
	
#2)例子
	1、explain select * from s1 where key1 > 'z' and key1 like '%a'; #使用索引下推
	2、SELECT * FROM people WHERE zipcode= '000001' AND lastname LIKE '%张%' AND address LIKE '%北京市%';
4.4 其它查询优化策略
#1)EXISTS 和 IN 的区分
	#适合大表驱动小表【无关子查询】
	SELECT *FROM A WHERE cc IN (SELECT cc FROM B)
	#适合小表驱动大表【相关子查询】
	SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE B.cc=A.cc)

#2)COUNT(*)与COUNT(具体字段)效率【前提,非空字段】
	1、SELECT COUNT(*)和SELECT COUNT(1)
		InnoDB:系统自动采用占空间最少的二级索引,没有索引采用主键统计
		MyISAM:数据表的mate信息存储row_count值,一致性由表级锁保证
	2、SELECT COUNT(具体字段)
		InnoDB:【尽量使用二级索引】,因为主键索引包含真实数据,【信息多】,效率慢
		MyISAM:同上
		
#3)SELECT(*)
	1、MySQL 在【解析、的过程中,会通过 【查询数据字典】 将【*】按序【转换】成【所有列名】,这会大大的耗费资源和时间。
	2、无法使用 覆盖索引
4.5 主键设计
#1)自增主键问题
	1、可靠性不高。自增回溯,8.0版本解决
	2、安全性不高。对外暴露的接口容易猜测对应的信息,例【/uesr/1】,猜测用户id,进行数据爬取
	3、性能差。需要在服务端生成
	4、交互多。额外执行last_insert_id(),去获取自增的id
	5、局部唯一性。只对当前数据库唯一,分布式系统来说,是噩梦

#2)业务字段作为左键
	建议尽量不要用跟业务有关的字段做主键。毕竟,作为项目设计的技术人员,我们谁也无法预测在项目的整个生命周期中,哪个业务字段会因为项目的业务需求而有重复,或者重用之类的情况出现。

#3)淘宝订单
	订单ID = 时间 + 去重字段 + 用户ID后6位尾号

#4)UUID
	1.组成【UUID = 时间+UUID版本(16字节)- 时钟序列(4字节) - MAC地址(12字节)】
	2.全局唯一
		时间:从1582-10-15 00:00:00.00到现在的100ns的计数【重复率1/100ns】
		时钟序列:避免时钟回拨导致产生的时间重复
		MAC地址:全局唯一
	3、36字节【32字符 + 4个短红线】
	4、无序【时间最低为放在前面】	
	
	#改造UUID:
		将时间高低位互换,时间递增,即UUID递增【8.0】
        SET @uuid = UUID();
        SELECT @uuid,uuid_to_bin(@uuid),uuid_to_bin(@uuid,TRUE);
        # uuid_to_bin(@uuid) 转成16进制存储
        # uuid_to_bin(@uuid,TRUE); 修改成先高位 中位 地位,就可以保证uuid递增了

5. 数据库设计范式

1. 三大范式
1.1 概念
#1)定义
	在关系型数据库中,关于数据表设计的基本原则、规则就称为范式

#2)常见范式
	第一范式(1NF)、
	第二范式(2NF)、
	第三范式(3NF)、
	巴斯-科德范式(BCNF)、
	第四范式(4NF)、
    第五范式(5NF,又称完美范式)
	#关系:
         1、数据库的范式设计越高阶,冗余度就越低,同时高阶的范式一定符合低阶范式的要求,
         	满足最低要求的范式是第一范式(1NF)
         2、在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。    	
         
#3)键和相关属性的概念
	1、超键:能唯─标识元组的属性集叫做超键。【非空唯一字段和其他字段组合】
	2、候选键:如果超键不包括多余的属性,那么这个超键就是候选键。【特殊超键】
    3、主键:用户可以从候选键中选择一个作为主键。
    4、外键:如果数据表R1中的某属性集不是R1的主键,而是另一个数据表R2的主键,那么这个属性集就是数据表R1的外键。
    5、主属性:包含在任一候选键中的属性称为主属性。【候选键属性】
    6、非主属性:与主属性相对,指的是不包含在任何一个候选键中的属性。                            
    #候选键 --> 码;主键 --> 主码                                   
1.2 第一范式
#1)概念
	确保数据表中每个字段的值必须具有【原子性】,
	也就是说数据表中每个字段的值为【不可再次拆分】的最小数据单元。

#2)例子【违反】
	1、一个字段存在两个电话
	2、用户信息字段包含地址、电话、爱好等
1.2 第二范式
#1)概念
	1、满足第一范式(1NF)
	2、满足数据表里的每一条数据记录,都是可【唯一标识】的。
		而且所有【非主键字段】,都必须完全【依赖主键】,不能只【依赖主键的一部分】

#2)例子
	1、成绩表(学号,课程号,成绩)
		#完全依赖关系
		(学号,课程号)→ 成绩
	2、比赛表(球员编号、姓名、年龄、比赛编号、比赛时间和比赛场地)
		# 姓名和年龄部分依赖球员编号。
        (球员编号) → (姓名,年龄)
        # 比赛时间, 比赛场地部分依赖(球员编号, 比赛编号)。
        (比赛编号) → (比赛时间, 比赛场地)
         
#3)产生问题
    1、数据冗余:1球员--》n个比赛【球员信息重复n-1次】
    2、插入异常:插入比赛还需知道球员信息
    3、删除异常:删除球员对应的比赛也可能删除
    4、更新异常:调整比赛时间,需要把每个球员统一比赛都修改
    #改进:
         球员表(球员编号、姓名、年龄)
         比赛表(比赛编号、时间、场地)
         关联表(球员编号、比赛编号、得分等)
1.3 第三范式
#1)概念
	1、满足第二范式(2NF)
	2、要求数据表中的所有【非主键字段】不能依赖于【其他非主键字段】
	
#2)例子
	1、部门表(部门编号、部门名称、部门简介)
	   员工表(员工编号、姓名、部门编号)
	2、商品表(商品主键id、类比id、类别名称、商品名称、价格)
		#非主键类别名称依赖与类别id【违反3NF】
		#改进:
			商品表(商品主键id、类比id、商品名称、价格)
			分类表(类比id、类别名称)
	3、
	
#3)

#4)

1.4 小结
#第一范式(1NF),确保每列保持原子性
	数据库的每一列都是不可分割的原子数据项,不可再分的最小数据单元,而不能是集合、数组、记录等非原子数据项。
#第二范式(2NF),确保每列都和主键完全依赖
	尤其在复合主键的情况下,非主键部分不应该依赖于部分主键。
#第三范式(3NF)确保每列都和主键列直接相关,而不是间接相关

#优点:
	消除数据库中的数据冗余
	性能、扩展性和数据完整性方面达到了最好的平衡。
	
#缺点:
	降低查询的效率
	关联多张表
	索引策略无效
2. 反范式
2.1 例子
#1)例1【查询员工部门名称】
	select employee_id,department_name
    from employees e join departments d
    on e.department_id = d.department_id;
	#问题:频繁操作,连接查询浪费时间【在employee表加冗余字段dept_name】

#2)例2【】
	商品流水表(流水编号、商品编号、数量、价格、金额、日期)#+商品名称
	商品信息表(商品编号、名称、规格、条码)
	#问题:对流水表查看频繁,关联表只需要商品名称【流水表冗余字段商品名称】
2.2 返范式的问题
#1)存储 【空间变大】 了

#2)一个表中字段做了修改,另一个表中冗余的字段也需要做【同步修改】,否则 【数据不一致】

#3)若采用存储过程来支持数据的更新、删除等额外操作,如果更新频繁,会非常 【消耗系统资源】

#4)在 【数据量小】 的情况下,反范式不能体现性能的优势,可能还会让数据库的设计更加 【复杂】
2.3 使用场景
#1)增加冗余字段的建议【修改不频繁】
	这个冗余字段【不】需要经常进行【修改】;
	这个冗余字段【查询】的时候【不可或缺】。

#2)历史快照、历史数据的需要
3. BCNF(巴斯范式)
3.1 定义
#1)【3NF】的基础上进行了【改进】,提出了巴斯范式(BCNF),也叫做巴斯-科德范式(Boyce-Codd NormalForm)

#2)没有新的设计规范加入,只是对第三范式中【设计规范要求更强】,使得数据库【冗余度更小】。
	所以,称为是【修正】的第三范式,或【扩充】的第三范式,【BCNF不被称为第四范式】。

#3)要求:
		1、若一个关系达到了第三范式,
		2、并且它只有一个候选键,或者它的每个候选键都是单属性,则该关系自然达到BC范式。

#4)

3.1 例子分析

#1)说明
	1、管理员 <--> 仓库的关系: 一对一 
	2、候选键:【管理员、物品名】和【仓库名、物品名】 
	3、主属性:管理员、仓库名、物品名【候选键中任意一个】
	4、非主属性:【数量】

#2)是否符合三范式
	1、1NF:【符合】原子性,字段不可拆分
	2、2NF:【符合】非主属性全部依赖于候选键
		数量依赖 --> 管理员+物品名
				--> 仓库名+物品名
    3、3NF:【符合】非主属性,不传递依赖于主属性,是直接依赖主属性

#3)存在问题
	1、增加仓库,没有物品。主键可能为空
	2、更改管理员,需要修改多条数据
	3、商品卖空,仓库和管理员随之删除

#4)解决问题【拆分】
	#原因:主属性仓库名对于候选键(管理员,物品名)是部分依赖的关系
	仓库表(仓库名、管理员)
	库存表(仓库名、物品名、数量)
4. 第四范式(4NF)
4.1 定义
#1)概念
	1、多值依赖即属性之间的一对多关系,记为K→→A。
    2、函数依赖事实上是单值依赖,所以不能表达属性值之间的一对多关系。
    3、平凡的多值依赖:全集U=K+A,一个K可以对应于多个A,即K→→A。此时整个表就是一组一对多关系。
    4、非平凡的多值依赖:全集U=K+A+B,一个K可以对应于多个A,也可以对应于多个B,A与B互相独立,即K→一A,K→一B。整个表有多组一对多关系,且有:“一"部分是相同的属性集合,“多"部分是互相独立的属性集合。

#2)定义
	1、第四范式即在满足巴斯-科德范式(BCNF)的基础上,
    2.消除非平凡且非函数依赖的多值依赖【即把同一表内的多对多关系删除】
4.2 例子
#1)职工表(职工编号,职工孩子姓名,职工选修课程)
	职工 --> 孩子【一对多】
	职工 --> 课程【一对多】
	#改进:
		职工表一 (职工编号,职工孩子姓名)
		职工表二 (职工编号,职工选修课程)
5. 第五范式、域键范式
5.1 定义
#1)第五范式
	1、在满足第四范式(4NF)的基础上,
    2.消除不是由候选键所蕴含的连接依赖。
    	如果关系模式R中的每一个连接依赖均由R的候选键所隐含,则称此关系模式符合第五范式。

#2)域键范式
	域键范式试图定义一个 终极范式 ,该范式考虑所有的依赖和约束类型,但是实用价值也是最小的,只存在理论研究中。
6. 实战案例

6.1 迭代1次:考虑1NF
#拆分 property 字段

6.2 迭代2次:考虑2NF
#1)主键:
	listnumber(单号)+barcode(条码)

#2)部分依赖主键
	goodsname(名称)、specification(规格)、unit(单位)-->barcode(条码)
	supplierid(供应商编号)、suppliername(供应商名称)、stock(仓库)-->listnumber(单号)

进货单头表

商品表

进货明细表

6.3 迭代3次:考虑3NF
#进货单头表 :
	suppliername依赖supplierid

供应商表

进货单头表

7. 表设计原则
#1)数据表的个数越少越好

#2)数据表中的字段个数越少越好
	通常会在【数据冗余】和【检索效率】中进行【平衡】
	
#3)数据表中联合主键的字段个数越少越好

#4)使用主键和外键越多越好

8. 建议
#【强制】程序端SELECT语句必须指定具体字段名称,禁止写成*。

#【建议】程序端insert语句指定具体字段名称,不要写成INSERT INTO t1 VALUES(…)。

#【建议】除静态表或小表(100行以内),DML语句必须有WHERE条件,且使用索引查找。

#【建议】INSERT INTO…VALUES(XX),(XX),(XX)..这里XX的值不要超过5000个。 值过多虽然上线很快,但会引起主从同步延迟。

#【建议】SELECT语句不要使用UNION,推荐使用UNION ALL,并且UNION子句个数限制在5个以内。

#【建议】线上环境,多表JOIN不要超过5个表。

#【建议】减少使用ORDER BY,和业务沟通能不排序就不排序,或将排序放到程序端去做。ORDER BY、GROUP BY、DISTINCT这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。

#【建议】包含了ORDER BY、GROUP BY、DISTINCT这些查询的语句,WHERE条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。

#【建议】对单表的多次alter操作必须合并为一次
对于超过100W行的大表进行alter table,必须经过DBA审核,并在业务低峰期执行,多个alter需整合在一起。 因为alter table会产生 表锁 ,期间阻塞对于该表的所有写入,对于业务可能会产生极大影响。

#【建议】批量操作数据时,需要控制事务处理间隔时间,进行必要的sleep。

#【建议】事务里包含SQL不超过5个。
因为过长的事务会导致锁数据较久,MySQL内部缓存、连接消耗过多等问题。

#【建议】事务里更新语句尽量基于主键或UNIQUE KEY,如UPDATE… WHERE id=XX;
否则会产生间隙锁,内部扩大锁定范围,导致系统性能下降,产生死锁。

6. 数据库其他调策略

6.1 问题定位
#1)用户的反馈(主要)

#2)日志分析(主要)

#3)服务器资源使用监控

#4)数据库内部状况监控

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

6.2 调优策略

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

7. 事务基础

7.1 概述
①事物的ACID特性
#1)原子性(atomicity)
	要么全部提交
	要么全部失败回滚
	【不存在中间状态】

#2)一致性(consistency)
	事务执行前后,数据从一个【合法性状态】变换到另外一个【合法性状态】---业务合法
	#例子
		1、账户余额200,转出300
		2、A给B转账,A和B的总数要保持一致
		3、唯一约束

#3)隔离型(isolation)
	一个事务的执行【不能被其他事务干扰】
	即一个事务内部的操作及使用的数据对【并发】的其他事务是隔离的,并发执行的各个事务之间不能互相干扰

#4)持久性(durability)
	一个事务一旦被提交,它对数据库中数据的改变就是【永久性的】
	持久性是通过【事务日志】来保证的。日志包括了【重做日志】和【回滚日志】
②事务状态

7.2 使用事务
① 显示事务
#START TRANSACTION && BEGIN【开启事务】
	1、START TRANSACTION:
		1、READ ONLY【只读,防止其他事务访问表数据】 --- 临时表可以进行写操作
		2、READ WRITE【读写】---默认
		3、WITH CONSISTENT SNAPSHOT【一致性读】
	2、例子
		START TRANSACTION READ ONLY;#开启一个只读事务
        START TRANSACTION READ ONLY,WITH CONSISTENT SNAPSHOT;#开启只读事务和一致性读
        START TRANSACTION READ WRITE,WITH CONSISTENT SNAPSHOT#开启读写事务和一致性读
	3、ROLLBACK TO [SAVEPOINT]回滚指定保存点
		SAVEPOINT 保存点名称;
		RELEASE SAVEPOINT 保存点名称;
② 隐式事务
#1)数据定义语言(Data definition language,缩写为:DDL)
	数据库对象:库、表、视图、存储过程等
	当我们使用【create、alter、drop】去修改数据库对象时,会隐式提交之前的事务
	
#2)隐式使用或修改mysql数据库中的表
	alter、 create、 drop、 grant、 rename、 revoke、 set password也会提交

#3)事务控制或关于锁定的语句
	1、没有提交之前,使用start transaction、 begin 会提交
	2、当前的autocommit系统变量的值为OFF,我们手动把它调为ON时,也会隐式的提交前边语句所属的事务。
	3、使用LOCK TABLES、UNLOCK TABLES等关于锁定的语句也会隐式的提交前边语句所属的事务。

#4)加载数据的语句
	使用LOAD DATA语句来批量往数据库中导入数据

#5)关于MySQL复制的一些语句
	使用START SLAVE、STOP SLAVE、RESET SLAVE、CHANGE MASTER TO
	
#6)其他
	使用ANALYZE TABLE、CACHE INDEX、CHECK TABLE、FLUSH、LOAD INDEX INTO CACHE、OPTIMIZE TABLE、REPAIR TABLE、RESET等语句
7.3 事务隔离级别
① 数据并发问题
# 脏写
	事务Session A【修改】了另一个【未提交】事务Session B【修改】过的数据

# 脏读
	事务Session A【读取】了Session B【已更新】但是【未提交】的数据

# 不可重复读
	事务Session A【读取】数据
	事务Session B【更新】数据
	然后,Session A【读取】数据----读取数据不同

# 幻读
	事务Session A【读取】数据
	事务Session B【插入】数据
	然后,事务Session A【读取】数据 -- 幻读

# SQL标准

② 设置隔离级别
#1)查看
	SHOW VARIABLES LIKE 'transaction_isolation';
	SELECT @@transaction_isolation;

#2)设置
	SET [GLOBAL|SESSION] TRANSACTION_ISOLATION = '隔离级别'
        #其中,隔离级别格式:
        > READ-UNCOMMITTED
        > READ-COMMITTED
        > REPEATABLE-READ
        > SERIALIZABLE
	#注意:设置global时,需要重开会话生效
③ 例子

读未提交

读已提交

可重复读

#幻读----后面锁【解决】
7.4 事务日志
说明
#1)事务的【隔离性】是由【锁机制】实现的

#2)事务的【原子性、一致性、持久性】是由【redo log、undo log】实现的
	1、REDO LOG 称为【重做日志】,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的【持久性】----物理级别
	2、UNDO LOG 称为【回滚日志】,回滚行记录到某个特定版本,用来保证事务的【原子性、一致性】---逻辑操作
	
	
①redo log日志
#1)执行流程
	1、先将【磁盘上】的数据页加载到内存的【buffer pool】中
	2、所有【数据变更】,【先更新buffer pool】数据,
	3、在将【脏页】数据以一定频率刷新到磁盘【checkPoint机制】

#2)为啥有?
	1、消除【CPU和磁盘】的鸿沟,【checkPonint机制】保证数据的最终落盘
	2、【持久性】事务提交之后,即使系统崩溃、宕机也不会造成数据丢失

#3)直接刷新磁盘【问题】
	1、修改量与刷新磁盘工作量严重不成比例【以页为单位进行磁盘IO】
	2、随机IO效率低

#4)解决
	InnoDB引擎的事务采用了WAL技术(Write-Ahead Logging )
	先写日志,再写磁盘---成功【redo log】	

好处、特点
#1)好处
	1、降低了刷盘频率
	2、占用空间少
	#存储表空间ID、页号、偏移量、更新的值等【空间少、刷盘快】

#2)特点
	1、顺序写入磁盘【执行一条语句--->若干redo log】
	2、事务执行中,redo log不断执行
		#redo log:存储引擎产生【不断写入】
		#bin log:数据阵层产生【提交后,一次写入】
组成redo buffer&file
#1)重做日志的缓冲(redo log buffer)---内存,易失
	启动服务器--->
	申请连续的内存空间(redo buffer)--->
	分成若干个redo log block(一个占512字节)	
	#参数设置
		innodb_log_buffer_size【默认16M】	
		
#2)重做日志的文件(redo log file)---硬盘,持久
	ib_logfile0和ib_logfile1两个文件【路径:/var/lib/mysql 】

整体流程

1、从磁盘读取数据到内存中,修改数据的内存拷贝
2、生成一条日志,写入redo log buffer中,记录数据是修改后的值
3、事务提交时,将redo buffer内容刷新到redo file中,对其采用追加写的方式
4、定期将数据刷新到磁盘中
	
#体会:
	Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。
刷盘策略

#1)注意
	redo buffer--->file 不是真正刷新到磁盘上
	而是刷入【文件系统缓存page cache】---操作系统提高写入的一种优化
	#问题:
		操作系统宕机---数据丢失【小概率,会发生】

#2)解决
	提供【innnodb_flush_log_at_trx_commit】参数
	三种策略
		1、设为0:commit时,不进行刷盘【系统默认master thread每1s进行一次同步】
		2、设为1:commit时,都进行同步,刷盘操作【默认】
		3、设为2:commit时,只把redo buffer写入page cache中【os自己决定同步磁盘时间】

……

演示
#设为1
	只要事务提交成功,redo log记录一定在磁盘中,数据不会丢失

#设为2
	事务提交后,写入page cache中
	数据库挂了不会丢失数据,os挂了可能会丢失1s内数据
	
#设为0
	后台线程每1s同步一次数据
	有丢失数据风险

补充信息
#1)checkpoint
	write pos:当前位置【一边写一边后移】
	checkpoint:要擦除的位置【循环后移】
	#注意:
		如果 write pos 追上 checkpoint ,表示日志文件组满了
		需要等待最前事务提交,清除部分记录

② undo log日志
理解
#1)原子性【回滚】
	1、事务执行过程中,服务器本身错误、os错误、断电等等
	2、手动rollback

#2)修改数据
	1、insert:记录主键,回滚删除
	2、update:记录旧值,回滚更新为旧值
	3、delete:记录内容,回滚插入数据
	#select:不修改数据,不需要回滚

#3)undo日志 作用
	1、回滚数据:逻辑操作【做相反的操作】,不是恢复原来状态【例数据结果、页回滚后大不相同】
	2、MVCC:主要实现读不加锁,写加锁【提高性能】
存储结构
#1)回滚段(rollback segment)---1.1版本之后,支持128个
	|__	undo log segment【1024个】
		|__	undo页
	
#2)undo页重用
	undo日志提交后,放在链表中,
	如果使用空间【小于3/4】,重用

#3)回滚段分类
	1、未提交的回滚数据:实现一致性
	2、已提交未过期:【解决不可重复读】其他事务可能会得到之前的版本--【先放入链表中,purge判断是否删除】
	3、提交已过期:会优先覆盖

#4)流程

生命周期
#1)执行
	begin;
	INSERT INTO user (name) VALUES ("tom"); #记录主键信息
	UPDATE user SET name= "Sun" WHERE id=1;#原数据写入undo log,更新行回滚指针指向undo log
	UPDATE user SET id=2 WHERE id=1;  #原数据deleteremark置为0【逻辑删】,新增一条数据

#2)回滚
	通过undo no=3的日志把id=2的数据删除
	通过undo no=2的日志把id=1的数据的deletemark还原成0
	通过undo no=1的日志把id=1的数据的name还原成Tom
	通过undo no=0的日志把id=1的数据删除
# 总结
	1、insert undo log:不影响其他事务,提交后直接删除
	2、update undo log:MVCC机制使用,读不加锁【放入链表中,等待purge线程结束删除】

8.锁

1. 概述
当多个线程【并发访问】,保证这个数据在任何时刻最多只有一个线程在访问,保证数据的【完整性】和【一致性】
2. 并发访问相同记录3情况
2.1 读-读
# 并发事务读取相同记录 --- 无影响
2.2 写-写
#1)并发事务相继对相同记录改动 ---脏写
	
#2)解决方式 ---排队等待执行

#3)执行过程
	1、开始事务,修改记录,生成一个【锁结构】
		# trx信息:代表所结构是那个事务生成的
		# is_waiting:代表当前事务是否在等待
	2、由于之前没有加锁的记录,所以is_waiting为false----获取锁成功【下图1】
	3、另一事务也想对此记录进行改动,方法记录加锁,即is_waiting为true--获取锁失败,等待【下图2】
	4、第一个事务提交后,锁结构释放,第二个事务is_waiting变为false---获取锁成功【下图3】

2.3 读-写 & 写-读
#1)问题
	可能发生【脏读、不可重复读、幻读】问题-----主要解决
	每个数据库标准不一样,例如mysql的【RR】已经解决了【幻读】问题
	
#2)解决方案
	1、多版本并发控制(MVCC)----读不加锁、写加锁
		生成一个ReadView,找到符合条件的记录版本【undo日志实现】
		#读:只读【生成ReadView之前】【已提交事务的更改】
		#写:针对最新记录,不影响
		
		RC:【每次】select都生成一个ReadView,解决【脏读】
		RR:【第一次】select生成一个ReadView,解决【不可重复读、幻读】
		
	2、读、写 都加锁
		每次读取的都是最新记录,意味着读操作和写操作也像写-写操作那样【排队执行】
	
#3)小结
	1、采用MVCC方式的话,【读-写】操作彼此并【不冲突】,【性能更高】
	2、采用加锁方式的话,【读-写】操作彼此需要【排队】执行,【影响性能】
3. 锁分类

3.1 按类型划分:读、写锁
#1)定义
	1、读写:也叫共享锁、S锁。多个事务【读-读】互不影响
	2、写锁:也叫排他锁、X锁。会阻塞其它【写锁和读锁】
X锁S锁
X锁不兼容不兼容
S锁不兼容兼容
① 锁定读
#1)读取记录加S锁
	select ... lock in share mode
	#或者
	select ... for share 【8.0版本】
	
	#说明:
		一个事务过程中,记录【加读锁】,
		另一个事务以【加读锁】,但【不能加写锁】---阻塞,等待事务提交后释放
	

#2)读取记录加X锁
	select ... for update
	
	#说明:
		一个事务过程中,记录【加写锁】,
		另一个事务【不能加读锁】,也【不能加写锁】
② 写操作
#1)delete
	B+树定位记录的位置--> 获取记录的X锁【不阻塞】--> 执行delete mark删除记录

#2)update
	1、【未修改】记录,且【存储空间】修改前后【没有改变】
		# 定位 ---> 获取X锁 ---> 修改
	2、【未修改】记录,且至少有一个【存储空间】修改前后【发生改变】
		# 定位 ---> 获取X锁 ---> 彻底删除 ---> 插入
	3、【修改】了记录,相当先delete、在insert,参照其它两种情况即可
		# delete ---> insert
		
#3)insert
	一般情况下,新插入一条记录的操作并【不加锁】,
	通过一种称之为【隐式锁】的结构来保护这条新插入的记录在本事务提交前不被别的事务访问
3.2 按粒度划分:表级、行、页锁
① 表级锁(table lock)
定义:
	MySQL最基本锁策略。【不依赖】于存储引擎(不同存储引擎表策略一致)
	【开销小、避免死锁】,但是【并发效率低】
–1)表级S锁、X锁
#1)说明
	1、增删改查【DQL】时,【不会】添加表级锁
	2、alter、drop【DDL】时,其它事务【并发执行增删改查】语句会发生【阻塞】
	
#2)sql
	lock tables t read; 	#表加S锁
	lock tables t write;	#表加X锁

加读锁

加写锁

总结

锁类型自己可读自己可写自己可操作其他表他人可读他人可写
读锁否,等
写锁否,等否,等
–2)意向锁(intention lock)
#1)定义---【表锁】
	#在加入行锁时,会在更大一级【页锁、表锁】加入【意向锁】
        1、为了【协调表锁和行锁】的关系,支持多粒度【行锁和表锁】的锁共存
        2、意向锁【不与行级锁冲突】的表锁【重要】	
        3、表明 事务【持有】某个【行级锁】或者【将要持有】某个【行级锁】
	
#2)sql
	select ... lock in share mode;	#先获取IS锁,在获取S锁
	select ... for update;			#先获取IX锁,在获取X锁
	# 说明:
		意向锁是存储引擎【自己维护】的,用户无法手动操作,
		在【加行锁之前】,会【先获取】该行所在【表的意向锁】

#3)解决问题---提高性能
	因为在【获取表锁】时,需要【判断】表中有没有存在锁,
	如果没有意向锁,需要【遍历】所有数据去看【有没有行锁】,效率低
	
#4)示例
	# 事务A
    BEGIN;
    SELECT * FROM teacher WHERE id = 6 FOR UPDATE; #行级排他锁【自动添加一个表级IX锁】
    
    # 事务B
    BEGIN;
    LOCK TABLES teacher READ;	#阻塞【事务A开启IX锁,阻塞事务B获取表级锁】
    
    # 事务C
    BEGIN;
    SELECT * FROM teacher WHERE id = 5 FOR UPDATE;#不阻塞【与事务A不同行】


#5)总结
	1、【多粒度锁】,支持行级锁和表级锁共存
	2、【意向锁之间】互补【排斥】
		【IS锁】和【S锁】兼容之外,【其它】意向锁和表级锁【排斥】
	3、提高了【并发】性能,同时实现表锁和行锁【并存】,满足事务【隔离性】的要求
意向共享锁(lS)意向排他锁(IX)
意向共享锁(IS)兼容兼容
意向排他锁(IX)兼容兼容
意向共享锁(lS)意向排他锁(IX)
共享锁(S)表兼容互斥
排他锁(X)表互斥互斥
–3)自增锁(AUTO-INC)
#1)插入数据的分类
	1、Simple inserts(简单插入)
		INSERT...VALUES()
		REPLACE
	2、Bulk inserts(批量插入)
		INSERT ... SELECT
		REPLACE... SELECT
		LOAD DATA
	3、Mixed-mode inserts(混合模式插入)
		INSERT INTO teacher (id,name) VALUES (1,'a'), (NULL,'b'), (NULL,'d');
		INSERT ... ON DUPLICATE KEY UPDATE

#2)定义
	AUTO-INC锁是当向使用含有【AUTO_INCREMENT列】的表中插入数据时需要获取的一种【特殊】的【表级锁】
	
#3)原理
	【插入】数据时,获取一个【自增锁】,每条记录的auto_increment【递增】,结束后所【释放】
	#在事务执行中,其它事务插入要阻塞,每条语句都要对表锁进行竞争【并发低】
	
#4)3种锁定机制
	1、innodb_autoinc_lock_mode = 0(“传统”锁定模式)
		insert加自增表锁,保证【主从复制不会出错】,但【限制并发】
	2、innodb_autoinc_lock_mode = 1(“连续”锁定模式)【默认】
		简单插入,知道行数,一次性获取,然后释放锁
	3、innodb_autoinc_lock_mode = 2(“交错”锁定模式)【8.0开始默认】
		不加自增锁
			#从二进制日志重播SQL语句时,这是不安全的。(主从复制id可能不一致)
			#为任何给定语句插入的行生成的值可能不是连续的。
		
–4)元数据锁(MDL)
#1)定义
	当对一个表做【增删改查操作】的时候,加 MDL【读锁】
	当要对表做【结构变更】操作的时候,加 MDL 【写锁】

#2)解决问题
	解决了DML和DDL操作之间的一致性问题。
	不需要显式使用,在访问一个表的时候会被自动加上。
② 行级锁
#1)定义
	行锁(ROW LOCK),又叫记录锁。就是锁住一行数据【在存储引擎层实现的】

#2)优缺点
	#优:力度小,降低锁冲突概率,提高并发性能
	#缺:锁开销表较大,枷锁会很慢,容易出现死锁
–1)记录锁(record locks)

#总结
	事务获取记录的【S型记录锁】,其它事务可以获取该记录的【S型记录锁】,不能获取【X型记录锁】
	事务获取记录的【X型记录锁】,其它事务【不能】获取该记录的【S型记录锁】,【也不能】获取【X型记录锁】
–2)间隙锁(Gap Locks)

#1)解决【幻读】两种方式:
	1、MVCC
	2、加间隙锁【gap locks锁】

#2)使用
	id=8数据加间隙锁,不允许别的事务在id值为 8 的记录【前边的间隙】插入新记录,【即3-8之间的数据】
	虽然有【共享gap锁】和【独占gap锁】这样的说法,但是它们起到的【作用是相同的】

#3)例子
	事务1查询id=5记录,没有记录,加间隙锁(3,8)
	事务2再给间隙锁加锁,【不会阻塞】
	#注意:
		插入数据时,在这条记录前面的间隙,会阻塞

# 如何解决(20,+∞)区间的间隙锁问题
	1、数据页属性:
    	Infimum记录,表示该页面中最小的记录
		supremum记录,表示该页面中最大的记录
	2、给索引的【最后一条记录】,【所在页】的【supremum记录】加上一个gap锁

# 死锁问题
	事务1加间隙锁(3,8)
	事务2也加间隙锁(3,8)
	事务2插入数据阻塞
	事务1插入数据阻塞【死锁】
	

–3)临键锁(Next-Key Locks)
#1)定义
	既要【锁住该记录】,又要阻止其他事务在记录前面【间隙插入数据】,产生Next-Key Locks的锁

#2)使用
	select * from student where id <= 8 and id > 3 for update;
	#是一个【记录锁】和一个【gap锁】的合体

#3)

#4)

–4)插入意向锁(Insert Intention Locks)
#1)定义
	事务【插入】记录时,判断是否加了【gap间隙锁】,有需要等待
	innoDB在【等待的时候会】在内存中【生成一个锁结构】,即插入意向锁

#2)例子
	表中【有】数据4,7时,当两个事务【同时插入】5,6记录时,
	每个事务都获取插入行上的排他锁,都会获取间隙锁,即【不会产生阻塞】
	
#3)特性
	1、插入意向锁是一种特殊的【间隙锁】―—间隙锁可以锁定开区间内的部分记录。
	2、插入意向锁之间互【不排斥】,所以即使多个事务在同一区间插入多条记录,只要记录本身(主键、唯一索引引)不冲突,那么事务之间就不会出现冲突等待。

#4)注意
	虽然插入意向锁中含有意向锁三个字,
	但是它并【不属于意向锁】而【属于间隙锁】,因为【意向锁是表锁】而【插入意向锁是行锁】
# 例子
	T1是id=8加gap锁
	T2、T3是插入id=4、5的记录

# 结果
	事务T1执行,T2、T3等待
	事务T1提交,事务T2、T3同时获取gap锁【不会阻塞】
	# 插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。
	

③ 页级锁
#1)页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。

#2)每个层级的锁数量是有限制的,因为锁会占用内存空间,锁空间的大小是有限的。
	当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了。
3.3 按对待锁的态度划分:乐观锁、悲观锁
① 悲观锁
#1)定义
	共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程
	类似Java中【synchronized】和【ReentrantLock】等独占锁就是悲观锁思想的实现

#2)案例1
	#第1步:查出商品库存
    select quantity from items where id = 1001;
    #第2步:如果库存大于日,则根据商品信息生产订单
    insert into orders (item_id)values( 1801 );
    #第3步:修改商品的库存,num表示购买数量
    update items set quantity = quantity-num where id = 1001;
                        
#3)改进
    #第1步:查出商品库存
    select quantity from items where id = 1001 for update;
    #第2步:如果库存大于8,则根据商品信息生产订单
    insert into orders (item_id) values(1001);
    #第3步:修改商品的库存,num表示购买数量
    update items set quantity = quantity-num where id = 1001;

#注意
    select .. for update话句执行过程中所有扫描的行都会被锁上,因此在MySQL中用悲观锁【必须】确定使用了索引,而不是全表扫描,否则将会把整个【表锁住】。

② 乐观锁
#1)定义
	不采用数据库自身的锁机制,而是通过程序来实现
	采用【版本号机制]或者【CAS机制】实现
	# 乐观锁适用于多读的应用类型,这样可以提高吞吐量

#2)两种机制
	① 乐观锁的版本号机制
		类似【代码提交】,本地和服务器对比,一致更新;否则先更新本地代码,在提交
	② 乐观锁的时间戳机制
		类似版本号。通过时间戳更新 提交

#3)例子
	#第1步:查出商品库存
    select quantity from items where id = 1001 ;

    #第2步:如果库存大于日,则根据商品信息生产订单
    insert into orders (item_id values( 1001);

    #第3步:修改商品的库存,num表示购买数量
    update items set quantity = quantity-num , version = version+1 where id = 1001 and version = #{version};
                       
#4)改进
    #第1步:查出商品库存
    select quantity from items where id = 1001;

    #第2步:如果库存大于8,则根据商品信息生产订单
    insert into orders (item_id) values (1001) ;

    #第3步:修改商品的库存,num表示购买数量
    update items set quantity = quantity-num where id = 1001 and quantity-num>0;

结论:

3.4 按加锁的方式划分:显式锁、隐式锁
① 隐式锁
#1)定义
	一个事务在执行【insert】操作时,
	如果【另一个事务】在间隙插入了【gap锁】
	即当前事务会在该间隙加插入意向锁【一般情况不加锁】

#2)问题
	首次事务插入记录(内存中还没有锁结构),
		1、获取该记录的S锁【脏读】
		2、获取该记录的X锁【脏写】

#3)解决
	1、聚簇索引
		隐藏列trx_id【最后修改事务的id】
		如果其它事务想添加【S锁】或者【X锁】,会判断当前事务id是否活跃【未提交、回滚】
		如果活跃,【帮助】其创建一个【X锁】----is_waiting为false	
	2、二级索引
		二级索引页面的Page Header部分有一个PAGE_MAX_TRX_ID属性
		如果值<当前活跃trx_id值,说明事务已经提交
		否则,回表操作,执行聚簇索引方式
	#隐式锁是一种【延迟加锁】的机制,从而来【减少】加锁的【数量】

#4)结论
	1、InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于聚簇索引的B+Tree中。
	2、在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交或回滚)。如果是活动的事务,首先将隐式锁转换为显式锁(就是为该事务添加一个锁)。
	3、检查是否有锁冲突,如果有冲突,创建锁,并设置为waiting状态。如果没有冲突不加锁,跳到E。
	4、等待加锁成功,被唤醒,或者超时。
	5、写数据,并将自己的trx_id写入trx_id字段。
② 显示锁
#1)定义
	通过特定的语句进行加锁,我们一般称之为显示加锁
	
#2)例子
	select ....  lock in share mode
	select ....  for update
3.5 其他锁
① 全局锁
#1)定义
	全局锁就是对整个【数据库实例加锁】。
	当你需要让整个库处于【只读】状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:
	数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。
	#全局锁的典型使用【场景】是:做全库【逻辑备份】。

#2)命令
	Flush tables with read lock
② 死锁
#1)定义
	两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁

#2)例子
事务1事务2
1start transaction;
update account set money=10 where id=1;
start transaction;
2update account set money=10 where id=2;
3update account set money=20 where id=2;
4update account set money=20 where id=1;
# 处理
	1、等待,直到超时(innodb_lock_wait_timeout=50s)。
	2、使用死锁检测进行死锁处理(wait-for graph算法)
		选择【回滚】 undo量【最小的事务】,让其他事务继续执行
		
# 避免死锁
	1、合理设计索引,使业务SQL尽可能通过索引定位更少的行,减少锁竞争。
	2、调整业务逻辑SQL执行顺序,避免update/delete长时间持有锁的SQL在事务前面。
	3、避免大事务,尽量将大事务拆成多个小事务来处理,小事务缩短锁定资源的时间,发生锁冲突的几率也更小。
	4、在并发比较高的系统中,不要显式加锁,特别是是在事务里显式加锁。如select …for update语句,如果是在事务里运行了start transaction或设置了autocommit等于0,那么就会锁定所查找到的记录。
	5、降低隔离级别。如果业务允许,将隔离级别调
3.6 锁结构

9.MVCC

定义:
	MVCC多版本并发配置。
	MVCC 是通过数据行的多个版本管理来实现数据库的并发控制。
	这项技术使得在InnoDB的事务隔离级别下执行【一致性】读操作有了保证
4.1 快照读&当前读
提高数据库并发性能
读不加锁,写加锁
①快照读
#1)定义
	又叫一致读,读取的时快照数据。

#2)例如:不加锁的简单select【不加锁的非阻塞读】
	SELECT * FROM player WHERE ...
②当前读
#1)定义
	读取的是记录的最新版本【最新数据,而不是历史版本的数据】
	读取时还要保证其他并发事务不能修改当前记录,会对【读】取的记录进行【加锁】

#2)例如
	1、SELECT * FROM student LOCK IN SHARE MODE;  # 共享锁
	2、SELECT * FROM student FOR UPDATE; # 排他锁
	3、INSERT INTO student values ...  # 排他锁
4.2 复习
① 隔离级别

MVCC 可以不采用锁机制,而是通过乐观锁的方式来【解决]不可重复读和幻读问题!
它可以在大多数情况下替代行级锁,降低系统的开销

② Undo Log版本链
#1)聚簇索引记录中两个隐藏列
	1.trx_id:每次一个事务对某条聚簇索引记录进行【改动】时,都会把该事务的事务id【赋值】给trx_id隐藏列。
	2.roll_pointer:每次对某条聚簇索引记录进行改动时,都会把【旧的版本】写入到【undo】日志中,然后这个隐藏列就相当于一个指针,可以通过它来【找到】该记录【修改前的信息】。
	
#2)例如
	插入事务id为8的记录
	#说明
		inset undo只在事务【回滚】时起作用,
		事务【提交后】,占用的undo log segment会被【系统回收】
		(也就是该undo日志占用的Undo页面链表要么被重用,要么被释放)

#原理
	每次对记录改动时,都会产生一个undo日志,每个日志都有一个roll_pointer
	把这些undo日志连起来,形成一个链表【版本链】

4.3 ReadView
① 什么是ReadView
#1)由来
	在MVCC机制中,多个事务对【同一条记录】进行【改动】,会产生多个【历史快照】
	#如果一个事务想要查询这个记录,需要读取那个版本记录?
		ReadView,解决行可见性

#2)定义
	事务在使用MVCC机制进行【快照读】产生的【视图读】。
	#事务启动时,回生成当前的快照。每个事务构建一个数组,用来记录维护当前活跃事务的ID
① 设计思路
#1)RU【读取最新版本】---可以读取事务未提交的数据
	
#2)Serializable【加锁】

#3)RC和RR
	#ReadView四个重要属性
        1、creator_trx_id【创建ReadView的事务id】
        	只有在对表增删改操作才会分配事务id,否则默认值为0
        2、trx_ids【生成ReadView时,活跃的事务id列表】
        3、up_limit_id【活跃事务中,最小的事务id】
        4、low_limit_id【生成ReadView时,应该分配的下一个事物的id值】--系统中最大的事务id
        #注意:
        	low_limit_id并不是trx_ids中的最大值,事务id是递增分配的。
        	比如:现在有id为 1 ,2 , 3 这三个事务,之后id为 3 的事务提交了。
                    那么一个新的读事务在生成ReadView时,trx_ids就包括 1 和 2 ,
                    up_limit_id的值就是 1 ,low_limit_id的值就是 4 

#4)例子
	rx_ids为tr2、tr3、tr5和trx8的集合,
	系统的最大事务ID (low_limit_id)为trx8+1(如果之前没有其他的新增事务),
	活跃的最小事务ID (up_limit_id)为trx2
	

③ 判断规则
#1)当前trx_id值  =  ReadView中creator_trx_id
	访问自己修改的记录【可以】

#2)当前trx_id值 < up_limit_id【最小活跃事务id】
	说明事务都已提交【可以】

#3)当前trx_id值 >= low_limit_id
	所有的trx_ids都未提交提交【不能】

#4)当前trx_id值在up_limit_id和low_limit_id之间
	1、存在:事务在活跃【不能】
	2、不存在:事务已提交【可以】
④ 执行流程
#1)流程
	1、首先获取事务自己的版本号,也就是事务 ID;
	2、生成 ReadView;
	3、查询得到的数据,然后与 ReadView 中的事务版本号进行比较;
	4、如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照;
	5、最后返回符合规则的数据。

#2)说明
	lnnoDB中,MVCC是通过Undo Log + Read View进行数据读取,Undo Log保存了历史快照,而Read View规则帮我们判断当前版本的数据是否可见。

#3)RC和RR
事务【RC】说明
begin;
select * from student where id >2;获取一次Read View
select * from student where id >2;获取一次Read View

4.4 例子
①例一(RC)–不可重复读

#1)事务1
	# Transaction 10
    BEGIN;
    UPDATE student SET name="李四" WHERE id= 1 ;
    UPDATE student SET name="王五" WHERE id= 1 ;
    
#2)事务2
	SELECT * FROM student WHERE id = 1 ; # 得到的列name的值为'张三'

#3)结果
	RC:得到的列name的值为'张三'
	RR:得到的列name的值为'张三'
①例二(RR)—解决不可重复读

#1)事务3
	1、提交例一事务1 commit
	2、# Transaction 20
        BEGIN;

        # 更新了一些别的表的记录
        UPDATE student SET name="钱七" WHERE id= 1 ;
        UPDATE student SET name="宋八" WHERE id= 1 ;

#2)事务2
	SELECT * FROM student WHERE id = 1 ; # 得到的列name的值为'张三'

    SELECT * FROM student WHERE id = 1 ; # 得到的列name的值为'王五'【RC】或'张三'【RR】

#3)结果
	第二个select
		1、RC:得到的列name的值为'王五'
		2、RR:得到的列name的值为'张三'【只生产一次】
①例三(Serializable)–解决幻读

#1)条件
	1、事务A的id=20,事务B的id=30
	2、事务A查数据产生ReadView【trx_ids=[20,30],up_limit_id=20,low_limit_id=31,create_trx_id=20】
	3、事务B插入两条数据

	#结果
		trx_id=30 在trx_ids=[20,30]中,不可见
4.5 总结
#1)RC 和 RR
	READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView
	REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。

#2)解决问题
	1、读写之间阻塞的问题。通过MVCC可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
	2、降低了死锁的概率。这是因为MVCC采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
	3、解决快照读的问题。当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果

10.数据库日志

10.1 分类
#1)类别
	二进制日志、
	错误日志、
	通用查询日志
	慢查询日志
	中继日志【8.0】
	数据定义语句日志【8.0】

#2)弊端
	1、降低MySQL数据库【性能】。
		查询频繁,开启通用日志查询和慢查询日志,会花费很多时间记录日志
	2、占用大量的【空间】。
10.2 慢查询日志(slow query log)
见前面性能分析工具
10.3 通用查询日志(general query log)
#1)定义
	记录用户的所有操作。包含:
		1、启动和关闭MySQL服务、
		2、所有用户的连接开始时间和截止时间、
		3、发给 MySQL 数据库服务器的所有 SQL 指令

#2)查看【默认关闭】
	show variables like '%general_log%'

#3)开启方式
	1、配置文件
		[mysqld]
		general_log=ON
		general_log_file=[path[filename]] #日志文件所在目录路径,filename为日志文件名		2、临时开启
		SET GLOBAL general_log=on;  # 开启通用查询日志
		SET GLOBAL general_log_file='path/filename'; # 设置日志文件保存位置

#4)关闭
	SET GLOBAL general_log=off;  # 关闭通用查询日志

#5)查看日志
	show variables like '%general_log_file%' #查看日志文件路径【打开文件即可】

#6)删除日志
	1、在其路径找到文件,删除
	2、执行mysqladmin -uroot -p flush-logs【重新生成】
10.4 错误日志(error log)
#1)定义【默认开启---不能关闭】
	1、MySQL服务器启动、
	2、停止运行的时间,
	3、以及系统启动、运行和停止过程中的诊断信息,包括错误、警告和提示等。

#2)设置默认错误日志路径
	[mysqld]
	log-error=[path/[filename]] #path为日志文件所在的目录路径,filename为日志文件名

#3)查看
	show variables like '%log_err%'
	
#4)内容【如下图】

#5)操作
	【删除】rm -f /var/lib/mysql/mysqld. log
	【重命名】mv /var/ log/mysqld.log /var/log/mysqld.log.old
	【重键】mysqladmin -uroot -p flush-logs

10.5 二进制日志(bin log)【重】
① 概念
#1)定义【8.0默认开启】
	记录所有数据库【DDL和DML】等数据库【更新】事件的语句
	不包括没有修改数据的语句【select、show等】

#2)应用场景
	#1、数据恢复
		数据库意外停止,查看对那些数据做了修改,恢复数据
	#2、数据复制
		由于日志的延续性和时效性,
		master把它的二进制日志传递给slaves来达到master-slave数据一致的目的

② 查看日志
#1)查看是否开启
	show variables like '%log_bin%';
		1、log_bin_basename 【存放路径】
		2、log_bin_index【索引文件,目录管理】
		3、log_bin_trust_function_creators【需要限制存储函数的创建、修改、调用】
		4、log_bin_use_v1_row_events【此只读系统变量已弃用】
			ON表示使用版本1 二进制日志行,
			OFF表示使用版本2 二进制日志行

#2)参数设置
	[mysqld]
    #启用二进制日志
    log-bin=monkey-bin#打开日志(主机需要打开)
    binlog_expire_logs_seconds= 600#保留的时长单位是秒
    max_binlog_size=100M#控制单个二进制日志大小,当前日志文件大小超过此变量时,执行切换动作

#3)临时开启
	set global sql_log_bin= 0 ;

#4)查看
	SHOW BINARY LOGS;
	mysqlbinlog -v "/var/lib/mysql/binlog/atguigu-bin.000002"	#查看二进制文件
	show binlog events in 'atguigu-bin.000002';	#查看pos位置
	#例子
		#e、指定查询 mysql-bin.880002这个文件,从pos点:391开始查起,偏移2行〈即中间跳过2个)查询5条(即5条语句)。
		show binlog events in 'atguigu-bin.088882' from 391 limit 2,5\G;
③ 恢复数据
#1)sql
	mysqlbinlog [option] filename|mysql –uuser -ppass;

#2)说明
	filename:是日志文件名。
	option:可选项,比较重要的两对option参数是–start-date、–stop-date 和 --start-position、–stop-position。
		--start-date 和--stop-date:可以指定恢复数据库的起始时间点和结束时间点。
		--start-position和--stop-position:可以指定恢复数据的开始位置和结束位置。
	#注意:
		使用mysqlbinlog命令进行恢复操作时,必须是编号小的先恢复,
		例如atguigu-bin.000001必须在atguigu-bin.000002之前恢复
		
#3)例子
	1、show binlog events in 'atguigu-bin.000002';
	2、/usr/bin/mysqlbinlog --start-position=464 --stop-position=1308 --database=atguigu14
/var/lib/mysq1/binlog/atguigu-bin.000005 | /usr/bin/mysql -uroot -pabc123 -v atguigu14
	
#4)删除
	PURGE {MASTER | BINARY} LOGS TO ‘指定日志文件名’#删除之前的
	PURGE {MASTER | BINARY} LOGS BEFORE ‘指定日期’

	RESET MASTER;#删除所有
④ 原理

#1)执行过程
	事务执行过程中,
        先把日志写到binlog cache,
        事务提交的时候,再把binlog cache写到binlog文件中。
        #因为一个事务的binlog不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache。

#2)刷盘策略
	sync_binlog=0【默认】服务器宕机,page cache部分数据会丢失
	sync_binlog=1【数据不会丢失】类似redo log刷盘。事务提交后,一定会刷盘

#3)binlog与redolog对比
	1、redo log 它是【物理日志】,记录内容是“在某个数据页上做了什么修改”,属于InnoDB存储引擎层产生的
	2、binlog是【逻辑日志】,记录内容是语句的原始逻辑,类似于“给ID=2这一行的c字段加1”,属于MySQL Server 层
	3、虽然它们都属于持久化的保证,但是【则重点不同】。
		redo log让InnoDB存储引擎拥有了崩溃【恢复】能力。
		binlog保证了MySQL集群架构的【数据一致性】。

10.6 中继日志(8.0)
#1)定义
	中继日志只在主从服务器架构的【从服务器】上存在 
	
	从服务器为了与主服务器保持一致,要从【主服务器】读取二进制日志的内容,并且把读取到的信息写入本地的日志文件中,这个从服务器本地的日志文件就叫中继日志。
	然后,从服务器读取中继日志,并根据中继日志的内容对【从服务器】的数据进行更新,完成主从服务器的数据同步
	
#2)格式
	文件名的格式是:从服务器名 - relay-bin.序号。
	中继日志还有一个索引文件:从服务器名 - relay-bin.index

11. 主从复制

11.1 作用

#1)读写分离
	主数据库写、从数据库读操作。提高数据库并发处理能力

#2)数据备份
	通过binlog把主数据库上的数据【复制】到从数据库上,是一种热备份机制,不会影响服务运行

#3)具有高可用性
	是一种冗余的机制,通过冗余方式换取数据库的高可用性。当出现【故障或宕机】时,可以【切换】到从服务器,保证服务正常运行。
11.2 原理
①原理刨析

#1)三个线程
	原理时是【基于binlog】进行数据同步的。主从复制过程中,会基于【三个线程】,一个主库、两个从库
	1、二进制转储线程
		当从库连接时,主库将二进制日志【发送】到从库,
		当主库读取事件时,会给【binlog上加锁】,读取完成,释放锁
	2、从库IO线程连接到主库
		当主库发送更新binlog请求时,【从库读取】主库发送的binlog【更新部分】,
		【拷贝】到本地的中继日志(Relay log)
	3、读取中继日志
		执行中继日志的事件,实现主从数据同步
	#注意
		1、主从同步时,要先【检查是否开启】二级制日志
		2、默认主库事件,从库也会执行特定的事件

#2)复制步骤
	1、Master将写操作记录到二进制日志(binlog)
	2、Slave将Master的binary log events拷贝到它的中继日志(relay log)
	3、Slave重做中继日志中的事件,将改变应用到自己的数据库中。
		MySQL复制是异步的且串行化的,而且重启后从接入点开始复制。
② 基本原则
#1)每个Slave只有一个Master

#2)每个Slave只能有一个唯一的服务器ID

#3)每个Master可以有多个Slave
11.3 搭建一主一从架构

①主机配置文件
[mysqld]
#[必须]主服务器唯一ID
server-id= 1

#[必须]启用二进制日志,指名路径。比如:自己本地的路径/log/mysqlbin
log-bin=atguigu-bin

#[可选] 0(默认)表示读写(主机), 1 表示只读(从机)
read-only= 0

#设置日志文件保留的时长,单位是秒
binlog_expire_logs_seconds= 6000

#控制单个二进制日志大小。此参数的最大和默认值是1GB
max_binlog_size=200M

#[可选]设置不要复制的数据库
binlog-ignore-db=test

#[可选]设置需要复制的数据库, 默认全部记录。比如:binlog-do-db=atguigu_master_slave
binlog-do-db=需要复制的主数据库名字

#[可选]设置binlog格式
binlog_format=STATEMENT
②从机配置文件
[mysqld]
#[必须]从服务器唯一ID
server-id= 2

#[可选]启用中继日志
relay-log=mysql-relay
③主机授权
#在主机MySQL里执行授权主从复制的命令
GRANT REPLICATION SLAVE ON *.* TO 'slave1'@'从机器数据库IP' IDENTIFIED BY '从机密码';

#刷新权限
flush privileges;

#查看状态
show master status;
②从机复制主机命令
CHANGE MASTER TO
MASTER_HOST='主机的IP地址',
MASTER_USER='主机用户名',
MASTER_PASSWORD='主机用户名的密码',
MASTER_LOG_FILE='mysql-bin.具体数字',
MASTER_LOG_POS=具体值;


#例子
	CHANGE MASTER TO
	MASTER_HOST='192.168.1.150',
	MASTER_USER='slave1',
	MASTER_PASSWORD='123456',
	MASTER_LOG_FILE='atguigu-bin.000007',
	MASTER_LOG_POS= 154 ;
	
②启动
#启动slave同步
START SLAVE;

mysql> reset slave; #删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件

#停止
stop slave;

12. 数据库备份和恢复
12.1 逻辑备份
① 备份
#1)sql
	mysqldump –u 用户名称 –h 主机名称 –p密码 待备份的数据库名称[tbname, [tbname...]]> 备份文件名称.sql

#2)例子【备份】
	1.备份【一个库】index_practice
	mysqldump -uroot -p index_practice>/var/lib/mysql/datatest.sql
	2.备份【全部库】
	mysqldump -uroot -p --all-databases>/var/lib/mysql/datatest.sql
	mysqldump -uroot -p  -A>/var/lib/mysql/datatest.sql
	4.备份【多个库】
	mysqldump -uroot -p -B atguigu index_practice > /var/lib/mysql/datatest.sql
	5.备份【多个表】
	mysqldump -uroot -p index_practice test teacher>/var/lib/mysql/datatest.sql
	6.备份【一个表】
	mysqldump -uroot -p index_practice test>/var/lib/mysql/datatest.sql
	7.备份【排除某些表】
	mysqldump -uroot -p atguigu --ignore-table=atguigu.student> no_stu_bak.sql
/var/lib/mysql/datatest.sql
	8、只备份【结构】或只备份【数据】
	mysqldump -uroot -p atguigu --no-data > atguigu_no_data_bak.sql
	mysqldump -uroot -p atguigu --no-create-info > atguigu_no_create_info_bak.sql
	9、包含【存储过程、函数、事件】
	mysqldump -uroot -p -R -E --databases atguigu > fun_atguigu_bak.sql
	#-R--->存储过程及函数;  -E--->备份事件
①恢复
#1)sql
	mysql –u root –p [dbname] < backup.sql

#2)例子【恢复】
	1.不带库名【sql包含了创建数据库的语句】
	mysql -uroot -p < atguigu.sql
	2.带库名【不包含创建数据库语句】
	mysql -uroot -p atguigu4< atguigu.sql
	3.全量恢复
	mysql –u root –p < all.sql
12.2 物理备份
#1)前提
	1、备份前,停止服务
	2、备份前,FLUSH TABLES WITH READ LOCK【表级锁】,完成后UNLOCK TABLES【释放锁】

#2)演示流程
	1、删除表数据【/var/lib/mysql】
	2、拷贝数据,重启服务器
	3、chown操作

#3)注意
	1、数据库主版本号必须一致
	2、对MyISAM有效
		对InnnoDB不可用【由于其表空间不能直接复制】
	3、chown操作
		chown -R mysql.mysql /var/lib/mysql/dbname
12.3 表导入、导出
①导出
#1)方式1
	SELECT * FROM account INTO OUTFILE "/var/lib/mysql-files/account.txt";

#2)方式2
	mysqldump -uroot -p -T "/var/lib/mysql-files/" atguigu account

#3)方式3
	mysql -uroot -p --execute="SELECT * FROM account;" atguigu> "/var/lib/mysqlfiles/account.txt"
②导入
#1)方式1
	LOAD DATA INFILE '/var/lib/mysql-files/account_0.txt' INTO TABLE atguigu.account;

#2)方式2
	SELECT * FROM atguigu.account INTO OUTFILE '/var/lib/mysql-files/account.txt' 		FIELDS
	TERMINATED BY ',' ENCLOSED BY '\"';
12.4 数据库迁移
# host1的机器中备份所有数据库,并将数据库迁移到名为host2的机器上
    mysqldump –h host1 –uroot –p –-all-databases|mysql –h host2 –uroot –p

#1)

#2)

#3)

#4)

10.设计模式

10.1 动态代理

1)jdk动态代理

class Test {
    public static void main(String[] args) {
        //1.动态获取被代理类接口
        Works works = (Works)Proxy.newProxyInstance(
            	Test.class.getClassLoader(), 
            	new Class[]{Works.class}, 
            	new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) {
                System.out.println("before......");
                Object invoke = method.invoke(new Server(), args);
                System.out.println("after......");
                return invoke;
            }
        });
        //2.直接接口方法,找子类实现
        works.show();
    }
}
interface Works{
    void show();
}
class Server implements Works{
    @Override
    public void show() {
        System.out.println("真实服务器执行。。。");
    }
}
结果:
	before......
    真实服务器执行。。。
    after......

2)cglib代理

class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //设置父类字节码文件
        enhancer.setSuperclass(Server.class);
        //设置拦截回调的函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(
                    Object o, 
                    Method method, 
                    Object[] objects, 
                    MethodProxy methodProxy) throws Throwable {
                System.out.println("before......");
                Object obj = methodProxy.invokeSuper(o, args);
                System.out.println("after......");
                return obj;
            }
        });
        //创建代理类
        Server server = (Server) enhancer.create();
        //执行方法
        server.show();
    }
}
class Server{
    public void show() {
        System.out.println("真实服务器执行。。。");
    }
}
结果:
	before......
    真实服务器执行。。。
    after......

10.2 单例模式

1)饿汉式

class Singleton{
    private static final Singleton singleton = new Singleton();
    private Singleton(){}
    
    public static Singleton getSingleton(){
        return singleton;
    }
}

2)懒汉式(线程不安全)

class Singleton{
    private static Singleton singleton = null;
    private Singleton(){}

    public static Singleton getSingleton(){
        if (singleton != null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

3)懒汉式(线程安全)

class Singleton{
    private volatile static Singleton singleton = null;
    private Singleton(){}
    //双重检查锁【减少同步锁等待时间】
    public static Singleton getSingleton(){
        if (singleton != null){
            synchronized (Singleton.class){
                if (singleton != null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
1.为什么使用volatile?【防止指令重排】
    对象创建步骤:
    	①分配内存空间
    	②构造器实例化
    	③返回内存地址的引用
    CPU进行指令重排
   		①分配内存空间
    	②返回内存地址的引用【当执行到这一步的时候有可能其他线程拿到了引用地址并进行使用,
    						此时对象还未初始化,因此会出现问题】
    	③构造器实例化

4)静态内部类

class Singleton{
    private static class LazySingle{
        private static final Singleton singleton = new Singleton();
    }
    private Singleton(){}

    public static Singleton getSingleton(){
        return LazySingle.singleton;
    }
}

10.3 工厂模式

1)简单工厂

优点:
	根据传参创建对象,屏蔽创建细节
	提高了系统的灵活性
缺点:
	不符合“开闭原则”,每次添加新产品就需要修改工厂类
	产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护
  • 产品类
interface Pay{
    void pay();
}
class AliPay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
class WxPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
  • 工厂类
class PayFactory{
    public Pay createPay(Pay pay){
        return pay;
    }

    public Pay createPay(Class<AliPay> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 用户类
class Test{
    public static void main(String[] args) {
        PayFactory payFactory = new PayFactory();
        Pay pay = payFactory.createPay(AliPay.class);
        pay.pay();  //支付宝支付
    }
}

2)工厂方法

优点:
	用户只关心产品, 符合开闭原则,提高系统扩展性
缺点:
	类的个数过多,增加了代码结构的复杂度,增加了系统的抽象性、复杂性
  • 产品类
interface Pay{
    void pay();
}
class AliPay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
class WxPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
  • 工厂方法
interface PayFactory{
    Pay create();
}
  • 具体工厂类
class AliPayFactory implements PayFactory{
    @Override
    public Pay create() {
        return new AliPay();
    }
}
class WxPayFactory implements PayFactory{
    @Override
    public Pay create() {
        return new WxPay();
    }
}
  • 用户类
class Test{
    public static void main(String[] args) {
        AliPayFactory aliPayFactory = new AliPayFactory();
        aliPayFactory.create().pay();  //支付宝支付
    }
}

10.4 抽象工厂模式

优点:
	抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象

缺点:
	添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。
  • 产品类
interface Pay{
    void pay();
}
interface Finish{
    void finish();
}
class AliPay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
class WxPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
class AliFinish implements Finish{
    @Override
    public void finish() {
        System.out.println("支付宝到账");
    }
}
class WxFinish implements Finish{
    @Override
    public void finish() {
        System.out.println("微信到账");
    }
}
  • 抽象工厂类
abstract class PayFactory{
    public void init(){
        System.out.println("开始支付。。。。。。");
    }
    abstract Pay startPay();
    abstract Finish finishPay();
}
  • 具体工厂类
class AliPayFactory extends PayFactory{
    @Override
    Pay startPay() {
        return new AliPay();
    }
    @Override
    Finish finishPay() {
        return new AliFinish();
    }
}
class WxPayFactory extends PayFactory{
    @Override
    Pay startPay() {
        return new WxPay();
    }
    @Override
    Finish finishPay() {
        return new WxFinish();
    }
}
  • 用户类
class Test{
    public static void main(String[] args) {
        PayFactory payFactory = new AliPayFactory();
        payFactory.init();  //开始支付。。。。。。
        payFactory.startPay().pay();    //支付宝支付
        payFactory.finishPay().finish();    //支付宝到账
    }
}

1、数据库主版本号必须一致
2、对MyISAM有效
对InnnoDB不可用【由于其表空间不能直接复制】
3、chown操作
chown -R mysql.mysql /var/lib/mysql/dbname


##### 12.3 表导入、导出

###### ①导出

```mysql
#1)方式1
	SELECT * FROM account INTO OUTFILE "/var/lib/mysql-files/account.txt";

#2)方式2
	mysqldump -uroot -p -T "/var/lib/mysql-files/" atguigu account

#3)方式3
	mysql -uroot -p --execute="SELECT * FROM account;" atguigu> "/var/lib/mysqlfiles/account.txt"
②导入
#1)方式1
	LOAD DATA INFILE '/var/lib/mysql-files/account_0.txt' INTO TABLE atguigu.account;

#2)方式2
	SELECT * FROM atguigu.account INTO OUTFILE '/var/lib/mysql-files/account.txt' 		FIELDS
	TERMINATED BY ',' ENCLOSED BY '\"';
12.4 数据库迁移
# host1的机器中备份所有数据库,并将数据库迁移到名为host2的机器上
    mysqldump –h host1 –uroot –p –-all-databases|mysql –h host2 –uroot –p

#1)

#2)

#3)

#4)

10.设计模式

10.1 动态代理

1)jdk动态代理

class Test {
    public static void main(String[] args) {
        //1.动态获取被代理类接口
        Works works = (Works)Proxy.newProxyInstance(
            	Test.class.getClassLoader(), 
            	new Class[]{Works.class}, 
            	new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) {
                System.out.println("before......");
                Object invoke = method.invoke(new Server(), args);
                System.out.println("after......");
                return invoke;
            }
        });
        //2.直接接口方法,找子类实现
        works.show();
    }
}
interface Works{
    void show();
}
class Server implements Works{
    @Override
    public void show() {
        System.out.println("真实服务器执行。。。");
    }
}
结果:
	before......
    真实服务器执行。。。
    after......

2)cglib代理

class Test {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //设置父类字节码文件
        enhancer.setSuperclass(Server.class);
        //设置拦截回调的函数
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(
                    Object o, 
                    Method method, 
                    Object[] objects, 
                    MethodProxy methodProxy) throws Throwable {
                System.out.println("before......");
                Object obj = methodProxy.invokeSuper(o, args);
                System.out.println("after......");
                return obj;
            }
        });
        //创建代理类
        Server server = (Server) enhancer.create();
        //执行方法
        server.show();
    }
}
class Server{
    public void show() {
        System.out.println("真实服务器执行。。。");
    }
}
结果:
	before......
    真实服务器执行。。。
    after......

10.2 单例模式

1)饿汉式

class Singleton{
    private static final Singleton singleton = new Singleton();
    private Singleton(){}
    
    public static Singleton getSingleton(){
        return singleton;
    }
}

2)懒汉式(线程不安全)

class Singleton{
    private static Singleton singleton = null;
    private Singleton(){}

    public static Singleton getSingleton(){
        if (singleton != null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

3)懒汉式(线程安全)

class Singleton{
    private volatile static Singleton singleton = null;
    private Singleton(){}
    //双重检查锁【减少同步锁等待时间】
    public static Singleton getSingleton(){
        if (singleton != null){
            synchronized (Singleton.class){
                if (singleton != null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
1.为什么使用volatile?【防止指令重排】
    对象创建步骤:
    	①分配内存空间
    	②构造器实例化
    	③返回内存地址的引用
    CPU进行指令重排
   		①分配内存空间
    	②返回内存地址的引用【当执行到这一步的时候有可能其他线程拿到了引用地址并进行使用,
    						此时对象还未初始化,因此会出现问题】
    	③构造器实例化

4)静态内部类

class Singleton{
    private static class LazySingle{
        private static final Singleton singleton = new Singleton();
    }
    private Singleton(){}

    public static Singleton getSingleton(){
        return LazySingle.singleton;
    }
}

10.3 工厂模式

1)简单工厂

优点:
	根据传参创建对象,屏蔽创建细节
	提高了系统的灵活性
缺点:
	不符合“开闭原则”,每次添加新产品就需要修改工厂类
	产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护
  • 产品类
interface Pay{
    void pay();
}
class AliPay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
class WxPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
  • 工厂类
class PayFactory{
    public Pay createPay(Pay pay){
        return pay;
    }

    public Pay createPay(Class<AliPay> clazz) {
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
  • 用户类
class Test{
    public static void main(String[] args) {
        PayFactory payFactory = new PayFactory();
        Pay pay = payFactory.createPay(AliPay.class);
        pay.pay();  //支付宝支付
    }
}

2)工厂方法

优点:
	用户只关心产品, 符合开闭原则,提高系统扩展性
缺点:
	类的个数过多,增加了代码结构的复杂度,增加了系统的抽象性、复杂性
  • 产品类
interface Pay{
    void pay();
}
class AliPay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
class WxPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
  • 工厂方法
interface PayFactory{
    Pay create();
}
  • 具体工厂类
class AliPayFactory implements PayFactory{
    @Override
    public Pay create() {
        return new AliPay();
    }
}
class WxPayFactory implements PayFactory{
    @Override
    public Pay create() {
        return new WxPay();
    }
}
  • 用户类
class Test{
    public static void main(String[] args) {
        AliPayFactory aliPayFactory = new AliPayFactory();
        aliPayFactory.create().pay();  //支付宝支付
    }
}

10.4 抽象工厂模式

优点:
	抽象工厂模式主要用于创建相关对象的家族。当一个产品族中需要被设计在一起工作时,通过抽象工厂模式,能够保证客户端始终只使用同一个产品族中的对象

缺点:
	添加新的行为时比较麻烦,如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。
  • 产品类
interface Pay{
    void pay();
}
interface Finish{
    void finish();
}
class AliPay implements Pay{
    @Override
    public void pay() {
        System.out.println("支付宝支付");
    }
}
class WxPay implements Pay{
    @Override
    public void pay() {
        System.out.println("微信支付");
    }
}
class AliFinish implements Finish{
    @Override
    public void finish() {
        System.out.println("支付宝到账");
    }
}
class WxFinish implements Finish{
    @Override
    public void finish() {
        System.out.println("微信到账");
    }
}
  • 抽象工厂类
abstract class PayFactory{
    public void init(){
        System.out.println("开始支付。。。。。。");
    }
    abstract Pay startPay();
    abstract Finish finishPay();
}
  • 具体工厂类
class AliPayFactory extends PayFactory{
    @Override
    Pay startPay() {
        return new AliPay();
    }
    @Override
    Finish finishPay() {
        return new AliFinish();
    }
}
class WxPayFactory extends PayFactory{
    @Override
    Pay startPay() {
        return new WxPay();
    }
    @Override
    Finish finishPay() {
        return new WxFinish();
    }
}
  • 用户类
class Test{
    public static void main(String[] args) {
        PayFactory payFactory = new AliPayFactory();
        payFactory.init();  //开始支付。。。。。。
        payFactory.startPay().pay();    //支付宝支付
        payFactory.finishPay().finish();    //支付宝到账
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值