JavaWeb学习笔记


JavaWeb

本文档学习视频:

黑马程序员JavaWeb全套基础教程 (IDEA版javaweb) 哔哩哔哩bilibili

1、JavaEE回顾

1.1、Junit单元测试(白盒测试)

  • 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望值
  • 白盒测试:需要写代码。关注程序具体的执行流程。

步骤

  1. 定义一个测试类(测试用例)
    • 建议:
      • 测试类名:被测试的类名Test 例如CalculatorTest
      • 包名:xxx.xxx.xx.test
  2. 定义测试方法:可以独立运行
    • 建议:
      • 方法名:test测试的方法名 testAdd()
      • 返回值:void
      • 参数列表:空参
  3. 给方法加@Test 注意是大写开头
  4. 导入junit依赖环境

判定结果:

  • 红色:失败
  • 绿色:成功
  • 一般会使用断言操作 Assert.assertEquals()来处理结果

补充:

  • @Before: 修饰的方法会在测试方法执行之前被自动执行
  • @After: 修饰的方法会在测试方法执行之后自动被执行
public class Calculator {
    /**
     * 加法
     * @param a
     * @param b
     * @return
     */
    public int add (int a , int b){
        return a + b;
    }

    /**
     * 减法
     * @param a
     * @param b
     * @return
     */
    public int sub (int a , int b){
        return a - b;
    }
}
public class CalculatorTest {
    /**
     * 初始化方法
     * 用于资源申请,所有测试方法Test在执行之前都会先执行该方法
     */
    @Before
    public void init(){
        //写入资源申请代码
        System.out.println("init...");
    }

    /**
     * 释放资源的方法
     * 在所有测试方法Test执行完后,都会自动执行该方法
     */
    @After
    public void close(){
        //写入资源释放代码
        System.out.println("close...");
    }
    /**
     * 测试add方法
     */
    @Test
    public void testAdd(){
        Calculator calculator = new Calculator();
        int result = calculator.add(2, 3);
        //断言
        Assert.assertEquals(5,result);
    }
}
控制台输出:
init...
TestAdd...
close...

1.2、反射:框架设计的灵魂

反射 - 廖雪峰的官方网站 (liaoxuefeng.com)

①简介

1、框架

半成品软件。可以在框架的基础上进行软件开发,简化代码

2、反射

将类的各个组成部分封装为其他对象,这就是反射机制。反射机制是构建框架技术的基础所在。

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过IDEA和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

3、Class类的好处
  1. 可以在程序的运行过程中,操作这些对象
  2. 可以解耦,来提高程序的可扩展性。

②获取Class对象的三种方式

1、Class.forName("全类名")

(全类名:包名.类名) 将字节码文件加载进内存,返回Class对象,对应Source 源代码阶段

多用于配置文件,将类名定义在配置文件中。读取文件,加载类

2、类名.class

通过类名的属性class获取 对应Class 类对象阶段

多用于参数的传递

3、对象.getClass()

getClass()方法在Object类中定义着,所有对象都能用。 对应Runtime 运行时阶段

多用于对象的获取字节码的方式

4、结论

同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个

public class ReflectDemo1 {
    /**
     *  获取Class对象的方式:
     *   1. Class.forName("全类名")     (全类名:包名.类名)    将字节码文件加载进内存,返回Class对象,**对应Source 源代码阶段**
     *   2. 类名.class: 通过类名的属性class获取   **对应Class 类对象阶段**
     *   3. 对象.getClass()  : getClass()方法在Object类中定义着,所有对象都能用。   **对应Runtime 运行时阶段**
     */
    public static void main(String[] args) throws Exception {
        //1. Class.forName("全类名")
        Class cls1 = Class.forName("com.domain.Person");
        System.out.println(cls1);//class com.domain.Person
        //2. 类名.class
        Class cls2 = Person.class;
        System.out.println(cls2);//class com.domain.Person
        //3. 对象.getClass()
        Person person = new Person();
        Class cls3 = person.getClass();
        System.out.println(cls3);//class com.domain.Person

        //用==去比较三个对象,结论:同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
        System.out.println(cls1 == cls2); //true
        System.out.println(cls1 == cls3); //true
    }
}

③Class对象功能

Class - Java 11中文版 - API参考文档 (apiref.com)

public class Person {
    private String name = "Smith";
    public int age = 17;

    public String public_A;
    protected String protected_B;
    String default_C;
    private String private_D;
}
1、获取成员变量们Field
Field 	getField(name):根据字段名获取某个public的field(包括父类和继承的接口)
Field 	getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类和继承的接口)
Field[] getFields():获取所有public的field(包括父类和继承的接口)
Field[] getDeclaredFields():获取当前类的所有field(不包括父类和继承的接口)
public static void main(String[] args) throws Exception {
    
    //Field[] getFields()
    Field[] fields = personClass.getFields();
    for (Field field : fields) {
        System.out.println(field);  //public int com.domain.Person.age
                                    //public java.lang.String com.domain.Person.public_A
    }
    System.out.println("------------------------------------------");
    
    //Field  getField(String name)
    Field public_A = personClass.getField("public_A");
    System.out.println(public_A);//public java.lang.String com.domain.Person.public_A
    
    //获取成员变量public_A的值
    Person person = new Person();
    Object value_A = public_A.get(person);
    System.out.println(value_A);//null   public_A的默认值就是null
    
    //设置成员变量public_A的值
    public_A.set(person,"this is public_A");
    System.out.println(person.public_A);//this is public_A
    System.out.println("--------------------------------------------");

    //Field[] getDeclaredFields()  所有字段,不管什么修饰符
    Field[] declaredFields = personClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);   //private java.lang.String com.domain.Person.name
                                             //public int com.domain.Person.age
                                          //public java.lang.String com.domain.Person.public_A
                                    //protected java.lang.String com.domain.Person.protected_B
                                                //java.lang.String com.domain.Person.default_C
                                	    //private java.lang.String com.domain.Person.private_D
        }

    //Field  getDeclaredField(String name)
    Field name = personClass.getDeclaredField("name");
    //先要忽略访问修饰符的安全检查,
    //不忽略会报非法访问异常Exception in thread "main" java.lang.IllegalAccessException
    name.setAccessible(true);//暴力反射
    Object valueName = name.get(person);
    System.out.println(valueName);//Smith
}

操作Field(反射拿到的成员变量)

Field - Java 11中文版 - API参考文档 (apiref.com)

1、设置值

void	Field对象.set(Object obj, Object value)//将指定对象参数上此 字段对象表示的字段设置为指定的新值。

2、获取值

Object	Field对象.get(Object obj)//返回指定对象上此 字段表示的字段的值。

3、忽略访问权限修饰符的安全检查(暴力反射)

Field对象.setAccessible(true);

2、获取构造方法们
Constructor		getConstructor(Class...):获取某个public的Constructor;
Constructor		getDeclaredConstructor(Class...):获取某个Constructor;
Constructor<?>[]	getConstructors():获取所有public的Constructor;
Constructor<?>[]	getDeclaredConstructors():获取所有Constructor。
public static void main(String[] args) throws Exception {
    //0.获取Person的Class对象
    Class personClass = Person.class;
    
    //Constructor		getConstructor(类<?>... parameterTypes)
    //有参构造函数
    Constructor constructor1 = personClass.getConstructor(String.class, int.class);
    System.out.println(constructor1);//public com.domain.Person(java.lang.String,int)
    	
    //创建对象
    Object person1 = constructor1.newInstance("张三", 31);
    System.out.println(person1);//Person{name='张三', age=31, public_A='null', protected_B='null', default_C='null', private_D='null'}
	
    //无参构造函数
    Constructor constructor2 = personClass.getConstructor();	
    Object person2 = constructor2.newInstance();
    System.out.println(person2);//Person{name='Smith', age=17, public_A='null', protected_B='null', default_C='null', private_D='null'}
    
    //无参构造函数的简化  clazz.newInstance()已弃用
    Object person3 = personClass.getDeclaredConstructor().newInstance();
    System.out.println(person3);//Person{name='Smith', age=17, public_A='null', protected_B='null', default_C='null', private_D='null'}
}
操作Constructor

1、创建对象

Object person1 = constructor1.newInstance("张三", 31);

2、如果使用空参数构造方法创建对象,操作可以简化

Object person3 = personClass.getDeclaredConstructor().newInstance();

3、constructor也有暴力反射方式

constructor2.setAccessible(true);

3、获取成员方法们
Method		getMethod(name, Class...):获取某个public的Method(包括父类和继承的接口)
Method		getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类和继承的接口)
Method[]	getMethods():获取所有public的Method(包括父类和继承的接口)
Method[]	getDeclaredMethods():获取当前类的所有Method(不包括父类和继承的接口)
public static void main(String[] args) throws Exception {
    //0.获取Person的Class对象
    Class personClass = Person.class;
    
    //Method	getMethod(String name, 类<?>... parameterTypes)
    Method method = personClass.getMethod("setAge", int.class);
    
    //执行方法
    Person person = new Person();
    method.invoke(person,52);
    System.out.println(person);//Person{name='Smith', age=52, public_A='null', protected_B='null', default_C='null', private_D='null'}
    
    //获取方法名称
    String name = method.getName();
    System.out.println(name);//setAge
    
    }

注意:

  • getMethods() :返回的Method[] 包括从父类和接口继承而来的public方法
  • getDeclaredMethods() :返回的Method[] 是自身所有访问类型的方法,但不包括继承而来的方法,
操作Method

Method - Java 11中文版 - API参考文档 (apiref.com)

1、执行方法

Object	invoke(Object obj, Object... args)	//在具有指定参数的指定对象上调用此 方法对象表示的基础方法。

2、支持暴力反射

method.setAccessible(true);

3、获取方法名称

String	getName()//返回此 方法对象表示的方法的名称,如 String 。

4、获取类名
String	getName()//返回此 类对象表示的实体名称(类,接口,数组类,基本类型或void),作为 String 。全类名
//获取类名
String className = personClass.getName();
System.out.println(className);//com.domain.Person   全类名

④反射案例

需求

写一个“框架”,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法

实现
  1. 配置文件
  2. 反射
步骤
  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载类文件进内存
  4. 创建对象
  5. 执行方法

具体代码

用例类 Person\Student

package com.domain;

private class Person {
    public void eat(){
        System.out.println("eating...");
    }
}

private class Student {
    public void sleep(){
        System.out.println("sleeping...");
    }
}

配置文件pro.properties

className=com.domain.Person
methodName=eat

自定义框架RefFrame

public class RefFrame {
    public static void main(String[] args) throws Exception {

        //1、加载配置文件
            //创建Properties对象
        Properties pro = new Properties();
            //加载配置文件,转换为一个集合,需要获取class目录下的配置文件
        ClassLoader classLoader = RefFrame.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);
        //2、获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        //3、加载该类进内存
        Class cls = Class.forName(className);
        //4、创建对象
        Object obj = cls.getDeclaredConstructor().newInstance();
        //5、获取方法对象
        Method method = cls.getDeclaredMethod(methodName);
        //6、执行方法(如果不是public需要暴力反射)
        method.setAccessible(true);
        method.invoke(obj);//eating...
    }
}

要执行另外一个类的方法只需改配置文件

className=com.domain.Student
methodName=sleep
public class RefFrame {
	...
    method.invoke(obj);//sleeping...
}

好处:当项目过大时,更改配置文件远比更改源代码简单,不需要重新上线测试等操作。


1.3、注解 Annotation

从JDK1.5开始, Java增加对元数据的支持,也就是注解。

①注解和注释的区别

  • 注释:用文字描述程序,是给程序员看的。注释会被编译器直接忽略
  • 注解: 注解是放在Java源码的类、方法、字段、参数前的一种特殊“注释”。注解则可以被编译器打包进入class文件,因此,注解是一种用作标注的“元数据”。

②注解的作用

从JVM的角度看,注解本身对代码逻辑没有任何影响,如何使用注解完全由工具决定。

Java的注解可以分为三类:

第一类:编译检查

通过代码里标识的元数据(注解)让编译器能够实现基本的编译检查

是由编译器使用的注解,例如:

  • @Override:让编译器检查该方法是否正确地实现了覆写;
  • @SuppressWarnings:告诉编译器忽略此处代码产生的警告。

这类注解不会被编译进入.class文件,它们在编译后就被编译器扔掉了。

第二类:编写文档

通过代码里标识的注解生成doc文档

方法:在xxx.java文件目录下,打开命令行窗口cmd,输入javadoc xxx.java 注意cmd窗口和java文件的编码要一致

第三类:代码分析(主要)

通过代码里标识的注解对代码进行分析【使用反射


③JDK中预定义的一些注解(非重点)

  • @Override:用在方法上,表示这个方法重写了父类的方法,如toString()。如果父类没有这个方法,那么就无法编译通过。

  • @Deprecated:表示这个方法已经过期,不建议开发者使用,但是仍然可以使用。(暗示在将来某个不确定的版本,有可能会取消掉)

  • @SuppressWarnings:Suppress英文的意思是抑制的意思,这个注解的用处是忽略警告信息。
    比如大家使用集合的时候,有时候为了偷懒,会不写泛型,像这样:

    List heros = new ArrayList();
    

    那么就会导致编译器出现警告,而加上

    @SuppressWarnings({ "rawtypes", "unused" })
    

    就对这些警告进行了抑制,即忽略掉这些警告信息。
    @SuppressWarnings 有常见的值,分别对应如下意思

    • deprecation:使用了不赞成使用的类或方法时的警告(使用@Deprecated使得编译器产生的警告);
    • unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型; 关闭编译器警告
    • fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
    • path:在类路径、源文件路径等中有不存在的路径时的警告;
    • serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
    • finally:任何 finally 子句不能正常完成时的警告;
    • rawtypes 泛型类型未指明
    • unused 引用定义了,但是没有被使用
    • all:关于以上所有情况的警告。
  • @FunctionalInterface这是 Java1.8 新增 的注解,用于约定函数式接口
    函数式接口概念: 如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。函数式接口其存在的意义,主要是配合Lambda 表达式 来使用。


④自定义注解

  1. 创建注解类型的时候即不使用class也不使用interface, 而是使用@interface

    public @interface MyAnnotation {
        //属性    但属性定义成 方法 的样子
    } 
    

    通过编译javac MyAnnotation.java和反编译javap MyAnnotation.class,可以得到一个MyAnnotation.java文件

    本质:注解本质上就是一个接口,该接口默认继承Annotation接口

    public interface MyAnnotation extends java.lang.annotation.Annotation {
    }
    
  2. 元注解
    @Target({METHOD,TYPE}) 表示这个注解可以用用在类/接口上,还可以用在方法上
    @Retention(RetentionPolicy.RUNTIME) 表示这是一个运行时注解,即运行起来之后,才获取注解中的相关信息,而不像基本注解如@Override 那种不用运行,在编译时IDEA就可以进行相关工作的编译时注解。
    @Inherited 表示这个注解可以被子类继承
    @Documented 表示当执行javadoc的时候,本注解会生成相关文档

  3. 注解元素,这些注解元素就用于存放注解信息,在解析的时候获取出来。

    接口的方法有默认修饰符public abstract

    public @interface MyAnnotation { 
       //默认为 public abstract
       String ip();
       int port() default 3306;
       String database();
       String encoding();
       String loginName();
       String password();
    }
    
  4. 注解元素的返回值类型有下列取值,例如:void 不行

    • 基本数据类型
    • String
    • 枚举Enum
    • 注解Annotation
    • 以上类型的数组 []
  5. 注解参数配置

    • 因为配置参数必须是常量,所以,上述限制保证了注解在定义时就已经确定了每个参数的值。
    • 注解的配置参数可以有默认值default,缺少某个配置参数时将使用默认值。
    • 此外,大部分注解会有一个名为value的配置参数,如果只value赋值,可以只写常量,例如@MyAnnotation(123)相当于省略了value参数。如果只写注解,相当于全部使用默认值。
    • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可省略。@MyAnnotation(strs={"abc","qwerty"})

⑤元注解

元注解 meta annotation:用于注解 自定义注解 的注解。

元注解有这么几种:

@Target

表示这个注解能作用的位置

@Target({ElementType.METHOD,ElementType.TYPE})// 省略了 value= 
//表示他可以用在方法和类型上(类和接口或枚举类型),但是不能放在属性等其他位置。

可以选择的位置列表如下:

  • ElementType.TYPE:能修饰类、接口或枚举类型
  • ElementType.FIELD:能修饰成员变量
  • ElementType.METHOD:能修饰方法
  • ElementType.PARAMETER:能修饰参数
  • ElementType.CONSTRUCTOR:能修饰构造器
  • ElementType.LOCAL_VARIABLE:能修饰局部变量
  • ElementType.ANNOTATION_TYPE:能修饰注解
  • ElementType.PACKAGE:能修饰包

@Retention

表示生命周期

Java的注解本身对代码逻辑没有任何影响。根据@Retention的配置:

  • RetentionPolicy.SOURCE类型的注解在编译期就被丢掉了;
  • RetentionPolicy.CLASS类型的注解仅保存在class文件中,它们不会被加载进JVM;
  • RetentionPolicy.RUNTIME类型的注解会被加载进JVM,并且在运行期可以被程序读取。

如何使用注解完全由工具决定。

  • SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。
  • CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。
  • 只有RUNTIME类型的注解不但要使用,还经常需要编写。

@Inherited

表示该注解具有继承性。


@Documented

描述在用javadoc命令生成API文档后,注解是否被抽取到API文档中


@Repeatable (java1.8 新增)

当没有@Repeatable修饰的时候,注解在同一个位置,只能出现一次

如例所示:
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
@JDBCConfig(ip = "127.0.0.1", database = "test", encoding = "UTF-8", loginName = "root", password = "admin")
重复做两次就会报错了。
使用@Repeatable之后,再配合一些其他动作,就可以在同一个地方使用多次了。


⑥注解的使用

如何使用注解完全由工具决定。

  • SOURCE类型的注解主要由编译器使用,因此我们一般只使用,不编写。
  • CLASS类型的注解主要由底层工具库使用,涉及到class的加载,一般我们很少用到。
  • 只有RUNTIME类型的注解不但要使用,还经常需要编写。

因此,我们只讨论如何读取RUNTIME类型的注解。

因为注解定义后也是一种class,所有的注解都继承自java.lang.annotation.Annotation,因此,读取注解,需要使用反射API。

Java提供的使用反射API读取Annotation的方法包括:

1、判断某个注解是否存在于ClassFieldMethodConstructor
  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)
// 判断@Report是否存在于Person类:
Person.class.isAnnotationPresent(Report.class);
2、使用反射API读取Annotation:
  • Class.getAnnotation(Class)
  • Field.getAnnotation(Class)
  • Method.getAnnotation(Class)
  • Constructor.getAnnotation(Class)
// 获取Person定义的@Report注解:
Report report = Person.class.getAnnotation(Report.class);
int type = report.type();
String level = report.level();

使用反射API读取Annotation有两种方法。

方法一

先判断Annotation是否存在,如果存在,就直接读取:

Class cls = Person.class;
if (cls.isAnnotationPresent(Report.class)) {
    Report report = cls.getAnnotation(Report.class);
    ...
}
方法二

直接读取Annotation,如果Annotation不存在,将返回null

Class cls = Person.class;
Report report = cls.getAnnotation(Report.class);
if (report != null) {
   ...
}

读取方法、字段和构造方法的Annotation和Class类似。

但要读取方法参数的Annotation就比较麻烦一点,因为方法参数本身可以看成一个数组,而每个参数又可以有多个注解,所以,一次获取方法参数的所有注解就必须用一个二维数组来表示

例如,对于以下方法定义的注解:

public void hello(@NotNull @Range(max=5) String name, @NotNull String prefix) {
}

要读取方法参数的注解,我们先用反射获取Method实例,然后读取方法参数的所有注解:

// 获取Method实例:
Method m = ...
// 获取所有参数的Annotation:
Annotation[][] annos = m.getParameterAnnotations();
// 第一个参数(索引为0)的所有Annotation:
Annotation[] annosOfName = annos[0];
for (Annotation anno : annosOfName) {
    if (anno instanceof Range) { // @Range注解
        Range r = (Range) anno;
    }
    if (anno instanceof NotNull) { // @NotNull注解
        NotNull n = (NotNull) anno;
    }
}
3、解析注解(重要)

获取注解中定义的属性值,代替配置文件xxx.properties

步骤:

  1. 获取注解定义位置的对象 (Class、Method、Field)

    • Class ---> 类名.class
    • Method ---> 拿到Class对象后可以调用方法 cls.getDeclaredMethod(methodName)
    • Field ---> 同上
  2. 获取指定的注解定义位置对象 obj.getAnnotation(注解名.class);

    //其实就是在内存中生成了一个该注解接口的子类实现对象
    public class ProImpl implements Pro{
        public String className(){
            return "com.domain.Student";
        }
        public String methodName(){
            return "sleep";
        }
    }
    
  3. 调用注解对象对象中定义的抽象方法来获取返回值 注解对象.方法名();

  4. 使用反射来执行方法

    • 加载该类进内存

      Class<?> aClass = Class.forName(className);
      
    • 创建对象

      Object obj = aClass.getDeclaredConstructor().newInstance();
      
    • 获取方法对象

      Method method = aClass.getDeclaredMethod(methodName);
      
    • 调用该方法 , 如果方法不是public需使用暴力反射

      method.invoke(obj, param);//如无参数则可不填
      

案例:

//需要执行类中的方法
public class Student {
    private void sleep(){
        System.out.println("sleep...");
    }
}
//自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    String className();
    String methodName();
}
@Pro(className = "com.domain.Student",methodName = "sleep")
public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        /*
         * 可以创建任意类的对象,可以执行任意方法,包括私有
         * 前提:不能改变该类的任何代码
         * 用注解的方式
         */

    //   解析注解
    //1 获取该类的字节码文件对象
        //这里必须使用泛型,不然下一步需要强制向下转型Annotation->Pro, 
        //因为.getAnnotation(Pro.class)返回的是Annotation类型
        Class annotationTestClass = AnnotationTest.class;
    //2 获取该类上的注解对象
        Pro pro = annotationTestClass.getAnnotation(Pro.class);
        //其实就是在内存中生成了一个该注解接口的子类实现对象
        /*
        public class ProImpl implements Pro{
            public String className(){
                return "com.domain.Student";
            }
            public String methodName(){
                return "sleep";
            }
        }
        */
    //3 调用注解对象对象中定义的抽象方法来获取返回值
        String className = pro.className();
        String methodName = pro.methodName();
    //4 利用反射来执行方法
        Class<?> aClass = Class.forName(className);//加载该类进内存
        Object obj = aClass.getDeclaredConstructor().newInstance();//创建对象
        Method method = aClass.getDeclaredMethod(methodName);//获取方法对象
        method.setAccessible(true);//暴力反射,因为该方法是private
        method.invoke(obj);//sleeping...
    }
}

⑦测试框架用例

1、待测试方法,在类里面
public class Calculator {

    //加法
    @Check
    public void add(){
        System.out.println("1 + 0 = " + (1 + 0));
    }

    //减法
    @Check
    public void sub(){
        System.out.println("1 - 0 = " + (1 - 0));
    }

    //乘法
    @Check
    public void mul(){
        System.out.println("1 * 0 = " + (1 * 0));
    }

    //除法
    @Check
    public void div(){
        System.out.println("1 / 0 = " + (1 / 0));   //这里会有除零异常
    }
    
    @Check
    public void toStr(){
        String str = null;
        String s = str.toString();   //空指针异常
    }

    public void show(){
        System.out.println("这个方法未被测试");
    }
}
2、自定义注解(别忘了写元注解)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}
3、写测试框架
/**
 * 简单的测试框架
 * 当main方法执行后,会自动执行被@Check注解的方法
 * 判断方法是否有异常,记录到文件中
 * @author 肖南海
 * @version 1.0
 */
public class TestCheck {
    public static void main(String[] args) throws IOException {
        //1、创建计算器对象
        Calculator calculator = new Calculator();
        //2、获取字节码文件对象
        Class cls = calculator.getClass();
        //3、获取所有的方法
        Method[] methods = cls.getMethods();

        int cnt = 0;//出现异常的次数
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));

        for (Method method : methods) {
            //4、判断方法上是否有Check注解
            if (method.isAnnotationPresent(Check.class)){
                //5、如有,则执行方法
                try {
                    method.invoke(calculator);
                    //6、捕获异常
                } catch (Exception e) {
                    //7、记录文件信息到文件中
                    cnt++;

                    bw.write(method.getName() + " 方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因:" + e.getCause().getMessage());
                    bw.newLine();
                    bw.write("---------------------------");
                    bw.newLine();
                }
            }
        }

        bw.write("本次测试一共出现 " + cnt + " 次异常");
        bw.flush();
        bw.close();
    }
}
4、测试结果 bug.txt
toStr 方法出异常了
异常的名称:NullPointerException
异常的原因:null
---------------------------
div 方法出异常了
异常的名称:ArithmeticException
异常的原因:/ by zero
---------------------------
本次测试一共出现 2 次异常

⑧小结

  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用?
    • 编译器
    • 给解析程序用
  3. 注解不是程序的一部分,可以理解为注解就是一个标签

2、Mysql

MySQL 教程 | 菜鸟教程 (runoob.com)

MySQL是一款关系数据库MySQL里面的My 不是 我的 意思。是创始人Michael Widenius 的闺女 My Widenius。
这是My Widenius的照片~

2.1、准备工作

1、启动数据库

要以管理员身份运行命令行窗口,搜索命令提示符-->右键-->更多-->以管理员身份运行

net start mysql服务名   //启动,可以设置开机自动启动,
				       //在我的电脑-管理-服务与应用程序-Mysql服务-启动方式(自动/手动)或者cmd - services.msc
net stop mysql服务名    //关闭

2、登陆数据库

mysql -h主机名 -P端口 -u用户名 -p密码 
mysql --host=ip --user=用户名 --password=密码     //都是两根杠
  • -p密码不要有空格
  • -p后面没有写密码,回车会要求输入密码
  • 如果没有写-h 主机,默认就是本机
  • 如果没有写-P 端口,默认就是3306
  • 在实际工作中,3306一般会修改

3、退出数据库

  1. quit
  2. exit

4、安装目录

  • bin:二进制可执行文件,配置环境变量就是用的这个目录

  • data:数据目录,存放日志和数据文件,数据库就是存放在这个目录

  • include:C语言头信息

  • lib:LIB文件中存放的是函数调用的信息,数据库有静态数据库(.lib文件)和动态数据库(.dll文件)。

  • share:存放mysql错误信息

  • 主目录下的my.ini 是mysql的配置文件,安装时必须设置配置文件,如果没有,则需自己创建。

    例如 D:\mysql-5.7.19-winx64\my.ini

5、MySQL的默认数据库

  • information_schema(信息数据库)

    information_schema 中的表实际上是视图,而不是基本表,因此,文件系统上没有与之相关的文件。它保存了MySQl服务所有数据库的信息。具体MySQL服务有多少个数据库,各个数据库有哪些表,各个表中的字段是什么数据类型,各个表中有哪些索引,各个数据库要什么权限才能访问。

    • SCHEMATA表:提供了当前mysql实例中所有数据库的信息。是show databases的结果取之此表。
    • TABLES表:提供了关于数据库中的表的信息(包括视图)。详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息。是show tables from schemaname的  结果取之此表。
    • COLUMNS表:提供了表中的列信息。详细表述了某张表的所有列以及每个列的信息。是show columns from schemaname.tablename的结果取之此表。
    • STATISTICS表:提供了关于表索引的信息。是show index from schemaname.tablename的结果取之此表。
    • USER_PRIVILEGES(用户权限)表:给出了关于全程权限的信息。该信息源自mysql.user授权表。是非标准表。
    • SCHEMA_PRIVILEGES(方案权限)表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。是非标准表。
    • TABLE_PRIVILEGES(表权限)表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。是非标准表。
    • COLUMN_PRIVILEGES(列权限)表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。是非标准表。
    • CHARACTER_SETS(字符集)表:提供了mysql实例可用字符集的信息。是SHOW CHARACTER SET结果集取之此表。
    • COLLATIONS表:提供了关于各字符集的对照信息。
    • COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。这些列等效于SHOW COLLATION的前两个显示字段。
    • TABLE_CONSTRAINTS表:描述了存在约束的表。以及表的约束类型。
    • KEY_COLUMN_USAGE表:描述了具有约束的键列。
    • ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。名为“mysql.proc name”的列指明了对应于INFORMATION_SCHEMA.ROUTINES表的mysql.proc表列。
    • VIEWS表:给出了关于数据库中的视图的信息。需要有show views权限,否则无法查看视图信息。
    • TRIGGERS表:提供了关于触发程序的信息。必须有super权限才能查看该表

  • mysql(核心数据库)

    • 保存MySQL的权限、参数、对象和状态信息。
    • 如哪些user可以访问这个数据、DB参数、插件、主从
  • performance_schema

    • 主要用于收集数据库服务器性能参数
    • 提供进程等待的详细信息,包括锁、互斥变量、文件信息;
    • 保存历史的事件汇总信息,为提供MySQL服务器性能做出详细的判断;
    • 对于新增和删除监控事件点都非常容易,并可以随意改变mysql服务器的监控周期,例如(CYCLE、MICROSECOND)
  • test

    这是一个空表,测试表,可以删除。

  • sys

    Sys库所有的数据源来自:performance_schema。目标是把performance_schema的把复杂度降低,让DBA能更好的阅读这个库里的内容。让DBA更快的了解DB的运行情况。


2.2、SQL

SQL: (Structured Query Language) 结构化查询语言, 能够操作所有的关系型数据库(Relational DBMS)。在mysql> 里面书写

1、通用语法

  1. SQL语句可以单行或者多行书写,以分号结尾。

  2. 可使用空格和缩进提高语句的可读性

  3. Mysql数据库的SQL语句不区分大小写,关键字建议用大写。

  4. 有三种注释:

    快捷键: 注释shift+ctrl+c 取消注释shift+ctrl+r

    • 单行注释:-- 注释内容 或者 #注释内容 ,#注释是MySQL特有的注释方式,可以不加空格
    • 多行注释:*/* 注释 /

2、SQL分类

  1. DDL(Data Definition Language)数据定义语言

    用来定义数据库对象:数据库,表,列等。关键字:CREATE,DROP,ALTER等

  2. DML(Data Manipulation Language)数据操作语言

    用来对数据库中表的数据进行增删改。关键字:INSERT,DELETE,UPDATE等

  3. DQL(Data Query Language)数据查询语言(难点)

    用来查询数据库中表的记录(数据)。关键字:SELECT,WHERE等

  4. DCL(Data Control Language)数据控制语言(了解)

    用来定义数据库的访问权限和安全级别以及创建用户。关键字:GRANT,REVOKE等


DDL

(Data Definition Language)数据定义语言

1、操作数据库:CRUD

[ ] 表示可写可不写的语句

  1. C(Create):创建

    CREATE DATABASE 数据库名称;  #创建数据库,使用默认字符集utf8,默认字符集校对准则utf8_general_ci (不区分大小写)
    CREATE DATABASE IF NOT EXISTS 数据库名; #先检查是否有该数据库再创建,如果不检查,重名会报错
    CREATE DATABASE [IF NOT EXISTS] 数据库名 [CHARACTER SET utf8] [COLLATE utf8_general_ci]
        #先检查,再创建,设置字符集为utf8,设置字符集校对准则utf8_general_ci (不区分大小写)
        
    [默认]CHARACTER SET utf8
    [默认]COLLATE utf8_general_ci          --(utf8_bin区分大小写)
    
    #当在该数据库创建表时,字符集和校对规则与数据库的一致
    
  2. R(Retrieve):查询

    SHOW DATABASES;  #查询所有数据库的名称
    SHOW CREATE DATABASE 数据库名称; #查看某个数据库的创建语句,包括字符集等
    
  3. U(Update):修改

    ALTER DATABASE 数据库名称 CHARACTER SET 字符集名称  #修改数据库字符集
    
  4. D(Delete):删除

    DROP DATABASE [IF EXISTS] 数据库名称;
    
  5. 使用数据库

    SELECT DATABASE(); #有括号,查询当前正在使用的数据库名称
    USE 数据库名称; 
    

2、操作表
  1. C(Create):创建

    1. 创建表

      CREATE TABLE 表名(                 #列名就是表头
          列名1 数据类型1,
          列名2 数据类型2,
          ...
          列名n 数据类型n    #最后一列不需要逗号
      );
      
    2. 数据类型

      SQL 数据类型 (w3school.com.cn)

      数据类型 描述
      INT(size) -2147483648 到 2147483647 常规。0 到 4294967295 无符号*。在括号中size规定最大位数。
      DOUBLE(size,d) 带有浮动小数点的大数字。在括号中规定size最大位数。在 d 规定小数点右侧的最大位数。
      DATE() 日期。格式:YYYY-MM-DD 注释:支持的范围是从 '1000-01-01' 到 '9999-12-31'
      DATETIME() 日期和时间的组合。格式:YYYY-MM-DD HH:MM:SS 注释:支持的范围是从 '1000-01-01 00:00:00' 到 '9999-12-31 23:59:59'
      TIMESTAMP() 时间戳。TIMESTAMP 值使用 Unix 纪元('1970-01-01 00:00:00' UTC) 至今的描述来存储。格式:YYYY-MM-DD HH:MM:SS 注释:支持的范围是从 '1970-01-01 00:00:01' UTC 到 '2038-01-09 03:14:07' UTC
      注意:即便 DATETIME 和 TIMESTAMP 返回相同的格式,它们的工作方式很不同。在 INSERT 或 UPDATE 查询中,TIMESTAMP 自动把自身设置为当前的日期和时间。TIMESTAMP 也接受不同的格式,比如 YYYYMMDDHHMMSS、YYMMDDHHMMSS、YYYYMMDD 或 YYMMDD。
      CHAR(size) 保存固定长度的字符串(可包含字母、数字以及特殊字符)。在括号中size指定字符个数。最多 255 个字符。 例如: zhangsan 八个字符 张三 2个字符
      VARCHAR(size) 保存可变长度的字符串(可包含字母、数字以及特殊字符)。在括号中size指定字符串的字符个数。但是最多保存65535 个字节。 UTF8编码最大21844字符 1-3个字节用于记录大小
      如果表的编码是UTF8 varchar(size) 那么size <= (65535-3)/3 = 21844
      如果表的编码是GBK varchar(size) 那么size <= (65535-3)/2 = 32766
      查询速度:CHAR > VARCHAR
      • utf8 英文1字节 中文3字节
      • unicode-ucs-2 英文、中文都是2字节
      • gbk 英文1字节,中文2字节

  2. R(Retrieve):查询

    SHOW TABLES; #查询某个数据库中所有的表名称
    DESC 表名;   #查询表结构
    
  3. U(Update):修改

    • 修改表名

      ALTER TABLE 表名 RENAME TO 新的表名;   -- 方法1
      RENAME TABLE 表名 TO 新表名;           -- 方法2
      
    • 修改表的字符集

      ALTER TABLE 表名 CHATACTER SET 字符集名称;
      
    • 添加一列

      ALTER TABLE 表名 ADD [COLUMN] 列名 数据类型;
      
      #如果想在一个已经建好的表中添加一列,可以用诸如:
      alter table TABLE_NAME add [column] NEW_COLUMN_NAME varchar(20) not null;
      
      #这条语句会向已有的表中加入新的一列,这一列在表的最后一列位置。如果我们希望添加在指定的一列,可以用:
      alter table TABLE_NAME add [column] NEW_COLUMN_NAME varchar(20) not null after COLUMN_NAME;
      
      #注意,上面这个命令的意思是说添加新列到某一列后面。如果想添加到第一列的话,可以用:
      alter table TABLE_NAME add [column] NEW_COLUMN_NAME varchar(20) not null first;
      
    • 修改列名和类型

      ALTER TABLE 表名 CHANGE 列名 新列名 新的数据类型; #同时改列名和数据类型
      ALTER TABLE 表名 MODIFY 列名 新的数据类型; #只改数据类型
      
    • 删除列

      ALTER TABLE 表名 DROP 列名;
      
  4. D(Delete):删除

    DROP TABLE [IF EXISTS] 表名; #删除表,需要进入相应数据库  
    

DML

(Data Manipulation Language)数据操作语言,增删改表中的数据

1、添加数据
INSERT INTO 表名(列名1,列名2,...列名n) VALUES(值1,值2,...值n); #列名和值要相对应
INSERT INTO 表名 VALUES(值1,值2,...值n); #给所有列添加值,同样要对应

注意:除了数字类型,其他类型需要使用引号(单双都可以)

2、删除数据
DELETE FROM 表名 [WHERE 条件];   #例如:DELETE FROM stu WHERE ID=1;  

注意:如果不加条件,则删除表中所有记录,非常危险!

? 如果真要删除所有记录

DELETE FROM 表名;#不推荐使用。有多少条记录就会执行多少次删除操作,效率低
TRUNCATE TABLE 表名;#推荐使用。删除表,然后再创建一个一模一样的空表,效率高  
3、修改数据
UPDATE 表名 SET 列名1=值1, 列名2=值2,...列名n=值n [WHERE 条件];

注意:如果不加条件,则会将表中所有记录全部修改,非常危险!


DQL(重点)

(Data Query Language)数据查询语言

1、语法
SELECT [DISTINCT] 字段列表 [[AS] 别名]   #DISTINCT表示去除重复的结果集,  AS可用空格替代
    FROM 表名列表 
    WHERE 条件列表 
    GROUP BY 分组字段 
    ORDER BY 排序
    HAVING 分组之后的条件限定 
    LIMIT 分页限定;

2、基础查询
SELECT English, Math, English + IFNULL(Math,0) [AS] 总分 FROM class;
#查询班级里英语、数学、总分
  1. 多个字段查询

    SELECT 字段名1,字段名2,... FROM 表名;  #如果查询所有字段,则可以用*来替代字段列表。
    
  2. 去除重复

    DISTINCT
    
  3. 计算列

    • 一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算)
    • IFNULL(表达式1,表达式2) 有NULL参与的计算,计算结果都为NULL
      • 表达式1:哪个字段需要判断是否为NULL
      • 表达式2:如果该字段为NULL则将其替换为表达式2
  4. 起别名

    字段/表达式 [AS] 别名   #AS可用空格替代
    

3、条件查询where
  1. WHERE 条件 条件并不一定带比较运算符,当不带运算符时,会执行一个隐式转换。当 0 时转化为 false,1 转化为 true。

  2. 运算符

    下面的运算符可以在 WHERE 子句中使用:

    运算符 描述
    = 等于
    <> 或 != 不等于。注释:在 SQL 的一些版本中,该操作符可被写成 !=
    > 大于
    < 小于
    >= 大于等于
    <= 小于等于
    BETWEEN x AND y [x,y]之间的值。x,y可以是表达式。 Select * from emp where sal between 1500 and 3000;
    LIKE (重点讲) 模糊查询。Select * from emp where ename like 'M%';
    IN 指定针对某个列的多个可能值 Select * from emp where age IN (19,22,27);
    AND 或 && 并且 Select * from emp where sal >= 1500 AND sal <= 3000; 与BETWEEN...AND...等效
    OR 或 || 或者
    NOT 或 !
    IS NULL 查询空值。Select * from emp where comm is null; NULL值不能用=或!=来判断
    IS NOT NULL 查询不为空值。Select * from emp where comm is not null;

    LIKE(模糊查询):

    1. 占位符

      • _ : 单个任意字符
      • % : 多个任意字符
    2. 使用举例

       Select * from emp where `name` = 'M%' ; #表示查询信息为M开头的。
       Select * from emp where `name` = '%M%'; #表示查询包含M的所有内容。
       Select * from emp where `name` = '%M_'; #表示查询以M在倒数第二位的所有内容。
      

4、排序查询order by
  1. 基本语法:

    • order by 子句
    • order by 排序字段1 排序方式1,排序字段2 排序方式2
  2. 排序方式:

    • ASC: 升序,默认的

    • DESC: 降序

      SELECT * FROM student ORDER BY math [ASC]; #升序
      SELECT * FROM student ORDER BY math DESC;  #降序
      SELECT * FROM student ORDER BY math [ASC], english [ASC];#如果数学成绩一样,则按英语成绩升序排列
      SELECT * FROM student ORDER BY math [ASC], english DESC;#如果数学成绩一样,则按英语成绩降序排列
      
    • 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。


5、聚合函数

聚合函数:将一列数据作为一个整体,进行纵向计算

注意:聚合函数的计算,会排除 NULL 值,解决方案有两种

  • 选择非空的列进行计算。 COUNT(主键)
  • IFNULL函数
  1. count:计算个数

    SELECT COUNT(name) FROM student; #计算有多少个学生,会排除name=NULL的人
    SELECT COUNT(IFNULL(english,0)) FROM student; #这样就不会排除英语成绩为NULL的人了
    SELECT COUNT(id) FROM student;  #若id为主键,主键不能为空
    
  2. max:计算最大值

    SELECT MAX(math) FROM student;#会排除NULL值进行计算
    
  3. min:计算最小值

    SELECT MIN(math) FROM student;#会排除NULL值进行计算
    
  4. sum:求和

    SELECT SUM(math) FROM student;#会排除NULL值进行计算
    
  5. avg:计算平均值

    SELECT AVG(math) FROM student;#会排除NULL值进行计算
    

6、分组查询group by
  1. 语法

    SELECT 查询的字段 FROM 表名 GROUP BY 分组的字段
    
  2. 注意:

    • 分组之后想要查询的字段可以是:分组的字段、聚合函数

      SELECT sex,AVG(math),COUNT(id) FROM student GROUP BY sex;#显示性别,数学平均分,人数
      
    • where和having的区别?

      • where: 分组之前对分组条件进行限定,后面不可以跟聚合函数
      • having: 分组之后对查询结果进行过滤可以跟聚合函数
      SELECT sex,AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex;
      #数学分数高于70才参与分组
      SELECT sex,AVG(math),COUNT(id) 人数 FROM student WHERE math > 70 GROUP BY sex HAVING 人数 > 2;
      #只显示分组人数大于2的组,后面的COUNT(id)可以用已定义的别名替换,AS省略
      

7、分页查询limit

查询结果太多时可以使用分页操作,便于显示

  1. 语法

    LIMIT 开始的索引,每页查询的条数;
    SELECT * FROM student LIMIT 0,3; #第一页,每页三条数据
    SELECT * FROM student LIMIT 3,3; #第二页,每页三条数据
    #设当前的页码为n,每页显示的条数为m,则开始的索引为 startIndex = (n-1)*m
    SELECT * FROM student LIMIT startIndex,m; #第n页,每页m条数据
    
  2. 公式:开始的索引 = (当前的页码 - 1)* 每页显示的条数

  3. 分页操作是一个“方言”,LIMIT 这个语句只能在MySQL中使用


DCL

(Data Control Language)数据控制语言

数据库管理员(Database Administrator,简称 DBA),是从事管理和维护数据库管理系统(DBMS)的相关工作人员的统称,属于运维工程师的一个分支,主要负责业务数据库从设计、测试到部署交付的全生命周期管理。DBA 的核心目标是保证数据库管理系统的稳定性、安全性、完整性和高性能。

1、管理用户

MySQL用户管理 (biancheng.net)

  1. 添加用户

    -- 先要进入mysql数据库
    CREATE USE 'user_name'@'host_name' [IDENTIFIED BY '密码'];
    -- 如果在创建的过程中,只给出了用户名,而没指定主机名,那么主机名默认为“%”,表示一组主机,即对所有主机开放权限。
    -- IDENTIFIED BY用于指定用户密码。新用户可以没有初始密码,若该用户不设密码,可省略此子句。
    

    使用 CREATE USER 语句时应注意以下几点:

    • CREATE USER 语句可以不指定初始密码。但是从安全的角度来说,不推荐这种做法。
    • 使用 CREATE USER 语句必须拥有 mysql 数据库的 INSERT 权限或全局 CREATE USER 权限。
    • 使用 CREATE USER 语句创建一个用户后,MySQL 会在 mysql 数据库的 user 表中添加一条新记录。
    • CREATE USER 语句可以同时创建多个用户,多个用户用逗号隔开。
  2. 删除用户

    DROP USER 'user_name'@'host_name';  -- user_name + host_name 才叫一个完整的用户
    
  3. 修改用户密码

    1. 修改自己密码

      SET PASSWORD = PASSWORD('密码');
      

      问题:MySQL中忘记了root用户密码怎么办?

      1. (管理员身份)cmd > net stop mysql 停止MySQL的服务
      2. 使用无验证方式启动MySQL服务:mysqld --skip-grant-tables ,保持这个cmd窗口存在
      3. 打开一个新的cmd窗口,直接输入mysql回车
      4. 进入mysql数据库,修改user中的数据,关闭两个窗口
      5. 在任务管理器中,进程--> mysqld.exe --> 结束进程

    2. 修改他人密码(需要有修改用户密码的权限

      SET PASSWORD FOR 'user_name'@'host_name' = PASSWORD('新密码');
      -- 也可以按照修改数据的语句来改表中的数据
      UPDATE USER SET authentication_string = PASSWORD('新密码') WHERE USER = 'user_name';
      
      • PASSWORD( ) 其实是一个加密函数,表示使用哈希值设置密码
      • MySQL 5.7 的 user 表中的密码字段从 Password 变成了 authentication_string
      • 如果使用的是 MySQL 5.7 之前的版本,将 authentication_string 字段替换成 Password 即可。

  4. 查询用户

    1. 切换到MySQL数据库

      USE mysql;
      
    2. 查询user表

      SELECT * FROM USER;     -- 通配符%表示可以在任意主机登陆数据库
      

2、管理权限

首先进入mysql数据库

  1. 查询权限

    SHOW GRANTS FOR 'user_name'@'host_name';
    
  2. 授予权限

    在 MySQL 中,拥有 GRANT 权限的用户才可以执行 GRANT 语句,其语法格式如下:

    GRANT priv_type [(column_list)] ON database.table
    TO user [IDENTIFIED BY [PASSWORD] 'password']
    [, user[IDENTIFIED BY [PASSWORD] 'password']] ...
    [WITH with_option [with_option]...]
    

    其中:

    • priv_type 参数表示权限类型,可以同时授予多个权限,由于权限类型太多,不在此罗列;
    • columns_list 参数表示权限作用于哪些列上,省略该参数时,表示作用于整个表
    • database.table 用于指定权限的级别;
    • user 参数表示用户账户,格式是'username'@'hostname'
    • IDENTIFIED BY 参数用来为用户设置密码;
    • password 参数是用户的新密码。

    WITH 关键字后面带有一个或多个 with_option 参数。这个参数有 5 个选项,详细介绍如下:

    • GRANT OPTION:被授权的用户可以将这些权限赋予给别的用户;
    • MAX_QUERIES_PER_HOUR count:设置每个小时可以允许执行 count 次查询
    • MAX_UPDATES_PER_HOUR count:设置每个小时可以允许执行 count 次更新
    • MAX_CONNECTIONS_PER_HOUR count:设置每小时可以建立 count 个连接;
    • MAX_USER_CONNECTIONS count:设置单个用户可以同时具有的 count 个连接

    database.table,在 GRANT 语句中可用于指定权限级别的值有以下几类格式:

    • *:表示当前数据库中的所有表。
    • *.*:表示所有数据库中的所有表。
    • db_name.*:表示某个数据库中的所有表,db_name 指定数据库名。
    • db_name.tbl_name:表示某个数据库中的某个表或视图,db_name 指定数据库名,tbl_name 指定表名或视图名。
    • db_name.routine_name:表示某个数据库中的某个存储过程或函数,routine_name 指定存储过程名或函数名。
    • TO 子句:如果权限被授予给一个不存在的用户,MySQL 会自动执行一条 CREATE USER 语句来创建这个用户,但同时必须为该用户设置密码。

  3. 删除权限

    1. 第一种

      删除用户某些特定的权限,语法格式如下:

      REVOKE priv_type [(column_list)]...
      ON database.table
      FROM user [, user]...
      

      REVOKE 语句中的参数与 GRANT 语句的参数意思相同。其中:

      • priv_type 参数表示权限的类型;
      • column_list 参数表示权限作用于哪些列上,没有该参数时作用于整个表上;
      • user 参数由用户名和主机名构成,格式为 ‘username'@'hostname'

    2. 第二种

      删除特定用户的所有权限,语法格式如下:

      REVOKE ALL PRIVILEGES, GRANT OPTION FROM user [, user] ...
      

    删除用户权限需要注意以下几点:

    • REVOKE 语法和 GRANT 语句的语法格式相似,但具有相反的效果。
    • 要使用 REVOKE 语句,必须拥有 MySQL 数据库的全局 CREATE USER 权限或 UPDATE 权限。

3、约束

约束是对表中的数据进行限定,保证数据的正确性、有效性和完整性。比如禁止添加一条人名为NULL的数据

分类:

  1. 非空约束:not null
  2. 唯一约束:unique
  3. 主键约束:primary key
  4. 外键约束:foreign key

非空约束 not null

某一列的值不能为null

1、在创建表时,添加约束
CREATE TABLE stu(
	id INT,
    `name` VARCHAR(20) NOT NULL # name为非空
);  #当添加name=null的数据时,会报错,Field 'name' doesn't have a default value
2、在创建表后,添加约束
ALTER TABLE stu MODIFY `name` VARCHAR(20) NOT NULL;
3、删除约束
ALTER TABLE stu MODIFY `name` VARCHAR(20); #不指定not null即为删除了非空约束
4、注意

在创建表后,能添加非空约束的前提是该字段的值都不为NULL


唯一约束 unique

某一列的值不能重复

1、在创建表时,添加约束
CREATE TABLE stu(
	id INT,
    phone_num CHAR(11) UNIQUE #每个人的手机号唯一
);
2、在创建表后,添加约束
ALTER TABLE stu MODIFY phone_num CHAR(11) UNIQUE; #能添加唯一约束的前提是该字段的值没有重复
3、删除约束(特殊)
#正确方式
ALTER TABLE stu DROP INDEX phone_num; #与删除非空约束不同
#错误方式
ALTER TABLE stu MODIFY phone_num CHAR(11);
4、注意
  1. 唯一约束可以有NULL值,且可以有多条记录为NULL,前提是没有设置非空
  2. 在创建表后,能添加唯一约束的前提是,该字段的值没有重复
  3. 删除唯一约束使用 DROP INDEX 字段名

主键约束 primary key

非空且唯一,一张表只能有一个字段为主键, 是表中记录的唯一标识,效果等效为unique not null

1、在创建表时,添加主键
  • 方法1:直接在字段后指定

    CREATE TABLE stu(
    	id INT PRIMARY KEY, #给id添加主键约束
        `name` VARCHAR(20)
    ); 
    #当添加id为重复或者NULL的数据时,会报错,Field 'name' doesn't have a default value
    
  • 方法2:在表定义的最后写 primary key(列名)

    CREATE TABLE stu(
    	id INT, 
        `name` VARCHAR(20),
        PRIMARY KEY(id)  #给id添加主键约束
    ); 
    
2、在创建表后,添加主键
ALTER TABLE stu MODIFY id INT PRIMARY KEY;
3、删除主键(特殊)
#正确方式
ALTER TABLE stu DROP PRIMARY KEY; #不用指定哪个字段,因为主键唯一
#错误方式
ALTER TABLE stu MODIFY id INT;
4、复合主键

一张表最多只能有一个主键,但可以是复合主键(比如 id + name)

CREATE TABLE stu(
	id INT, 
    `name` VARCHAR(20),
    math DOUBLE(4,1), #最大位数为4,小数点后最多1位
    PRIMARY KEY(id,'name') #id和name做成复合主键,当id和name都相同时才叫重复,但是id和name都不能为NULL
); 
5、注意
  1. 创建表后,能添加主键约束的前提是,该字段的值不重复且非NULL
  2. 删除主键约束使用 DROP PRIMARY KEY,不加字段名
  3. 可以使用复合主键,复合主键的所有字段都相同时才叫重复,但是每一个字段都不能为NULL。

外键约束 foreign key

用于定义从表和主表之间的关系

1、创建表时,添加外键
#创建主表
CREATE TABLE my_class(
	id INT PRIMARY KEY,                       -- 班级编号
    `name` VARCHAR(32) NOT NULL DEFAULT ''    -- 默认值为空字符串
);
#创建从表
CREATE TABLE my_stu(
	id INT PRIMARY KEY,                       -- 学生编号
    `name` VARCHAR(32) NOT NULL DEFAULT '',   -- 默认值为空字符串
    class_id INT,                             -- 学生所在班级编号
    #指定外键关系
    [CONSTRAINT 外键名称] FOREIGN KEY (class_id) REFERENCES my_class(id)
);
#外键名称一般命名为:从表名_主表名_fk     fk是外键的缩写
2、创建表后,添加外键
ALTER TABLE 从表 ADD [CONSTRAINT 外键名称] FOREIGN KEY (从表字段) REFERENCES 主表(字段);
3、删除外键
ALTER TABLE 从表 DROP FOREIGN KEY 外键名称;     -- 是外键名称不是外键字段
4、级联操作(更新、删除)

如果父表试图UPDATE或者DELETE任何子表中存在或匹配的外键值,最终动作取决于外键约束定义中的ON UPDATE和ON DELETE选项。InnoDB支持5种不同的动作,默认的动作为ON DELETE RESTRICT ON UPDATE RESTRICT 。

  1. CASCADE: 从父表中删除或更新对应的行,同时自动的删除或更新子表中匹配的行。ON DELETE CANSCADEON UPDATE CANSCADE都被InnoDB所支持。

  2. SET NULL: 从父表中删除或更新对应的行,同时将子表中的外键列设为空。注意,这些在外键列没有被设为NOT NULL时才有效。ON DELETE SET NULL和ON UPDATE SET SET NULL都被InnoDB所支持。

  3. NO ACTION: InnoDB拒绝删除或者更新父表。

  4. RESTRICT: 拒绝删除或者更新父表,使用时替换CASCADE的位置,指定RESTRICT(或者NO ACTION)和忽略ON DELETE或者ON UPDATE选项的效果是一样的。

  5. SET DEFAULT: InnoDB目前不支持。

  6. 添加级联

    • 创建表时,添加级联

      #创建从表
      CREATE TABLE my_stu(
      	id INT PRIMARY KEY,                       -- 学生编号
          `name` VARCHAR(32) NOT NULL DEFAULT '',   -- 默认值为空字符串
          class_id INT,                             -- 学生所在班级编号
          #指定外键关系
          [CONSTRAINT 外键名称] FOREIGN KEY (class_id) REFERENCES my_class(id) 
          					[ON UPDATE CASCADE] [ON DELETE CASCADE]
      );
      
    • 创建表后,添加级联

      #如果添加级联的时候已经有外键约束,则需先删除外键,再同时添加外键和级联。
      #ALTER TABLE 从表 DROP FOREIGN KEY 外键名称;
      ALTER TABLE 表名 ADD [CONSTRAINT 外键名称] FOREIGN KEY (外键字段) REFERENCES 主表(字段) 
      [ON UPDATE CASCADE] [ON DELETE CASCADE];
      
  7. 删除级联(外键)

    ALTER TABLE 从表 DROP FOREIGN KEY 外键名称;#取消级联只能通过删除外键实现
    
  8. 级联操作只能在主表

    • ON UPDATE CASCADE:主表修改级联的数据会导致从表数据修改
    • ON DELETE CASCADE:主表删除级联的数据会导致从表数据删除
    • 从表的级联数据修改(其中不符合外键的修改会失败)或者删除不会影响主表
5、注意
  1. 外键指向的主表的字段,要求是primary key 或者是 unique

  2. 表的类型是innoDB,这样的表才支持外键

    InnoDB:是MySQL的数据库引擎之一,现为MySQL的默认存储引擎,支持了ACID兼容的事务(Transaction)功能。

  3. 外键字段的类型要和主键字段类型一致(长度可以不同)

  4. 外键字段的值,必须在主键字段中出现过,或者为NULL(前提是从表外键字段允许为NULL)

  5. 删除主表数据时,需要先把从表中有关联的数据改变或删除,确保该条数据与从表数据无关联,才可以删除;添加主表数据无约束。

  6. 删除从表数据无约束;添加从表数据有约束。

  7. 级联操作的前提是有外键约束,在从表设置级联,在主表操作级联。

  8. 级联有弊端,主表操作会影响从表,需慎重使用。


4、自增长

如果某一列是数值类型的,使用auto_increment 可以来完成自动增长

1、创建表时,添加自增长
CREATE TABLE stu(
	id INT PRIMARY KEY AUTO_INCREMENT, #给id添加主键约束,并设置自增长
    'name' VARCHAR(20)
); 
INSERT INTO stu VALUES(NULL,'Jack');# id=1, name=Jack,自增长默认从1开始
INSERT INTO stu VALUES(NULL,'Mary');# id=2, name=Mary
INSERT INTO stu VALUES(10,'Smith'); # id=10,name=Smith ,也可以手动委派值,自增长开始值也会变
INSERT INTO stu VALUES(NULL,'Tom');# id=11, name=Tom
2、创建表后,添加自增长
ALTER TABLE stu MODIFY id [PRIMARY KEY] INT AUTO_INCREMENT;#如无主键,则需添加主键或者唯一约束 
3、删除自增长
ALTER TABLE stu MODIFY id INT; #只删除了自增长,主键不能通过这种方式删除,需要通过drop primary key
4、设置自增长开始值
ALTER TABLE stu AUTO_INCREMENT = 开始值;

注意:

  1. 一般来说自增长是跟primary key配合使用的
  2. 自增长也可以单独使用 【但是需要配合一个unique
  3. 自增长修饰的字段为数值型,一般为整数,很少使用小数
  4. 自增长默认从1开始,也可以自定义自增长的开始值
  5. 如果添加数据时,给自增长字段指定的有值,则以指定的值为准;如果指定了自增长,一般来说,就按照自增长的规则来添加数据。

2.3、数据库的设计

1、多表之间的关系

分类

  • 一对一:人和身份证号。很少用,因为可以做成同一张表

    实现:可以在任意一方添加有唯一约束unique的外键指向另一方的主键。

  • 一对多:部门和员工

    实现:在多张从表建立 外键 对应一张主表

  • 多对多:学生和课程

    实现:需要借助一张中间表,这中间表至少有两个字段,分别为两张表的主键,这两个字段分别作为外键指向两张主表的主键,

    再把中间表的两个字段做成复合主键


2、三大范式

为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。

范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。

1、第一范式(1NF)

确保每列保持原子性,比如一个班级可以分为班级号和班主任

第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。

? 原表格

学号 姓名 班号 班主任 课程 分数
1 张无忌 102 张三丰 数学 95
1 张无忌 102 张三丰 英语 23
3 任我行 113 风清扬 物理 77
4 杨过 107 小龙女 生物 86

存在的问题:

  1. 存在非常严重的数据冗余问题。例如:姓名、班号、班主任
  2. 在数据添加时存在问题,新添加的数据有可能存在数据不合法的情况。 例如:添加新开的班级和班主任,但是暂时没有招收学生
  3. 删除数据时也存在问题。例如:张无忌毕业了,删除张无忌的数据后,该班级的信息也消失了

2、第二范式(2NF)

消除部分依赖

在1NF的基础上,非主属性必须完全依赖于主属性,消除非主属性对主属性的部分依赖。

概念
  • 函数依赖:A--->B,如果通过A属性(组)的值,可以确定唯一B属性的值,则称B依赖于A。

    例如:姓名、班号、班主任都可以由学号确定,而课程和分数不能确定。

  • 完全函数依赖:A--->B,如果A是一个属性组,则B属性值的确定需要依赖于A属性组中所有的属性值。

    例如:(学号,课程)为属性组,则分数需要由学号和课程共同确定。而其他字段则可以只由学号确定(部分函数依赖)。

  • 部分函数依赖:A--->B,如果A是一个属性组,则B属性值的确定只需要依赖于A属性组中部分的属性值。

    例如:(学号,课程)为属性组,班主任只需要由学号确定。

  • 传递函数依赖:A--->B,B--->C,如果通过A属性(组)的值,可以唯一确定B属性(组)的值,再通过B属性(组)的值可以唯一确定C属性的值。

    例如:学号--->班号--->班主任

  • 码(用作主键):如果在一张表中,一个属性(组),被其他所有属性所完全依赖,则称这个属性(组)为该表的码。

    例如:该表的码为(学号,课程名称)或者(姓名,课程名称)

    • 主属性:码属性组中的所有属性
    • 非主属性:码属性组之外的属性
表格拆分

? 学生表

学号 姓名 班号 班主任
1 张无忌 102 张三丰
1 张无忌 102 张三丰
3 任我行 113 风清扬
4 杨过 107 小龙女

? 选课表

学号 课程 分数
1 数学 95
1 英语 23
3 物理 77
4 生物 86

存在的问题:

  1. 存在非常严重的数据冗余问题。例如:姓名、班号、班主任
  2. 在数据添加时存在问题,新添加的数据有可能存在数据不合法的情况。 例如:添加新开的班级和班主任,但是暂时没有招收学生
  3. 删除数据时也存在问题。例如:张无忌毕业了,删除张无忌的数据后,该班级的信息也消失了

3、第三范式(3NF)

消除传递依赖, 学号--->班号--->班主任

在2NF的基础上,任何非主属性不依赖于其他非主属性。确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

? 学生表

学号 姓名 班号
1 张无忌 102
1 张无忌 102
3 任我行 113
4 杨过 107

? 选课表

学号 课程 分数
1 数学 95
1 英语 23
3 物理 77
4 生物 86

? 班级表

班号 班主任
102 张三丰
113 风清扬
107 小龙女

存在的问题:

  1. 存在非常严重的数据冗余问题。例如:姓名、班号、班主任

  2. 在数据添加时存在问题,新添加的数据有可能存在数据不合法的情况。 例如:添加新开的班级和班主任,但是暂时没有招收学生

    可以单独添加班级表中的数据

  3. 删除数据时也存在问题。例如:张无忌毕业了,删除张无忌的数据后,该班级的信息也消失了

    可以单独删除学生表和选课表中的数据


2.4、数据库的备份和还原

1、命令行

备份数据库

在DOS执行,不是mysql中

//备份一个数据库[表]
mysqldump -u用户名 -p密码 dbname [tbname ...] > filename.sql    //filename包括了路径
//也可以是txt文件,但是建议sql,容易识别这是个数据库文件,文件中其实就是一些SQL语句

//备份多个数据库
mysqldump -u用户名 -p密码 -B 数据库1 数据库2 数据库n > filename.sql //写法1
mysqldump -u用户名 -p密码 --databases 数据库1 数据库2 数据库n > filename.sql  //写法2
mysqldump -u用户名 -p密码 --all-databases > filename.sql    //备份所有数据库
  • dbname:表示需要备份的数据库名称;
  • tbname:表示数据库中需要备份的表,可以指定多个表。省略该参数时,会备份整个数据库;
还原数据库

方法1:

  1. 登陆数据库

  2. 创建数据库

    CREATE DATABASE 数据库;
    
  3. 使用数据库

    USE 数据库;
    
  4. 执行source文件

    SOURCE filename.sql
    

方法2:

  1. 打开filename.sql,在顶部手动添加创建数据库和使用数据库的语句

    CREATE DATABASE db_test;
    USE db_test;
    
  2. 在DOS中

    mysql -u用户名 -p密码 [dbname] < filename.sql //如果指定数据库名称,指定的数据库名不存在将会报错;
    

2、图形化工具(SQLyog)

右键数据库 ---> 备份/导出 ---> 备份数据库,转储到SQL


2.5、多表查询

1、笛卡尔集

在多表查询中,如果同时查询表A、表B而不加查询条件,A X B所形成的集合就叫笛卡尔集

注意: 查询条件不能少于表的个数-1,不然会出现笛卡尔集 , 多表查询需要消除其中的无用数据.


2、多表查询的分类

1、内连接查询
  • 从哪些表中查询数据
  • 查询条件是什么
  • 查询哪些字段
1、隐式内连接

使用where条件来消除无用数据

SELECT emp.name,emp.gender,dept.name FROM emp,dept WHERE emp.dept_id = dept.id;

语句优化,可读性好

SELECT
	t1.name,		 #员工姓名
	t1.gender,		 #员工性别
	t2.name			 #部门名称
FROM				 
	emp t1, 		 #别名
	dept t2    	     
WHERE
	t1.dept_id = t2.id;
2、显式内连接
SELECT 字段列表 FROM 表1 [INNER] JOIN 表2 ON 条件;
SELECT
	t1.name,		 #员工姓名
	t1.gender,		 #员工性别
	t2.name			 #部门名称
FROM				 
	emp t1, 		 #别名
[INNER] JOIN
	dept t2          	     
ON
	t1.dept_id = t2.id;

2、外连接查询

? my_stu

id name class_id
2019210236 赵宏韬 2
2019210237 肖南海 2
2019210238 王宣程 3
2019210239 魏鑫 4
2019210240 黄俊凯 1

? my_class

id name
1 通信工程
2 电子信息工程
3 信息工程
1、左外链接
  • 表1 为基表,表2 为参考表。左连接查询时,可以查询出 表1 中的所有记录表2 中匹配连接条件的记录。
  • 如果 表1 的某行在 表2 中没有匹配行,那么在返回结果中,属于 表2 的字段值均为空值(NULL)。
SELECT 字段列表 FROM 表1 LEFT [OUTER] JOIN 表2 ON 条件;
SELECT
	t1.id AS '学号',
	t1.name AS '姓名',
	t2.id AS '班级'
FROM
	my_stu AS t1
LEFT JOIN
	my_class AS t2
ON
	t1.class_id = t2.id;

? 左外链接查询表

学号 姓名 班级
2019210236 赵宏韬 2
2019210237 肖南海 2
2019210238 王宣程 3
2019210239 魏鑫 (NULL)
2019210240 黄俊凯 1
2、右外链接

与左外连接同理

SELECT 字段列表 FROM 表1 RIGHT [OUTER] JOIN 表2 ON 条件;

3、子查询

WHERE <表达式> <操作符> (子查询)

其中,操作符可以是比较运算符IN、NOT IN、EXISTS、NOT EXISTS 等关键字。

EXISTS 表达式返回 true或者false,外层查询收到true返回所有记录, false则一条记录都不返回,这与 = 不同。

1、概念

将一个查询语句嵌套在另一个查询语句中。子查询可以在 SELECT、UPDATE 和 DELETE 语句中使用,而且可以进行多层嵌套。在实际开发时,子查询经常出现在 WHERE 子句中。

外层的 SELECT 查询称为父查询,圆括号中嵌入的查询称为子查询(子查询必须放在圆括号内)。

MySQL 在处理上例的 SELECT 语句时,执行流程为:先执行子查询,再执行父查询。

2、基本演示

子查询在 WHERE 中的语法格式如下:

-- 查询工资最高的员工信息
-- 1 查询最高工资是多少?  假如是9000
SELECT MAX(salary) FROM emp;
-- 2 查询工资为9000的员工信息
SELECT * FROM emp WHERE salary = 9000;

-- 用子查询合并语句
SELECT * FROM emp WHERE salary = (SELECT MAX(salary) FROM emp);
3、子查询的不同情况
  1. 子查询的结果是单行单列的 (一个确定值)

    查询结果可以作为条件,当作一个确定值,使用运算符( > >= < <= = != )进行判断。

  2. 子查询的结果是多行单列的 (数组)

    查询结果可以当作一个数组。使用运算符 IN 进行判断。

  3. 子查询的结果是多行多列的 (子表)

    查询结果可以当作一张虚拟表,而且可以起别名。再用这张表进行下一步查询


4、自连接

在同一张表的连接查询,把同一张表分别取两个别名,当成两张表查询

如果列名不明确,可以指定列的别名

-- 综合题
-- 需求:显示公司员工名字和他上级的名字,没有上级的也需要查询,公司员工的信息都在同一张表
-- 显示公司员工名字和他上级的名字,自连接
-- 没有上级的也需要查询,左外连接
SELECT
	worker.name [AS] '职员名',
	boss.name [AS] '上级名'
FROM
	emp [AS] worker
LEFT [OUTER] JOIN
	emp [AS] boss
ON
	worker.mgr = boss.empno;

5、合并查询

有时候在实际应用中,为了合并多个select语句的结构,可以使用集合操作符union 和 union all

1、union

该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。

2、union all

与union类似,但是不会取消重复行

SELECT ename,sal,job FROM emp WHERE sal > 2500;         -- 结果集1
SELECT ename,sal,job FROM emp WHERE job = 'MANAGER';	-- 结果集2
-- 合并查询
SELECT ename,sal,job FROM emp WHERE sal > 2500;
UNION          -- 去重
SELECT ename,sal,job FROM emp WHERE job = 'MANAGER';

SELECT ename,sal,job FROM emp WHERE sal > 2500;
UNION ALL      -- 不去重
SELECT ename,sal,job FROM emp WHERE job = 'MANAGER';

2.6、事务

1、事务的基本介绍

1、概念

事务用于保证数据的一致性,它由一组相关的DML语句(增删改,不包括查)组成,该组的DML语句要么全部成功,要么全部失败。

例如:张三给李四转账500元

  1. 查询张三账户余额是否有500元
  2. 张三账户余额 - 500元
  3. 李四账户余额 + 500元
2、操作
  1. 开启事务 start transaction
  2. 设置保存点 savepoint
  3. 回滚至指定保存点 rollback to
  4. 回滚至起点 rollback
  5. 提交事务 commit :确认事务的变化,结束事务,删除所有保存点,释放锁,数据生效,不能回滚,其他会话[连接]可以查看到事务变化后的新数据[所有数据正式生效]
-- 开始事务
START TRANSACTION;

-- 执行DML操作
#此处为DML语句

-- 设置保存点
SAVEPOINT a;
#此处为DML语句

-- 设置保存点
SAVEPOINT b;
#此处为DML语句

...

-- 回退到b
[ROLLBACK TO b;]

-- 回退到a
[ROLLBACK TO a;]

-- 回退到事务开始
[ROLLBACK;]

-- 提交事务
COMMIT;
3、细节
  1. 如果不开始事务,默认情况下,MySQL是自动提交的,不能回滚;Oracle是手动提交的,可以回滚。
  2. 如果开始一个事务,且没有创建保存点。可以执行rollback,默认回退到事务开始的状态。
  3. 在事务中(还未提交),可以创建多个保存点。
  4. 在事务还没有提交前,可以选择回退到哪个保存点。
  5. MySQL事务机制需要innoDB的存储引擎才可以使用,myisam不行。
  6. 查看事务的默认提交方式:select @@autocommit; -- 1代表自动提交 0代表手动提交
  7. 开始一个事务有两种方式
    1. start transaction;
    2. set autocommit = off ;或者 set @@autocommit = 0;

2、事务的四大特征ACID(常见面试题)

  1. 原子性(Atomicity)

    整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  2. 一致性(Correspondence)

    在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。

  3. 隔离性(Isolation)

    隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆, 必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。

  4. 持久性(Durability)

    在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。


3、事务的隔离级别(了解)

彻底搞懂 MySQL 事务的隔离级别-阿里云开发者社区 (aliyun.com)

1、事务并发可能出现的情况
① 脏读(Dirty Read)

一个事务读到了另一个未提交事务修改过的数据。

② 不可重复读/虚读(Non-Repeatable Read)

在同一个事务中,两次读取到的数据不一样。一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。(不可重复读在读未提交和读已提交隔离级别都可能会出现)

③ 幻读(Phantom)

一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现)


2、事务的隔离级别

MySQL的事务隔离级别一共有四个,分别是读未提交、读已提交、可重复读以及可串行化。

MySQL的隔离级别的作用就是让事务之间互相隔离,互不影响,这样可以保证事务的一致性。

隔离级别比较:可串行化>可重复读>读已提交>读未提交

隔离级别对性能的影响比较:可串行化>可重复读>读已提交>读未提交

由此看出,隔离级别越高,所需要消耗的MySQL性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,MySQL默认的隔离级别也是可重复读。

① 读未提交(READ UNCOMMITTED)
  • 在读未提交隔离级别下,事务A可以读取到事务B修改过但未提交的数据。
  • 可能发生脏读、不可重复读和幻读问题,一般很少使用此隔离级别。
② 读已提交(READ COMMITTED)
  • 在读已提交隔离级别下,事务A只能在事务B修改过并且已提交后才能读取到事务B修改的数据。
  • 读已提交隔离级别解决了脏读的问题,但可能发生不可重复读和幻读问题,一般很少使用此隔离级别。
  • Oracle的默认隔离级别
③ 可重复读(REPEATABLE READ)
  • 在可重复读隔离级别下,事务A只能在事务B修改过数据并提交后,自己(A)也提交事务后,才能读取到事务B修改的数据。
  • 可重复读隔离级别解决了脏读和不可重复读的问题,但可能发生幻读问题。
  • MySQL的默认隔离级别

? 提问:为什么上了写锁(写操作),别的事务还可以读操作?
? 答: 因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞。

④ 可串行化(SERIALIZABLE)
  • 各种问题(脏读、不可重复读、幻读)都不会发生,通过加锁实现(读锁和写锁)。
脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 不会 可能 可能
可重复读 不会 不会 可能
可串行化 不会 不会 不会

3、查询和设置隔离级别
  1. 查询当前会话隔离级别(2种方式)

    mysql> SHOW VARIABLES LIKE 'tx_isolation';
    mysql> SELECT @@tx_isolation;
    
  2. 设置隔离级别

    设置隔离级别后要重新登陆MySQL才生效

    SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level;
    -- 其中level有4种值:
    level: {
         REPEATABLE READ
       | READ COMMITTED
       | READ UNCOMMITTED
       | SERIALIZABLE
    }
    
    关键词:GLOBAL
    SET GLOBAL TRANSACTION ISOLATION LEVEL level;
    -- 只对执行完该语句之后产生的会话起作用
    -- 当前已经存在的会话无效
    
    关键词:SESSION
    SET SESSION TRANSACTION ISOLATION LEVEL level;
    -- 对当前会话的所有后续的事务有效
    -- 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务
    -- 如果在事务之间执行,则对后续的事务有效。
    
    无关键词
    SET TRANSACTION ISOLATION LEVEL level;
    -- 只对当前会话中下一个即将开启的事务有效
    -- 下一个事务执行完后,后续事务将恢复到之前的隔离级别
    -- 该语句不能在已经开启的事务中间执行,会报错的
    

2.7、JDBC

Java DataBase Connectivity Java数据库连接

1、简介

JDBC本质

其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。

举例:

  • 官方接口:Person接口

  • MySQL实现类:Worker类

  • 用户使用接口:

    Person p = new Worker(); 
    p.eat();          //真正调用的是 实现类Worker 里面的方法,多态          
    

2、执行步骤

  1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
    1. 复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
    2. 右键-->Add As Library
  2. 注册驱动
  3. 获取数据库连接对象 Connection
  4. 定义sql
  5. 获取执行sql语句的对象 Statement
  6. 执行sql,接受返回结果
  7. 处理结果
  8. 释放资源
public class JdbcDemo1 {
    public static void main(String[] args) throws Exception {
        //1、注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        
        //2、获取数据库连接对象
        Connection connection = DriverManager.getConnection(
                "jdbc:mysql://localhost:3306/db_test", "root", "whyzazn");
        
        //3、定义sql语句
        String sql = "update my_stu set class_id = 1 where id = 2019210239";
        
        //4、获取执行sql语句的对象  Statement
        Statement statement = connection.createStatement();
        
        //5、执行sql
        int count = statement.executeUpdate(sql);
        
        //6、处理结果
        System.out.println(count);
        
        //7、释放资源,先开启,后释放原则
        statement.close();
        connection.close();
    }
}       

这其实是一份不太规范的代码,可以进行如下细节优化

public class JdbcDemo2 {
    public static void main(String[] args) {
        //提升作用域,不然释放资源的时候finally块中无法使用try块的变量
        Statement statement = null;
        Connection connection = null;
        try {
            //1、注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2、定义sql
            String sql = "update my_stu set class_id = 7 where id = 2019210239";
            //3、获取Connection对象
            connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/db_test", "root", "whyzazn");
            //4、获取执行sql的对象
            statement = connection.createStatement();
            //5、执行sql
            int lines = statement.executeUpdate(sql);   //影响的行数
            //6、处理结果
            System.out.println("影响的行数为:" + lines);
            if (lines > 0){
                System.out.println("更新数据成功!");
            } else {
                System.out.println("更新数据失败!");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //7、释放资源
            //避免空指针异常
            if (statement != null){
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3、各个对象功能详解

1、DriverManager

驱动管理对象

① 注册驱动

作用:告诉程序该使用哪一个数据库驱动jar

//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");   
//类加载会执行静态代码块中的注册驱动方法
static void registerDriver(Driver driver)   //注册与给定的驱动程序 DriverManager 。

通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块,随着类加载自动执行。

static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());    //真正注册驱动的代码
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

注意:MySQL 5之后的驱动jar包可以省略注册驱动的步骤。

在这个文件中:libs\mysql-connector-java-5.1.37-bin.jar\META-INF\services\java.sql.Driver

com.mysql.jdbc.Driver  //如果没有显示注册驱动(),这句话会帮你注册驱动     
com.mysql.fabric.jdbc.FabricMySQLDriver

② 获取数据库连接

方法

//2、获取数据库连接对象
Connection connection = DriverManager.getConnection(String url, String user, String password);
//DriverManager中的静态方法
static Connection getConnection(String url, String user, String password) 

参数

  • url:指定连接的路径

    • 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称

    • 例子:jdbc:mysql://localhost:3306/db3

    • 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306
      则url可以简写为:jdbc:mysql:///数据库名称,即ip:端口可以省略

  • user:用户名

  • password:密码


2、Connection

数据库连接对象

① 获取执行sql的对象
  • Statement createStatement()
  • PreparedStatement prepareStatement(String sql)
② 管理事务
  • 开启事务:setAutoCommit(boolean autoCommit) 调用该方法设置参数为false,即开启事务
  • 提交事务:commit()
  • 回滚事务:rollback()

3、Statement

执行sql的对象, 但是存在SQL注入风险, 实际一般使用 PreparedStatement

执行SQL的方法有:

  • boolean execute(String sql) :可以执行任意的sql 了解,很少用
  • int executeUpdate(String sql)
    • 执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句。
    • 返回值:影响的行数
      • 如果执行DML语句:返回值 > 0的则执行成功;反之,则失败。
      • 如果执行DDL语句:返回值则是0。(很少用)
  • ResultSet executeQuery(String sql) :执行DQL(select)语句

4、ResultSet

结果集对象,封装查询结果

id name balance
1 Smith 3000
2 Jack 5000
3 Rose 4000

取得结果集对象时,指针指向结果集的第一行(表头)

//执行sql语句,DQL
ResultSet resultSet = statement.executeQuery(sql);
//循环遍历结果集
while(resultSet.next()){
    //获取数据,每次只能获取一个字段
    int id = resultSet.getInt(1);      //获取第一列的字段
    String name = resultSet.getString("name");  //获取列名为name的字段
    double balance = resultSet.getDouble(3);   

    System.out.println(id + "---" + name + "---" + balance);
}
//释放资源的顺序,先开启,后释放原则,即resultSet.close() --> statement.close() --> connection.close()   

使用步骤:
1. 游标向下移动一行
2. 判断是否有数据
3. 获取数据

相关方法:

  1. boolean next(): 游标尝试向下移动一行,如果成功,则返回true,如果不成功(已是最后一行),则返回false.

  2. getXxx(参数): 获取数据

    • Xxx:代表数据类型 如: int getInt() , String getString()
      • 参数:

        1. int:代表列的编号, 从1开始,获取第几列的字段 如: getString(1)
        2. String:代表列名称。 如: getDouble("balance")

作业:定义一个方法,查询db_test库中my_stu表的数据将其封装为对象,然后装载集合,返回。

/**
 * 封装my_stu表数据的JavaBean,必须有无参构造函数
 */
public class Stu {
    private int id;
    private String name;
    private int class_id;
    
    //无参构造函数、Getter、Setter、toString()
}
/**
 * 定义一个方法,查询db_test库中my_stu表的数据将其封装为对象,然后装载集合,返回。
 */
public class JdbcDemo3 {
    public static void main(String[] args) {
        List list = new JdbcDemo3().findAll();
        System.out.println(list);
    }
    
    /**
     * 查询所有stu对象的方法
     * @return
     */
    public List findAll(){
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        List stuList = null;

        try {
            //1 注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2 获取connection对象
            connection = DriverManager.getConnection(
                    "jdbc:mysql:///db_test","root","whyzazn");
            //3 获取statement对象
            statement = connection.createStatement();
            //4 定义sql语句
            String sql = "select * from my_stu";
            //5 执行sql语句,DQL
            resultSet = statement.executeQuery(sql);
            //6 遍历结果集,封装为对象
            Stu stu = null;//把引用写在循环外面,避免重复创建引用
            stuList = new ArrayList<>();
            while(resultSet.next()){
                //List中装的是对象,每一次都要创建一个新对象加入
                stu = new Stu();
                stu.setId(resultSet.getInt("id"));
                stu.setName(resultSet.getString("name"));
                stu.setClass_id(resultSet.getInt(3));
                //将封装对象加入集合
                stuList.add(stu);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //7 释放资源
            if (resultSet != null){
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return stuList;
    }
}

5、PreparedStatement

执行SQL的对象,其中的SQL是预编译的,参数使用 ? 作为占位符

① Statement对象存在的SQL注入问题

在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题

例如:用户输入账号密码时,用户名随便输入,密码:a' or 'a' = 'a

当执行用户名密码判断时:

select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a' 
-- false && false || true = true , 形成了万能密码    
② 解决SQL注入问题

使用PreparedStatement对象

步骤:

  1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar

  2. 注册驱动

  3. 获取数据库连接对象 connection

  4. 定义sql

**注意:sql的参数使用?作为占位符。**

```sql
select * from user where username = ? and password = ?;
```
  1. 获取执行sql语句的对象
```java
PreparedStatement preparedStatement = connection.prepareStatement(String sql); //需要传sql
```
  1. 给?赋值
方法: setXxx(index, value)

- index:?的位置,编号 从1 开始
- value:?的值

```java
preparedStatement.setString(1,username);  //给第一个 ? 赋值
preparedStatement.setString(2,password);  //给第二个 ? 赋值
```
  1. 执行sql,接受返回结果,不需要传递sql语句
```java
ResultSet resultSet = preparedStatement.executeQuery();
```
  1. 处理结果

  2. 释放资源

注意:后期都会使用PreparedStatement来完成增删改查的所有操作

	1. 可以防止SQL注入
	2. 效率更高

2.8、JDBCUtils

抽取JDBC工具类,目的是简化书写

1、分析

  1. 静态代码块中,注册驱动和读取配置文件,只需加载一次

  2. 抽取获取连接对象Connection的方法

    • 需求:不想传递参数(麻烦),还得保证工具类的通用性。
    • 解决:配置文件
      jdbc.properties
      url=
      user=
      password=
  3. 抽取一个方法释放资源


2、代码实现

1、编写工具类
public class JDBCUtils {
    private static String url;
    private static String user;
    private static String password;
    private static String driver;
    /**
     * 文件的读取,只需要读取一次即可拿到这些值。使用静态代码块
     */
    static {

        try {
            //1. 创建Properties集合类。
            Properties pro = new Properties();

            //获取src路径下的文件的方式--->ClassLoader 类加载器
            ClassLoader classLoader = JDBCUtils.class.getClassLoader();
            URL resource = classLoader.getResource("jdbc.properties");
            String path = resource.getPath(); //得到绝对路径

            //2. 加载文件
            pro.load(new FileReader(path));

            //3. 获取数据,赋值
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");

            //4. 注册驱动
            Class.forName(driver);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接,通过配置文件获取参数
     * @return 连接对象
     */
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url,user,password);
    }

    /**
     * 释放资源
     * @param statement
     * @param connection
     */
    public static void close(Statement statement, Connection connection){
        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 释放资源
     * @param resultSet
     * @param statement
     * @param connection
     */
    public static void close(ResultSet resultSet, Statement statement, Connection connection){
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
2、使用工具类
public class JdbcDemo4 {
    public static void main(String[] args) {
        List list = new JdbcDemo4().findAll();
        System.out.println(list);
    }
    /**
     * 演示JDBCUtils
     * @return
     */
    public List findAll(){
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        List stuList = null;

        try {
            //1 注册驱动
            //2 获取connection对象
            connection = JDBCUtils.getConnection();
            //3 获取statement对象
            statement = connection.createStatement();
            //4 定义sql语句
            String sql = "select * from my_stu";
            //5 执行sql语句,DQL
            resultSet = statement.executeQuery(sql);
            //6 遍历结果集,封装为对象
            Stu stu = null;
            stuList = new ArrayList<>();
            while(resultSet.next()){
                //List中装的是对象,每一次都要创建一个新对象加入
                stu = new Stu();
                stu.setId(resultSet.getInt("id"));
                stu.setName(resultSet.getString("name"));
                stu.setClass_id(resultSet.getInt(3));
                //将封装对象加入集合
                stuList.add(stu);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //7 释放资源
			JDBCUtils.close(resultSet,statement,connection);
        }
        return stuList;
    }
}

3、练习

需求:

  1. 通过键盘录入用户名和密码

  2. 判断用户是否登录成功, 防止sql注入(PreparedStatement)

- select * from user where username = ? and password = ?;

* 如果这个sql有查询结果,则成功;反之,则失败

步骤:

  1. 创建数据库表 user

    CREATE TABLE `user`(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	`username` VARCHAR(20) NOT NULL,
    	`password` VARCHAR(32) NOT NULL
    	);
    
    INSERT INTO `user` VALUES(NULL,'张三','zhangsan');
    INSERT INTO `user` VALUES(NULL,'李四','lisi');
    
  2. 代码实现

    public class JdbcDemo6 {
        public static void main(String[] args) {
            //1、键盘录入,接收用户名和密码
            Scanner scanner = new Scanner(System.in);
            System.out.println("请输入用户名:");
            String username = scanner.nextLine();
            System.out.println("请输入密码:");
            String password = scanner.nextLine();
            //2、验证账号合法性
            boolean flag = new JdbcDemo6().check(username, password);
            if (flag){
                System.out.println("登陆成功");
            } else {
                System.out.println("登陆失败");
            }
    
        }
    
        /**
         * 验证用户名和密码的方法
         * @param username
         * @param password
         * @return
         */
        public boolean check(String username, String password){
            if (username == null || password == null){
                return false;
            }
            //连接数据库判断是否登陆成功
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            try {
                //获取connection对象
                connection = JDBCUtils.getConnection();
                String sql = "select * from user where username = ? and password = ?";
                //获取preparedStatement对象
                preparedStatement = connection.prepareStatement(sql);
                //给sql的 ? 赋值
                preparedStatement.setString(1,username);
                preparedStatement.setString(2,password);
                //获取查询结果
                resultSet = preparedStatement.executeQuery();
                return resultSet.next();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                //关闭连接
                JDBCUtils.close(resultSet,preparedStatement,connection);
            }
            return false;
        }
    }
    

2.9、JDBC控制事务

  1. 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
  2. 操作:
    1. 开启事务
    2. 提交事务
    3. 回滚事务
  3. 使用Connection对象来管理事务
    • 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
      • 在执行sql之前开启事务
    • 提交事务:commit()
      • 当所有sql都执行完提交事务
    • 回滚事务:rollback()
      • 在catch中回滚事务
  4. 代码实现
public class JdbcDemo7 {
    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement1 = null;
        PreparedStatement preparedStatement2 = null;

        try {
            //1.获取连接
            connection = JDBCUtils.getConnection();
            //开启事务
            connection.setAutoCommit(false);

            //2.定义sql
            //2.1 张三 - 500
            String sql1 = "update bank set balance = balance - ? where id = ?";
            //2.2 李四 + 500
            String sql2 = "update bank set balance = balance + ? where id = ?";
            //3.获取执行sql对象
            preparedStatement1 = connection.prepareStatement(sql1);
            preparedStatement2 = connection.prepareStatement(sql2);
            //4. 设置参数
            preparedStatement1.setDouble(1, 500);
            preparedStatement1.setInt(2, 1);

            preparedStatement2.setDouble(1, 500);
            preparedStatement2.setInt(2, 2);
            //5.执行sql
            preparedStatement1.executeUpdate();  //张三-500
            preparedStatement2.executeUpdate();  //李四+500
            //提交事务
            connection.commit();
        } catch (Exception e) {
            //捕捉到异常后,进行事务回滚
            try {
                if (connection != null) {
                    connection.rollback();
                }
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        } finally {
            JDBCUtils.close(preparedStatement1, connection);
            JDBCUtils.close(preparedStatement2, null);
        }
    }
}

2.10、数据库连接池

1、简介

JDBC传统模式开发存在的主要问题
  • 时间和内存资源消耗巨大

    普通的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再根据JDBC代码(或配置文件)中的用户名和密码进行验证其正确性。这一过程一般会花费0.05~1s,一旦需要数据库连接的时候就必须向数据库请求一个,执行完后再断开连接。显然,如果同一个数据库在同一时间有数十人甚至上百人请求连接势必会占用大量的系统资源,严重的会导致服务器崩溃。

  • 有内存泄漏的风险

    因为每一次数据库连接使用完后都需要断开连接,但如果程序出现异常致使连接未能及时关闭,这样就可能导致内存泄漏,最终只能以重启数据库的方法来解决;另外使用传统JDBC模式开发不能控制需要创建的连接数,系统一般会将资源大量分出给连接以防止资源不够用,如果连接数超出一定数量也会有极大的可能导致内存泄漏。    


数据库连接池概念
  • 其实就是一个容器(集合),存放数据库连接的容器。
  • 当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象。
  • 当请求连接超过最大连接数时,会将多余的请求加入缓冲队列。
  • 用户访问完之后,会将连接对象归还给容器。

好处
  • 资源的高效利用

    由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销,减小了系统资源消耗的同时也提高了系统运行环境的平稳性。

  • 更快的系统反应速度

    数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接可以避免数据库在连接初始化和释放过程所需的时间开销,从而减少了系统的响应时间,提高了系统的反应速度。

  • 减少了资源独占的风险

    新的资源分配手段对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置实现对某一应用最大可用数据库连接数的限制,避免了应用独占所有数据库资源的风险。

  • 统一的连接管理,避免数据库连接泄露

    在实现较为完善的数据库连接池时,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。


实现
  1. 标准接口:javax.sql包下的DataSource,该接口由驱动程序供应商去实现。
  2. 通过DataSource对象访问的驱动程序不会向DriverManager注册自己。 而是通过查找操作检索DataSource对象,然后用于创建Connection对象。 通过基本实现,通过DataSource对象获得的连接与通过DriverManager工具获得的连接相同。
    • 获取连接:getConnection()
    • 归还连接:Connection.close()。如果连接对象Connection是从连接池中获取的,那么调用Connection.close()方法,则不会再关闭连接了。而是归还连接
  3. 一般我们不去实现它,有数据库厂商来实现
    1. C3P0:数据库连接池技术
    2. Druid:数据库连接池实现技术,由阿里巴巴提供的

2、C3P0

步骤:

  1. 导入jar包 (两个):

    • c3p0-0.9.5.2.jar
    • mchange-commons-java-0.2.12.jar

    不要忘记导入数据库驱动jar包

  2. 定义配置文件

    • 指定名称: c3p0.properties 或者 c3p0-config.xml
    • 路径:直接将文件放在src目录下即可。

    满足以上两个条件之后,程序会自动读取配置文件。

  3. 创建核心对象 数据库连接池对象 ComboPooledDataSource

  4. 获取连接: getConnection

  5. 归还连接: connection.close() 并不会关闭连接


代码:

//1 创建数据库连接池对象,指定使用哪个配置,如果不传参数,则使用默认配置
    DataSource dataSource = new ComboPooledDataSource("xiao");
//2 获取连接对象
    Connection connection = dataSource.getConnection();
	...
//3 归还连接
    connection.close();

3、Druid德鲁伊

步骤:

  1. 导入jar包 druid-1.0.9.jar

  2. 定义配置文件

    • 是properties形式的

    • 可以叫任意名称,可以放在任意目录下

  3. 加载配置文件。Properties

  4. 获取数据库连接池对象:通过工厂来来获取 DruidDataSourceFactory

  5. 获取连接:getConnection

  6. 归还连接: connection.close() 并不会关闭连接


代码:

//1 加载配置文件
    Properties properties = new Properties();
	//方法1:使用文件流
    properties.load(new FileInputStream("src//druid.properties"));
	//方法2:使用类加载器
	InputStream is = DruidDemo1.class.getClassLoader().getResourceAsStream("druid.properties");
    properties.load(is);
//2 获取连接池对象
	DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//3 获取connection对象
	Connection connection = dataSource.getConnection();
	...
//4 归还连接
    connection.close();

4、Druid_JDBCUtils

定义工具类

  1. 定义一个类 JDBCUtils
  2. 提供静态代码块加载配置文件,初始化连接池对象
  3. 提供方法
    1. 获取连接方法:通过数据库连接池获取连接
    2. 释放资源
    3. 获取连接池的方法
  4. 代码实现
public class Druid_JDBCUtils {
    // 定义成员变量DataSource
    private static DataSource dataSource;
    static {

        try {
            // 加载配置文件
            Properties properties = new Properties();
            properties.load(new FileInputStream("src//druid.properties"));
            // 获取DataSource对象
            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    /***
     * 获取DataSource对象
     * @return
     */
    public static DataSource getDataSource(){
        return dataSource;
    }

    /**
     * 释放资源
     * @param statement
     * @param connection
     */
    public static void close(Statement statement,Connection connection){
        close(null,statement,connection);
    }

    /**
     * 释放资源
     * @param resultSet
     * @param statement
     * @param connection
     */
    public static void close(ResultSet resultSet, Statement statement, Connection connection){
        if (resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if (connection != connection){
            try {
                connection.close();//归还连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}


2.11、Spring JDBC

JDBC已经能够满足大部分用户最基本的需求,但是在使用JDBC时,必须自己来管理数据库资源如:获取PreparedStatement,设置SQL语句参数,关闭连接等步骤。

JdbcTemplate是Spring对JDBC的封装,目的是使JDBC更加易于使用。JdbcTemplate是Spring的一部分。JdbcTemplate处理了资源的建立和释放。他帮助我们避免一些常见的错误,比如忘了总要关闭连接。他运行核心的JDBC工作流,如Statement的建立和执行,而我们只需要提供SQL语句和提取结果。

使用JdbcTemplate步骤:

  1. 导入jar包(5个)

    commons-logging-1.2.jar
    spring-beans-5.0.0.RELEASE.jar
    spring-core-5.0.0.RELEASE.jar
    spring-jdbc-5.0.0.RELEASE.jar
    spring-tx-5.0.0.RELEASE.jar
    
  2. 创建JdbcTemplate对象。依赖于数据源DataSource

    JdbcTemplate template = new JdbcTemplate(ds);
    
  3. 调用JdbcTemplate的方法来完成CRUD的操作

    • update():执行DML语句。增、删、改语句

      • jdbc默认返回的是匹配的行数,就算修改为原先一样的数据,也会返回1;
      • MySQL返回的是影响的行数,修改为原先一样的数据,返回值为0。
    • queryForMap():查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合

      • 注意:这个方法查询的结果集长度只能是1
    • queryForList():查询结果将结果集封装为list集合

      • 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
    • query():查询结果,将结果封装为JavaBean对象

      query的参数:RowMapper

      • 一般我们使用BeanPropertyRowMapper实现类。可以完成数据到JavaBean的自动封装

      • new BeanPropertyRowMapper<类型>(类型.class)

      • queryForObject():查询结果,将结果封装为对象

      • 一般用于聚合函数的查询
  4. 练习

public class JdbcTemplateDemo1 {

    //1 获取JdbcTemplate对象
    private JdbcTemplate template = new JdbcTemplate(Druid_JDBCUtils.getDataSource());
    //2 调用方法

    //需求:
    //1. 修改1号数据的 salary 为 10000
    @Test
    public void test1(){
        String sql1 = "update user set password = ? where id = ?";
        int cnt = template.update(sql1,"ZHANGSAN",1);
        System.out.println(cnt);
    }

    //2. 添加一条记录
    @Test
    public void test2(){
        String sql2 = "insert into user values(?,?,?)";
        int cnt = template.update(sql2,3,"王五","wangwu");
        System.out.println(cnt);
    }

    //3. 删除刚才添加的记录
    @Test
    public void test3(){
        String sql3 = "delete from user where id = ?";
        int cnt = template.update(sql3,3);
        System.out.println(cnt);
    }

    //4. 查询id为1的记录,将其封装为Map集合
    @Test
    public void test4(){
        String sql4 = "select * from user where id = ?";
        Map map = template.queryForMap(sql4, 1);
        System.out.println(map);
    }

    //5. 查询所有记录,将其封装为List
    @Test
    public void test5(){
        String sql5 = "select * from user";
        List> maps = template.queryForList(sql5);
        for (Map map : maps) {
            System.out.println(map);
        }
    }

    //6. 查询所有记录,将其封装为User对象的List集合
    //方法1:自己写实现类
    @Test
    public void test6_1(){
        String sql6 = "select * from user";
        //匿名内部类
        List list = template.query(sql6, new RowMapper() {

            @Override
            public User mapRow(ResultSet resultSet, int i) throws SQLException {
                User user = new User();
                Integer id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                String password = resultSet.getString(3);
                user.setId(id);
                user.setName(name);
                user.setPassword(password);
                return user;
            }
        });

        for (User user : list) {
            System.out.println(user);
        }
    }

    //方法2:调用现成的实现类,传入泛型和字节码文件类型
    @Test
    public void test6_2(){
        String sql6 = "select * from user";
        List list = template.query(sql6, new BeanPropertyRowMapper(User.class));
        for (User user : list) {
            System.out.println(user);
        }
    }

    //7. 查询总记录数
    @Test
    public void test7(){
        String sql7 = "select count(id) from user";
        Integer total = template.queryForObject(sql7, Integer.class);
        System.out.println(total);
    }

}

3、静态网页技术(前端)

3.1、web概念概述

1、软件架构

C/S: Client/Server 客户端/服务器端

  • 在用户本地有一个客户端程序,在远程有一个服务器端程序

  • 如:QQ,迅雷...

  • 优点:

    用户体验好

  • 缺点:

    开发、安装,部署,维护 麻烦

B/S: Browser/Server 浏览器/服务器端

  • 只需要一个浏览器,用户通过不同的网址(URL),客户访问不同的服务器端程序
  • 优点:

    开发、安装,部署,维护 简单

  • 缺点:

    1. 如果应用过大,用户的体验可能会受到影响
    2. 对硬件要求过高

2、B/S架构详解

资源分类:

  1. 静态资源:

    使用静态网页开发技术发布的资源。

    特点:

    • 所有用户访问,得到的结果是一样的。
    • 如:文本,图片,音频、视频, HTML,CSS,JavaScript
    • 如果用户请求的是静态资源,那么服务器会直接将静态资源发送给浏览器。浏览器中内置了静态资源的解析引擎,可以展示静态资源
  2. 动态资源:

    使用动态网页及时发布的资源。

    特点:

    • 所有用户访问,得到的结果可能不一样。
    • 如:jsp/servlet,php,asp...
    • 如果用户请求的是动态资源,那么服务器会执行动态资源,转换为静态资源,再发送给浏览器

我们要学习动态资源,必须先学习静态资源!

静态资源:

  • HTML:用于搭建基础网页,展示页面的内容
  • CSS:用于美化页面,布局页面
  • JavaScript:控制页面的元素,让页面有一些动态的效果(并不是动态资源)

3、网络通信三要素

  • IP:电子设备(计算机)在网络中的唯一标识。
  • 端口:应用程序在计算机中的唯一标识。 0~65536
    • 1024以前的端口可能被已有的服务占用了,尽量不要使用
  • 传输协议:规定了数据传输的规则
    • 基础协议:
      1. tcp: 可靠协议,三次握手。 速度稍慢
      2. udp:不安全协议。 速度快

3.2、HTML

Hyper Text Markup Language 超文本标记语言,结构化标准语言,是最基础的网页开发语言。

超文本

  • 超文本是用超链接的方法,将各种不同空间的文字信息组织在一起的网状文本.

标记语言

  • 由标签构成的语言。<标签名称> 如 html,xml

  • 标记语言不是编程语言


1、标签

HTML 标签参考手册 - 功能排序 (w3school.com.cn)

1、文件标签

构成html最基本的标签

  • :html文档的根标签

  • :头标签。用于指定html文档的一些属性。引入外部的资源

    
        
           
    	
    
  • :标题标签。

  • :体标签

  • :html5中定义该文档是html文档


2、文本标签

和文本有关的标签

  • 注释:
  • to
    :标题标签,h1~h6:字体大小逐渐递减
  • :段落标签

  • :换行标签

  • :展示一条水平线
  • : 字体斜体
  • : 定义强调文本。
    • 显示效果相同,但是意义不同。
    • : 仅仅是一种样式/风格需求。
    • : 表示“强调”的意思,搜索引擎会了解这些语义,一些屏幕阅读器可能使用不同的inflection,更利于SEO。
  • : 字体加粗
  • :定义语气更为强烈的强调文本。
    • 显示效果相同,但是意义不同
    • 为了加粗而加粗,是一种样式/风格需求。
    • 为了标明重点而加粗,表示该文本比较重要,提醒读者/终端注意。
  • :字体标签,HTML5 中不支持。请使用 CSS 代替。
  • : 居中标签。HTML5 中不支持。请使用 CSS 代替。

文本属性

  • color:
    1. 英文单词:red,green,blue
    2. rgb(值1,值2,值3):值的范围:0~255 如 rgb(0,0,255)
    3. #值1值2值3:值的范围:00~FF之间。如: #FF00FF
  • width:
    1. 数值:width='20' ,数值的单位,默认是 px(像素)
    2. 数值%:占比相对于父元素的比例

特殊符号(需要什么符号去百度查)

  • 空格键的空格:无论多少个,只显示一个
  • 空格: 
  • 大于符号(greater than):>
  • 小于符号(less than):<
  • 版权符号?:©

3、图片标签

:展示图片

属性:

  • src(必填):图片地址,可以填相对地址(推荐)和绝对地址,../ 代表上一级目录。
  • alt(必填 ):图片加载失败会返回的内容(图片地址/图片名不对等原因)
  • title:鼠标悬停显示的文字
  • width : 图片规格pixel 像素
  • height : ...
  • ...

4、列表标签
  • 有序列表(unordered list)

    可以通过type属性指定序号的样式,start属性指定序号从哪个数字开始

    1. Java
    2. Python
    3. C
    4. C++
    1. Java
    2. Python
    3. C
    4. C++
  • 无序列表(order list)

    可以通过type属性指定序号的样式

    • Java
    • Python
    • C
    • C++
  • 自定义列表(definition list)

    机器语言
    Java
    Python
    C
    C++
    Go

5、链接标签




  图片加载失败了

这里是顶部
... 回到顶部
跳转到图像标签页的底部
图片加载失败了
  • 超文本链接

    
    点击跳转到百度
    
  • 锚链接

    
    
    
    这里是顶部
    ... 回到顶部
    跳转到图像标签页的底部
  • 功能性链接

    
    
    
    点击联系我
    点击这里给我发消息

  • 6、页面结构标签

    html5中为了提高程序的可读性,提供了一些标签。

    • : 标题头部区域的内容(用于页面或页面中的一块区域)
    • : 标记脚部区域的内容(用于整个页面或页面的一块区域)
    • : Web页面中的一块独立的区域
    • : 独立的文章内容
    • : 相关内容或应用(常用于侧边栏)
    • : 导航类辅助内容

    7、表格标签table
    • table:定义表格
      • width:宽度
      • border:边框
      • cellpadding:定义内容和单元格的距离
      • cellspacing:定义单元格之间的距离。如果指定为0,则单元格的线会合为一条、
      • bgcolor:背景色
      • align:对齐方式
    • tr:定义行
      • bgcolor:背景色
      • align:对齐方式
    • td:定义单元格
      • colspan:合并列
      • rowspan:合并行
    • th:定义表头单元格
    • :表格标题
    • :表示表格的头部分
    • :表示表格的体部分
    • :表示表格的脚部分
    1-1
    2-1 2-2 2-3 2-4
    3-2 3-3 3-4

    8、
    • :每一个div占满一整行。块级标签
    • :文本信息在一行展示,行内标签 内联标签

    9、视频和音频
    
    
    
    
    
    
    
    

    10、内联框架iframe
    
    
    
    点击跳转到B站

    2、表单form(内容多)

    用于采集用户输入的数据的。用于和服务器进行交互。

    :用于定义表单的。可以定义一个范围,范围代表采集用户数据的范围

    1、表单属性
    • action:指定提交数据的URL,#为当前页面

    • method:指定提交方式(必填),一共7种,2种比较常用

      • get:
        1. 请求参数会在地址栏中显示。会封装到请求行中(HTTP协议后讲解)。
        2. 请求参数大小是有限制的。
        3. 不太安全,但是快速
      • post:
        1. 请求参数不会再地址栏中显示。会封装在请求体中(HTTP协议后讲解)
        2. 请求参数的大小没有限制。
        3. 较为安全,可以传输大文件
      • 如何查看post提交的表单信息? 检查页面元素 -> 网络 -> 提交之后会有个表单文件,点开 -> Payload

    注意:表单项中的数据要想被提交,必须指定其name属性。


    2、表单项标签
    1、

    可以通过type属性值,改变元素展示的样式

    • type: 指定元素的类型,默认为text

      • text:文本输入框,默认值

        • placeholder:指定输入框的提示信息,当输入框的内容发生变化,会自动清空提示信息
      • password:密码输入框

      • radio:单选框

        1. 要想让多个单选框实现单选的效果,则多个单选框的name属性值必须一样。
        2. 一般会给每一个单选框提供value属性,指定其被选中后提交的值
        3. checked属性,可以指定默认值
      • checkbox:复选框

        1. 一般会给每一个单选框提供value属性,指定其被选中后提交的值
        2. checked属性,可以指定默认值
      • file:文件选择框

      • hidden:隐藏域,用于提交一些信息。

      • 按钮:

        1. submit:提交按钮。可以提交表单
        2. button:普通按钮
        3. image:图片提交按钮
    • cols:指定列数,每一行有多少个字符

    • rows:默认多少行。

    • name: 指定表单元素的名称,表单项中的数据要想被提交,必须指定其name属性。 必填

    • value: 元素的初始值。type 为 radio(单选框) 时必须指定一个值

    • size: 指定表单元素的初始宽度。当type为text或password时,表单元素的大小以字符为单位。对于其他类型,宽度以像素为单位

    • maxlength: type为text或password时,输入的最大字符数

    • checked:type为radio或checkbox时,指定按钮是否是被选中

    • readonly: 只读

    • disable: 禁用

    • hidden: 隐藏(可以用来提交一些默认值)

    2、

    指定输入项的文字描述信息

    • label的for属性一般会和 input 的 id属性值 对应。
    • 如果对应了,则鼠标移至label区域,会让input输入框获取焦点,点击label也相当于点击input
    3、

    下拉列表

    • 子元素:option,指定列表项
    • selected: 默认下拉框的选项
    4、

    文本域

    • cols:指定列数,每一行有多少个字符
    • rows:默认多少行。

    3、表单中元素值的初级验证
    • placeholder:提示信息
    • required:非空验证
    • pattern: 正则表达式

    4、代码举例

    名字:

    密码:

    性别:

    爱好: 敲代码 唱歌 游戏 聊天 睡觉

    按钮:

    国家(下拉框):

    反馈:

    提交文件:

    邮箱:

    自定义邮箱:

    网址:

    商品数量:

    音量:

    搜索:

    注册post提交

    名字:

    密码:


    3.3、CSS

    1、 什么是CSS

    Cascading Style Sheet(层叠/级联样式表)

    层叠:多个样式可以作用在同一个html的元素上,同时生效

    2、作用

    表现层,美化网页

    • 内容
    • 字体
    • 颜色
    • 边距
    • 宽高
    • 背景图片
    • 网页浮动
    • ...

    3、优势

    • 内容和表现分离
    • 网页结构表现统一,可以实现复用
    • 样式十分丰富
    • 建议使用独立于html的css文件
    • 利用SEO, 容易被搜索引擎收录

    4、CSS的三种导入方式

    • 内部样式
    • 外部样式
    • 行内样式
    
    
    
        
        Title
    
      
      
    
      
    
      
    
      
    
    
    
    
    
    
    
    

    标题

    拓展:外部样式的两种写法

    • 链接式:link标签,只能在html源代码中使用

        
        
      
    • 导入式:

      @import是 CSS2.1 特有的

      
        
      

      首页link和import语法结构不同,前者 link 是html标签,只能放入html源代码中使用,后者可看作为css样式,作用是引入css样式功能。import在html使用时候需要 style 标签,同时可以直接“@import url(CSS文件路径地址);”放如css文件或css代码里引入其它css文件。

      本质上两者使用选择区别不大,但为了软件中编辑布局网页html代码,一般使用link较多,也推荐使用link。


    5、(重点)选择器

    作用:选择页面上的某一个或者某一类元素

    5.1、基本选择器

    ? 优先级: 不遵循就近原则,id选择器 > class选择器 > 标签选择器

    1、标签选择器

    ? 格式:标签{},会选择到页面上所有的这个标签的元素

    
    
    
        
        Title
    
      
    
    
    
    

    学java

    学汇编

    学Python

    学C++


    2、类选择器

    CSS 类选择器 (w3school.com.cn)

    ? 格式: .class名称{},好处,可以多个标签归类,是同一个class

    
    
    
        
        Title
    
        
    
    
    
    

    标题1

    标题2

    标题3

    标题4

    标题5


    3、ID选择器

    CSS id 选择器 (w3school.com.cn)

    ? 格式: #id名称{}, ID必须保证全局唯一!

    
    
    
        
        Title
    
      
    
    
    
    
    

    标题3

    标题4


    5.2、高级选择器
    1、层次选择器
    
    
    

    p1

    p2

    p3

    • p4

    • p5

    • p6

    • p7与li同级

    p8

    p9

    1.1、后代选择器:在某个元素的后代所有的同类标签

     /* 1. 后代选择器,选择后代中所有的同类标签,ul标签的id为u_list*/
        #u_list p{
          color: #dc1428;
        }
    

    1.2、子选择器: 选择下一个层次的标签

    /* 2. 子选择器,选择下一个层次的同类标签,ul标签的id为u_list */
        #u_list > li{
          color: crimson;
        }
    

    1.3、相邻兄弟选择器(弟弟选择器,不选自己)

     /* 3. 相邻兄弟选择器 同辈,对下不对上(弟弟选择器),p2和p8的class都是active */
        .active + p{
          color: crimson;
        }
    

    1.4、通用选择器(所有弟弟选择器,不选自己)

    /* 4. 通用选择器 选择所有同辈,对下不对上(所有弟弟选择器)*/
        .active ~ p{
            color: crimson;
        }
    

    2、结构伪类选择器(带冒号,不常用)

    CSS 类选择器详解 (w3school.com.cn)

    
    
    
        
        Title
        
      
    
    
    
        
    

    h1

    p1

    p2

    p3

    • li1
    • li2
    • li3

    3、属性选择器(常用)
    
    
    
        
        Title
    
        
    
    
    
      

    链接1 链接2 链接3 链接4 链接5 链接6 链接7 链接8 链接9








    标题1


    6、美化网页元素

    6.1、span标签
    
    
    
        
        Title
      
    
    
    
    欢迎学习java
    
    
    
    


    6.2、字体样式

    CSS 字体 (w3school.com.cn)

    
    
    
        
        Title
    
      
    
    
    
    

    故事简介

    Thank you!
    “潘子!”我惊了一下,但是没法靠过去看。
    对方道:“小三爷,快走。”声音相当微弱。
    接着我听到一连串的咳嗽声。“你怎么样?”我问道,“你怎么会在这儿?”
    潘子在黑暗中说道:“说来话长了,小三爷,你有烟吗?”“在这儿你还抽烟,不怕肺烧穿?”
    我听着潘子的语气,觉得他特别地淡定,忽然起了一种非常不详的预感。

    6.3、文本样式

    一般都是现用现找

    CSS 文本 (w3school.com.cn)

    1、颜色
    2、文本对齐方式
    3、首行缩进
    4、行高
    5、装饰
    6.4、超链接伪类

    CSS 伪类 (w3school.com.cn)

    /* 未访问的链接 */
    a:link {
      color: #FF0000;
    }
    
    /* 已访问的链接 */
    a:visited {
      color: #00FF00;
    }
    
    /* 鼠标悬停链接 */
    a:hover {
      color: #FF00FF;
    }
    
    /* 已选择的链接 */
    a:active {
      color: #0000FF;
    }
    
    6.5、列表
    
    
    
        
        Title
    
      
    
    
    
    
    
    
    
    
    #nav{
        width: 200px;
    }
    .title{
        color: #d61326;
        /*粗体*/
        font-weight: bold;
        /*首行间距*/
        text-indent: 1cm;
        /*行高*/
        line-height: 35px;
        background: cyan;
    }
    
    ul li{
        height: 30px;
        /*无列表样式*/
        list-style: none;
        /*首行间距*/
        text-indent: 1cm;
        background: #dcd914;
    }
    
    a{
        /*无下划线*/
        text-decoration-line: none;
    }
    

    6.6、背景图像

    CSS 背景图像 (w3school.com.cn)

    CSS 背景简写 (w3school.com.cn)

    在使用简写属性时,属性值的顺序为:

    • background-color 颜色
    • background-image 图像
    • background-repeat 平铺方式
    • background-attachment
    • background-position
    属性 描述
    background 在一条声明中设置所有背景属性的简写属性。
    background-attachment 设置背景图像是固定的还是与页面的其余部分一起滚动。
    background-clip 规定背景的绘制区域。
    background-color 设置元素的背景色。
    background-image 设置元素的背景图像。
    background-origin 规定在何处放置背景图像。
    background-position 设置背景图像的开始位置。
    background-repeat 设置背景图像是否及如何重复。
    background-size 规定背景图像的尺寸。
    6.7、渐变

    CSS 渐变 (w3school.com.cn)

    CSS 渐变使您可以显示两种或多种指定颜色之间的平滑过渡。

    CSS 定义了两种渐变类型:

    • 线性渐变(向下/向上/向左/向右/对角线)
    • 径向渐变(由其中心定义)

    如需创建线性渐变(默认从上到下),您必须定义至少两个色标。色标是您要呈现平滑过渡的颜色。您还可以设置起点和方向(或角度)以及渐变效果。

    语法

    background-image: linear-gradient(direction, color-stop1, color-stop2, ...);
    

    7、盒子模型

    7.1、margin:外边距

    CSS 外边距 (w3school.com.cn)

    CSS 拥有用于为元素的每一侧指定外边距的属性:(默认顺序 上右下左 顺时针)

    • margin-top
    • margin-right
    • margin-bottom
    • margin-left

    所有外边距属性都可以设置以下值:

    • auto - 浏览器来计算外边距
    • length - 以 px、pt、cm 等单位指定外边距
    • % - 指定以包含元素宽度的百分比计的外边距
    • inherit - 指定应从父元素继承外边距

    提示:允许负值。


    7.2、padding:内边距

    CSS 内边距 (w3school.com.cn)

    CSS 拥有用于为元素的每一侧指定内边距的属性:(默认顺序 上右下左 顺时针

    • padding-top
    • padding-right
    • padding-bottom
    • padding-left

    所有内边距属性都可以设置以下值:

    • length - 以 px、pt、cm 等单位指定内边距
    • % - 指定以包含元素宽度的百分比计的内边距
    • inherit - 指定应从父元素继承内边距

    提示:不允许负值。


    7.3、border:边框

    默认顺序 -> border: (线宽 样式 颜色)

    CSS 边框 (w3school.com.cn)

    1、边框宽度width

    border-width 属性指定四个边框的宽度。

    可以将宽度设置为特定大小(以 px、pt、cm、em 计),也可以使用以下三个预定义值之一:thin、medium 或 thick


    2、边框样式style

    border-style 属性指定要显示的边框类型。

    允许以下值:

    • dotted - 定义点线边框
    • dashed - 定义虚线边框
    • solid - 定义实线边框
    • double - 定义双边框
    • groove - 定义 3D 坡口边框。效果取决于 border-color 值
    • ridge - 定义 3D 脊线边框。效果取决于 border-color 值
    • inset - 定义 3D inset 边框。效果取决于 border-color 值
    • outset - 定义 3D outset 边框。效果取决于 border-color 值
    • none - 定义无边框
    • hidden - 定义隐藏边框

    border-style 属性可以设置一到四个值(用于上边框、右边框、下边框和左边框)。


    3、边框颜色color

    border-color 属性用于设置四个边框的颜色。

    可以通过以下方式设置颜色:

    • name - 指定颜色名,比如 "red"
    • HEX - 指定十六进制值,比如 "#ff0000"
    • RGB - 指定 RGB 值,比如 "rgb(255,0,0)"
    • HSL - 指定 HSL 值,比如 "hsl(0, 100%, 50%)"
    • transparent

    注释:如果未设置 border-color,则它将继承元素的颜色。

    7.4、圆角边框和阴影

    CSS 圆角边框 (w3school.com.cn)

    圆角边框border-radius

    属性用于向元素添加圆角边框

    p {
      border: 2px solid red;
      border-radius: 5px;
    }
    

    CSS 阴影效果 (w3school.com.cn)

    • text-shadow
    • box-shadow

    文字阴影text-shadow

    CSS text-shadow 属性为文本添加阴影。

    最简单的用法是只指定水平阴影(2px)和垂直阴影(2px)

    h1 {
      text-shadow: 2px 2px red;
    }
    

    盒子阴影box-shadow

    CSS Box Shadow (w3school.com.cn)

    div {
      box-shadow: 10px 10px 5px grey;
    }
    
    7.5、浮动(会离开当前图层)
    1、块级元素,独占一行
    h1-h6   p   div  列表...
    
    2、行内元素,不独占一行
    span   a   img   strong...
    

    行内元素可以被包含在块级元素中,反之不可以。

    3、display

    CSS 布局 - display 属性 (w3school.com.cn)

    display 属性规定是否/如何显示元素。

    每个 HTML 元素都有一个默认的 display 值,具体取决于它的元素类型。大多数元素的默认 display 值为 block (块级)或 inline(行内)。

    描述
    none 此元素不会被显示。
    block 此元素将显示为块级元素,此元素前后会带有换行符。
    inline 默认。此元素会被显示为内联元素,元素前后没有换行符。
    inline-block 行内块元素。(CSS2.1 新增的值)
    list-item 此元素会作为列表显示。
    run-in 此元素会根据上下文作为块级元素或内联元素显示。
    ... ...
    4、浮动float

    CSS 布局 - 浮动和清除 (w3school.com.cn)

    float 属性用于定位和格式化内容,例如让图像向左浮动到容器中的文本那里。

    float 属性可以设置以下值之一:

    • left - 元素浮动到其容器的左侧
    • right - 元素浮动在其容器的右侧
    • none - 元素不会浮动(将显示在文本中刚出现的位置)。默认值。
    • inherit - 元素继承其父级的 float 值

    最简单的用法是,float 属性可实现(报纸上)文字包围图片的效果。


    CSS 布局 - 浮动和清除

    CSS float 属性规定元素如何浮动。

    CSS clear 属性规定哪些元素可以在清除的元素旁边以及在哪一侧浮动。


    如果一个元素比包含它的元素高,并且它是浮动的,它将“溢出”到其容器之外:

    然后我们可以向包含元素添加 overflow: auto;

    
    
    
    
    
    
    
    

    Clearfix

    在本例中,图像高于包含它的元素,然而它被浮动了,所以它会在容器之外溢出:

    W3School Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum...

    请为包含元素添加一个带有 overflow: auto; 的 clearfix 类,以解决此问题:

    W3School Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum...

    但是,新的现代 clearfix hack 技术使用起来更安全,使用伪类 :after

    .clearfix:after {
      content: "";
      clear: both;
      display: table;
    }
    
    7.6、定位

    CSS 布局 - position 属性 (w3school.com.cn)

    所有 CSS 定位属性

    属性 描述
    bottom 设置定位框的底部外边距边缘。
    clip 剪裁绝对定位的元素。
    left 设置定位框的左侧外边距边缘。
    position 规定元素的定位类型。
    right 设置定位框的右侧外边距边缘。
    top 设置定位框的顶部外边距边缘。
    z-index 设置元素的堆叠顺序。
    1、相对定位 position: relative

    CSS 相对定位 (w3school.com.cn)

    相对原始的位置偏移

    如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直或水平位置,让这个元素“相对于”它的起点进行移动。仍然在当前图层(文档流)

    如果将 top 设置为 20px,那么框将在原位置顶部下面 20 像素的地方。如果 left 设置为 30 像素,那么会在元素左边创建 30 像素的空间,也就是将元素向右移动。

    #box_relative {
      position: relative;
      left: 30px;
      top: 20px;
    }
    

    2、绝对定位 position: absolute

    CSS 绝对定位 (w3school.com.cn)

    1、父级元素不存在定位(父级元素没有position属性)的情况下,相对于浏览器定位

    2、父级元素存在定位的情况下,相对于父级元素定位

    绝对定位使元素的位置与文档流无关,因此不占据空间。这一点与相对定位不同,相对定位实际上被看作普通流定位模型的一部分,因为元素的位置相对于它在普通流中的位置。

    普通流中其它元素的布局就像绝对定位的元素不存在一样

    #box_relative {
      position: absolute;
      left: 30px;
      top: 20px;
    }
    

    3、固定定位 position:fixed

    相对于浏览器位置偏移

    p.one
    {
    position:fixed;
    left:5px;
    top:5px;
    }
    
    4、设置当前图层层级 z-index

    3.4、JavaScript(重要)

    详见另一份JavaScript文档


    3.5、Bootstrap

    不用记,需要用的时候去查就行

    Bootstrap 5 教程 (w3school.com.cn)

    1、入门

    • 概念: 一个前端开发的框架,Bootstrap,来自 Twitter,是目前很受欢迎的前端框架。Bootstrap 是基于 HTML、CSS、JavaScript 的,它简洁灵活,使得 Web 开发更加快捷。

    • 框架:一个半成品软件,开发人员可以在框架基础上,再进行开发,简化编码。

    • 好处:
      1. 定义了很多的css样式和js插件。我们开发人员直接可以使用这些样式和插件得到丰富的页面效果。
      2. 响应式布局。
        • 同一套页面可以兼容不同分辨率的设备。
    • 快速入门
      1. 下载用于生产环境的Bootstrap Bootstrap中文网 (bootcss.com)
      2. 在项目中将这三个文件夹复制,依赖JQuery
      3. 创建html页面,引入必要的资源文件
    bootstrap/
    ├── css/
    │   ├── bootstrap.css
    │   ├── bootstrap.css.map
    │   ├── bootstrap.min.css
    │   ├── bootstrap.min.css.map
    │   ├── bootstrap-theme.css
    │   ├── bootstrap-theme.css.map
    │   ├── bootstrap-theme.min.css
    │   └── bootstrap-theme.min.css.map
    ├── js/
    │   ├── bootstrap.js
    │   └── bootstrap.min.js
    └── fonts/
        ├── glyphicons-halflings-regular.eot
        ├── glyphicons-halflings-regular.svg
        ├── glyphicons-halflings-regular.ttf
        ├── glyphicons-halflings-regular.woff
        └── glyphicons-halflings-regular.woff2
    
    
    
    
    
      
      
      
      
      Bootstrap 101 Template
    
      
      
      
      <script src="js/jQuery-3.6.0.js" crossorigin="anonymous"></script>
      
      <script src="js/bootstrap.min.js" crossorigin="anonymous"></script>
    
    
    
    

    你好,世界!


    2、响应式布局

    同一套页面可以兼容不同分辨率的设备。

    • 实现:依赖于栅格系统:将一行平均分成12个格子,可以指定元素占几个格子

      需要注意的是每一个div 的 行 都可以分为12个格子,而不是仅仅指屏幕分为12个格子


    • 工作原理

    • 步骤:

      1. 定义容器。相当于之前的table
        • 容器分类:
          1. container:两边留白
          2. container-fluid:每一种设备都是100%宽度
      2. 定义行。相当于之前的tr 样式:row
      3. 定义元素。指定该元素在不同的设备上,所占的格子数目。样式:col - 设备代号 - 元素宽度(占几个格子)
        • 设备代号:
          1. xs:超小屏幕 手机 (<768px) 举例(下面三个同理):class的命名 col-xs-1 到 col-xs-12
          2. sm:小屏幕 平板 (≥768px)
          3. md:中等屏幕 桌面显示器 (≥992px)
          4. lg:大屏幕 大桌面显示器 (≥1200px)
        • 元素宽度:1 - 12
      • 注意:
        1. 一行中如果格子数目超过12,则超出部分自动换行。
        2. 栅格类属性可以向上兼容。栅格类适用于与屏幕宽度大于或等于分界点大小的设备。
        3. 如果真实设备宽度小于了设置栅格类属性的设备代码的最小值,会一个元素沾满一整行。

    3、CSS样式和JS插件

    3.1、常用全局CSS样式
    1、按钮

    按钮样式

    
    
    
    
    
    
    
    
    
    

    按钮尺寸

    使用 .btn-lg.btn-sm.btn-xs 就可以获得不同尺寸的按钮。

    还有激活和禁用状态等样式,具体查文档


    2、图片
    • class="img-responsive":图片在任意尺寸都占100%

    • 图片形状

      ...:方形

      ... : 圆形

      ... :相框

    • src(必填):图片地址,可以填相对地址(推荐)和绝对地址,../ 代表上一级目录。

    • alt(必填 ):图片加载失败会返回的内容(图片地址/图片名不对等原因)

    • title:鼠标悬停显示的文字


    3、表格
    • 条纹状表格

      通过 .table-striped 类可以给 之内的每一行增加斑马条纹样式。不被 Internet Explorer 8 支持。

      
        ...
      
    • 带边框的表格

      添加 .table-bordered 类为表格和其中的每个单元格增加边框。

    • 鼠标悬停

      通过添加 .table-hover 类可以让 中的每一行对鼠标悬停状态作出响应。

    • 紧缩表格

      通过添加 .table-condensed 类可以让表格更加紧凑,单元格中的内补(padding)均会减半。

    • 响应式表格

      将任何 .table 元素包裹在 .table-responsive 元素内,即可创建响应式表格,其会在小屏幕设备上(小于768px)水平滚动。当屏幕大于 768px 宽度时,水平滚动条消失。

    • 状态类(行或单元格)

      通过这些状态类可以为 **行或单元格 **设置颜色。

      Class 描述
      .active 鼠标悬停在行或单元格上时所设置的颜色
      .success 标识成功或积极的动作
      .info 标识普通的提示信息或动作
      .warning 标识警告或需要用户注意
      .danger 标识危险或潜在的带来负面影响的动作
      active active active active active
      success success success success success
      warning warning warning warning warning
      danger danger danger danger danger
      info info info info info
      active success warning danger info


    4、表单

    单独的表单控件会被自动赋予一些全局样式。所有设置了 .form-control 类的 <script> var input = $('#test-input'), select = $('#test-select'), textarea = $('#test-textarea'); input.val(); // 'test' input.val('abc@example.com'); // 文本框的内容已变为abc@example.com select.val(); // 'BJ' select.val('SH'); // 选择框已变为Shanghai textarea.val(); // 'Hello' textarea.val('Hi'); // 文本区域已更新为'Hi' </script>

    可见,一个val()就统一了各种输入框的取值和赋值的问题。


    6、JQuery 高级

    1、动画

    三种方式显示和隐藏元素

    1、默认显示和隐藏方式

    1. show([speed,[easing],[fn]])

      • 参数,所有方法的参数都是这三个:
        1. speed:动画的速度。三个预定义的值("slow","normal", "fast")或表示动画时长的毫秒数值(如:1000)
        2. easing:用来指定切换效果,默认是"swing",可用参数"linear"
          • swing:动画执行时效果是 先慢,中间快,最后又慢
          • linear:动画执行时速度是匀速的
        3. fn:在动画完成时执行的函数,每个元素执行一次。
    2. hide([speed,[easing],[fn]])

    3. toggle([speed],[easing],[fn]): 在显示和隐藏之间切换

    2、滑动显示和隐藏方式

    1. slideDown([speed],[easing],[fn])
    2. slideUp([speed,[easing],[fn]])
    3. slideToggle([speed],[easing],[fn])

    3、淡入淡出显示和隐藏方式

    1. fadeIn([speed],[easing],[fn])
    2. fadeOut([speed],[easing],[fn])
    3. fadeToggle([speed,[easing],[fn]])

    2、遍历

    1、js的遍历方式

    • for(初始化值;循环结束条件;步长)
    //2.遍历li
    for (var i = 0; i < citys.length; i++) {
        if("上海" == citys[i].innerHTML){
            //break; 结束循环
            //continue; //结束本次循环,继续下次循环
        }
        //获取内容
        alert(i+":"+citys[i].innerHTML);
    }
    

    2、jq的遍历方式

    1. jq对象.each(callback)

      1. 语法:
        jquery对象.each(function(index,element){});

        • index:就是当前元素在集合中的索引

        • element:遍历集合中的当前元素对象

        • this:遍历时,当前元素对象

      2. 回调函数返回值:

        • true: 如果当前function返回为true,“还需要循环吗? true”, 则结束本次循环,继续下次循环(continue)
        • false: 如果当前function返回为false,“还需要循环吗? false” ,则结束循环(break)。
    2. $.each(object, [callback])

    3. for..of : jquery 3.0 版本之后提供的方式
      for(元素对象 of 容器对象)

        //2. jq对象.each(callback)
        citys.each(function (index,element) {
            //3.1 获取li对象 第一种方式 this
            //alert(this.innerHTML);
            //alert($(this).html());
            //3.2 获取li对象 第二种方式 在回调函数中定义参数   index(索引) element(元素对象)
            //alert(index+":"+element.innerHTML);
            //alert(index+":"+$(element).html());
    
            //判断如果是上海,则结束循环
            if("上海" == $(element).html()){
                //如果当前function返回为false,则结束循环(break)。
                //如果返回为true,则结束本次循环,继续下次循环(continue)
                return true;
            }
            alert(index+":"+$(element).html());
        });
    
        //3 $.each(object, [callback])
        $.each(citys,function () {
            alert($(this).html());
        });
    
        //4. for ... of:jquery 3.0 版本之后提供的方式
    
        for(li of citys){
            alert($(li).html());
        }
    

    3、事件绑定

    1、jquery标准的绑定方式

    • jq对象.事件方法(回调函数)
    • 注:如果调用事件方法,不传递回调函数,则会触发浏览器默认行为。
      • 表单对象.submit();//让表单提交
    $(function () {
               //1.获取name对象,绑定click事件
               $("#name").click(function () {
                   alert("我被点击了...")
               });
    
               //给name绑定鼠标移动到元素之上事件。绑定鼠标移出事件
                /*$("#name").mouseover(function () {
                   alert("鼠标来了...")
                });
    
                $("#name").mouseout(function () {
                    alert("鼠标走了...")
                });*/
    
                //简化操作,链式编程
                $("#name").mouseover(function () {
                    alert("鼠标来了...")
                }).mouseout(function () {
                    alert("鼠标走了...")
                });
        
                alert("我要获得焦点了...")
                //$("#name").focus();//让文本输入框获得焦点
                //表单对象.submit();//让表单提交
            });
    

    2、on绑定事件/off解除绑定

    • jq对象.on("事件名称",回调函数)
    • jq对象.off("事件名称")
      • 如果off方法不传递任何参数,则将组件上的所有事件全部解绑
    $(function () {
               //1.使用on给按钮绑定单击事件  click
               $("#btn").on("click",function () {
                   alert("我被点击了。。。")
               }) ;
    
               //2. 使用off解除btn按钮的单击事件
                $("#btn2").click(function () {
                    //解除btn按钮的单击事件
                    //$("#btn").off("click");
                    $("#btn").off();//将组件上的所有事件全部解绑
                });
            });
    

    3、事件切换:toggle

    • jq对象.toggle(fn1,fn2...)

      • 当单击jq对象对应的组件后,会执行fn1,第二次点击会执行fn2.....
    • 注意:1.9版本 .toggle() 方法删除,jQuery Migrate(迁移)插件可以恢复此功能。

      <script src="../js/jquery-migrate-1.0.0.js" type="text/javascript" charset="utf-8"></script>
      
      $(function () {
                 //获取按钮,调用toggle方法
                 $("#btn").toggle(function () {
                     //改变div背景色backgroundColor 颜色为 green
                     $("#myDiv").css("backgroundColor","green");
                 },function () {
                     //改变div背景色backgroundColor 颜色为 pink
                     $("#myDiv").css("backgroundColor","pink");
                 });
              });
      

    4、案例

    案例1

    广告显示和隐藏

    需求:
        1. 当页面加载完,3秒后。自动显示广告
        2. 广告显示5秒后,自动消失。
    
    分析:
        1. 使用定时器来完成。setTimeout (执行一次定时器)
        2. 分析发现JQuery的显示和隐藏动画效果其实就是控制display
        3. 使用  show/hide方法来完成广告的显示
    
     $(function () {
               //定义定时器,调用adShow方法 3秒后执行一次
               setTimeout(adShow,3000);
               //定义定时器,调用adHide方法,8秒后执行一次
                setTimeout(adHide,8000);
            });
            //显示广告
            function adShow() {
                //获取广告div,调用显示方法
                $("#ad").show("slow");
            }
            //隐藏广告
            function adHide() {
                //获取广告div,调用隐藏方法
                $("#ad").hide("slow");
            }
    

    案例2

    抽奖

    分析:
    	1. 给开始按钮绑定单击事件
    		1.1 定义循环定时器
    		1.2 切换小相框的src属性
    		* 定义数组,存放图片资源路径
    		* 生成随机数。数组索引
    
    
    	2. 给结束按钮绑定单击事件
    		1.1 停止定时器
    		1.2 给大相框设置src属性
    
    var imgs = ["../img/man00.jpg",
                        "../img/man01.jpg",
                        "../img/man02.jpg",
                        "../img/man03.jpg",
                        "../img/man04.jpg",
                        "../img/man05.jpg",
                        "../img/man06.jpg",
                        ];
            var startId;//开始定时器 的id
            var index;//随机角标
    
    //入口函数
    $(function () {
                //处理按钮是否可以使用的效果
                $("#startID").prop("disabled",false);
                $("#stopID").prop("disabled",true);
    
    
               //1. 给开始按钮绑定单击事件
                $("#startID").click(function () {
                    
                     //处理按钮是否可以使用的效果,不要写进循环定时器里面
                        $("#startID").prop("disabled",true);
                        $("#stopID").prop("disabled",false);
                    
                    // 1.1 定义循环定时器 20毫秒执行一次 设置该定时器的id为startId
                    startId = setInterval(function () {
                       
                        //1.2生成随机角标 0-6
                        index = Math.floor(Math.random() * 7);//0.000--0.999 --> * 7 --> 0.0-----6.9999
                        //1.3设置小相框的src属性
                        $("#img1ID").prop("src",imgs[index]);
    
                    },20);
                });
    
    
                //2. 给结束按钮绑定单击事件
                $("#stopID").click(function () {
                    //处理按钮是否可以使用的效果
                    $("#startID").prop("disabled",false);
                    $("#stopID").prop("disabled",true);
    
    
                   // 1.1 停止定时器
                    clearInterval(startId);
                   // 1.2 给大相框设置src属性,先不显示,在之后的一秒之内完成显示,链式调用
                    $("#img2ID").prop("src",imgs[index]).hide().show(1000);
                });
            });
    

    5、插件(自定义方法)

    增强JQuery的功能

    实现方式:

    1. $.fn.extend(object)
      • 为Jquery的所有对象定义可以调用的方法 $("#id")
    2. $.extend(object)
    • 为JQeury对象自身定义全局方法 , $或者jQuery

    $.fn.extend(object) 案例

    
    
    
        
        01-jQuery对象进行方法扩展
        <script src="../js/jQuery-3.6.0.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
           //使用jquery插件 给jq对象添加2个方法 check()选中所有复选框,uncheck()取消选中所有复选框
    
            //1.定义jquery的对象插件
            $.fn.extend({
                //定义了一个check()方法。所有的jq对象都可以调用该方法
                check:function () {
                   //让复选框选中
    
                    //this:调用该方法的jq对象
                    this.prop("checked",true);
                },
                uncheck:function () {
                    //让复选框不选中
    
                    this.prop("checked",false);
                }
                
            });
    
            $(function () {
               // 获取按钮,并绑定事件
                //$("#btn-check").check();
                //复选框对象.check();
    
                $("#btn-check").click(function () {
                    //获取复选框对象
                    $("input[type='checkbox']").check();
    
                });
    
                $("#btn-uncheck").click(function () {
                    //获取复选框对象
                    $("input[type='checkbox']").uncheck();
    
                });
            });
        </script>
    
    
    
    
    
    足球 篮球 排球

    $.extend(object)案例

    
    
    
        
        01-jQuery对象进行方法扩展
        <script src="../js/jQuery-3.6.0.js" type="text/javascript" charset="utf-8"></script>
        <script type="text/javascript">
            //对全局方法扩展2个方法,扩展min方法:求2个值的最小值;扩展max方法:求2个值最大值
            
            $.extend({
                max:function (a,b) {
                    //返回两数中的较大值
                    return a >= b ? a:b;
                },
                min:function (a,b) {
                    //返回两数中的较小值
                    return a <= b ? a:b;
                }
            });
    
            //调用全局方法
            var max = $.max(4,3);
            //alert(max);
    
            var min = $.min(1,2);
            alert(min);
        </script>
    
    
    
    
    

    7、AJAX

    ASynchronous JavaScript And XML 异步的JavaScript 和 XML

    AJAX 教程 | 菜鸟教程 (runoob.com)

    1、简介

    • Ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
    • 通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。
    • 传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。
    • 提升用户的体验。

    异步和同步:客户端和服务器端相互通信的基础上

    • 同步:客户端必须等待服务器端的响应。在等待的期间客户端不能做其他操作。
    • 异步:客户端不需要等待服务器端的响应。在服务器处理请求的过程中,客户端可以进行其他的操作。

    2、实现方式

    1、原生的JS实现方式(了解,基本不会使用)

    
    
    
        
        Title
        <script>
            
            //定义方法
            function  fun() {
                //发送异步请求
                //1.创建核心对象
                var xmlhttp;
                if (window.XMLHttpRequest)
                {// IE7+, Firefox, Chrome, Opera, Safari 浏览器执行代码
                    xmlhttp=new XMLHttpRequest();
                }
                else
                {// IE6, IE5 浏览器执行代码
                    xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
                }
    
                //2. 建立连接
                xmlhttp.open("GET","ajaxServlet?username=tom",true);
                /*
                    参数:
                        1. 请求方式:GET、POST
                            * get方式,请求参数在URL后边拼接。send方法为空参
                            * post方式,请求参数在send方法中定义
                        2. 请求的URL:
                        3. 同步或异步请求:true(异步)或 false(同步)
                 */
                
                //3.请求方式
                xmlhttp.send();
    
                //4.接受并处理来自服务器的响应结果
                //获取方式 :xmlhttp.responseText
                //什么时候获取?当服务器响应成功后再获取
    
                //当xmlhttp对象的就绪状态改变时,触发事件onreadystatechange。
                xmlhttp.onreadystatechange=function()
                {
                    //判断readyState就绪状态是否为4,判断status响应状态码是否为200
                    if (xmlhttp.readyState==4 && xmlhttp.status==200)
                    {
                       //获取服务器的响应结果
                        var responseText = xmlhttp.responseText;
                        alert(responseText);
                    }
                }
    
            }
            
        </script>
        
        
    
    
    
        
    
        
    
    
    

    2、JQeury实现方式

    1、$.ajax()

    语法:$.ajax({键值对});

    <script>
        
        //定义方法
        function  fun() {
            //使用$.ajax()发送异步请求
            $.ajax({
                url:"ajaxServlet1111" , // 请求路径
                type:"POST" , //请求方式
                //data: "username=jack&age=23",     //请求参数,第一种写法
                data:{"username":"jack","age":23},  //第二种写法,JSON格式,推荐
                success:function (data) {
                    alert(data);
                },//响应成功后的回调函数
                error:function () {
                    alert("出错啦...")
                },//表示如果请求响应出现错误,会执行的回
                dataType:"text"//设置接受到的响应数据的格式
            });
        }
        
    </script>
    
    2、$.get():发送get请求, 默认就是GET

    语法:$.get(url, [data], [callback], [type])

    • 参数:
      • url:请求路径
      • data:请求参数
      • callback:回调函数
      • type:响应结果的类型
    //定义方法
    <script>
    	function  fun() {
        	$.get("ajaxServlet",{username:"rose"},function (data) {
            	alert(data);
        	},"text");
    	}
    </script>
    
    3、$.post():发送post请求

    语法:$.post(url, [data], [callback], [type])

    • 参数:
      • url:请求路径
      • data:请求参数
      • callback:回调函数
      • type:响应结果的类型
    <script>
    //定义方法
        function  fun() {
            $.post("ajaxServlet",{username:"rose"},function (data) {
                alert(data);
            },"text");
        }    
    </script>
    

    8、JSON

    JavaScript Object Notation JavaScript对象表示法,一种数据交换格式

    1、简介

    在JSON出现之前,大家一直用XML来传递数据。因为XML是一种纯文本格式,所以它适合在网络上交换数据。XML本身不算复杂,但是,加上DTD、XSD、XPath、XSLT等一大堆复杂的规范以后,任何正常的软件开发人员碰到XML都会感觉头大了,最后大家发现,即使你努力钻研几个月,也未必搞得清楚XML的规范。

    终于,在2002年的一天,道格拉斯·克罗克福特(Douglas Crockford)同学为了拯救深陷水深火热同时又被某几个巨型软件企业长期愚弄的软件工程师,发明了JSON这种超轻量级的数据交换格式。

    道格拉斯同学长期担任雅虎的高级架构师,自然钟情于JavaScript。他设计的JSON实际上是JavaScript的一个子集。在JSON中,一共就这么几种数据类型

    1. number:和JavaScript的number完全一致;
    2. boolean:就是JavaScript的truefalse
    3. string:就是JavaScript的string
    4. null:就是JavaScript的null
    5. array:就是JavaScript的Array表示方式——[]
    6. object:就是JavaScript的{ ... }表示方式。
    7. 所有的键值对 都是用 key : value,且key和字符串需要用双引号
    8. 以及上面的任意组合。

    并且,JSON还定死了字符集必须是UTF-8,表示多语言就没有问题了。为了统一解析,JSON的字符串规定必须用双引号""KEY也必须用双引号""

    由于JSON非常简单,很快就风靡Web世界,并且成为ECMA标准。几乎所有编程语言都有解析JSON的库,而在JavaScript中,我们可以直接使用JSON,因为JavaScript内置了JSON的解析。

    把任何JavaScript对象变成JSON,就是把这个对象序列化成一个JSON格式的字符串,这样才能够通过网络传递给其他计算机。

    如果我们收到一个JSON格式的字符串,只需要把它反序列化成一个JavaScript对象,就可以在JavaScript中直接使用这个对象了。


    2、语法

    1、基本规则

    数据在名称/值对中:json数据是由键值对构成的

    • KEK用双引号引起来,不用双引号的格式将无法被Python识别
      • 值的取值类型:
        1. 数字(整数或浮点数)
        2. 字符串(在双引号中)
        3. 逻辑值(true 或 false)
        4. 数组(在方括号中) {"persons":[{},{}]}
        5. 对象(在花括号中) {"address":{"province":"陕西"....}}
        6. null
    • 数组由逗号分隔:多个键值对由逗号分隔
    • 花括号保存对象:使用{}定义json 格式
    • 方括号保存数组:[]

    2、获取数据

    1. json对象.键名
    2. json对象["键名"]
    3. 数组对象[索引]
    4. 遍历
        <script>
            //1.定义基本格式
            var person = {"name": "张三", "age": 23, "gender": true};
    
            //获取name的值
            //var name = person.name;
            var name = person["name"];
           
            //2.对象的嵌套格式   {}———> []
            var persons = {
                "persons": [
                    {"name": "张三", "age": 23, "gender": true},
                    {"name": "李四", "age": 24, "gender": true},
                    {"name": "王五", "age": 25, "gender": false}
                    ]
            };
           
            //获取王五值
            var name1 = persons.persons[2].name;
    
            //2.数组的嵌套格式   []———> {}
            var ps = [{"name": "张三", "age": 23, "gender": true},
                		{"name": "李四", "age": 24, "gender": true},
                		{"name": "王五", "age": 25, "gender": false}];
            
            //获取李四值
            alert(ps[1].name);
    
    
        </script>
    
        <script>
            //1.定义基本格式
            var person = {"name": "张三", age: 23, 'gender': true};
    
            var ps = [{"name": "张三", "age": 23, "gender": true},
                {"name": "李四", "age": 24, "gender": true},
                {"name": "王五", "age": 25, "gender": false}];
    
            //获取person对象中所有的键和值
            //for in 循环
            for(var key in person){
                //这样的方式获取不行。因为获取到的key现在是字符串,相当于  person."name"
                //alert(key + ":" + person.key);
                //正确方式
                alert(key+":"+person[key]);
            }
    
           //获取ps中的所有值
            for (var i = 0; i < ps.length; i++) {
                var p = ps[i];
                //双重遍历
                for(var key in p){
                    alert(key+":"+p[key]);
                }
            }
        </script>
    

    3、JS对象与JSON对象转化

    序列化js到json

    JSON.stringify (value, replacer, space) 静态方法

    • value – 要转换为 JSON 字符串的值。

    • replacer – 一个改变字符串化过程行为的函数,或一个String和Number数组,用作选择/过滤要包含在 JSON 字符串中的值对象的属性的允许列表。 如果此值为null或未提供,则对象的所有属性都包含在生成的 JSON 字符串中。

    • space – 一个String或Number对象,用于在输出 JSON 字符串中插入空格以提高可读性。

      如果这是一个Number ,它表示代替一个空格的空格数量; 此数字上限为 10(如果更大,则该值仅为10 )。 小于 1 的值表示不应使用空格。

      如果这是一个String ,则该字符串(或字符串的前 10 个字符,如果它比那个长)替代一个空格。 如果未提供此参数(或为null ),则不使用空格。

    让我们先把小明这个对象序列化成JSON格式的字符串:

    'use strict';
    
    var xiaoming = {
        name: '小明',
        age: 14,
        gender: true,
        height: 1.65,
        grade: null,
        'middle-school': '\"W3C\" Middle School',
        skills: ['JavaScript', 'Java', 'Python', 'Lisp']
    };
    
    var s = JSON.stringify(xiaoming,null, '  ');
    console.log(s);
    
    输出:
    {
      "name": "小明",
      "age": 14,
      "gender": true,
      "height": 1.65,
      "grade": null,
      "middle-school": "\"W3C\" Middle School",
      "skills": [
        "JavaScript",
        "Java",
        "Python",
        "Lisp"
      ]
    }
    

    反序列化json到js

    JSON.parse() 静态方法

    • text -- 要解析为 JSON 的字符串。有关 JSON 语法的描述,请参阅JSON对象。
    • reviver - 如果是一个函数,它规定了在返回之前如何转换最初由解析产生的值。

    拿到一个JSON格式的字符串,我们直接用JSON.parse()把它变成一个JavaScript对象:

    JSON.parse('[1,2,3,true]'); // [1, 2, 3, true]  字符串
    JSON.parse('{"name":"小明","age":14}'); // Object {name: '小明', age: 14}    
    JSON.parse('true'); // true  字符串
    JSON.parse('123.45'); // 123.45  number
    

    JSON.parse()还可以接收一个函数,用来转换解析出的属性:

    var obj = JSON.parse('{"name":"小明","age":14}', function (key, value) {
        if (key === 'name') {
            return value + '同学';
        }
        return value;
    });
    console.log(JSON.stringify(obj)); // {name: '小明同学', age: 14}
    

    JSON和JS对象的区别

    var obj = {a:'hello', b:'world'};
    var json = {"a":"hello", "b":"world"};
    

    4、JSON数据和Java对象的相互转换

    1、JSON解析器

    常见的解析器,工具类:Jsonlib,Gson,阿里fastjson,jackson

    2、JSON转为Java对象

    1. 导入jackson的相关jar包 下载地址 Databind、Core、Annotations
    2. 创建Jackson核心对象 ObjectMapper
    3. 调用ObjectMapper的相关方法进行转换 readValue(json字符串数据,Class)
    //演示 JSON字符串转为Java对象
        @Test
        public void test5() throws Exception {
            //1.初始化JSON字符串
            String json = "{\"gender\":\"男\",\"name\":\"张三\",\"age\":23}";
    
            //2.创建ObjectMapper对象
            ObjectMapper mapper = new ObjectMapper();
            //3.转换为Java对象 Person对象
            Person person = mapper.readValue(json, Person.class);
    
            System.out.println(person);
        }
    

    3、Java对象转换JSON

    使用步骤

    1. 导入jackson的相关jar包
    2. 创建Jackson核心对象 ObjectMapper
    3. 调用ObjectMapper的相关方法进行转换
      1. 转换方法:
        • writeValue(参数1,obj)
          • 参数1:
            • File:将obj对象转换为JSON字符串,并保存到指定的文件中
            • Writer:将obj对象转换为JSON字符串,并将json数据填充到字符输出流中
            • OutputStream:将obj对象转换为JSON字符串,并将json数据填充到字节输出流中
        • writeValueAsString(obj):将对象转为json字符串
      2. 注解:
        1. @JsonIgnore:排除属性。
        2. @JsonFormat:属性值的格式化
          • @JsonFormat(pattern = "yyyy-MM-dd")
      3. 复杂java对象转换
        1. List:数组
        2. Map:对象格式一致

    public class Person {
        private String name;
        private int age;
        private String gender;
        
        @JsonFormat(pattern = "yyyy-MM-dd")
        private Date birthday;
    	
        //构造器,getter/setter/toString
    }    
    
    public class JacksonTest {
        //Java对象转为JSON字符串
        @Test
        public void test1() throws Exception {
            //1.创建Person对象
            Person p  = new Person();
            p.setName("张三");
            p.setAge(23);
            p.setGender("男");
    
            //2.创建Jackson的核心对象  ObjectMapper
            ObjectMapper mapper = new ObjectMapper();
            //3.转换
            String json = mapper.writeValueAsString(p);
            System.out.println(json);//{"name":"张三","age":23,"gender":"男"}
    
            //writeValue,将数据写到d://a.txt文件中
            mapper.writeValue(new File("d://a.txt"),p);
    
            //writeValue.将数据关联到Writer中
            mapper.writeValue(new FileWriter("d://b.txt"),p);
        }
    
        @Test
        public void test2() throws Exception {
            //1.创建Person对象
            Person p = new Person();
            p.setName("张三");
            p.setAge(23);
            p.setGender("男");
            p.setBirthday(new Date());
    
            //2.转换
            ObjectMapper mapper = new ObjectMapper();
            //将java对象转为json字符串
            String json = mapper.writeValueAsString(p);
    
            //birthday使用了注解@JsonFormat(pattern = "yyyy-MM-dd")
            System.out.println(json);//{"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"}
        }
    
        @Test
        public void test3() throws Exception {
            //1.创建Person对象
            Person p = new Person();
            p.setName("张三");
            p.setAge(23);
            p.setGender("男");
            p.setBirthday(new Date());
    
            Person p1 = new Person();
            p1.setName("张三");
            p1.setAge(23);
            p1.setGender("男");
            p1.setBirthday(new Date());
    
            Person p2 = new Person();
            p2.setName("张三");
            p2.setAge(23);
            p2.setGender("男");
            p2.setBirthday(new Date());
    
    
            //创建List集合
            List ps = new ArrayList();
            ps.add(p);
            ps.add(p1);
            ps.add(p2);
    
    
            //2.转换
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(ps);
            // [{},{},{}]
            System.out.println(json);
            /*
            [{"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"},
             {"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"},
             {"name":"张三","age":23,"gender":"男","birthday":"2018-07-07"}]
             */
        }
    
        @Test
        public void test4() throws Exception {
            //1.创建map对象
            Map map = new HashMap();
            map.put("name","张三");
            map.put("age",23);
            map.put("gender","男");
    
    
            //2.转换
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(map);
            
            System.out.println(json);//{"gender":"男","name":"张三","age":23}
        }
    }
    

    4、案例

    校验用户名是否存在,我写在jackson项目里了

    服务器响应的数据,在客户端使用时,要想当做json数据格式使用。有两种解决方案:

    • $.get(type):将最后一个参数type指定为"json"
    • 在服务器端设置MIME类型:response.setContentType("application/json;charset=utf-8");

    1、注册页面(关键)
    
    
    
        
        注册页面
      <script src="js/jQuery-3.6.0.js"></script>
    
      <script>
        //入口函数
        $(function (){
          //给用户名输入框绑定失去焦点事件blur
          $("#username").blur(function (){
            //获取username文本输入框的值
            var username = $(this).val();
            //发送Ajax请求
            //期望服务器响应返回的数据格式: {"userExist":true,"msg":"此用户名太受欢迎,请换一个"}
            //                        {"userExist":false,"msg":"此用户名可以使用"}
            $.get("/jackson/RegistServlet"
                    ,{username:username}
                    ,function (data) {
                      var span = $("#s_username");
                      if (data.userExist){
                        //用户名存在
                        span.css("color","red"),
                        span.html(data.msg)
                       }else{
                        //用户名不存在
                        span.css("color","green"),
                        span.html(data.msg)
                       }
            }
            ,"json")
          })
        })
      </script>
    
    
    
    


    2、RegistServlet
    package com.xiao.web.servlet; /**
     * @author 肖南海
     * @version 1.0
     */
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.xiao.dao.impl.UserDaoImpl;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.*;
    import java.io.IOException;
    import java.util.HashMap;
    
    @WebServlet("/RegistServlet")
    public class RegistServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.setCharacterEncoding("utf-8");
            response.setContentType("text/html;charset=utf-8");
            //获取注册用户名
            String username = request.getParameter("username");
            //查询数据库判断用户名是否存在
            UserDaoImpl userDao = new UserDaoImpl();
            boolean flag = userDao.findUsername(username);
            //期望服务器响应返回的数据格式: {"userExist":true,"msg":"此用户名太受欢迎,请换一个"}
            //                        {"userExist":false,"msg":"此用户名可以使用"}
            HashMap map = new HashMap<>();
            map.put("userExist",flag);
            if (flag){
                //存在
                map.put("msg","此用户名太受欢迎,请换一个");
            }else{
                //不存在
                map.put("msg","此用户名可以使用");
            }
            //将map转为json,并且传递给客户端
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(response.getWriter(),map);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
    
    3、UserDaoImlpl
    package com.xiao.dao.impl;
    
    import com.xiao.dao.UserDao;
    import com.xiao.util.JDBCUtils;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * @author 肖南海
     * @version 1.0
     */
    public class UserDaoImpl implements UserDao {
        private Connection connection;
        private PreparedStatement preparedStatement;
        private ResultSet resultSet;
        @Override
        public boolean findUsername(String registName) {
    
            try {
                connection = JDBCUtils.getConnection();
                String sql = "select username from login where username = ?";
                preparedStatement = connection.prepareStatement(sql);
                preparedStatement.setString(1,registName);
                resultSet = preparedStatement.executeQuery();
                if (resultSet.next()){
                    return true;
                } else {
                    return false;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                try {
                    JDBCUtils.close(resultSet,preparedStatement,connection);
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }
    
    

    9、Redis

    redis是一款高性能的NOSQL系列的非关系型数据库,而MySQL是关系型数据库

    1、简介

    1.1、什么是NOSQL

    NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,是一项全新的数据库理念,泛指非关系型的数据库。

    随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。

    1、NOSQL和关系型数据库比较
    优点
    1. 成本:nosql数据库简单易部署,基本都是开源软件,不需要像使用oracle那样花费大量成本购买使用,相比关系型数据库价格便宜。

    2. 查询速度:nosql数据库将数据存储于缓存之中,关系型数据库将数据存储在硬盘中,自然查询速度远不及nosql数据库。

    3. 存储数据的格式:nosql的存储格式是key,value形式、文档形式、图片形式等等,所以可以存储基础类型以及对象或者是集合等各种格式,而数据库则只支持基础类型。

    4. 扩展性:关系型数据库有类似join这样的多表查询机制的限制导致扩展很艰难。

    缺点
    1. 维护的工具和资料有限,因为nosql是属于新的技术,不能和关系型数据库10几年的技术同日而语。

    2. 不提供对sql的支持,如果不支持sql这样的工业标准,将产生一定用户的学习和使用成本。

    3. 不提供关系型数据库对事务的处理。

    2、非关系型数据库的优势
    1. 性能NOSQL是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。

    2. 可扩展性同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

    3、关系型数据库的优势
    1. 复杂查询可以用SQL语句方便的在一个表以及多个表之间做非常复杂的数据查询。

    2. 事务支持使得对于安全性能很高的数据访问要求得以实现。对于这两类数据库,对方的优势就是自己的弱势,反之亦然。

    4、总结

    关系型数据库与NoSQL数据库并非对立而是互补的关系,即通常情况下使用关系型数据库,在适合使用NoSQL的时候使用NoSQL数据库,让NoSQL数据库对关系型数据库的不足进行弥补。一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据


    1.2、主流的NOSQL产品

    键值(Key-Value)存储数据库
    • 相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB

    • 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。

    • 数据模型: 一系列键值对

    • 优势: 快速查询

    • 劣势: 存储的数据缺少结构化

    列存储数据库
    • 相关产品:Cassandra, HBase, Riak

    • 典型应用:分布式的文件系统

    • 数据模型:以列簇式存储,将同一列数据存在一起

    • 优势:查找速度快,可扩展性强,更容易进行分布式扩展

    • 劣势:功能相对局限

    文档型数据库
    • 相关产品:CouchDB、MongoDB

    • 典型应用:Web应用(与Key-Value类似,Value是结构化的)

    • 数据模型: 一系列键值对

    • 优势:数据结构要求不严格

    • 劣势: 查询性能不高,而且缺乏统一的查询语法

    图形(Graph)数据库
    • 相关数据库:Neo4J、InfoGrid、Infinite Graph

    • 典型应用:社交网络

    • 数据模型:图结构

    • 优势:利用图结构相关算法。

    • 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。


    1.3 什么是Redis

    Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s ,且Redis通过提供多种键值数据类型来适应不同场景下的存储需求。

    目前为止Redis支持的键值数据类型如下:

    1. 字符串类型 string
    2. 哈希类型 hash
    3. 列表类型 list
    4. 集合类型 set
    5. 有序集合类型 sortedset

    redis的应用场景

    • 缓存(数据查询、短连接、新闻内容、商品内容等等)

    • 聊天室的在线好友列表

    • 任务队列。(秒杀、抢购、12306等等)

    • 应用排行榜

    • 网站访问统计

    • 数据过期处理(可以精确到毫秒)

    • 分布式集群架构中的session分离


    2、下载安装

    1. 官网:https://redis.io

    2. 中文网:http://www.redis.net.cn/

    3. 下载地址:https://github.com/tporadowski/redis/releases。

      Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-xxx.zip压缩包到 C 盘,解压后,将文件夹重新命名为 redis

    4. 解压直接可以使用:

      • redis.windows.conf:配置文件
      • redis-cli.exe:redis的客户端
      • redis-server.exe:redis服务器端

    3、命令操作

    redis命令手册

    redis的数据结构(重要)

    redis存储的是:key,value 格式的数据,其中key都是字符串,value有5种不同的数据结构

    value的数据结构:

    1. 字符串类型 string
    2. 哈希类型 hash : map格式
    3. 列表类型 list : linkedlist格式。支持重复元素
    4. 集合类型 set : 不允许重复元素
    5. 有序集合类型 sortedset:不允许重复元素,且元素有顺序

    1、字符串类型 string

    1. 存储: set key value

      127.0.0.1:6379>  set username zhangsan
      OK
      
    2. 获取: get key

    127.0.0.1:6379> get username
    "zhangsan"
    
    1. 删除: del key
    127.0.0.1:6379> del age
    (integer) 1    
    

    2、哈希类型 hash

    1. 存储: hset key field value

      127.0.0.1:6379> hset myhash username lisi
      (integer) 1
      127.0.0.1:6379> hset myhash password 123
      (integer) 1
      
    2. 获取:

      • hget key field: 获取指定的field对应的值

        127.0.0.1:6379> hget myhash username
        "lisi"
        
      • hgetall key:获取所有的field和value

        127.0.0.1:6379> hgetall myhash
        1) "username"
        2) "lisi"
        3) "password"
        4) "123"
        
    3. 删除:

      • 删除map中的字段:hdel key field
      • 删除map:del key
      127.0.0.1:6379> hdel myhash username
      (integer) 1
      127.0.0.1:6379> del myhash
      (integer) 1
      

    3、列表类型 list

    可以添加一个元素到列表的头部(左边)或者尾部(右边)

    1. 添加:

      • lpush key value: 将元素加入列表左表

      • rpush key value:将元素加入列表右边

      127.0.0.1:6379> lpush myList a
      (integer) 1
      127.0.0.1:6379> lpush myList b
      (integer) 2
      127.0.0.1:6379> rpush myList c
      (integer) 3
      
    2. 获取:

      • lrange key start end :返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
      127.0.0.1:6379> lrange myList 0 -1
      1) "c"
      2) "b"
      3) "a"
      
    3. 删除:

      • lpop key: 删除列表最左边的元素,并将元素返回
      • rpop key: 删除列表最右边的元素,并将元素返回

    4、集合类型 set

    不允许重复元素,不保证顺序

    1. 存储:sadd key value

      127.0.0.1:6379> sadd myset a
      (integer) 1
      127.0.0.1:6379> sadd myset a
      (integer) 0
      127.0.0.1:6379> sadd myset b c d e
      (integer) 4
      
    2. 获取:smembers key:获取set集合中所有元素

      127.0.0.1:6379> smembers myset
      1) "d"
      2) "b"
      3) "a"
      4) "c"
      5) "e"
      
    3. 删除:srem key value:删除set集合中的某个元素

      127.0.0.1:6379> srem myset a
      (integer) 1
      

    7、有序集合类型 sortedset

    不允许重复元素,且元素有顺序。每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

    1. 存储:zadd key score value,也可以覆盖值

      127.0.0.1:6379> zadd mysort 60 zhangsan
      (integer) 1
      127.0.0.1:6379> zadd mysort 50 lisi
      (integer) 1
      127.0.0.1:6379> zadd mysort 80 wangwu
      (integer) 1
      
    2. 获取:zrange key start end [withscores]

      127.0.0.1:6379> zrange mysort 0 -1
      1) "lisi"
      2) "zhangsan"
      3) "wangwu"
      
      127.0.0.1:6379> zrange mysort 0 -1 withscores
      1) "zhangsan"
      2) "60"
      3) "wangwu"
      4) "80"
      5) "lisi"
      6) "500"
      
    3. 删除:zrem key value

      127.0.0.1:6379> zrem mysort lisi
      (integer) 1
      

    7、通用命令

    1. keys * : 查询所有的键

      127.0.0.1:6379> keys *
      1) "username"
      2) "myset"
      3) "myList"
      
    2. type key : 获取键对应的value的类型

      127.0.0.1:6379> type myset
      set
      
    3. del key:删除指定的key value


    4、持久化

    1. redis是一个内存数据库,当redis服务器重启,或者电脑重启,数据会丢失,我们可以将redis内存中的数据持久化保存到硬盘的文件中。

    2. redis持久化机制

      1. RDB默认方式,不需要进行配置,默认就使用这种机制

        • 在一定的间隔时间中,检测key的变化情况,然后持久化数据到 dump.rdb文件中
        1. 编辑redis.windows.conf文件

          //after 900 sec (15 min) if at least 1 key changed, 900秒(15分钟)后,如果至少1个KEY更改,就持久化一次
          save 900 1
          //after 300 sec (5 min) if at least 10 keys changed
          save 300 10
          //after 60 sec if at least 10000 keys changed
          save 60 10000
          
        2. 重新启动redis服务器,并指定配置文件名称,读取配置文件

          D:\redis-2.8.9>redis-server.exe redis.windows.conf
          
      2. AOF:日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据,保存到appendonly.aof文件

        1. 编辑redis.windwos.conf文件

          • appendonly no(关闭aof) --> appendonly yes (开启aof)

          • appendfsync always : 每一次操作都进行持久化

          • appendfsync everysec : 每隔一秒进行一次持久化

          • appendfsync no : 不进行持久化

        2. 重新启动redis服务器,并指定配置文件名称,读取配置文件

          D:\redis-2.8.9>redis-server.exe redis.windows.conf
          

    5、Java客户端 Jedis

    一款java操作redis数据库的工具

    1、Jedis的环境配置

    1. 下载jedis的jar包 jedis/4.1.1

    2. 添加编译依赖,5个jar包 Compile Dependencies (4) slf4j-simple ? 1.7.32

    3. 使用

      //1. 先要运行数据库,获取连接,指定主机名和端口,redis的默认(空参)主机和端口是localhost/6379
      Jedis jedis = new Jedis("localhost",6379);
      //2. 操作
      jedis.set("username","zhangsan");
      //3. 关闭连接
      jedis.close();
      

    2、Jedis操作各种redis中的数据结构

    1、字符串类型 string
    • set("key","value")
    • get("key")
        @Test
        public void String(){
            //1. 获取连接
            Jedis jedis = new Jedis();//如果使用空参构造,默认值 "localhost",6379端口
            //2. 操作
            //存储
            jedis.set("username","zhangsan");
            //获取
            String username = jedis.get("username");
            System.out.println(username);
    
            //可以使用setex()方法存储可以指定过期时间的 key value
            jedis.setex("activecode",20,"hehe");//将activecode:hehe键值对存入redis,并且20秒后自动删除该键值对
    
            //3. 关闭连接
            jedis.close();
        }
    
    2、哈希类型 hash : map格式
    • hset("key","field","value")
    • hget("key","field")
    • hgetAll("key")
        @Test
        public void testHash(){
            //1. 获取连接
            Jedis jedis = new Jedis("localhost", 6379);
            //2. 操作
            // 存储hash
            jedis.hset("user","name","lisi");
            jedis.hset("user","age","23");
            jedis.hset("user","gender","female");
    
            // 获取hash
            String name = jedis.hget("user", "name");
            System.out.println(name);//lisi
            // 获取hash的所有map中的数据
            Map user = jedis.hgetAll("user");
    
            // keyset
            Set keySet = user.keySet();
            for (String key : keySet) {
                //获取value
                String value = user.get(key);
                System.out.println(key + ":" + value);
            }
            //3. 关闭连接
            jedis.close();
        }
    }
    
    3、列表类型 list : linkedlist格式
    • lpush("key") /rpush("key")
    • lpop("key")/rpop("key")
    • lrange("key",start,stop)
        @Test
        public void testList(){
            //1. 获取连接
            Jedis jedis = new Jedis();
            //2. 操作
            // list 存储
            jedis.lpush("mylist","a","b","c");//从左边存
            jedis.rpush("mylist","a","b","c");//从右边存
    
            // list 范围获取
            List mylist = jedis.lrange("mylist", 0, -1);
            System.out.println(mylist);
    
            // list 弹出
            String element1 = jedis.lpop("mylist");//c
            System.out.println(element1);
    
            String element2 = jedis.rpop("mylist");//c
            System.out.println(element2);
    
            // list 范围获取
            List mylist2 = jedis.lrange("mylist", 0, -1);
            System.out.println(mylist2);
            //3. 关闭连接
            jedis.close();
        }
    
    4、集合类型 set
    • sadd("key","value")
    • smembers("key")
        @Test
        public void testSet(){
            //1. 获取连接
            Jedis jedis = new Jedis();
            //2. 操作
            // set 存储
            jedis.sadd("myset","java","php","c++");
    
            // set 获取
            Set myset = jedis.smembers("myset");
            System.out.println(myset);//[php, c++, java]
            //3. 关闭连接
            jedis.close();
        }
    }
    
    5、有序集合类型 sortedset
    • zadd("key",double,"value")
    • zrange("key",start,stop)
        @Test
        public void testSortedSet(){
            //1. 获取连接
            Jedis jedis = new Jedis();
            //2. 操作
            // sortedset 存储
            jedis.zadd("mysortedset",3,"亚瑟");
            jedis.zadd("mysortedset",30,"后裔");
            jedis.zadd("mysortedset",55,"孙悟空");
    
            // sortedset 获取
            List list = jedis.zrange("mysortedset", 0, -1);
    
            System.out.println(list);//[亚瑟, 后裔, 孙悟空]
            //3. 关闭连接
            jedis.close();
        }
    }
    

    3、jedis连接池

    1、使用
    1. 创建JedisPool连接池对象
    2. 调用方法 getResource()方法获取Jedis连接

    public class JedisPoolTest {
        @Test
        public void test(){
            //0.创建一个配置对象
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(50);
            config.setMaxIdle(10);
    
            //1.创建Jedis连接池对象
            JedisPool jedisPool = new JedisPool(config,"localhost",6379);
    
            //2.获取连接
            Jedis jedis = jedisPool.getResource();
            //3. 使用
            jedis.set("hehe","heihei");
            String hehe = jedis.get("hehe");
            System.out.println(hehe);//heihei
            //4. 关闭 归还到连接池中
            jedis.close();
        }
    }
    

    2、Jedis连接池详细配置
    #最大活动对象数     
    redis.pool.maxTotal=1000    
    #最大能够保持idel状态的对象数      
    redis.pool.maxIdle=100  
    #最小能够保持idel状态的对象数   
    redis.pool.minIdle=50    
    #当池内没有返回对象时,最大等待时间    
    redis.pool.maxWaitMillis=10000    
    #当调用borrow Object方法时,是否进行有效性检查    
    redis.pool.testOnBorrow=true    
    #当调用return Object方法时,是否进行有效性检查    
    redis.pool.testOnReturn=true  
    #“空闲链接”检测线程,检测的周期,毫秒数。如果为负值,表示不运行“检测线程”。默认为-1.  
    redis.pool.timeBetweenEvictionRunsMillis=30000  
    #向调用者输出“链接”对象时,是否检测它的空闲超时;  
    redis.pool.testWhileIdle=true  
    # 对于“空闲链接”检测线程而言,每次检测的链接资源的个数。默认为3.  
    redis.pool.numTestsPerEvictionRun=50  
    #redis服务器的IP    
    redis.ip=xxxxxx  
    #redis服务器的Port    
    redis1.port=6379  
    
    3、连接池工具类
    /**
     * 工具类
     * 加载配置文件,配置连接池的参数
     * 提供获取连接的方法
     *
     * @author 肖南海
     * @version 1.0
     */
    public class JedisPoolUtils {
    
        private static JedisPool jedisPool;
    
        static {
            //读取配置文件
            InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
            //创建Properties对象
            Properties pro = new Properties();
            //关联文件
            try {
                pro.load(is);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //获取数据,设置到JedisPoolConfig中
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
            config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
    
            //初始化JedisPool
            jedisPool = new JedisPool(config, pro.getProperty("host"), Integer.parseInt(pro.getProperty("port")));
        }
    
        /**
         * 获取连接方法
         */
        public static Jedis getJedis() {
            return jedisPool.getResource();
        }
    }
    
     @Test
    public void test2(){
        // 使用工具类获取jedis
        Jedis jedis = JedisPoolUtils.getJedis();
        //使用
        jedis.sadd("myset", "a", "b", "c","d");
        Set mySet = jedis.smembers("myset");
        System.out.println(mySet);//[d, a, b, c]
        //归还连接
        jedis.close();
    }
    

    6、案例(综合)

    1、需求

    1. 提供index.html页面,页面中有一个省份 下拉列表
    2. 当 页面加载完成后 发送ajax请求,加载所有省份

    注意:使用redis缓存一些不经常发生变化的数据。数据库的数据一旦发生改变,则需要更新缓存。本案例暂不与实现

    • 数据库的表执行 增删改的相关操作,需要将redis缓存数据情况,再次存入
    • 在service对应的增删改方法中,将redis数据删除。

    2、图解

    3、项目目录


    4、相关jar包和js

    mysql连接common1,Druid连接池,JDBCTemplate,servlet,jedis,redis连接common2,jQuery.js


    4、代码

    index.html
    
    
    
        
        Title
        <script src="js/jQuery-3.6.0.js"></script>
    
        <script>
            //入口函数
            $(function () {
                $.get("ProvinceServlet",{},function (data) {
                    //获取select
                    let province = $("#province");
                    //遍历json数组
                    $(data).each(function () {
                        //创建option标签,调用select的append追加
                        let option = "";
                        province.append(option);
                    })
                })
            })
        </script>
    
    
    
    
    
    
    ProvinceDaoImpl.java
    public class ProvinceDaoImpl implements ProvinceDao {
        //声明成员变量 template
        private JdbcTemplate template = new JdbcTemplate(DruidPoolUtils.getDatasource());
        @Override
        public List findAll() {
            String sql = "select * from province";
            List list = template.query(sql, new BeanPropertyRowMapper(Province.class));
            return list;
        }
    }
    
    ProvinceServiceImpl.java
    public class ProvinceServiceImpl implements ProvinceService {
        //声明dao
        private ProvinceDao dao = new ProvinceDaoImpl();
        @Override
        public List findAll() {
            return dao.findAll();
        }
    
        /**
         * 使用redis缓存
         * @return
         */
        @Override
        public String findAllJson() {
            //先从redis中查询数据
            Jedis jedis = JedisPoolUtils.getJedis();
            String province_json = jedis.get("province");
    
            //判断province_json是否为null
            if (province_json == null || province_json.length() == 0){
                //redis缓存中没有数据,则从数据库中查询
                System.out.println("redis中无数据,查询数据库...");
                List list = dao.findAll();
                //将list序列化为json
                ObjectMapper mapper = new ObjectMapper();
                try {
                    province_json = mapper.writeValueAsString(list);
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
                //将json数据存入redis
                jedis.set("province",province_json);
                //归还连接
                jedis.close();
            } else {
                System.out.println("redis中有数据,查询缓存");
            }
            return province_json;
        }
    }
    
    ProvinceServlet.java
    @WebServlet("/ProvinceServlet")
    public class ProvinceServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //调用service查询
            ProvinceServiceImpl service = new ProvinceServiceImpl();
            String json = service.findAllJson();
            //System.out.println(json);
    
            //响应结果
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(json);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
    德鲁伊连接池工具类
    public class DruidPoolUtils {
        private static DataSource ds;
        static {
            try {
                //读取配置文件
                Properties pro = new Properties();
                InputStream is = DruidPoolUtils.class.getClassLoader().getResourceAsStream("druid.properties");
                pro.load(is);
                ds = DruidDataSourceFactory.createDataSource(pro);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static DataSource getDatasource(){
            return ds;
        }
    
        public static Connection getConnection() throws SQLException {
            return ds.getConnection();
        }
    }
    
    Jedis连接池工具类
    public class JedisPoolUtils {
    
        private static JedisPool jedisPool;
    
        static {
            //读取配置文件
            InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream("jedis.properties");
            //创建Properties对象
            Properties pro = new Properties();
            //关联文件
            try {
                if (is != null) {
                    pro.load(is);
                }else{
                    System.out.println("文件流空指针");
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            //获取数据,设置到JedisPoolConfig中
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal")));
            config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle")));
    
            //初始化JedisPool
            jedisPool = new JedisPool(config, pro.getProperty("host"), Integer.parseInt(pro.getProperty("port")));
        }
    
        /**
         * 获取连接方法
         */
        public static Jedis getJedis() {
            return jedisPool.getResource();
        }
    }