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<Person> 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<T>		getConstructor(Class...):获取某个public的Constructor;
Constructor<T>		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<T>		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<AnnotationTest> 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,小数点后最多1PRIMARY 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 字段列表 FROM1 [INNER] JOIN2 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 字段列表 FROM1 LEFT [OUTER] JOIN2 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 字段列表 FROM1 RIGHT [OUTER] JOIN2 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<Stu> list = new JdbcDemo3().findAll();
        System.out.println(list);
    }
    
    /**
     * 查询所有stu对象的方法
     * @return
     */
    public List<Stu> findAll(){
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        List<Stu> 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<Stu> list = new JdbcDemo4().findAll();
        System.out.println(list);
    }
    /**
     * 演示JDBCUtils
     * @return
     */
    public List<Stu> findAll(){
        Connection connection = null;
        Statement statement = null;
        ResultSet resultSet = null;
        List<Stu> 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<String, Object> map = template.queryForMap(sql4, 1);
        System.out.println(map);
    }

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

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

            @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<User> list = template.query(sql6, new BeanPropertyRowMapper<User>(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>:html文档的根标签

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

    <head>
        <!-- 指定字符集 -->
        <meta charset = "UTF-8">   
    </head>	
    
  • <title> </title>:标题标签。

  • <body> </body>:体标签

  • <!DOCTYPE html>:html5中定义该文档是html文档


2、文本标签

和文本有关的标签

  • 注释:<!-- 注释内容 -->
  • <h1> </h1> to <h6> </h6>:标题标签,h1~h6:字体大小逐渐递减
  • <p> </p>:段落标签
  • <br>:换行标签
  • <hr>:展示一条水平线
  • <i>: 字体斜体
  • <em>: 定义强调文本。
    • <i><em>显示效果相同,但是意义不同。
    • <i>: 仅仅是一种样式/风格需求。
    • <em>: 表示“强调”的意思,搜索引擎会了解这些语义,一些屏幕阅读器可能使用不同的inflection,更利于SEO。
  • <b>: 字体加粗
  • <strong>:定义语气更为强烈的强调文本。
    • <b><strong>显示效果相同,但是意义不同
    • <b>为了加粗而加粗,是一种样式/风格需求。
    • <strong>为了标明重点而加粗,表示该文本比较重要,提醒读者/终端注意。
  • <font>:字体标签,HTML5 中不支持。请使用 CSS 代替。
  • <center>: 居中标签。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. 数值%:占比相对于父元素的比例

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

  • 空格键的空格:无论多少个,只显示一个
  • 空格:&nbsp;
  • 大于符号(greater than):&gt;
  • 小于符号(less than):&lt;
  • 版权符号©:&copy;

3、图片标签<img>

<img>:展示图片

属性:

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

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

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

    <ol>
      <li>Java</li>
      <li>Python</li>
      <li>C</li>
      <li>C++</li>
    </ol>
    
    <!--
    效果:    
    		1.Java
    		2.Python
    		3.C
    		4.C++
    -->
    
    <ol type="A" start="5">
      <li>Java</li>
      <li>Python</li>
      <li>C</li>
      <li>C++</li>
    </ol>
    
    <!--
    效果:    
    		E.Java
    		F.Python
    		G.C
    		H.C++
    -->
    
  • 无序列表(order list)

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

    <ul>
      <li>Java</li>
      <li>Python</li>
      <li>C</li>
      <li>C++</li>
    </ul>
    
    <!--
    效果:
    		·Java
    		·Python
    		·C
    		·C++
    -->
    
  • 自定义列表(definition list)

    <dl>
      <dt>机器语言</dt>
      <dd>Java</dd>
      <dd>Python</dd>
      <dd>C</dd>
      <dd>C++</dd>
      <dd>Go</dd>
    </dl>
    
    <!--
    效果:    
    	机器语言
    		Java
    		Python
    		C
    		C++
    -->
    

5、链接标签<a>

<a> </a>

<!--
href: 跳转路径 必填
      标签内可以放图片标签作为内容
target: 表示窗口在哪里打开
        _blank  在新窗口打开
        _self   在当前窗口打开(默认)
-->
<!-- 这是个图片链接 -->
<a href="https://limestart.cn/?from=old" target="_blank">
  <img src="../resource/image/小号头像.jpg" alt="图片加载失败了" 
       title="点击跳转到青柠主页" width="300" height="250">
</a><br>



<!--锚链接
1.需要一个标记,定义一个标记
2.跳转到标记
-->
<!--使用name作为 标记 -->
<a name="#top">这里是顶部</a><br>
       ...
<!--跳转到当前页面的#top标记处 -->
<a href="#top">回到顶部</a><br>
<!--跳转到指定页面的#down标记处 -->
<a href="3.图像标签.html#down">跳转到图像标签页的底部</a><br>

<!--功能性链接
邮件链接: mailto:
QQ链接:去QQ推广里面制作链接
  • 图片链接

    <!--
    href: 跳转路径 必填
          标签内可以放图片标签作为内容
    target: 表示窗口在哪里打开
            _blank  在新窗口打开
            _self   在当前窗口打开(默认)
    -->
    
    <!-- 这是个图片链接 -->
    <a href="https://limestart.cn/?from=old" target="_blank">
      <img src="../resource/image/小号头像.jpg" alt="图片加载失败了" 
           title="点击跳转到青柠主页" width="300" height="250">
    </a>
    
  • 超文本链接

    <!-- 这是个超文本链接 -->
    <a href="https://www.baidu.com/">点击跳转到百度</a>
    
  • 锚链接

    <!--锚链接
    1.需要一个标记,定义一个标记
    2.跳转到标记
    -->
    
    <!--使用name作为 标记 -->
    <a name="#top">这里是顶部</a><br>
           ...
    <!--跳转到当前页面的#top标记处 -->
    <a href="#top">回到顶部</a><br>
    <!--跳转到指定页面的#down标记处 -->
    <a href="3.图像标签.html#down">跳转到图像标签页的底部</a><br>
    
  • 功能性链接

    <!--功能性链接
    邮件链接: mailto:
    QQ链接:去QQ推广里面制作链接 -->
    
    <!-- 邮件链接 -->
    <a href="mailto:348689061@qq.com">点击联系我</a><br>
    
    <!-- QQ链接 -->
    <a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=&site=qq&menu=yes">
      <img border="0" src="http://wpa.qq.com/pa?p=2:348689061:53" alt="点击这里给我发消息" title="点击这里给我发消息"/>
    </a>
    

6、页面结构标签

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

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

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

</table>

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

9、视频和音频
<!--视频元素
controls: 控制界面
autoplay: 自动播放(需要浏览器允许)
muted: 静音 (默认不静音)
-->

<!-- 视频 -->
<video src="../resource/video/大气震撼七夕情人节片头.mp4" controls autoplay></video>

<!-- 音频 -->
<audio src="../resource/audio/阿YueYue - 云与海.flac" controls autoplay></audio>

10、内联框架iframe
<!--iframe内联框架
src: 地址
w-h: 宽高
name: 别名-->

<iframe src="//player.bilibili.com/player.html?aid=55631961&bvid=BV1x4411V75C&cid=97257627&page=10"
        scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"
        name="this" width="1420px" height="1080px">
</iframe><br>

<!--在iframe的位置显示跳转的页面-->
<a href="https://www.bilibili.com/" target="this">点击跳转到B站</a>

2、表单form(内容多)

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

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

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

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

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

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


2、表单项标签
1、<input>

可以通过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> </label>

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

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

下拉列表

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

文本域

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

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

4、代码举例
<form action="1.我的第一个网页.html" method="get">

  <!--文本输入框input
  type: 文本格式
  -->
  <p>名字: <input type="text" name="username" placeholder="请输入用户名" required></p>
  <p>密码: <input type="password" name="pwd"></p>

  <!--radio单选框
  用name控制分组,实现组的单选
  checked: 默认选中-->
  <p>性别:
    <input type="radio" value="boy" name="sex" checked><input type="radio" value="girl" name="sex"></p>

  <!--checkbox多选框
  也是用name控制分组
  checked: 默认选中
    -->
  <p>爱好:
    <input type="checkbox" name="hobby" value="code">敲代码
    <input type="checkbox" name="hobby" value="singing">唱歌
    <input type="checkbox" name="hobby" value="game" checked>游戏
    <input type="checkbox" name="hobby" value="chat">聊天
    <input type="checkbox" name="hobby" value="sleep">睡觉
  </p>

  <!--button按钮
   -->
  <p>按钮:
    <input type="button" name="bt1" value="点击变长"><br>
  <!--图片按钮
  具有自动提交的功能,跳转到提交表单的页面-->
    <input type="image" src="../resource/image/QQ头像.jpg" width="300px" height="250px">
  </p>

<!--select下拉框
  selected: 默认下拉框的选项
  name: 列表名称
  -->
  <p>国家(下拉框):
    <select name="country" id="1">
      <option value="选项的值">中国</option>
      <option value="选项的值">美国</option>
      <option value="选项的值" selected>瑞士</option>
      <option value="选项的值">英国</option>
      <option value="选项的值">巴基斯坦</option>
    </select>
  </p>

<!--textarea文本域
-->
  <p>反馈:
    <textarea name="textarea" id="2" cols="30" rows="10">文本内容</textarea>
  </p>

<!--文件域
  input type="file" name="files"-->
  <p>提交文件:
    <input type="file" name="files">
  </p>

<!--邮件验证-->
  <p>邮箱:
    <input type="email" name="email">
  </p>
  <p>自定义邮箱:
    <input type="text" name="diy_email" required pattern="^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$">
  </p>


<!--URL验证-->
  <p>网址:
    <input type="url" name="url">
  </p>

<!--数字-->
  <p>商品数量:
    <input type="number" name="num" max="100" min="0" step="1">
  </p>

<!--滑块-->
  <p>音量:
    <input type="range" name="voice" max="255" min="0" step="2">
  </p>

<!--搜索框-->
  <p>搜索:
    <input type="search" name="search">
  </p>

<!--增强鼠标可用性-->
  <label for="mark">你点我试试</label>
  <input type="reset" id="mark">
  <p>
    <input type="submit">
    <input type="reset">
  </p>
</form>


<h1>注册post提交</h1>
<form action="1.我的第一个网页.html" method="post">
  <!--文本输入框input
  type: 文本格式
  -->
  <p>名字: <input type="text" name="username"></p>
  <p>密码: <input type="password" name="pwd"></p>
  <p>
    <input type="submit">
    <input type="reset">
  </p>
</form>

3.3、CSS

1、 什么是CSS

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

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

2、作用

表现层,美化网页

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

3、优势

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

4、CSS的三种导入方式

  • 内部样式
  • 外部样式
  • 行内样式
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

  <!-- 内部样式 -->
  <style>
    h1{
      color: blue;
    }
  </style>

  <!-- 外部样式 -->
<!--  链接式:-->
  <link rel="stylesheet" href="css/style.css">
<!--  导入式-->
  <style>
    @import url("css/style.css");
  </style>

</head>
<body>

<!--样式优先级:
    遵循就近原则, 离代码的远近, 此处是内部 > 外部 > 内部-->

<!--行内样式: 在标签元素中,编写一个style属性,编写样式即可,不符合结构与表现分离-->
<h1 style="color: crimson">标题</h1>

</body>
</html>

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

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

      <!-- 外部样式 -->
      <link rel="stylesheet" href="css/style.css">
    
  • 导入式:

    @import是 CSS2.1 特有的

    <!--  导入式-->
      <style>
        @import url("css/style.css");
      </style>
    

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

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


5、(重点)选择器

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

5.1、基本选择器

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

1、标签选择器

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

  <style>
    /**标签选择器,会选择到页面上所有的这个标签的元素**/
    h1{
      color: crimson;
      background: aquamarine;
      border-radius: 10px;
    }
    p{
      color: blue;
    }
  </style>
</head>
<body>

<h1>学java</h1>
<h1>学汇编</h1>
<p>学Python</p>
<p>学C++</p>

</body>
</html>

2、类选择器

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

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>
      /* 类选择器格式: .class名称{}
        好处,可以多个标签归类,是同一个class */
      .title_1{
        color: crimson;
      }
      .title_2{
        color: blue;
      }
    </style>
</head>
<body>

<h1 class="title_1">标题1</h1>
<h1 class="title_2">标题2</h1>
<h1 class="title_1">标题3</h1>
<h1 class="title_2">标题4</h1>
<!--不同的标签也可以用同一个class-->
<p class="title_1">标题5</p>

</body>
</html>

3、ID选择器

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

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

  <style>
    
    /*ID选择器格式: #id名称{}
      ID必须保证全局唯一!*/
    #title_3{
      color: #dcd914;
    }
    #title_4{
      color: #f33a13;
    }
  </style>

</head>
<body>

<h1 id="title_3">标题3</h1>
<h1 id="title_4">标题4</h1>

</body>
</html>

5.2、高级选择器
1、层次选择器
<body>

<p>p1</p>
<p class="active">p2</p>
<p>p3</p>
    
<ul id="u_list">
  <li>
    <p>p4</p>
  </li>
  <li>
    <p>p5</p>
  </li>
  <li>
    <p>p6</p>
  </li>
  <p>p7与li同级</p>
</ul>
    
<p class="active">p8</p>
<p>p9</p>

</body>

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)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!-- 避免使用class,id选择器 -->
  <style>
      /*ul的第1个子元素*/
      ul li:first-child{
        background: #dcd914;
      }

      /*ul的最后1个子元素*/
      ul li:last-child{
        background: #d61326;
      }

      /*ul的第2个子元素*/
      ul li:nth-child(2){
        background: blue;
      }

      /* 选中p1: 定位到父元素(body), 选择当前的第2个元素,并且这个元素标签要跟:前的标签一样  (不好用)*/
      p:nth-child(2){
        color: blue;
      }

      /* 选中p2: 定位到父元素下(body)的p的第2个元素 (不好用)*/
      p:nth-of-type(2){
        color: blueviolet;
      }

  </style>

</head>
<body>
    
<h1>h1</h1>
<p>p1</p>
<p>p2</p>
<p>p3</p>
<ul>
  <li>li1</li>
  <li>li2</li>
  <li>li3</li>
</ul>
    
</body>
</html>

3、属性选择器(常用)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>
      .demo a{
        /*左浮动*/
        float: left;
        /*块级元素*/
        display: block;
        /*宽高*/
        width: 50px;
        height: 50px;
        /*圆角*/
        border-radius: 25px;
        /*对齐方式,居中*/
        text-align: center;
        /*文字颜色*/
        color: chocolate;
        /*去掉下划线*/
        text-decoration: none;
        /*外边距*/
        margin: 10px;
        /*行高,使文字居中*/
        line-height: 50px;
      }

      /* 选中存在指定属性的元素,可以写多个属性 a[属性名1][属性名2][...]{} */
      a[id]{
        background: #dcd914;
      }
      /*
      [abc^="def"]	选择 abc 属性值以 "def" 开头的所有元素
      [abc$="def"]	选择 abc 属性值以 "def" 结尾的所有元素
      [abc*="def"]	选择 abc 属性值中包含子串 "def" 的所有元素
      */
      /* 选中class中有first的元素 ,正则匹配*/
      a[class*="active"]{
        background: #dc1428;
      }

      /* 选中class中first开头的元素*/
      a[class^="first"]{
        background: blue;
      }

      /* 选中class中item结尾的元素 */
      a[class$="item"]{
        background: chartreuse;
      }

      /* 结合元素选择器 选中链接4  只会匹配class中的字串*/
      a.second.first{
        background: black;
      }
      /* 多类选择器 选中链接3,h1, 如果不指定匹配元素,则会匹配所有元素*/
      .demo{
        background: #183de8;
      }
    </style>
</head>
<body>

  <p class="demo">
    <!-- 超链接 -->
    <a href="" class="links item 1" id="first">链接1</a>
    <a href="" class="links item active" target="_blank" title="test">链接2</a>
    <a href="" class="links item 3 demo">链接3</a>
    <a href="" class="links item second first">链接4</a>
    <a href="" class="links item 5">链接5</a>
    <a href="" class="links item first">链接6</a>
    <a href="" class="links item 7" id="second">链接7</a>
    <a href="" class="first links item">链接8</a>
    <a href="" class="links item 9">链接9</a>
  </p>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <p>
    <h1 class="demo">标题1</h1>
  </p>
</body>
</html>

6、美化网页元素

6.1、span标签
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <style>
    #java{
      color: #dc1428;
    }
  </style>
</head>
<body>

欢迎学习<span id="java">java</span>

</body>
</html>


6.2、字体样式

CSS 字体 (w3school.com.cn)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
<!--
 字体系列font-family
 字体风格font-style
 字体变形font-variant
 ...
 -->
  <style>
    body{
      font-family: 宋体;
      font-style: oblique;/* 斜体 */
      font-variant: small-caps;/* 小型大写字母 ,原本是大写的不会改变 */
      ...
    }
  </style>
</head>
<body>

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

</body>
</html>

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、列表
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

  <link rel="stylesheet" href="css/style.css">
</head>
<body>
<!-- 效果:
      链接不要下划线,无序列表前的 点 去掉-->
<div id="nav">
  <h2 class="title">全部商品分类</h2>
  <ul>
    <li><a href="#">图书</a>&nbsp;&nbsp;<a href="#">音像</a></li>
    <li><a href="#">家电</a>&nbsp;&nbsp;<a href="#">手机</a></li>
    <li><a href="#">电脑</a>&nbsp;&nbsp;<a href="#">办公</a></li>
    <li><a href="#">家居</a>&nbsp;&nbsp;<a href="#">家装</a></li>
    <li><a href="#">服饰</a>&nbsp;&nbsp;<a href="#">化妆</a></li>
    <li><a href="#">食品</a>&nbsp;&nbsp;<a href="#">保健</a></li>
  </ul>
</div>

</body>
</html>
#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;

<!DOCTYPE html>
<html>
<head>
<style>
div {
  border: 3px solid #4CAF50;
  padding: 5px;
}

.img1 {
  float: right;
}

.clearfix {
  overflow: auto;
}

.img2 {
  float: right;
}
</style>
</head>
<body>

<h1>Clearfix</h1>

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

<div>
  <img class="img1" src="/i/logo/w3logo-3.png" alt="W3School" width="180" height="167">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum...
</div>

<p style="clear:right">请为包含元素添加一个带有 overflow: auto; 的 clearfix 类,以解决此问题:</p>

<div class="clearfix">
  <img class="img2" src="/i/logo/w3logo-3.png" alt="W3School" width="180" height="167">
  Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus imperdiet, nulla et dictum interdum...
</div>

</body>
</html>

但是,新的现代 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
<!-- 模板页面 -->
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
  <title>Bootstrap 101 Template</title>

  <!-- Bootstrap -->
  <link rel="stylesheet" href="css/bootstrap.min.css" crossorigin="anonymous">
  <!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
  <script src="js/jQuery-3.6.0.js" crossorigin="anonymous"></script>
  <!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
  <script src="js/bootstrap.min.js" crossorigin="anonymous"></script>

</head>
<body>
<h1>你好,世界!</h1>


</body>
</html>

2、响应式布局

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

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

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

<div class="container">
  <div class="row">
    <div class="col-md-4" inner>
      <span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span>
    </div>
    <div class="col-md-8" inner>
        <!-- 这里的row重新被分为了12个栅格 -->
      <div class="row">
        <div class="col-md-4"><span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span></div>
        <div class="col-md-4"><span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span></div>
        <div class="col-md-4"><span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span></div>
      </div>
      <div class="row">
        <div class="col-md-4"><span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span></div>
        <div class="col-md-4"><span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span></div>
        <div class="col-md-4"><span><img src="img/QQ头像.jpg" alt="" class="img-responsive"></span></div>
      </div>
    </div>
  </div>
</div>

  • 工作原理

  • 步骤:

    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、按钮

<a><button><input> 元素添加按钮类(button class)即可使用 Bootstrap 提供的样式。

<a class="btn btn-default" href="#" role="button">Link</a>
<button class="btn btn-default" type="submit">Button</button>
<input class="btn btn-default" type="button" value="Input">
<input class="btn btn-default" type="submit" value="Submit">
  • 虽然按钮类可以应用到 <a><button> 元素上,但是,导航和导航条组件只支持 <button> 元素。
  • 如果 <a> 元素被作为按钮使用 -- 并用于在当前页面触发某些功能 -- 而不是用于链接其他页面或链接当前页面中的其他部分,那么,务必为其设置 role="button" 属性。
  • 强烈建议尽可能使用 <button> 元素来获得在各个浏览器上获得相匹配的绘制效果。

按钮样式

<body>
<button type="button" class="btn btn-default">(默认样式)Default</button>
<button type="button" class="btn btn-primary">(首选项)Primary</button>
<button type="button" class="btn btn-success">(成功)Success</button>
<button type="button" class="btn btn-info">(一般信息)Info</button>
<button type="button" class="btn btn-warning">(警告)Warning</button>
<button type="button" class="btn btn-danger">(危险)Danger</button>
<button type="button" class="btn btn-link">(链接)Link</button>
</body>

按钮尺寸

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

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


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

  • 图片形状

    <img src="..." alt="..." class="img-rounded">:方形

    <img src="..." alt="..." class="img-circle"> : 圆形

    <img src="..." alt="..." class="img-thumbnail"> :相框

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

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

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


3、表格
  • 条纹状表格

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

    <table class="table table-striped">
      ...
    </table>
    
  • 带边框的表格

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

  • 鼠标悬停

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

  • 紧缩表格

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

  • 响应式表格

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

  • 状态类(行或单元格)

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

    Class 描述
    .active 鼠标悬停在行或单元格上时所设置的颜色
    .success 标识成功或积极的动作
    .info 标识普通的提示信息或动作
    .warning 标识警告或需要用户注意
    .danger 标识危险或潜在的带来负面影响的动作
    <div class="container">
        <table class="table">
            <!-- 作用在行上 -->
            <tr class="active">
                <td>active</td>
                <td>active</td>
                <td>active</td>
                <td>active</td>
                <td>active</td>
            </tr>
            <tr class="success">
                <td>success</td>
                <td>success</td>
                <td>success</td>
                <td>success</td>
                <td>success</td>
            </tr>
            <tr class="warning">
                <td>warning</td>
                <td>warning</td>
                <td>warning</td>
                <td>warning</td>
                <td>warning</td>
            </tr>
            <tr class="danger">
                <td>danger</td>
                <td>danger</td>
                <td>danger</td>
                <td>danger</td>
                <td>danger</td>
            </tr>
            <tr class="info">
                <td>info</td>
                <td>info</td>
                <td>info</td>
                <td>info</td>
                <td>info</td>
            </tr>
            <!-- 作用在列或者表头上 -->
            <tr>
                <td class="active">active</td>
                <td class="success">success</td>
                <td class="warning">warning</td>
                <td class="danger">danger</td>
                <td class="info">info</td>
            </tr>
        </table>
    </div>
    


4、表单

单独的表单控件会被自动赋予一些全局样式。所有设置了 .form-control 类的 <input><textarea><select> 元素都将被默认设置宽度属性为 width: 100%;。 将 label 元素和前面提到的控件包裹在 .form-group 中可以获得最好的排列。


3.2、常用组件
1、Glyphicons 字体图标)

包括 250 多个来自 Glyphicon Halflings 的字体图标。Glyphicons Halflings 一般是收费的,但是他们的作者允许 Bootstrap 免费使用。为了表示感谢,希望你在使用时尽量为 Glyphicons 添加一个友情链接。

  • 图标类不能和其它组件直接联合使用。它们不能在同一个元素上与其他类共同存在。应该创建一个嵌套的 <span> 标签,并将图标类应用到这个 <span> 标签上。
  • 图标类只能应用在不包含任何文本内容或子元素的元素上。
  • 可以把它们应用到按钮、工具条中的按钮组、导航或输入框等地方。
<button type="button" class="btn btn-default" aria-label="Left Align">
  <span class="glyphicon glyphicon-align-left" aria-hidden="true"></span>
</button>

<button type="button" class="btn btn-default btn-lg">
  <span class="glyphicon glyphicon-star" aria-hidden="true"></span> Star
</button>
2、导航条

务必使用 <nav> 元素,或者,如果使用的是通用的 <div> 元素的话,务必为导航条设置 role="navigation" 属性,这样能够让使用辅助设备的用户明确知道这是一个导航区域。

3、分页条

为您的网站或应用提供带有展示页码的分页组件,或者可以使用简单的翻页组件

<nav aria-label="Page navigation">
  <ul class="pagination">
    <li>
      <a href="#" aria-label="Previous">
        <span aria-hidden="true">&laquo;</span>
      </a>
    </li>
    <li><a href="#">1</a></li>
    <li><a href="#">2</a></li>
    <li><a href="#">3</a></li>
    <li><a href="#">4</a></li>
    <li><a href="#">5</a></li>
    <li>
      <a href="#" aria-label="Next">
        <span aria-hidden="true">&raquo;</span>
      </a>
    </li>
  </ul>
</nav>

3.3、常用JS插件
  • 轮播图需要注意的是图片够宽或者屏幕够窄才会实现轮播效果
  • 标签页:添加快速、动态的标签功能,甚至通过下拉菜单,也可以在本地内容的窗格中切换。不支持嵌套选项卡。

3.6、XML

详见另一份XML文档


4、JavaWeb核心技术

常见的java相关的web服务器软件

  • webLogic:oracle公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
  • webSphere:IBM公司,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
  • JBOSS:JBOSS公司的,大型的JavaEE服务器,支持所有的JavaEE规范,收费的。
  • Tomcat:Apache基金组织,中小型的JavaEE服务器,仅仅支持少量的JavaEE规范servlet/jsp。开源的,免费的。

4.1、Tomcat

web服务器软件,纯java编写,需要java开发环境

在服务器上配置tomcat指定的端口供其他电脑访问

1、下载

http://tomcat.apache.org/ 建议用9版本,暂时不要使用10版本,需要Servlet5.0支持

2、安装

解压压缩包即可。注意:安装目录建议不要有中文和空格

目录:

  • bin:可执行文件
  • conf:配置文件
  • lib:依赖jar包
  • logs:日志文件
  • temp:临时文件
  • webapps:存放web项目
  • work:存放运行时的数据

3、卸载

删除目录就行了

4、启动

  • bin/startup.bat ,双击运行该文件即可

  • 访问:浏览器输入:http://localhost:8080 回车访问自己 查看自身ip:cmd/ipconfig
    http://别人的ip:8080 访问别人

  • 可能遇到的问题:

    1. 黑窗口一闪而过:
      • 原因: 没有正确配置JAVA_HOME环境变量
      • 解决方案:正确配置JAVA_HOME环境变量
    2. 启动报错:
      1. 暴力:找到占用的端口号,并且找到对应的进程,杀死该进程
        • netstat -ano
      2. 温柔:修改自身的端口号
        • conf/server.xml
        • <Connector port="8888" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8445" />
        • port是http端口号,redirectPort是重定向端口号(https)。当用户用http请求某个资源,而该资源本身又被设置了必须要https方式访问,此时Tomcat会自动重定向到这个redirectPort设置的https端口。
        • 一般会将tomcat的默认端口号修改为80,80端口号是http协议的默认端口号。
          • 好处:在访问时,就不用输入端口号
    3. 乱码
    -`conf/logging.properties`里面将编码全部改为GBK(如果你的命令行窗口是GBK解码)
    

5、关闭

  1. 正常关闭:
    • bin/shutdown.bat
    • ctrl+c
  2. 强制关闭:
    • 点击启动窗口的 ×

6、配置

  • 部署项目的方式:
    1. 直接将项目放到webapps目录下即可。(一般不用)

      • /hello:项目的访问路径-->虚拟目录
      • 简化部署:将项目打成一个war包,再将war包放置到webapps目录下。
        • war包会自动解压缩
        • 删除项目时只需要删除war包,解压的文件夹会自动被删除
    2. 配置conf/server.xml文件(一般不用)
      <Host>标签体中配置
      <Context docBase="D:\hello" path="/hehe" />

      • docBase:项目存放的路径
      • path:虚拟目录,网址上访问的路径
    3. 在conf\Catalina\localhost创建任意名称的xml文件。在文件中编写(常用)
      <Context docBase="D:\hello" />

      • 虚拟目录:xml文件的名称,不带后缀
      • 关闭项目时方便,只需修改xml后缀名,让服务器访问不到即可,不用重启服务器
  • 静态项目和动态项目:
    • 目录结构
      • java动态项目的目录结构:
        • 项目的根目录
          • WEB-INF目录
            • web.xml:web项目的核心配置文件
            • classes目录:放置字节码文件的目录
            • lib目录:放置依赖的jar包
  • 将Tomcat集成到IDEA(2021)中,并且创建JavaEE的项目,部署项目。
    1. 新建一个项目
    2. 在 运行/编辑配置中,左上角+添加Tomcat服务器/本地。
    3. 在添加好的页面配置中,找到部署,左上角+部署项目,然后下拉,可以找到目录映射配置
    4. 右键项目目录,添加框架支持,web应用

4.2、Servlet

server applet 运行在服务器端的小程序,web的三大组件之一。

Servlet就是一个 接口 ,定义了Java类被浏览器访问到(tomcat识别)的 规则

将来我们自定义一个类,实现Servlet接口,复写方法。

Servlet 简介 | 菜鸟教程 (runoob.com)

1、快速入门

  1. 创建JavaEE项目

  2. 定义一个类,实现Servlet接口,需要去Tomcat的lib目录下找到Servlet的jar包

    • public class ServletDemo1 implements Servlet
  3. 实现接口中的抽象方法,一共五个

  4. 配置Servlet
    在服务器的web.xml中配置:

    <!--配置Servlet-->
    <servlet>
        <servlet-name>demo</servlet-name>
        <!-- 这里写全类名,tomcat会通过反射将对应的字节码文件加载进内存 Class.forName() -->
        <servlet-class>com.xiao.web.servlet.ServletDemo</servlet-class>
    </servlet>
    <!--配置映射-->
    <servlet-mapping>
        <servlet-name>demo</servlet-name>
        <!-- 这里写全类名的映射 -->
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>
    

2、执行原理

  1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
  2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
  3. 如果有,则在找到对应的<servlet-class>全类名
  4. tomcat会将字节码文件加载进内存,并且创建其对象
  5. 调用其方法

3、Servlet中的生命周期方法

  1. 被创建:执行init方法,只执行一次

    • Servlet什么时候被创建?

        * 默认情况下,**第一次被访问时,Servlet被创建**
      
      • 可以配置执行Servlet的创建时机。
        <servlet>标签下配置
        1. 第一次被访问时,创建

          • <load-on-startup>的值为负数
        2. 在服务器启动时,创建

          • <load-on-startup>的值为0或正整数
<!--配置Servlet-->
  <servlet>
      <servlet-name>demo</servlet-name>
      <servlet-class>com.xiao.web.servlet.ServletDemo</servlet-class>
      <!--1. 第一次被访问时,创建<load-on-startup>的值为负数
          2. 在服务器启动时,创建<load-on-startup>的值为0或正整数 -->
      <load-on-startup>0</load-on-startup>
  </servlet>

  • Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的

  • 多个用户同时访问,对数据进行修改时,可能存在线程安全问题。

    • 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对其修改值
  1. 提供服务:执行service方法,执行多次

    • 每次访问Servlet时,Service方法都会被调用一次。
  2. 被销毁:执行destroy方法,只执行一次

    • Servlet被销毁时执行。服务器关闭时,Servlet被销毁

    • 只有服务器正常关闭时,才会执行destroy方法。

    • destroy方法在Servlet被销毁之前执行,一般用于释放资源


4、Servlet3.0注解配置

JavaEE 6 开始支持

  • 好处:

    • 支持注解配置。可以不需要web.xml了。
  • 步骤:

    1. 创建JavaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml
    2. 定义一个类,实现Servlet接口
    3. 复写方法
    4. 在类上使用@WebServlet注解,进行配置
      • @WebServlet("资源路径")
  • 创建web项目时,idea创建servlet不能快速创建的问题

    解决方案:

    1.菜单栏File中找到-Project Structure打开--->Modules---->点击Web------>最下面,源根,勾中....../java  的这个目录---->apply>ok
    
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
    String name() default "";//相当于<Servlet-name>

    String[] value() default {};//代表urlPatterns()属性配置

    String[] urlPatterns() default {};//相当于<url-pattern>

    int loadOnStartup() default -1;//相当于<load-on-startup>

    WebInitParam[] initParams() default {};

    boolean asyncSupported() default false;

    String smallIcon() default "";

    String largeIcon() default "";

    String description() default "";

    String displayName() default "";
}

5、IDEA与tomcat的相关配置

  1. IDEA会为每一个tomcat部署的项目单独建立一份配置文件

    • 查看控制台的log:Using CATALINA_BASE: "C:\Users\one_uncle\AppData\Local\JetBrains\IntelliJIdea2021.1\tomcat\ffc74629-dea6-4a8c-936e-1f414cee8db5"
    • servlet.xml是Tomcat服务器的配置文件
    • catalina/下的xml是项目部署的配置目录
  2. 工作空间项目 和 tomcat部署的web项目

    • tomcat真正访问的是“tomcat部署的web项目”,"tomcat部署的web项目"对应着"工作空间项目" 的web目录下的所有资源
    • WEB-INF目录下的资源不能被浏览器直接访问。WEB-INF是Java的WEB应用的安全目录。所谓安全就是客户端无法访问,只有服务端可以访问的目录。如果想在页面中直接访问其中的文件,必须通过web.xml文件对要访问的文件进行相应映射才能访问。
  3. 断点调试:使用"小虫子"启动 dubug 启动


6、Servlet的体系结构

​ Servlet -- 接口 顶级
​ |
​ GenericServlet -- 抽象
​ |
​ HttpServlet -- 抽象

1、GenericServlet
  • 将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象
  • 将来定义Servlet类时,可以继承GenericServlet,实现service()方法即可
  • 一般不用,经典白学
2、HttpServlet

对http协议的一种封装,简化操作

  1. 定义类继承HttpServlet
  2. 复写doGet/doPost方法

7、Servlet相关配置

  1. urlpartten: Servlet访问路径
    1. 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
    2. 路径定义规则:
      1. /xxx:路径匹配
      2. /xxx/xxx:多层路径,目录结构
      3. /xxx/* :/xxx/任意字符
      4. *.do:扩展名匹配,不能加/,只要扩展名匹配上就行,前面的名不管

4.3、HTTP

Hyper Text Transfer Protocol 超文本传输协议

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

1、概念

  • 传输协议:定义了,客户端和服务器端通信时,发送数据的格式
  • 特点:
    1. 基于TCP/IP的高级协议
    2. 默认端口号:80
    3. 基于请求/响应模型的:一次请求对应一次响应
    4. 无状态的:每次请求之间相互独立,不能交互数据
  • 历史版本:
    • 1.0:每一次请求响应都会建立新的连接
    • 1.1:复用连接

2、请求消息数据格式

客户端请求消息

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

1、请求行
请求方式 请求url 请求协议/版本
GET login.html HTTP/1.1
  • 请求方式:

    • HTTP协议有7中请求方式,常用的有2种

      • GET:

        1. 请求参数在请求行中,在url后。
        GET /demo3?username=asdasdad HTTP/1.1
        Host: localhost
        Connection: keep-alive
        
        1. 请求的url长度有限制的
        2. 不太安全
      • POST:

        1. 请求参数在请求体中

        2. 请求的url长度没有限制的

        3. 相对安全

2、请求头

客户端浏览器告诉服务器一些信息

请求头名称 : 请求头值

常见的请求头:

  1. User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息

    • 可以在服务器端获取该头的信息,解决浏览器的兼容性问题
  2. Refererhttp://localhost/login.html

    • 告诉服务器,我(当前请求)从哪里来?

      • 作用:

        1. 防 盗链(盗取超链接)

          if(referer.equals("本人网址")){
              展示内容  
          } else{ 
              拒绝访问 }
          
        2. 统计工作,统计点击来源,比如广告投放应用

3、请求空行

空行,就是用于分割POST请求的请求头,和请求体的。

4、请求体(正文)

封装POST请求消息的请求参数的

POST /demo3 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 19
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Microsoft Edge";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36 Edg/98.0.1108.50
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost/login.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie:...

Payload
username = xiaonanhai

3、Request对象

1、客户端和服务器交互流程
  • 客户端输入url访问时,tomcat服务器会根据请求url中的资源路径,创建对应的ServletDemo(项目中自己创建的类名)对象。当然也可以设置为启动服务器时创建,<load-on-startup>的值为0或正整数
  • tomcat服务器收到请求后,会创建Request和Response对象,Request对象中封装请求消息数据。
  • tomcat将Request和Response两个对象传递给service方法,并且调用它。
  • 我们可以通过Request对象获取请求消息数据,通过Response对象来设置响应消息。
  • 服务器在给浏览器做出响应前,会从Response对象中获取程序员设置的响应消息数据。
2、request对象和response对象的原理
  • request和response对象是由服务器创建的。我们来使用它们
  • request对象是来获取请求消息,response对象是来设置响应消息
3、request对象继承体系结构:

ServletRequest -- 接口 顶级
| 继承
HttpServletRequest -- 接口
| 实现
org.apache.catalina.connector.RequestFacade 类(tomcat编写)

4、request功能:
① 获取请求消息数据

Servlet 客户端 HTTP 请求

序号 方法 & 描述
1 String getMethod() 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
2 String getContextPath() 返回指示请求上下文的请求 URI 部分。
3 String getServletPath() 返回调用 JSP 的请求的 URL 的一部分。
4 String getQueryString() 返回包含在路径后的请求 URL 中的查询字符串。
5 String getRequestURI() 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。
6 String getProtocol() 返回请求协议的名称和版本。
7 String getRemoteAddr() 返回发送请求的客户端的互联网协议(IP)地址。
8 Cookie[] getCookies() 返回一个数组,包含客户端发送该请求的所有的 Cookie 对象。
9 Enumeration getAttributeNames() 返回一个枚举,包含提供给该请求可用的属性名称。
10 Enumeration getHeaderNames() 返回一个枚举,包含在该请求中包含的所有的头名。
11 Enumeration getParameterNames() 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。
12 HttpSession getSession() 返回与该请求关联的当前 session 会话,或者如果请求没有 session 会话,则创建一个。
13 HttpSession getSession(boolean create) 返回与该请求关联的当前 HttpSession,或者如果没有当前会话,且创建是真的,则返回一个新的 session 会话。
14 Locale getLocale() 基于 Accept-Language 头,返回客户端接受内容的首选的区域设置。
15 Object getAttribute(String name) 以对象形式返回已命名属性的值,如果没有给定名称的属性存在,则返回 null。
16 ServletInputStream getInputStream() 使用 ServletInputStream,以二进制数据形式检索请求的主体。
17 String getAuthType() 返回用于保护 Servlet 的身份验证方案的名称,例如,"BASIC" 或 "SSL",如果JSP没有受到保护则返回 null。
18 String getCharacterEncoding() 返回请求主体中使用的字符编码的名称。
19 String getContentType() 返回请求主体的 MIME 类型,如果不知道类型则返回 null。
20 String getHeader(String name) 以字符串形式返回指定的请求头的值。
21 String getParameter(String name) 以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。
22 String getPathInfo() 当请求发出时,返回与客户端发送的 URL 相关的任何额外的路径信息。
23 String getRemoteHost() 返回发送请求的客户端的完全限定名称。
24 String getRemoteUser() 如果用户已通过身份验证,则返回发出请求的登录用户,或者如果用户未通过身份验证,则返回 null。
25 String getRequestedSessionId() 返回由客户端指定的 session 会话 ID。
26 String[] getParameterValues(String name) 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null。
27 boolean isSecure() 返回一个布尔值,指示请求是否使用安全通道,如 HTTPS。
28 int getContentLength() 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1。
29 int getIntHeader(String name) 返回指定的请求头的值为一个 int 值。
30 int getServerPort() 返回接收到这个请求的端口号。
31 int getParameterMap() 将参数封装成 Map 类型。
  1. 获取请求行数据

    @WebServlet(name = "RequestDemo1", value = "/RequestDemo1")
    public class RequestDemo1 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //请求行:GET /one_uncle/RequestDemo1?name=xiaonanhai HTTP/1.1
            //1. 获取请求方式 :GET
            String method = request.getMethod();
            System.out.println("method:  " + method);
            
            //2. 获取虚拟目录:/one_uncle
            String contextPath = request.getContextPath();
            System.out.println("contextPath:  " + contextPath);
            
            //3. 获取Servlet路径: /RequestDemo1
            String servletPath = request.getServletPath();
            System.out.println("servletPath:  " + servletPath);
            
            // 4. 获取get方式请求参数:name=xiaonanhai
            String queryString = request.getQueryString();
            System.out.println("queryString:  " + queryString);
            
            //5. 获取请求URI:/one_uncle/RequestDemo1
            //URI:统一资源标识符 : /one_uncle/RequestDemo1		范围大		
            String requestURI = request.getRequestURI();
            System.out.println("requestURI:  " + requestURI );
            
            //6. 获取请求URL   http://localhost/one_uncle/RequestDemo1
            //URL:统一资源定位符 : http://localhost/one_uncle/RequestDemo1	  范围小
            StringBuffer requestURL = request.getRequestURL();
            System.out.println("requestURL:  " + requestURL);
            
            //7. 获取协议及版本:HTTP/1.1
            String protocol = request.getProtocol();
            System.out.println("protocol: " + protocol);
            
            //8. 获取客户机的IP地址:0:0:0:0:0:0:0:1       ipv6的
            String localAddr = request.getLocalAddr();
            System.out.println("localAddr:  " + localAddr);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        }
    }
    
  2. 获取请求头数据

    • 方法:
      • String getHeader(String name):通过请求头的名称获取请求头的值
      • Enumeration<String> getHeaderNames():获取所有的请求头名称 (用迭代器的方式遍历)
      • 其中的user-agent比较重要,返还客户端浏览器版本
    //Enumeration<String> getHeaderNames():获取所有的请求头名称
            //String getHeader(String name):通过请求头的名称获取请求头的值
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String value = request.getHeader(name);
                System.out.println(name + " : " + value);
            }
    
  3. 获取请求 体数据:

    • 请求体:只有POST请求方式,才有请求体,在请求体中封装了POST请求的请求参数

    • 步骤:

      1. 获取流对象

        • BufferedReader getReader():获取字符输入流,只能操作字符数据
        @Override
            protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                //BufferedReader getReader():获取字符输入流,只能操作字符数据
                BufferedReader reader = request.getReader();
                //读取数据
                String line = null;
                while ((line = reader.readLine()) != null){
                    System.out.println(line);
                }
                //username=zhangsan&password=hahahah
            }
        
        • ServletInputStream getInputStream():获取字节输入流,可以操作所有类型数据
          • 在文件上传知识点后讲解
      2. 再从流对象中拿数据

② 其他功能
  1. 获取请求参数通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数

    • String getParameter(String name):根据参数名称获取参数值 username=zs&password=123 一个键对应一个值

    • String[] getParameterValues(String name):根据参数名称获取参数值的数组 hobby=xx&hobby=game 一个键对应多个值

    • Enumeration<String> getParameterNames():获取所有请求的参数名称

    • Map<String,String[]> getParameterMap():获取所有参数的map集合

    • 中文乱码问题:
      • get方式:tomcat 8 已经将get方式乱码问题解决了
      • post方式:会乱码
        • 解决:在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");
    @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //Post  获取请求参数
            request.setCharacterEncoding("UTF-8");
            String username = request.getParameter("username");
            System.out.println("Post");
            System.out.println(username);
            //String[] getParameterValues(String name):根据参数名称获取参数值的数组
            String[] hobbies = request.getParameterValues("hobby");
            for (String hobby : hobbies) {
                System.out.println(hobby);
            }
            //Enumeration`<String>` getParameterNames():获取所有请求的参数名称
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()){
                String name = parameterNames.nextElement();
                System.out.println(name);
                String value = request.getParameter(name);
                System.out.println(value);
                System.out.println("--------------");
            }
            //Map<String,String[]> getParameterMap():获取所有参数的map集合
            Map<String, String[]> parameterMap = request.getParameterMap();
            Set<String> keySet = parameterMap.keySet();
            for (String name : keySet) {
                //根据KEY获取值
                String[] strings = parameterMap.get(name);
                for (String value : strings) {
                    System.out.println(value);
                }
                System.out.println("--------------");
            }
        }
    
  2. 请求转发:一种在服务器内部的资源跳转方式,在不同的Servlet中跳转

    1. 步骤:
      1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
      2. 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)
    //跳转到/RequestDemo2的servlet
    request.getRequestDispatcher("/RequestDemo2").forward(request,response);
    
    1. 特点(面试)
      • 浏览器地址栏路径不发生变化
      • 只能转发到当前服务器内部资源中。
      • 转发是一次请求
  3. 共享数据:

    • 域对象:一个有作用范围的对象,可以在范围内共享数据
    • request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
    • 方法:
      1. void setAttribute(String name,Object obj):存储数据
      2. Object getAttitude(String name):通过键获取值
      3. void removeAttribute(String name):通过键移除键值对
  4. 获取ServletContext(后面会讲作用):

    • ServletContext getServletContext()

5、用户登录案例
需求
  1. 编写login.html登录页面 username & password 两个输入框
  2. 使用Druid数据库连接池技术,操作mysql,requestDemo数据库中user表
  3. 使用JdbcTemplate技术封装JDBC
  4. 登录成功跳转到SuccessServlet展示:登录成功!用户名,欢迎您
  5. 登录失败跳转到FailServlet展示:登录失败,用户名或密码错误

开发步骤
  1. 创建项目,导入html页面,配置文件,jar包

  2. 创建数据库环境

    CREATE TABLE USER(
    	id INT PRIMARY KEY AUTO_INCREMENT,
        gender CHAR(1) NOT NULL,
    	username VARCHAR(32) UNIQUE NOT NULL,
    	PASSWORD VARCHAR(32) NOT NULL
    	);
    	
    INSERT INTO USER VALUES(NULL,'男','zhangsan',123456);
    INSERT INTO USER VALUES(NULL,'男','lisi',234567);
    INSERT INTO USER VALUES(NULL,'女','wangwu',345678);
    
    SELECT * FROM USER;
    
  3. 创建包domain,创建类User

    public class User {
        private int id;
        private String gender;
        private String username;
        private String password;
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", gender='" + gender + '\'' +
                    ", username='" + username + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    
        public String getGender() {
            return gender;
        }
    
        public void setGender(String gender) {
            this.gender = gender;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
    }
    
  4. 创建包utils,编写工具类JDBCUtils

    /**
     * JDBC工具类   使用Druid连接池
     * @author 肖南海
     * @version 1.0
     */
    public class JDBCUtils {
        private static DataSource dataSource;
        static {
            try {
                //加载配置文件
                Properties properties = new Properties();
                //使用ClassLoader加载配置文件,获取字节输入流
                InputStream resourceAsStream = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");
                properties.load(resourceAsStream);
                //初始化连接池对象
                dataSource = DruidDataSourceFactory.createDataSource(properties);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        /**
         * 获取连接池对象
         */
        public static DataSource getDataSource(){
            return dataSource;
        }
        /**
         * 获取Connection连接对象
         */
        public static Connection getConnection() throws SQLException {
            return dataSource.getConnection();
        }
    }
    
  5. 创建包dao,创建类UserDao,提供login方法

    /**
     * 操作数据库中user表的类
     * @author 肖南海
     * @version 1.0
     */
    public class UserDao {
        //声明JDBCTemplate对象共用
        private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
        /**
         * 登陆方法
         * @param loginUser 只有用户名和密码
         * @return user包含用户全部数据
         */
        public User login(User loginUser){
            try {
                //1 编写SQL
                String sql = "select * from user where username = ? and password = ?";
                //2 调用query方法
                User user = template.queryForObject(sql,
                        new BeanPropertyRowMapper<>(User.class),
                        loginUser.getUsername(), loginUser.getPassword());
                return user;
            } catch (DataAccessException e) {
                e.printStackTrace();
                //如果没有查询到
                return null;
            }
        }
    }
    
  6. 编写web.servlet.LoginServlet、FailServlet和SuccessServlet类

    @WebServlet(name = "LoginServlet", value = "/LoginServlet")
    public class LoginServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //1 设置编码
            request.setCharacterEncoding("UTF-8");
            //2 获取请求参数
            /*String username = request.getParameter("username");
            String password = request.getParameter("password");
            //封装User对象
            User loginUser = new User();
            loginUser.setUsername(username);
            loginUser.setPassword(password);*/
            //2.1 获取所有请求参数
            Map<String, String[]> parameterMap = request.getParameterMap();
            //2.2 创建User对象
            User loginUser = new User();
            //2.3 使用BeanUtils封装
            try {
                BeanUtils.populate(loginUser,parameterMap);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            //3 调用UserDao的方法
            UserDao userDao = new UserDao();
            User user = userDao.login(loginUser);
            //4 判断user
            if (user == null) {
                //登陆失败
                //转发到登陆失败服务
                request.getRequestDispatcher("/FailServlet").forward(request,response);
            } else{
                //登陆成功
                //存储数据到请求头
                request.setAttribute("user",user);
                //转发给登陆成功服务
                request.getRequestDispatcher("/SuccessServlet").forward(request,response);
            }
    
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request,response);
            System.out.println("收到了表单数据");
        }
    }
    
    @WebServlet(name = "FailServlet", value = "/FailServlet")
    public class FailServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //设置响应头编码
            response.setContentType("text/html;charset=utf-8");
            //输出
            response.getWriter().write("登陆失败,用户名或密码错误");
        }
    }
    
    @WebServlet(name = "SuccessServlet", value = "/SuccessServlet")
    public class SuccessServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //设置响应头编码
            response.setContentType("text/html;charset=utf-8");
            User user = (User) request.getAttribute("user");
            if (user != null) {
                //输出
                response.getWriter().write("登陆成功!" + user.getUsername() + ", 欢迎您!");
            }
        }
    }
    
  7. login.html中form表单

    <body>
    
    <div class="container">
        <form action="LoginServlet" class="my_form" method="post">
            <div class="form-group">
                <input type="text" placeholder="请输入用户名" name="username">
            </div>
            <div class="form-group">
                <input type="password" placeholder="请输入密码" name="password">
            </div>
            <div class="form-group">
                <input type="submit" class="btn btn-info">
            </div>
        </form>
    </div>
    
    </body>
    
  8. BeanUtils工具类,简化数据封装

    用于封装JavaBean的

    1. JavaBean:标准的Java类

      1. 要求:
        • 类必须被public修饰
        • 必须提供空参的构造器
        • 成员变量必须使用private修饰
        • 提供公共setter和getter方法
      2. 功能:封装数据
    2. 概念

      • 成员变量:

      • 属性:setter和getter方法截取后的产物

        例如:getUsername() --> Username--> username

    3. 方法:

      • setProperty()
      • getProperty()
      • populate(Object obj , Map map):将map集合的键值对信息,封装到对应的JavaBean对象中

4、响应消息

服务器端发送给客户端的数据

数据格式

HTTP/1.1 200 OK
Bdpagetype: 2
Bdqid: 0xe6a89df4000cbf70
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Sat, 12 Feb 2022 09:13:21 GMT
Expires: Sat, 12 Feb 2022 09:13:21 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=356; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=35105_31253_35768_35775_34584_35490_35317_26350_35881_35877_35746; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1644657201023844916216620708096109231984
X-Frame-Options: sameorigin
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
1、响应行
  • 组成:协议/版本 响应状态码 状态码描述

  • 响应状态码:服务器告诉客户端浏览器本次请求和响应的一个状态。

    HTTP 状态码 | 菜鸟教程 (runoob.com)

    • 状态码都是3位数字

    • 分类:

      • 1xx:服务器就收客户端消息,但没有接受完成,等待一段时间后,发送1xx多状态码

      • 2xx:成功。代表:200

      • 3xx:重定向,类似转发。代表:302(重定向),304(访问缓存)

      • 4xx:客户端错误。

        • 代表:

          • 403Forbidden:服务器拒绝了你的地址请求,很有可能是你根本就没权限访问网站,就算你提供了身份验证也没用。讲真,很有可能是你被禁止访问了。

          • 404Not Found:找不到要查询的页面。非常有可能是网页被删除了。

          • 405Method Not Allowed:资源被禁止,有可能是文件目录权限不够导致的。这个时候其实,只要赋予“完全控制”的权限,也是可以解决的

      • 5xx:服务器端错误。代表:500Internal Server Error(服务器内部出现异常)

2、响应头
  • 格式:头名称:值

  • 常见的响应头:

    • Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式

    • Content-disposition:服务器告诉客户端以什么格式打开响应体数据

      • 值:

        • in-line:默认值,在当前页面内打开

        • attachment; filename=xxx:以附件形式打开响应体。文件下载

3、响应空行
4、响应体

传输的数据


5、Response对象

1、设置响应行
  • 格式:HTTP/1.1 200 ok
  • 设置状态码 setStatus(int sc)
2、设置响应头
  • setHeader(String name, String value)
3、设置响应体
  • 使用步骤:

    1. 获取输出流

      • 字符输出流:PrintWriter getWriter()

      • 字节输出流:ServletOutputStream getOutputStream()

    2. 使用输出流,将数据输出到客户端浏览器

      • write()

4、重定向
1、完成重定向

重定向:资源跳转的方式

代码实现

//1. 设置状态码为302
response.setStatus(302);
//2.  重定向方式1,设置响应头location跳转路径
response.setHeader("location","/day15/responseDemo2");
//	  重定向方式2,简单的重定向方法
response.sendRedirect("/day15/responseDemo2");

forward 和 redirect 区别(面试的笔试题)

  1. 重定向的特点: redirect
    1. 地址栏发生变化
    2. 重定向可以访问其他站点(服务器)的资源
    3. 重定向是两次请求。不能使用request对象来共享数据
  2. 转发的特点:forward
    1. 转发地址栏路径不变
    2. 转发只能访问当前服务器下的资源
    3. 转发是一次请求,可以使用request对象来共享数据

路径写法:

  1. 相对路径:通过相对路径不可以确定唯一资源

    • 如:./index.html

    • 不以/开头,以.开头路径

    • 规则:找到当前资源和目标资源之间的相对位置关系(并不是看项目中文件存放的位置,是看绝对路径的差别,因为有虚拟目录)

    • ./:当前目录

    • ../:后退一级目录

  2. 绝对路径:通过绝对路径可以确定唯一资源

    • 如:http://localhost/day15/responseDemo2

    • 以/开头的路径

    • 规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出
      给客户端浏览器使用:需要加虚拟目录(项目的访问路径),比如重定向
      * 建议虚拟目录动态获取request.getContextPath()
      * <a> , <form> 重定向...

      • 给服务器使用:不需要加虚拟目录
        • 转发路径

2、服务器输出字符数据到浏览器

步骤:

  1. 获取字符输出流

    //字符输出流:
    PrintWriter    getWriter()
    //字节输出流:
    ServletOutputStream    getOutputStream()
    
  2. 输出数据

    write() 用完不用关闭流,请求使用完会销毁

  3. 注意乱码问题

    • PrintWriter pw = response.getWriter(); Tomcat获取的流的默认编码是ISO-8859-1

    • 设置该流的默认编码

      response.setCharacterEncoding("utf-8");
      
    • 告诉浏览器响应体使用的编码

      //原始方式
      response.setHeader("content-type","text/html;charset=utf-8");
      //或者,简单的形式,设置编码,是在获取流之前设置
      response.setContentType("text/html;charset=utf-8");
      
3、服务器输出字节数据到浏览器

步骤:

  1. 获取字节输出流
  2. 输出数据
4、验证码
  1. 本质:图片
  2. 目的:防止恶意表单注册

6、ServletContext对象

1、概念

代表整个web应用,可以和程序的容器(服务器)来通信

2、获取ServletContext对象
  1. 通过request对象获取
    request.getServletContext();
  2. 通过HttpServlet获取
    this.getServletContext();
3、功能
  1. 获取MIME类型:

    • MIME类型:在互联网通信过程中定义的一种文件数据类型

    • 格式: 大类型/小类型 text/html image/jpeg

    • 获取:String getMimeType(String file)

  2. 域对象:共享数据

    1. setAttribute(String name,Object value)

    2. getAttribute(String name)

    3. removeAttribute(String name)

      ServletContext对象范围:所有用户所有请求的数据(谨慎使用)

  3. 获取文件的真实(服务器)路径

    方法:String getRealPath(String path)

    String b = context.getRealPath("/b.txt");//web目录下资源访问
    System.out.println(b);
    
    String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问
    System.out.println(c);
    
    String a = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下的资源访问
    System.out.println(a);
    

注意:scr下的文件最终会被放到/WEB-INF/classes下


4、案例

文件下载需求
1. 页面显示超链接
2. 点击超链接后弹出下载提示框
3. 完成图片文件下载

分析:
1. 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框。不满足需求
1. 任何资源都必须弹出下载提示框
1. 使用响应头设置资源的打开方式(以附件形式):content-disposition: attachment;filename=xxx

response.setHeader("content-disposition","attachment;filename=" + filename);

步骤:

  1. 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
  2. 定义Servlet
    1. 获取文件名称
    2. 使用字节输入流加载文件进内存
    3. 指定response的响应头: content-disposition:attachment;filename=xxx
    4. 将数据写出到response输出流

问题:

  • 中文文件问题
    • 解决思路:
      1. 获取客户端使用的浏览器版本信息
      2. 根据不同的版本信息,设置filename的编码方式不同

代码

<body>
<a href="/DownloadServlet?filename=QQ头像.jpg">图片</a>
</body>
@WebServlet(name = "DownloadServlet", value = "/DownloadServlet")
public class DownloadServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        //1 获取请求参数,即文件名称
        String filename = request.getParameter("filename");
        //2 使用字节输入流加载文件进内存
        //2.1 找到文件服务器路径
        ServletContext servletContext = this.getServletContext();
        String realPath = servletContext.getRealPath("/img/" + filename);
        //2.2 用字节流关联
        FileInputStream fileInputStream = new FileInputStream(realPath);
        //3 设置response的响应头
        //3.1 设置响应头类型:content-type
        String mimeType = servletContext.getMimeType(filename);
        response.setContentType(mimeType);
        //3.2 设置响应头打开方式:content-disposition
        response.setHeader("content-disposition","attachment;filename=" + filename);
        //4 将输入流的数据写出到输出流中
        ServletOutputStream outputStream = response.getOutputStream();
        byte[] buff = new byte[1024 * 8];
        int length = 0;
        while ((length = fileInputStream.read(buff)) != -1){
            outputStream.write(buff,0,length);
        }
        fileInputStream.close();
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request,response);
    }

}

4.4、会话技术

  • 会话:一次会话中包含多次请求和响应。
    • 一次会话:浏览器第一次给服务器资源发送请求,会话建立,直到有一方断开为止
  • 功能:在一次会话的范围内的多次请求间,共享数据
  • 方式:
    1. 客户端会话技术:Cookie
    2. 服务器端会话技术:Session

1、cookie

1、概念

客户端会话技术,将数据保存到客户端

2、快速入门
  • 使用步骤:
    1. 创建Cookie对象,绑定数据
      • new Cookie(String name, String value)
    2. 发送Cookie对象,添加在响应头里set-cookie: name = value
      • response.addCookie(Cookie cookie)
    3. 获取Cookie,从请求头拿到数据 cookie: name = value
      • Cookie[] request.getCookies()
@WebServlet(name = "CookieDemo1", value = "/CookieDemo1")
public class CookieDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1 创建cookie对象
        Cookie cookie1 = new Cookie("username", "hai0000444");
        Cookie cookie2 = new Cookie("password", "123456");
        //2 发送cookie
        response.addCookie(cookie1);
        response.addCookie(cookie2);
        //先访问CookieDemo1页面,再访问CookieDemo1页面,2页面会从cookie中取到信息并打印
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
@WebServlet(name = "CookieDemo2", value = "/CookieDemo2")
public class CookieDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取cookie
        Cookie[] cookies = request.getCookies();
        //遍历cookie
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                String value = cookie.getValue();
                System.out.println(name + ": " +value);
            }
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
//输出:

JSESSIONID: 120AE47646DA5D802D70B2BD69F34068
username: hai0000444
password: 123456
Idea-7fc50b3: 0012f192-77b6-43b3-b28e-a8e4b00ccd33
3、实现原理

基于响应头set-cookie和请求头cookie实现

4、cookie的细节
  1. 一次可不可以发送多个cookie?

    • 可以
    • 可以创建多个Cookie对象,使用response调用多次addCookie方法发送cookie即可。
  2. cookie在浏览器中保存多长时间?

    1. 默认情况下,当浏览器关闭后,Cookie数据被销毁
    2. 持久化存储:
      • cookie.setMaxAge(int seconds)
        1. 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
        2. 负数:默认值
        3. :删除cookie信息
  3. cookie能不能存中文?

    • 在tomcat 8 之前 cookie中不能直接存储中文数据。
      • 需要将中文数据转码---一般采用URL编码(%E3%A2%D3%D2%W2%E4 16进制格式)
    • 在tomcat 8 之后,cookie支持中文数据。特殊字符还是不支持,建议使用URL编码存储,URL解码解析
  4. cookie共享问题(范围)?

    1. 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?

      • 默认情况下cookie不能共享
      • setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录
      • 如果要共享,则可以将path设置为 "/"
    2. 不同的tomcat服务器间cookie共享问题?

      • setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
        • setDomain(".baidu.com"), 那么tieba.baidu.com和news.baidu.com中cookie可以共享
5、Cookie的特点和作用
  1. cookie存储数据在客户端浏览器
  2. 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)
  • 作用:
    1. cookie一般用于存储少量的不太敏感的数据
    2. 在不登录的情况下,完成服务器对客户端的身份识别
6、案例

记住上一次访问时间

  1. 需求:
  • 访问一个Servlet,如果是第一次访问,则提示:您好,欢迎您首次访问。
  • 如果不是第一次访问,则提示:欢迎回来,您上次访问时间为:显示时间字符串
  1. 分析:

  2. 可以采用Cookie来完成

  3. 在服务器中的Servlet判断是否有一个名为lastTime的cookie

1. 有:不是第一次访问

- 响应数据:欢迎回来,您上次访问时间为:2018年6月10日11:50:20

- 写回Cookie:lastTime=2018年6月10日11:50:01
	2. 没有:是第一次访问

- 响应数据:您好,欢迎您首次访问
- 写回Cookie:lastTime=2018年6月10日11:50:01
  1. 代码实现
@WebServlet("/CookieTest")
public class CookieTest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置响应的消息体的数据格式以及编码
        response.setContentType("text/html;charset=utf-8");
        response.setCharacterEncoding("utf-8");
        //1 获取所有cookie
        Cookie[] cookies = request.getCookies();
        boolean flag = false; //没有cookie为lastTime
        //2 遍历cookie
        if (cookies != null && cookies.length > 0){
            for (Cookie cookie : cookies) {
                //3 获取cookie的名称
                String name = cookie.getName();
                //4 判断名称是否是:lastTime
                if ("lastTime".equals(name)){
                    //有该cookie,不是第一次访问
                    flag = true;
                    String value = cookie.getValue();
                    //URL解码
                    String decode = URLDecoder.decode(value, "utf-8");
                    response.getWriter().write("<h1>欢迎回来!您上传访问的时间为:"+decode+"<h1/>");
                    // 设置本次登陆时间
                    Date date = new Date();
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                    String str_date = sdf.format(date);
                    // URL编码,cookie不能存特殊字符,比如空格
                    String encode = URLEncoder.encode(str_date, "utf-8");
                    cookie.setValue(encode);
                    // 设置cookie的持续时间并发送cookie
                    cookie.setMaxAge(60*60*24*30);//一个月
                    response.addCookie(cookie);
                    break;
                }
            }
        }

        if (cookies == null || cookies.length == 0 || flag == false){
            //没有lastTime
            // 设置本次登陆时间
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
            String str_date = sdf.format(date);
            // URL编码,cookie不能存特殊字符,比如空格
            String encode = URLEncoder.encode(str_date, "utf-8");
            Cookie cookie = new Cookie("lastTime", encode);
            // 设置cookie的持续时间并发送cookie
            cookie.setMaxAge(60*60*24*30);//一个月
            response.addCookie(cookie);
            response.getWriter().write("<h1>您好,欢迎您首次访问!<h1/>");
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

2、Session

服务器端会话技术,在一次会话的多次请求间共享数据,将数据保存在服务器端的对象中。HttpSession

JSP Session | 菜鸟教程 (runoob.com)

1、快速入门
  1. 获取HttpSession对象:

HttpSession session = request.getSession();

  1. 使用HttpSession对象:
方法 描述
public Object getAttribute(String name) 返回session对象中与指定名称绑定的对象,如果不存在则返回null
public void setAttribute(String name, Object value) 使用指定的名称和值来产生一个对象并绑定到session中
public void removeAttribute(String name) 移除session中指定名称的对象
@WebServlet("/SessionDemo1")
public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用session共享数据
        //1 获取session
        HttpSession session = request.getSession();
        //2 存储数据
        session.setAttribute("name","Smith");
        System.out.println("访问SessionDemo1:");
        System.out.println(session);//判断是否为同一个session
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
@WebServlet("/SessionDemo2")
public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //使用session共享数据
        //1 获取session
        HttpSession session = request.getSession();
        //2 取出数据
        Object name = session.getAttribute("name");
        System.out.println("访问SessionDemo2:");
        System.out.println(session);
        System.out.println(name);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
输出:

访问SessionDemo1:
org.apache.catalina.session.StandardSessionFacade@221fc5f
访问SessionDemo2:
org.apache.catalina.session.StandardSessionFacade@221fc5f
Smith

2、原理

Session的实现是依赖于Cookie的。


3、细节
  1. 当客户端不关闭,服务器不关闭,两次获取session是同一个。

  2. 当客户端关闭后,服务器不关闭,两次获取session是否为同一个?

    • 默认情况下。不是。

    • 如果需要相同,则可以创建Cookie, 键为JSESSIONID,设置最大存活时间,让cookie持久化保存。

    Cookie c = new Cookie("JSESSIONID",session.getId());
    c.setMaxAge(60*60);
    response.addCookie(c);
    
  3. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?

    • 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
      • session的钝化:
        • 在服务器正常关闭之前,将session对象序列化到硬盘上
      • session的活化:
        • 在服务器启动后,将session文件转化为内存中的session对象即可。
  4. session什么时候被销毁?

    1. 服务器关闭

    2. session对象调用invalidate(),自杀方法

    3. session默认失效时间 30分钟,可以选择性配置修改,tomcat/bin/conf/web.xml 中

      <session-config>
      <session-timeout>30</session-timeout>
      </session-config>
      

4、session的特点
  1. session用于存储一次会话的多次请求的数据,存在服务器端
  2. session可以存储任意类型,任意大小的数据
5、session与Cookie的区别
  1. session存储数据在服务器端,Cookie在客户端
  2. session没有数据大小限制,Cookie有
  3. session数据安全,Cookie相对于不安全

6、案例
案例需求
  1. 访问带有验证码的登录页面login.jsp
  2. 用户输入用户名,密码以及验证码。
    • 如果用户名和密码输入有误,跳转登录页面,提示:用户名或密码错误
    • 如果验证码输入有误,跳转登录页面,提示:验证码错误
    • 如果全部输入正确,则跳转到主页success.jsp,显示:用户名,欢迎您

代码

1、login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>login</title>

    <script>
        window.onload = function (){
            document.getElementById("img").onclick = function (){
                this.src = "/CheckCodeServlet?time=" + new Date().getTime();
            }
        }
    </script>

    <style>
        div{
            color: red;
        }
    </style>
</head>
<body>
<form action="/LoginServlet" method="post">
    <table>
        <tr>
            <td>用户名</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td>验证码</td>
            <td><input type="text" name="checkCode"></td>
        </tr>
        <tr>
            <%--登陆页面和验证码图片分别是两次请求,不能用request传输数据,要用session--%>
            <td colspan="2"><img src="/CheckCodeServlet" id="img"></td>
        </tr>
        <tr>
            <td colspan="2"><input type="submit" value="登陆"></td>
        </tr>
        <tr>
            <td>
                <div><%=request.getAttribute("cc_error") == null ? "" : request.getAttribute("cc_error")%></div>
            </td>
        </tr>
        <tr>
            <td>
                <div><%=request.getAttribute("login_error") == null ? "" : request.getAttribute("login_error")%></div>
            </td>
        </tr>
    </table>
</form>
</body>
</html>
2、success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h1><%=request.getSession().getAttribute("user")%>欢迎您!</h1>
</body>
</html>

3、LoginServlet
@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1 设置request编码
        request.setCharacterEncoding("utf-8");
        //2 获取参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String checkCode = request.getParameter("checkCode");
        //3 先判断验证码是否正确
        HttpSession session = request.getSession();
        String checkCode_session = (String) session.getAttribute("checkCode_session");
        //删除session中存储的验证码
        session.removeAttribute("checkCode_session");
        if (checkCode_session != null && checkCode_session.equalsIgnoreCase(checkCode)) { //忽略大小写比较
            //验证码正确
            //判断用户名和密码是否正确
            if ("zhangsan".equals(username) && "123456".equals(password)){ //调用UserDAO
                //登陆成功
                //存储信息,用户信息
                session.setAttribute("user",username);
                //重定向到success.jsp,页面跳转
                response.sendRedirect(request.getContextPath() + "/success.jsp");
            } else {
                //存储提示信息到request
                request.setAttribute("login_error","用户名或密码错误");
                //转发到登陆页面
                request.getRequestDispatcher("/login.jsp").forward(request,response);
            }
        } else {
            //验证码错误
            //存储提示信息到request
            request.setAttribute("cc_error","验证码错误");
            //转发到登陆页面
            request.getRequestDispatcher("/login.jsp").forward(request,response);
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}
4、CheckCodeServlet
@WebServlet("/CheckCodeServlet")
public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
        int width = 100;
        int height = 50;

        //1.创建一对象,在内存中图片(验证码图片对象)
        BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);


        //2.美化图片
        //2.1 填充背景色
        Graphics g = image.getGraphics();//画笔对象
        g.setColor(Color.PINK);//设置画笔颜色
        g.fillRect(0,0,width,height);

        //2.2画边框
        g.setColor(Color.BLUE);
        g.drawRect(0,0,width - 1,height - 1);

        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789";
        //生成随机角标
        Random ran = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= 4; i++) {
            int index = ran.nextInt(str.length());
            //获取字符
            char ch = str.charAt(index);//随机字符
            sb.append(ch);

            //2.3写验证码
            g.drawString(ch+"",width/5*i,height/2);
        }
        String checkCode_session = sb.toString();
        //将验证码存入session
        request.getSession().setAttribute("checkCode_session",checkCode_session);

        //2.4画干扰线
        g.setColor(Color.GREEN);

        //随机生成坐标点

        for (int i = 0; i < 10; i++) {
            int x1 = ran.nextInt(width);
            int x2 = ran.nextInt(width);

            int y1 = ran.nextInt(height);
            int y2 = ran.nextInt(height);
            g.drawLine(x1,y1,x2,y2);
        }


        //3.将图片输出到页面展示
        ImageIO.write(image,"jpg",response.getOutputStream());
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

4.5、JSP(理解)

Java Server Pages: java服务器端页面,由于不利于前后端分离,已被springboot淘汰

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

1、概念

  • 可以理解为:一个特殊的页面,其中既可以指定定义html标签,又可以定义java代码,用于简化书写,JSP文件后缀名为 .jsp
  • JSP本质上就是一个Servlet

2、注释

  • html注释:
    <!-- -->:只能注释html代码片段
  • jsp注释:推荐使用
    <%-- --%>:可以注释所有

3、JSP的脚本

JSP定义Java代码的方式

  1. <% 代码 %>:定义的java代码,在service方法中。service方法中可以定义什么,该脚本中就可以定义什么。
  2. <%! 代码 %>:定义的java代码,在jsp转换后的java类的成员位置。
  3. <%= 代码 %>:定义的java代码,会输出到页面上。输出语句中可以定义什么,该脚本中就可以定义什么。
<html>
<head>
    <title>$Title$</title>
</head>
<body>
<%--定义在servlet的service方法里面--%>
<%
    int i = 5;
    System.out.println(i);
%>
<%--定义在servlet的成员变量位置--%>
<%!
    int i = 3;
%>
<%--输出脚本,还是在service方法内,所以根据就近原则,i会输出5--%>
<%=
    i+"这里面写什么,就会在页面上输出什么。"
%>
</body>
</html>

4、JSP的隐式对象

  • 在jsp页面中不需要获取和创建,可以直接使用的对象

  • JSP所支持的九大隐式对象。JSP 隐式对象 | 菜鸟教程 (runoob.com)

    对象 描述
    request HttpServletRequest 接口的实例,一次请求访问的多个资源(转发)
    response HttpServletResponse 接口的实例,响应对象
    out JspWriter类的实例,用于把结果输出至网页上
    session HttpSession类的实例,一次会话的多个请求间共享数据
    application ServletContext类的实例,与应用上下文有关,所有用户间共享数据
    config ServletConfig类的实例,Servlet的配置对象
    pageContext PageContext类的实例,提供对JSP页面所有对象以及命名空间的访问,当前页面共享数据,还可以获取其他八个内置对象
    page 类似于Java类中的this关键字,当前页面(Servlet)的对象
    Exception Exception类的对象,代表发生错误的JSP页面中对应的异常对象,必须在page声明isErrorPage="true"才能使用

    out:字符输出流对象。需要导入tomcat中jsp-api.jar,可以将数据输出到页面上。和response.getWriter()类似

    • response.getWriter()out.write()的区别:
      • 在tomcat服务器真正给客户端做出响应之前,会先找response缓冲区数据,再找out缓冲区数据。
      • response.getWriter()数据输出永远在out.write()之前

5、指令

1、作用

用于配置JSP页面,导入资源文件

2、格式
<%@ 指令名称 属性名1=属性值1 属性名2=属性值2 ... %>
3、分类
page

配置JSP页面的

  • contentType:等同于response.setContentType()

    <%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
    
    • 设置响应体的mime类型以及字符集
    • 设置当前jsp页面的编码(只能是高级的IDE才能生效,如果使用低级工具,则需要设置pageEncoding属性设置当前页面的字符集)
  • import:导包

    <%@ page import="java.util.Date" %>
    <%@ page import="java.text.SimpleDateFormat" %>
    <%@ page import="java.net.URLEncoder" %>
    <%@ page import="java.net.URLDecoder" %>
    
  • errorPage:当前页面发生异常后,会自动跳转到指定的错误页面

  • isErrorPage:标识当前页面是否是错误页面。

    • true:是,可以使用内置对象exception
    • false:否。默认值。不可以使用内置对象exception

include

页面包含的。导入页面的资源文件

<%@include file="top.jsp"%>

taglib

导入资源, 一般用于导入标签库

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

prefix:自定义的前缀,比如JSTL用 c 前缀

<c:out ....>

6、案例:改造Cookie案例

<%@ page import="java.util.Date" %>
<%@ page import="java.text.SimpleDateFormat" %>
<%@ page import="java.net.URLEncoder" %>
<%@ page import="java.net.URLDecoder" %><%--
  Created by IntelliJ IDEA.
  User: one_uncle
  Date: 2022/2/13
  Time: 15:50
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

<%

    //1 获取所有cookie
    Cookie[] cookies = request.getCookies();
    boolean flag = false; //没有cookie为lastTime
    //2 遍历cookie
    if (cookies != null && cookies.length > 0) {
        for (Cookie cookie : cookies) {
            //3 获取cookie的名称
            String name = cookie.getName();
            //4 判断名称是否是:lastTime
            if ("lastTime".equals(name)) {
                //有该cookie,不是第一次访问
                flag = true;
                String value = cookie.getValue();
                //URL解码
                String decode = URLDecoder.decode(value, "utf-8");
%>

                <h1>欢迎回来!您上传访问的时间为:<%=decode%><h1/>

<%
                // 设置本次登陆时间
                Date date = new Date();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
                String str_date = sdf.format(date);
                // URL编码,cookie不能存特殊字符,比如空格
                String encode = URLEncoder.encode(str_date, "utf-8");
                cookie.setValue(encode);
                // 设置cookie的持续时间并发送cookie
                cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
                response.addCookie(cookie);
                break;
            }
        }
    }

    if (cookies == null || cookies.length == 0 || flag == false) {
        //没有lastTime
        // 设置本次登陆时间
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        String str_date = sdf.format(date);
        // URL编码,cookie不能存特殊字符,比如空格
        String encode = URLEncoder.encode(str_date, "utf-8");
        Cookie cookie = new Cookie("lastTime", encode);
        // 设置cookie的持续时间并发送cookie
        cookie.setMaxAge(60 * 60 * 24 * 30);//一个月
        response.addCookie(cookie);
%>

        <h1>您好,欢迎您首次访问!<h1/>

<%
  }
%>

</body>
</html>


4.6、MVC开发模式

  1. jsp演变历史

    1. 早期只有servlet,只能使用response输出标签数据,非常麻烦
    2. 后来又jsp,简化了Servlet的开发,如果过度使用jsp,在jsp中即写大量的java代码,又写html表,造成难于维护,难于分工协作
    3. 再后来,java的web开发,借鉴mvc开发模式,使得程序的设计更加合理性
  2. MVC:

    • M:Model,模型。JavaBean,DAO
      • 完成具体的业务操作,如:查询数据库,封装对象
    • V:View,视图。JSP
      • 展示数据
    • C:Controller,控制器。Servlet
      • 获取用户的输入
      • 调用模型
      • 将数据交给视图进行展示
  3. 优缺点:

    1. 优点:

      • 耦合性低,方便维护,可以利于分工协作
      • 重用性高
    2. 缺点:

      • 使得项目架构变得复杂,对开发人员要求高

4.7、JSP EL表达式

1、概念

Expression Language 表达式语言,在JSP中使用

2、作用

替换和简化jsp页面中java代码的编写

3、语法

${表达式}

4、注意

jsp默认支持el表达式的。如果要忽略el表达式

  1. 设置jsp中page指令中:isELIgnored="true" 忽略当前jsp页面中所有的el表达式
  2. \${表达式} :忽略当前这个el表达式

5、使用

1、运算

运算符:

  1. 算数运算符: + - * /(div) %(mod)
  2. 比较运算符: > < >= <= == !=
  3. 逻辑运算符: &&(and) ||(or) !(not)
  4. 空运算符: empty
    • 功能:用于判断字符串、集合、数组对象是否为null或者长度是否为0
    • ${empty list}:判断字符串、集合、数组对象是否为null或者长度为0
    • ${not empty str}:表示判断字符串、集合、数组对象是否不为null 并且 长度>0
2、获取值
  1. el表达式只能从域对象中获取值

  2. 语法:

    1. ${域名称.键名}:从指定域中获取指定键的值,如果没有找到,返回空字符串而不是null

      • 由上到下 域的范围增大

        EL表达式中的域名 对应jsp中的域名 作用域
        pageScope pageContext 当前页面
        requestScope request 一次请求,一个用户可有多个
        sessionScope session 一次会话,一个用户一个
        applicationScope application(ServletContext) 全局的储存信息的空间,服务器域,所有用户共用一个
      • 举例:在request域中存储了name=张三

      • 获取:${requestScope.name}

    2. ${键名}:表示依次从最小的域中查找是否有该键对应的值,直到找到为止。假如全部的范围都没有找到时,就回传空字符串

    3. 获取对象、List集合、Map集合中的值
      1. 对象${域名称.键名.属性名}
      * 本质上会去调用对象的getter方法,只要有对应的get方法,就能.出来

      1. List集合${域名称.键名[索引]}
      2. Map集合
        • ${域名称.键名.key名称}
        • ${域名称.键名["key名称"]}
<html>
<head>
    <title>el获取域中的数据</title>
</head>
<body>
    <%
        //在域中存储数据
        session.setAttribute("name","李四");
        request.setAttribute("name","张三");
        session.setAttribute("age","23");
        request.setAttribute("str","");
    
    	User user = new User();
        user.setName("张三");
        user.setAge(23);
        user.setBirthday(new Date());


        request.setAttribute("u",user);


        List list = new ArrayList();
        list.add("aaa");
        list.add("bbb");
        list.add(user);

        request.setAttribute("list",list);


        Map map = new HashMap();
        map.put("sname","李四");
        map.put("gender","男");
        map.put("user",user);

        request.setAttribute("map",map);
    %>
    
<h3>el获取值</h3>
    
    ${requestScope.name}
    ${sessionScope.age}
    ${name}
    ${sessionScope.name}

    ${requestScope.u.name}<br>
    ${u.age}<br>
    ${u.birthday}<br>
    ${u.birthday.month}<br>

    ${u.birStr}<br>

    <h3>el获取List值</h3>
    ${list}<br>
    ${list[0]}<br>
    ${list[1]}<br>
    ${list[10]}<br>

    ${list[2].name}

    <h3>el获取Map值</h3>
    ${map.gender}<br>
    ${map["gender"]}<br>
    ${map.user.name}
</body>
</html>

3、EL隐式对象

JSP 隐式对象 | 菜鸟教程 (runoob.com)

JSP EL支持11个隐含对象:

隐含对象 描述
pageScope page 作用域
requestScope request 作用域
sessionScope session 作用域
applicationScope application 作用域
param Request 对象的参数,字符串
paramValues Request对象的参数,字符串集合
header HTTP 信息头,字符串
headerValues HTTP 信息头,字符串集合
initParam 上下文初始化参数
cookie Cookie值
pageContext 当前页面的pageContext

pageContext:

  • 获取 jsp 其他八个隐式对象,其实是调用了pageContext的get方法

    • ${pageContext.request.contextPath}动态获取虚拟目录
    <form action="${pageContext.request.contextPath}/loginServlet" method="post">
    

4.8、JSTL

JavaServer Pages Tag Library JSP标准标签库,是由Apache组织提供的开源的免费的jsp标签

1、作用

用于简化和替换jsp页面上的java代码

2、使用步骤

  1. 导入jstl相关jar包,2个 javax.servlet.jsp.jstl.jar jstl-impl.jar

  2. 引入标签库:taglib指令: <%@ taglib %>

    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    
  3. 使用标签

3、常用的JSTL标签

  1. if:相当于java代码的if语句

    1. 属性:

      • test 必须属性,接受boolean表达式
        • 如果表达式为true,则显示if标签体内容,如果为false,则不显示标签体内容
        • 一般情况下,test属性值会结合el表达式一起使用
       <%
        /*判断request域中的一个list集合是否为空,如果不为null则显示遍历集合*/
        List list = new ArrayList();
        list.add("aaaa");
        request.setAttribute("list", list);
       %>
       <c:if test="${not empty list}">
        遍历集合...
       </c:if>
      
    2. 注意:

      • c:if 标签没有else情况,想要else情况,则可以再定义一个c:if标签
  2. choose:相当于java代码的switch语句

    • 使用choose标签声明 相当于switch声明
    • 使用when标签做判断 相当于case
    • 使用otherwise标签做其他情况的声明 相当于default
    <%--
    	完成数字编号对应星期几的案例
              1. 域中存储一个数字
              2. 使用choose标签取出数字         相当于switch
              3. 使用when标签做数字判断         相当于case
              4. otherwise标签做其他情况的声明   相当于default
              --%>
        <%
          request.setAttribute("number",3);
        %>
         <c:choose>
             <c:when test="${number == 1}">星期一</c:when>
             <c:when test="${number == 2}">星期二</c:when>
             <c:when test="${number == 3}">星期三</c:when>
             <c:when test="${number == 4}">星期四</c:when>
             <c:when test="${number == 5}">星期五</c:when>
             <c:when test="${number == 6}">星期六</c:when>
             <c:when test="${number == 7}">星期日</c:when>
             <c:otherwise>数字输入有误</c:otherwise>
         </c:choose>
    
  3. foreach:相当于java代码的for语句

<%--
    1.完成重复操作
        for(int i = 1;i <= 9;i = i + 2)
        * 属性:
            begin:开始值
            end:结束值
            var:临时变量
            step:步长
            varStatus:自定义循环状态对象,用这个对象调用index和count控制循环
                index:容器中元素的索引,从0开始
                count:循环次数,从1开始
    2.遍历容器
        Lst<User> list;
        for(User user:list){

        }
            * 属性:
                items:容器对象
                var:容器中元素的临时变量
                varStatus:自定义循环状态对象,用这个对象调用index和count控制循环
                    index:容器中元素的索引,从0开始
                    count:循环次数,从1开始
        --%>
<c:forEach begin="1" end="9" var="i" step="2" varStatus="s">
    number:${i}, index: ${s.index},count: ${s.count} <br/>
</c:forEach>
<hr>
<%
    List list = new ArrayList();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");

    request.setAttribute("list",list);
%>

<c:forEach items="${list}" var="str" varStatus="s">
    index:${s.index},count:${s.count},value:${str}<br>
</c:forEach>
页面显示:

number:1, index: 1,count: 1
number:3, index: 3,count: 2
number:5, index: 5,count: 3
number:7, index: 7,count: 4
number:9, index: 9,count: 5
---------------------------------------------
index:0,count:1,value:aaa
index:1,count:2,value:bbb
index:2,count:3,value:ccc

4、练习

需求:在request域中有一个存有User对象的List集合。需要使用jstl+el将list集合数据展示到jsp页面的表格table中,并且有隔行变色的功能

<body>
<%--
    需求:在request域中有一个存有User对象的List集合。需要使用jstl+el将list集合数据展示到jsp页面的表格table中
--%>
<%
    List<User> list = new ArrayList<User>();
    list.add(new User(1,"张三","male",23));
    list.add(new User(2,"李四","male",45));
    list.add(new User(3,"翠花","female",30));

    request.setAttribute("list",list);
%>
<table border="1" width="500" align="center">
    <tr>
        <td>id</td>
        <td>姓名</td>
        <td>性别</td>
        <td>年龄</td>
    </tr>
    <c:forEach items="${list}" var="user" varStatus="s">
        <c:if test="${s.index % 2 == 0}">
            <tr bgcolor="aqua">
                <td>${user.id}</td>
                <td>${user.name}</td>
                <td>${user.gender}</td>
                <td>${user.age}</td>
            </tr>
        </c:if>

        <c:if test="${s.index % 2 == 1}">
            <tr bgcolor="#6495ed">
                <td>${user.id}</td>
                <td>${user.name}</td>
                <td>${user.gender}</td>
                <td>${user.age}</td>
            </tr>
        </c:if>
    </c:forEach>
</table>
</body>

4.9、软件设计三层架构

目的是为了 “高内聚低耦合” 的思想。

1、UI界面层(表示层/web层)

User Interface Layer

  • 用户看的得界面。用户可以通过界面上的组件和服务器进行交互
  • 只接收用户输入的数据,并将业务逻辑层处理数据的结果显示给用户。
  • 对应框架:SpringMVC

2、DLL业务逻辑层(service层)

Business Logic Layer

  • 处理业务逻辑的。
  • 只负责对数据的业务处理,开发人员不要在业务逻辑层里写访问数据库的SQL语句。业务逻辑层可以验证用户输入的数据、缓存从数据库中读取的数据等等。
  • 对应框架:Spring

3、DAL数据访问层(dao层)

DAL:Data Access Layer DAO:Data Access Objects

  • 操作数据存储文件。

  • 最好不要出现任何与业务逻辑和界面设计相关的代码。也就是说,要保证数据访问层中方法的功能仅负责存储或读取数据就可以了。

  • 对应框架:MyBatis

4、优缺点

优点
  1. 开发人员可以只关注整个结构中的其中某一层。
  2. 可以很容易的用新的实现来替换原有层次的实现。
  3. 可以降低层与层之间的依赖。
  4. 有利于标准化。
  5. 利于各层逻辑的复用。
  6. 结构更加的明确。
  7. 在后期维护的时候,极大地降低了维护成本和维护时间。
缺点
  1. 降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。
  2. 有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。
  3. 增加了开发成本。

5、案例:用户信息列表展示

简单功能

  1. 列表查询
  2. 登录
  3. 添加
  4. 删除
  5. 修改

复杂功能

  1. 删除选中
  2. 分页查询
  3. 复杂条件查询

流程

  1. 需求:用户信息的增删改查操作

  2. 设计

    1. 技术选型:Servlet + JSP + MySQL + JDBCTemplate + Duird + BeanUtils + tomcat
    2. 数据库设计
  3. 开发:

  4. 环境搭建

  5. 创建数据库环境

  6. 创建项目,导入需要的jar包

  7. 编码

  8. 测试

  9. 部署运维



数据库

CREATE DATABASE queryuser;  -- 创建数据库
USE queryuser;		    -- 使用数据库
CREATE TABLE USER(	    -- 创建表和数据
	id INT PRIMARY KEY AUTO_INCREMENT,   -- 编号
	NAME VARCHAR(20) NOT NULL,			 -- 姓名
	gender VARCHAR(5),					 -- 性别
	age INT,							 -- 年龄
	address VARCHAR(32),				 -- 地址
	qq	VARCHAR(20),					 -- qq
	email VARCHAR(50)					 -- 邮箱
);

创建项目,导入需要的jar包


4.10、Filter(重要)

过滤器,web的三大组件之一。

1、概念

  • 生活中的过滤器:净水器,空气净化器,土匪
  • web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。
  • 过滤器的作用:
    • 一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤...

2、快速入门

  1. 定义一个类,实现接口Filter
  2. 复写方法
  3. 配置拦截路径,两种方式
    1. web.xml
    2. 注解
  4. 代码
@WebFilter("/*")//访问所有资源之前都会执行该过滤器
public class FilterDemo1 implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("FilterDemo1被执行了");
        //放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

3、过滤器细节

  1. web.xml配置

    <!--web.xml方式配置-->
        <filter>
            <filter-name>demo1</filter-name>
            <filter-class>com.xiao.web.filter.FilterDemo1</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>demo1</filter-name>
            <!-- 拦截路径 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    
  2. 过滤器执行流程

    1. 执行过滤器

    2. 执行放行后的资源

    3. 回来执行过滤器放行代码下边的代码

      注意:如果执行了两次Filter,可能是Tomcat里面勾选了服务器启动自动打开页面,会多一次请求网站图标的请求,

      请求/favicon.ico是浏览器为了展示tab上的网站图标

  3. 过滤器生命周期方法

    1. init(): 在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
    2. doFilter(): 每一次请求被拦截资源时,会执行。执行多次
    3. destroy(): 在服务器关闭后,Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源
  4. 过滤器配置详解

    • 拦截路径配置:

      1. 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
      2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
      3. 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
      4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
    • 拦截方式配置:资源被访问的方式

      • 注解配置:

        • 设置dispatcherTypes属性,可以设置多个值
        • @WebFilter(value = "/*",dispatcherTypes = {DispatcherType.FORWARD, DispatcherType.REQUEST})
        1. REQUEST:默认值。浏览器直接请求资源
        2. FORWARD:只有转发访问资源才执行过滤
        3. INCLUDE:包含访问资源
        4. ERROR:错误跳转资源
        5. ASYNC:异步访问资源
      • web.xml配置

        • 设置<dispatcher></dispatcher>标签即可
  5. 过滤器链(配置多个过滤器)

    • 执行顺序(嵌套关系):如果有两个过滤器:过滤器1和过滤器2

      1. 过滤器1
      2. 过滤器2
      3. 资源执行
      4. 过滤器2
      5. 过滤器1
    • 过滤器先后顺序问题(要看配置方式)

      1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
        • 如: AFilter 和 BFilter,AFilter就先执行了。
      2. web.xml配置<filter-mapping>谁定义在上边,谁先执行

4、案例

案例1_登录验证

需求:

  • 访问day17_case案例的资源。验证其是否登录
  • 如果登录了,则直接放行。
  • 如果没有登录,则跳转到登录页面,提示"您尚未登录,请先登录"。
@WebFilter("/*")
public class LoginFilter implements Filter {


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        System.out.println(req);
        //0.强制转换
        HttpServletRequest request = (HttpServletRequest) req;

        //1.获取资源请求路径
        String uri = request.getRequestURI();
        //2.判断是否包含登录相关资源路径,要注意排除掉 css/js/图片/验证码等资源
        if(uri.contains("/login.jsp") || uri.contains("/loginServlet") || uri.contains("/css/") || uri.contains("/js/") || uri.contains("/fonts/") || uri.contains("/checkCodeServlet")  ){
            //包含,用户就是想登录。放行
            chain.doFilter(req, resp);
        }else{
            //不包含,需要验证用户是否登录
            //3.从获取session中获取user
            Object user = request.getSession().getAttribute("user");
            if(user != null){
                //登录了。放行
                chain.doFilter(req, resp);
            }else{
                //没有登录。跳转登录页面

                request.setAttribute("login_msg","您尚未登录,请登录");
                request.getRequestDispatcher("/login.jsp").forward(request,resp);
            }
        }


        // chain.doFilter(req, resp);
    }

    public void init(FilterConfig config) throws ServletException {

    }

    public void destroy() {
    }

}

案例2_敏感词汇过滤

1、需求
  1. 对day17_case案例录入的数据进行敏感词汇过滤
  2. 敏感词汇参考《敏感词汇.txt》
  3. 如果是敏感词汇,替换为 ***
2、分析
  1. 对request对象进行增强。增强获取参数相关方法
  2. 放行。传递代理对象
3、增强对象的功能

设计模式:一些通用的解决固定问题的方式

  • 装饰模式

  • 代理模式

    1. 概念:

      • 真实对象:被代理的对象

      • 代理对象:

      • 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的

  1. 实现方式:

    • 静态代理:有一个类文件描述代理模式
      • 动态代理:在内存中形成代理类

        1. 实现步骤(看代码):

          • 代理对象和真实对象实现相同的接口

          • 代理对象 = Proxy.newProxyInstance( );

          • 使用代理对象调用方法。

          • 增强方法

    1. 增强方式:

      • 增强参数列表
        - 增强返回值类型
      • 增强方法体执行逻辑
public interface SaleComputer {

    public String sale(double money);

    public void show();
}
/**
 * 真实类
 */
public class Lenovo implements SaleComputer {
    @Override
    public String sale(double money) {

        System.out.println("花了"+money+"元买了一台联想电脑...");
        return "联想电脑";
    }

    @Override
    public void show() {
        System.out.println("展示电脑....");
    }
}
public class ProxyTest {

    public static void main(String[] args) {
        //1.创建真实对象
        Lenovo lenovo = new Lenovo();
        
        //2.动态代理增强lenovo对象
        /*
            三个参数:
                1. 类加载器:真实对象.getClass().getClassLoader()
                2. 接口数组:真实对象.getClass().getInterfaces()
                3. 处理器:new InvocationHandler()
         */
        SaleComputer proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {

            /*
                代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
                    参数:
                        1. proxy:代理对象
                        2. method:代理对象调用的方法,被封装为的对象
                        3. args:代理对象调用的方法时,传递的实际参数
                    返回值:
                    	作为invoke外代理对象调用的方法的返回值
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //System.out.println("该方法执行了....");
                //System.out.println(method.getName());   //show
                //System.out.println(args[0]);

                //判断是否是sale方法
                if(method.getName().equals("sale")){
                    //1.增强参数
                    double money = (double) args[0];
                    //代理商吃15%的回扣,剩下的钱给联想公司
                    money = money * 0.85; 
                    System.out.println("专车接你....");
                    //使用真实对象(联想公司)调用该方法,反射
                    String obj = (String) method.invoke(lenovo, money);
                    System.out.println("免费送货...");
                    //2.增强返回值,代理商附赠礼品
                    return obj+"_鼠标垫";
                //如果是show方法,就不增强,原样调用
                }else{
                    Object obj = method.invoke(lenovo, args);
                    return obj;
                }
            }
        });

        //3.调用方法

       /* String computer = proxy_lenovo.sale(8000);
        System.out.println(computer);*/

        proxy_lenovo.show();
    }
}

/**
 * 敏感词汇过滤器
 */
@WebFilter("/*")
public class SensitiveWordsFilter implements Filter {


    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        //1.创建代理对象,增强getParameter方法

        ServletRequest proxy_req = (ServletRequest) Proxy.newProxyInstance(req.getClass().getClassLoader(), req.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //增强getParameter方法
                //判断是否是getParameter方法
                if(method.getName().equals("getParameter")){
                    //增强返回值
                    //获取返回值
                    String value = (String) method.invoke(req,args);
                    if(value != null){
                        for (String str : list) {
                            if(value.contains(str)){
                                value = value.replaceAll(str,"***");
                            }
                        }
                    }
                    
                    return  value;
                }

                //判断方法名是否是 getParameterMap

                //判断方法名是否是 getParameterValue

                return method.invoke(req,args);
            }
        });

        //2.放行
        chain.doFilter(proxy_req, resp);
    }
    
    private List<String> list = new ArrayList<String>();//敏感词汇集合
    
    public void init(FilterConfig config) throws ServletException {

        try{
            //1.获取文件真实路径
            ServletContext servletContext = config.getServletContext();
            String realPath = servletContext.getRealPath("/WEB-INF/classes/敏感词汇.txt");
            //2.读取文件
            BufferedReader br = new BufferedReader(new FileReader(realPath));
            //3.将文件的每一行数据添加到list中
            String line = null;
            while((line = br.readLine())!=null){
                list.add(line);
            }

            br.close();

            System.out.println(list);

        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public void destroy() {
    }

}
@WebServlet("/testServlet")
public class TestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String name = request.getParameter("name");
        String msg = request.getParameter("msg");

        System.out.println(name+":"+msg);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request, response);
    }
}

4.11、Listener

监听器,web的三大组件之一。

1、事件监听机制

  • 事件 :一件事情
  • 事件源 :事件发生的地方
  • 监听器 :一个对象
  • 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码

2、ServletContextListener

监听ServletContext对象的创建和销毁

  • 方法:

    • void contextDestroyed(ServletContextEvent sce) :ServletContext对象被销毁之前会调用该方法
    • void contextInitialized(ServletContextEvent sce) :ServletContext对象创建后会调用该方法
  • 步骤:

    1. 定义一个类,实现ServletContextListener接口

    2. 复写方法

    3. 配置的两种方式

      1. web.xml
       <listener>
       	<listener-class>cn.itcast.web.listener.ContextLoaderListener</listener-class>
       </listener>
       <!--可选项,指定初始化参数-->
       <context-param>
             <param-name>contextConfigLocation</param-name>
             <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
       </context-param>
      
      1. 注解:@WebListener
       @WebListener
       public class ContextLoaderListener implements ServletContextListener {
       
           /**
            * 监听ServletContext对象创建的。ServletContext对象服务器启动后自动创建。
            *
            * 在服务器启动后自动调用
            * @param servletContextEvent
            */
           @Override
           public void contextInitialized(ServletContextEvent servletContextEvent) {
               //加载资源文件
               //1.获取ServletContext对象
               ServletContext servletContext = servletContextEvent.getServletContext();
       
               //2.加载资源文件
               String contextConfigLocation = servletContext.getInitParameter("contextConfigLocation");
       
               //3.获取真实路径
               String realPath = servletContext.getRealPath(contextConfigLocation);
       
               //4.加载进内存
               try{
                   FileInputStream fis = new FileInputStream(realPath);
                   System.out.println(fis);
               }catch (Exception e){
                   e.printStackTrace();
               }
               System.out.println("ServletContext对象被创建了。。。");
           }
       
           /**
            * 在服务器关闭后,ServletContext对象被销毁。当服务器正常关闭后该方法被调用
            * @param servletContextEvent
            */
           @Override
           public void contextDestroyed(ServletContextEvent servletContextEvent) {
               System.out.println("ServletContext对象被销毁了。。。");
           }
       }
       
      

5、JQuery基础

jQuery API 中文文档

5.1、简介

jQuery是JavaScript世界中使用最广泛的一个库。全世界大约有80~90%的网站直接或间接地使用了jQuery。

jQuery这么流行,肯定是因为它解决了一些很重要的问题。实际上,jQuery能帮我们干这些事情:

  • 消除浏览器差异:你不需要自己写冗长的代码来针对不同的浏览器来绑定事件,编写AJAX等代码;
  • 简洁的操作DOM的方法:写$('#test')肯定比document.getElementById('test')来得简洁;
  • 轻松实现动画、修改CSS等各种操作。

**jQuery的理念 ** “Write Less, Do More“,让你写更少的代码,完成更多的工作!


jQuery版本

目前jQuery有三个大版本:
1.x:兼容ie678,使用最为广泛的,官方只做BUG维护,
功能不再新增。因此一般项目来说,使用1.x版本就可以了,
最终版本:1.12.4 (2016年5月20日)
2.x:不兼容ie678,很少有人使用,官方只做BUG维护,
功能不再新增。如果不考虑兼容低版本的浏览器可以使用2.x,
最终版本:2.2.4 (2016年5月20日)
3.x:不兼容ie678,只支持最新的浏览器。除非特殊要求,
一般不会使用3.x版本的,很多老的jQuery插件不支持这个版本。
目前该版本是官方主要更新维护的版本。最新版本:3.6.0(2022年2月15日)

jQuery官网可以下载最新版本。jQuery只是一个jquery-xxx.js文件,但你会看到有compressed(已压缩)和uncompressed(未压缩)两种版本,使用时完全一样但如果你想深入研究jQuery源码,那就用uncompressed版本。


使用jQuery

使用jQuery只需要在页面的<head>引入jQuery文件即可

<html>
<head>
    <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
	...
</head>
<body>
    ...
</body>
</html>

js对象和jquery对象的区别

  • JS对象,是一个名值对的无序集合。

  • jquery对象,是jquery特有的对象,只有调用jquery框架才存在。其实jquery对象,也是一种js对象。

  • jquery对象和js对象可以相互转换,例如 $("#div").get(),即可以把一个jquery对象转换为js对象。

  • 两者相互转换

    • jq -- > js : jq对象[索引] 或者 jq对象.get(索引)
    • js -- > jq : $(js对象)

最主要的区别:

  • js对象上的方法,不能直接用在jquery对象上,如果一定要给jquery对象使用js对象的方法,必须把jquery对象转换为js对象。
  • jquery对象,则可以随意使用jquery定义的方法。

$符号

基本用法: $(选择器).事件()

$是著名的jQuery符号。实际上,jQuery把所有功能全部封装在一个全局变量jQuery中,而$也是一个合法的变量名,它是变量jQuery的别名:

window.jQuery; // jQuery(selector, context)
window.$; // jQuery(selector, context)
$ === jQuery; // true
typeof($); // 'function'

$本质上就是一个函数,但是函数也是对象,于是$除了可以直接调用外,也可以有很多其他属性。

注意,你看到的$函数名可能不是jQuery(selector, context),因为很多JavaScript压缩工具可以对函数名和参数改名,所以压缩过的jQuery源码$函数可能变成a(b, c)

绝大多数时候,我们都直接用$(因为写起来更简单嘛)。但是,如果$这个变量不幸地被占用了,而且还不能改,那我们就只能让jQuery$变量交出来,然后就只能使用jQuery这个变量:

$; // jQuery(selector, context)
jQuery.noConflict();
$; // undefined
jQuery; // jQuery(selector, context)

这种黑魔法的原理是jQuery在占用$之前,先在内部保存了原来的$,调用jQuery.noConflict()时会把原来保存的变量还原。


5.2、选择器(与CSS语法一样)

选择器是jQuery的核心。

一个选择器写出来类似$('#dom-id')

为什么jQuery要发明选择器?回顾一下DOM操作中我们经常使用的代码:

// 按ID查找:
var a = document.getElementById('dom-id');

// 按tag查找:
var divs = document.getElementsByTagName('div');

// 查找<p class="red">:
var ps = document.getElementsByTagName('p');
// 过滤出class="red":
// TODO:

// 查找<table class="green">里面的所有<tr>:
var table = ...
for (var i=0; i<table.children; i++) {
    // TODO: 过滤出<tr>
}

这些代码实在太繁琐了,并且,在层级关系中,例如,查找<table class="green">里面的所有<tr>,一层循环实际上是错的,因为<table>的标准写法是:

<table>
    <tbody>
        <tr>...</tr>
        <tr>...</tr>
    </tbody>
</table>

很多时候,需要递归查找所有子节点。

jQuery的选择器就是帮助我们快速定位到一个或多个DOM节点。

示例:

<p>
    <a href="" id="test-jQuery">点我</a>     //点击后出现hello,jquery的弹窗
</p>
<script>
    $('#test-jQuery').click(function () {     //按ID查找
        alert('hello,jquery')
    })
</script>

1、基本选择器

1、按ID查找

如果某个DOM节点有id属性,利用jQuery查找如下:

// 查找<div id="abc">:
var div = $('#abc');

注意#abc#开头。返回的对象是jQuery对象

什么是jQuery对象?jQuery对象类似数组,它的每个元素都是一个引用了DOM节点的对象。

以上面的查找为例,如果idabc<div>存在,返回的jQuery对象如下:

[<div id="abc">...</div>]

如果idabc<div>不存在,返回的jQuery对象如下:

[]

总之jQuery的选择器不会返回undefined或者null,这样的好处是你不必在下一行判断if (div === undefined)

jQuery对象和DOM对象之间可以互相转化:

var div = $('#abc'); // jQuery对象
var divDom = div.get(0); // 假设存在div,获取第1个DOM元素
var another = $(divDom); // 重新把DOM包装为jQuery对象

通常情况下你不需要获取DOM对象,直接使用jQuery对象更加方便

如果你拿到了一个DOM对象,那可以简单地调用$(aDomObject)把它变成jQuery对象,这样就可以方便地使用jQuery的API了。


2、按tag查找

按tag查找只需要写上tag名称就可以了:

var ps = $('p'); // 返回所有<p>节点
ps.length; // 数一数页面有多少个<p>节点

3、按class查找

按class查找注意在class名称前加一个.

var a = $('.red'); // 所有节点包含`class="red"`都将返回
// 例如:
// <div class="red">...</div>
// <p class="green red">...</p>

通常很多节点有多个class,我们可以查找同时包含redgreen的节点:

var a = $('.red.green'); // 注意没有空格!
// 符合条件的节点:
// <div class="red green">...</div>
// <div class="blue green red">...</div>

4、按属性查找

一个DOM节点除了idclass外还可以有很多属性,很多时候按属性查找会非常方便,比如在一个表单中按属性来查找:

var email = $('[name=email]'); // 找出<??? name="email">
var passwordInput = $('[type=password]'); // 找出<??? type="password">
var a = $('[items="A B"]'); // 找出<??? items="A B">
var email = $('[name!=email]'); // 找出name值不等于email的所有标签

当属性的值包含空格等特殊字符时,需要用双引号括起来。

正则匹配:

按属性查找还可以使用前缀查找或者后缀查找:

var icons = $('[name^=icon]'); // 找出所有name属性值以icon开头的DOM
// 例如: name="icon-1", name="icon-2"
var names = $('[name$=with]'); // 找出所有name属性值以with结尾的DOM
// 例如: name="startswith", name="endswith"

这个方法尤其适合通过class属性查找,且不受class包含多个名称的影响:

var icons = $('[class^="icon-"]'); // 找出所有class包含至少一个以`icon-`开头的DOM
// 例如: class="icon-clock", class="abc icon-home"

5、组合查找

组合查找就是把上述简单选择器组合起来使用。如果我们查找$('[name=email]'),很可能把表单外的<div name="email">也找出来,但我们只希望查找<input>,就可以这么写:

var emailInput = $('input[name=email]'); // 不会找出<div name="email">

同样的,根据tag和class来组合查找也很常见:

var tr = $('tr.red'); // 找出<tr class="red ...">...</tr>

6、多项选择器

多项选择器就是把多个选择器用,组合起来一块选:

$('p,div'); // 把<p>和<div>都选出来
$('p.red,p.green'); // 把<p class="red">和<p class="green">都选出来

要注意的是,选出来的元素是按照它们在HTML中出现的顺序排列的,而且不会有重复元素。例如,<p class="red green">不会被上面的$('p.red,p.green')选择两次。


2、高级选择器

1、后代选择器 $('ancestor descendant')

(Descendant Selector) 可以在所有后代中选择

如果两个DOM元素具有层级关系,就可以用$('ancestor descendant')来选择,层级之间用空格隔开。例如:

<!-- HTML结构 -->
<div class="testing">
    <ul class="lang">
        <li class="lang-javascript">JavaScript</li>
        <li class="lang-python">Python</li>
        <li class="lang-lua">Lua</li>
    </ul>
</div>

要选出JavaScript,可以用层级选择器:

$('ul.lang li.lang-javascript'); // [<li class="lang-javascript">JavaScript</li>]
$('div.testing li.lang-javascript'); // [<li class="lang-javascript">JavaScript</li>]

因为<div><ul>都是<li>的祖先节点,所以上面两种方式都可以选出相应的<li>节点。

要选择所有的<li>节点,用:

$('ul.lang li');

这种层级选择器相比单个的选择器好处在于,它缩小了选择范围,因为首先要定位父节点,才能选择相应的子节点,这样避免了页面其他不相关的元素。

例如:

$('form[name=upload] input');

就把选择范围限定在name属性为upload的表单里。如果页面有很多表单,其他表单的<input>不会被选择。

多层选择也是允许的:

$('form.test p input'); // 在form表单选择被<p>包含的<input>

2、子代选择器 $('parent>child')

(Child Selector) 只能选择子代

子选择器$('parent>child')类似层级选择器,但是限定了层级关系必须是父子关系,就是<child>节点必须是<parent>节点的直属子节点。

<!-- HTML结构 -->
<div class="testing">
    <ul class="lang">
        <li class="lang-javascript">JavaScript</li>
        <li class="lang-python">Python</li>
        <li class="lang-lua">Lua</li>
    </ul>
</div>
$('ul.lang>li.lang-javascript'); // 可以选出[<li class="lang-javascript">JavaScript</li>]
$('div.testing>li.lang-javascript'); // [], 无法选出,因为<div>和<li>不构成父子关系

3、过滤器 带冒号过滤

(Filter)类似CSS结构伪类选择器

  1. 首元素选择器
    • 语法: :first 获得选择的元素中的第一个元素
  2. 尾元素选择器
    • 语法: :last 获得选择的元素中的最后一个元素
  3. 非元素选择器
    • 语法: :not(selector) 不包括指定内容的元素
  4. 偶数选择器
    • 语法: :even 偶数,从 0 开始计数
  5. 奇数选择器
    • 语法: :odd 奇数,从 0 开始计数
  6. 等于索引选择器
    • 语法: :eq(index) 指定索引元素
  7. 大于索引选择器
    • 语法: :gt(index) 大于指定索引元素
  8. 小于索引选择器
    • 语法: :lt(index) 小于指定索引元素
  9. 标题选择器
    • 语法: :header 获得全部标题(h1~h6)元素,固定写法,冒号前面不需要写东西

过滤器一般不单独使用,它通常附加在选择器上,帮助我们更精确地定位元素。观察过滤器的效果:

$('ul.lang li'); // 选出JavaScript、Python和Lua 3个节点

$('ul.lang li:first-child'); // 仅选出JavaScript
$('ul.lang li:last-child'); // 仅选出Lua
$('ul.lang li:nth-child(2)'); // 选出第N个元素,N从1开始
$('ul.lang li:nth-child(even)'); // 选出序号为偶数的元素
$('ul.lang li:nth-child(odd)'); // 选出序号为奇数的元素

4、表单相关

针对表单元素,jQuery还有一组特殊的选择器:

  • :input:可以选择<input><textarea><select><button>
  • :file:可以选择<input type="file">,和input[type=file]一样;
  • :checkbox:可以选择复选框,和input[type=checkbox]一样;
  • :radio:可以选择单选框,和input[type=radio]一样;
  • :focus:可以选择当前输入焦点的元素,例如把光标放到一个<input>上,用$('input:focus')就可以选出;
  • :checked:选择当前勾上的单选框和复选框,用这个选择器可以立刻获得用户选择的项目,如$('input[type=radio]:checked')
  • :enabled:可以选择可以正常输入的<input><select> 等,也就是没有灰掉的输入;
  • :disabled:和:enabled正好相反,选择那些不能输入的。

此外,jQuery还有很多有用的选择器,例如,选出可见的或隐藏的元素:

$('div:visible'); // 所有可见的div
$('div:hidden'); // 所有隐藏的div

5.3、查找和过滤

1、find()

在子节点中查找

通常情况下选择器可以直接定位到我们想要的元素,但是,当我们拿到一个jQuery对象后,还可以以这个对象为基准,进行查找和过滤。

最常见的查找是在某个节点的所有子节点中查找,使用find()方法,它本身又接收一个任意的选择器。例如如下的HTML结构:

  • JavaScript
  • Python
  • Swift
  • Scheme
  • Haskell
<!-- HTML结构 -->
<ul class="lang">
    <li class="js dy">JavaScript</li>
    <li class="dy">Python</li>
    <li id="swift">Swift</li>
    <li class="dy">Scheme</li>
    <li name="haskell">Haskell</li>
</ul>

find()查找:

var ul = $('ul.lang'); // 获得<ul>
var dy = ul.find('.dy'); // 获得JavaScript, Python, Scheme
var swf = ul.find('#swift'); // 获得Swift
var hsk = ul.find('[name=haskell]'); // 获得Haskell

如果要从当前节点开始向上查找,使用parent()方法:

var swf = $('#swift'); // 获得Swift
var parent = swf.parent(); // 获得Swift的上层节点<ul>
var a = swf.parent('.red'); // 获得Swift的上层节点<ul>,同时传入过滤条件。如果ul不符合条件,返回空jQuery对象

对于位于同一层级的节点,可以通过next()prev()方法,例如:

当我们已经拿到Swift节点后:

var swift = $('#swift');

swift.next(); // Scheme
swift.next('[name=haskell]'); // 空的jQuery对象,因为Swift的下一个元素Scheme不符合条件[name=haskell]

swift.prev(); // Python
swift.prev('.dy'); // Python,因为Python同时符合过滤器条件.dy

2、filter()

在接收的Array中过滤

和函数式编程的map、filter类似,jQuery对象也有类似的方法。

<!-- HTML结构 -->
<ul class="lang">
    <li class="js dy">JavaScript</li>
    <li class="dy">Python</li>
    <li id="swift">Swift</li>
    <li class="dy">Scheme</li>
    <li name="haskell">Haskell</li>
</ul>

filter()方法可以过滤掉不符合选择器条件的节点:

var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell
var a = langs.filter('.dy'); // 拿到JavaScript, Python, Scheme

或者传入一个函数,要特别注意函数内部的this被绑定为DOM对象,不是jQuery对象

var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell
langs.filter(function () {
    return this.innerHTML.indexOf('S') === 0; // 返回S开头的节点
}); // 拿到Swift, Scheme

map()方法把一个jQuery对象包含的若干DOM节点转化为其他对象:

var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell
var arr = langs.map(function () {
    return this.innerHTML;
}).get(); // 用get()拿到包含string的Array:['JavaScript', 'Python', 'Swift', 'Scheme', 'Haskell']

此外,一个jQuery对象如果包含了不止一个DOM节点,first()last()slice()方法可以返回一个新的jQuery对象,把不需要的DOM节点去掉:

var langs = $('ul.lang li'); // 拿到JavaScript, Python, Swift, Scheme和Haskell
var js = langs.first(); // JavaScript,相当于$('ul.lang li:first-child')
var haskell = langs.last(); // Haskell, 相当于$('ul.lang li:last-child')
var sub = langs.slice(2, 4); // Swift, Scheme, 参数和数组的slice()方法一致

5.4、事件

因为JavaScript在浏览器中以单线程模式运行,页面加载后,一旦页面上所有的JavaScript代码被执行完后,就只能依赖触发事件来执行JavaScript代码。

浏览器在接收到用户的鼠标或键盘输入后,会自动在对应的DOM节点上触发相应的事件。如果该节点已经绑定了对应的JavaScript处理函数,该函数就会自动调用。

由于不同的浏览器绑定事件的代码都不太一样,所以用jQuery来写代码,就屏蔽了不同浏览器的差异,我们总是编写相同的代码。

举个例子,假设要在用户点击了超链接时弹出提示框,我们用jQuery这样绑定一个click事件:

/* HTML:
 *
 * <a id="test-link" href="#0">点我试试</a>
 *
 */

// 获取超链接的jQuery对象:
var a = $('#test-link');
a.on('click', function () {     //on方法用来绑定一个事件,我们需要传入事件类型和对应的处理函数。
    alert('Hello!');
});

另一种更简化的写法是直接调用click()方法:

a.click(function () {
    alert('Hello!');
});

两者完全等价。我们通常用后面的写法。


1、鼠标事件

  • click: 鼠标单击时触发;
  • dblclick:鼠标双击时触发;
  • mouseenter:鼠标进入时触发;
  • mouseleave:鼠标移出时触发;
  • mousemove:鼠标在DOM内部移动时触发;
  • hover:鼠标进入和退出时触发两个函数,相当于mouseenter加上mouseleave。

2、键盘事件

键盘事件仅作用在当前焦点的DOM上,通常是<input><textarea>

  • keydown:键盘按下时触发;
  • keyup:键盘松开时触发;
  • keypress:按一次键后触发。

3、其他事件

  • focus:当DOM获得焦点时触发;
  • blur:当DOM失去焦点时触发;
  • change:当<input><select><textarea>的内容改变时触发;
  • submit:当<form>提交时触发;
  • ready:当页面被载入并且DOM树完成初始化后触发。

其中,ready仅作用于document对象。由于ready事件在DOM完成初始化后触发,且只触发一次,所以非常适合用来写其他的初始化代码。假设我们想给一个<form>表单绑定submit事件,下面的代码没有预期的效果:

<html>
<head>
    <script>
        // 代码有误:
        $('#testForm').on('submit', function () {
            alert('submit!');
        });
    </script>
</head>
<body>
    <form id="testForm">
        ...
    </form>
</body>

因为JavaScript在此执行的时候,<form>尚未载入浏览器,所以$('#testForm)返回[],并没有绑定事件到任何DOM上。

所以我们自己的初始化代码必须放到document对象的ready事件中,保证DOM已完成初始化:

<html>
<head>
    <script>
        $(document).on('ready', function () {
            $('#testForm).on('submit', function () {
                alert('submit!');
            });
        });
    </script>
</head>
<body>
    <form id="testForm">
        ...
    </form>
</body>

这样写就没有问题了。因为相关代码会在DOM树初始化后再执行。

由于ready事件使用非常普遍,所以可以这样简化:

$(document).ready(function () {
    // on('submit', function)也可以简化:
    $('#testForm).submit(function () {
        alert('submit!');
    });
});

甚至还可以再简化为:

<!--入口函数-->
$(function () {
    // init...     init是初始化函数
});

上面的这种写法最为常见。如果你遇到$(function () {...})的形式,牢记这是document对象的ready事件处理函数。

  • $(function () {...})可以反复绑定事件处理函数,它们会依次执行
  • 但是window.onload只能定义一次,如果定义多次,后边的会将前边的覆盖
$(function () {
    console.log('init A...');
});
$(function () {
    console.log('init B...');
});
$(function () {
    console.log('init C...');
});

4、事件参数

有些事件,如mousemovekeypress,我们需要获取鼠标位置和按键的值,否则监听这些事件就没什么意义了。所有事件都会传入Event对象作为参数,可以从Event对象上获取到更多的信息:

<p>
        mousemove:<span id="testMouseMoveSpan"></span>
    </p>

    <div id="testMouseMoveDiv" style="display: block; width: 300px; height: 120px; border: 1px solid #ccc;">
        在此区域移动鼠标试试
    </div>

    <script>
        $(function () {
            $('#testMouseMoveDiv').mousemove(function (e) {
                $('#testMouseMoveSpan').text('pageX = ' + e.pageX + ', pageY = ' + e.pageY);
            });
        });
    </script>

5、取消绑定

一个已被绑定的事件可以解除绑定,通过off('click', function)实现:

function hello() {
    alert('hello!');
}

a.click(hello); // 绑定事件

// 10秒钟后解除绑定:
setTimeout(function () {
    a.off('click', hello);
}, 10000);

需要特别注意的是,下面这种写法是无效的:

// 绑定事件:
a.click(function () {      //匿名函数1
    alert('hello!');
});

// 解除绑定:
a.off('click', function () {    ///匿名函数2
    alert('hello!');
});

这是因为两个匿名函数虽然长得一模一样,但是它们是两个不同的函数对象,off('click', function () {...})无法移除已绑定的第一个匿名函数。

为了实现移除效果,可以使用off('click')一次性移除已绑定的click事件的所有处理函数。

同理,无参数调用off()一次性移除已绑定的所有类型的事件处理函数。


6、事件触发条件

一个需要注意的问题是,事件的触发总是由用户操作引发的。例如,我们监控文本框的内容改动:

var input = $('#test-input');
input.change(function () {
    console.log('changed...');
});

当用户在文本框中输入时,就会触发change事件。但是,如果用JavaScript代码去改动文本框的值,将不会触发change事件:

var input = $('#test-input');
input.val('change it!'); // 无法触发change事件

有些时候,我们希望用代码触发change事件,可以直接调用无参数的change()方法来触发该事件:

var input = $('#test-input');
input.val('change it!');
input.change(); // 触发change事件

input.change()相当于input.trigger('change'),它是trigger()方法的简写。


7、浏览器安全限制

在浏览器中,有些JavaScript代码只有在用户触发下才能执行,自动弹窗将被拦截,例如,window.open()函数:

// 无法弹出新窗口,将被浏览器屏蔽:
$(function () {
    window.open('/');
});

这些“敏感代码”只能由用户操作来触发:

var button1 = $('#testPopupButton1');
var button2 = $('#testPopupButton2');

function popupTestWindow() {
    window.open('/');
}

button1.click(function () {
    popupTestWindow();
});

button2.click(function () {
    // 不立刻执行popupTestWindow(),3秒后执行:
    setTimeout(popupTestWindow, 3000);
});

当用户点击button1时,click事件被触发,由于popupTestWindow()click事件处理函数内执行,这是浏览器允许的,而button2click事件并未立刻执行popupTestWindow(),延迟执行的popupTestWindow()将被浏览器拦截 (实测Edge可以延迟弹窗,可能是由于也是绑定的click事件,是经过用户点击允许的)。


5.5、操作DOM

jQuery的选择器很强大,用起来又简单又灵活,但是搞了这么久,我拿到了jQuery对象,到底要干什么?

答案当然是操作对应的DOM节点啦!

回顾一下修改DOM的CSS、文本、设置HTML有多么麻烦,而且有的浏览器只有innerHTML,有的浏览器支持innerText,有了jQuery对象,不需要考虑浏览器差异了,全部统一操作!


  • html(): 获取/设置元素的a标签体html内容 <a><font>内容</font></a> 得到--> <font>内容</font>
  • text(): 获取/设置元素的a标签体纯文本内容 <a><font>内容</font></a> 得到--> 内容
  • val(): 获取/设置元素的value属性值

1、修改Text(显示文本)和HTML(源码)

jQuery对象的text()html()方法分别获取节点的文本和原始HTML文本,例如,如下的HTML结构:

<!-- HTML结构 -->
<ul id="test-ul">
    <li class="js">JavaScript</li>
    <li name="book">Java &amp; JavaScript</li>
</ul>

分别获取文本和HTML:

$('#test-ul li[name=book]').text(); // 'Java & JavaScript'
$('#test-ul li[name=book]').html(); // 'Java &amp; JavaScript'

如何设置文本或HTML?

jQuery的API设计非常巧妙:无参数调用text()是获取文本,传入参数就变成设置文本,HTML也是类似操作

另外:

一个jQuery对象可以包含0个或任意个DOM对象,它的方法实际上会作用在对应的每个DOM节点上。在上面的例子中试试:

$('#test-ul li').text('JS'); // 两个节点都变成了JS

所以jQuery对象的另一个好处是我们可以执行一个操作,作用在对应的一组DOM节点上。即使选择器没有返回任何DOM节点,调用jQuery对象的方法仍然不会报错:

// 如果不存在id为not-exist的节点:
$('#not-exist').text('Hello'); // 代码不报错,没有节点被设置为'Hello'

这意味着jQuery帮你免去了许多if语句。


2、修改CSS

jQuery对象有“批量操作”的特点,这用于修改CSS实在是太方便了。考虑下面的HTML结构:

<!-- HTML结构 -->
<ul id="test-css">
    <li class="lang dy"><span>JavaScript</span></li>      //文字和背景颜色会改变
    <li class="lang"><span>Java</span></li>
    <li class="lang dy"><span>Python</span></li>		  //文字和背景颜色会改变
    <li class="lang"><span>Swift</span></li>
    <li class="lang dy"><span>Scheme</span></li>		  //文字和背景颜色会改变
</ul>

要高亮显示动态语言,调用jQuery对象的css('name', 'value')方法,我们用一行语句实现:

$('#test-css li.dy>span').css('background-color', '#ffd351').css('color', 'red'); //子代选择器

注意,jQuery对象的所有方法都返回一个jQuery对象(可能是新的也可能是自身),这样我们可以进行链式调用,非常方便。

jQuery对象的css()方法可以这么用:不传入值就是获取,传入值就是设置,同text()和html()

var div = $('#test-div');
div.css('color'); // '#000033', 获取CSS属性   
div.css('color', '#336699'); // 设置CSS属性
div.css('color', ''); // 清除CSS属性

为了和JavaScript保持一致,CSS属性可以用'background-color''backgroundColor'两种格式。

css()方法将作用于DOM节点的style属性,具有最高优先级。如果要修改class属性,可以用jQuery提供的下列方法:

var div = $('#test-div');
div.hasClass('highlight'); // false, class是否包含highlight
div.addClass('highlight'); // 添加highlight这个class
div.removeClass('highlight'); // 删除highlight这个class

3、显示和隐藏DOM

要隐藏一个DOM,我们可以设置CSS的display属性为none,利用css()方法就可以实现。不过,要显示这个DOM就需要恢复原有的display属性,这就得先记下来原有的display属性到底是block还是inline还是别的值。

考虑到显示和隐藏DOM元素使用非常普遍,jQuery直接提供show()hide()方法,我们不用关心它是如何修改display属性的,总之它能正常工作:

var a = $('a[target=_blank]');
a.hide(); // 隐藏   a.css('display',none)
a.show(); // 显示   a.css('display',block/inline)  需要记住原先的属性值

注意,隐藏DOM节点并未改变DOM树的结构,它只影响DOM节点的显示。这和删除DOM节点是不同的。


4、获取DOM信息

利用jQuery对象的若干方法,我们直接可以获取DOM的高宽等信息,而无需针对不同浏览器编写特定代码:

// 浏览器可视窗口大小:
$(window).width(); // 800
$(window).height(); // 600

// HTML文档大小:
$(document).width(); // 800
$(document).height(); // 3500

// 某个div的大小:
var div = $('#test-div');
div.width(); // 600
div.height(); // 300
div.width(400); // 设置CSS属性 width: 400px,是否生效要看CSS是否有效
div.height('200px'); // 设置CSS属性 height: 200px,是否生效要看CSS是否有效

5、属性操作

1、通用属性操作
  • attr(): 获取/设置元素的自定义属性
  • removeAttr():删除自定义属性
  • prop():获取/设置元素的固有属性
  • removeProp():删除固有属性
    • attr和prop区别?
      • 如果操作的是元素的固有属性,则建议使用prop
      • 如果操作的是元素自定义的属性,则建议使用attr
2、对class属性操作
  • addClass() : 添加class属性值
  • removeClass(): 删除class属性值
  • toggleClass() : 切换class属性,相当于取反
    • toggleClass("one"):
      • 判断如果元素对象上存在class="one",则将属性值one删除掉。 如果元素对象上不存在class="one",则添加
  • css(): 样式修改
// <div id="test-div" name="Test" start="1">...</div>
var div = $('#test-div');
div.attr('data'); // undefined, 属性不存在
div.attr('name'); // 'Test'
div.attr('name', 'Hello'); // div的name属性变为'Hello'
div.removeAttr('name'); // 删除name属性
div.attr('name'); // undefined

prop()方法和attr()类似,但是HTML5规定有一种属性在DOM节点中可以没有值,只有出现与不出现两种,例如:

<input id="test-radio" type="radio" name="test" checked value="1">

等价于:

<input id="test-radio" type="radio" name="test" checked="checked" value="1">

attr()prop()对于属性checked处理有所不同:

prop() 方法设置或返回被选元素的属性和值。 property 属性

当该方法用于返回属性值时,则返回第一个匹配元素的值。

当该方法用于设置属性值时,则为匹配元素集合设置一个或多个属性/值对。

注意:prop() 方法应该用于检索属性值,例如 DOM 属性(如 selectedIndex, tagName, nodeName, nodeType, ownerDocument, defaultChecked, 和 defaultSelected)。

提示:如需检索 HTML 属性,请使用 attr() 方法代替。

提示:如需移除属性,请使用 removeProp() 方法。

var radio = $('#test-radio');
radio.attr('checked'); // 'checked'
radio.prop('checked'); // true

prop()返回值更合理一些。不过,用is()方法判断更好:

var radio = $('#test-radio');
radio.is(':checked'); // true

类似的属性还有selected,处理时最好用is(':selected')


6、CRUD操作

父子关系操作
  1. append():父元素将子元素追加到末尾
    • 对象1.append(对象2): 将对象2添加到对象1元素内部,并且在末尾
  2. prepend():父元素将子元素追加到开头
    • 对象1.prepend(对象2):将对象2添加到对象1元素内部,并且在开头
  3. appendTo(): 可以实现和append()一样的功能
    • 对象1.appendTo(对象2):将对象1添加到对象2内部,并且在末尾
  4. prependTo():可以实现和prepend()一样的功能
    • 对象1.prependTo(对象2):将对象1添加到对象2内部,并且在开头
兄弟关系操作
  1. after():添加元素到元素后边
    • 对象1.after(对象2): 将对象2添加到对象1后边。对象1和对象2是兄弟关系
  2. before():添加元素到元素前边
    • 对象1.before(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
  3. insertAfter()
    • 对象1.insertAfter(对象2):将对象2添加到对象1后边。对象1和对象2是兄弟关系
  4. insertBefore()
    • 对象1.insertBefore(对象2): 将对象2添加到对象1前边。对象1和对象2是兄弟关系
自身操作
  1. remove():移除元素
    • 对象.remove(): 将对象删除掉,自杀
  2. clone():复制一个对象用于使用,会保留原来的对象,常配合添加、追加操作使用。
  3. empty():清空元素的所有后代元素。
    • 对象.empty():将对象的后代元素全部清空,但是保留当前对象以及其属性节点

7、操作表单

对于表单元素,jQuery对象统一提供val()方法获取和设置对应的value属性:

    <input id="test-input" name="email" value="test">
    <select id="test-select" name="city">               //下拉框
        <option value="BJ" selected>Beijing</option>    //默认值为BJ
        <option value="SH">Shanghai</option>
        <option value="SZ">Shenzhen</option>
    </select>
    <textarea id="test-textarea">Hello</textarea>

<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) 案例

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>01-jQuery对象进行方法扩展</title>
    <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>
</head>
<body>
<input id="btn-check" type="button" value="点击选中复选框">
<input id="btn-uncheck" type="button" value="点击取消复选框选中">
<br/>
<input type="checkbox" value="football">足球
<input type="checkbox" value="basketball">篮球
<input type="checkbox" value="volleyball">排球

</body>
</html>

$.extend(object)案例

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>01-jQuery对象进行方法扩展</title>
    <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>
</head>
<body>
</body>
</html>

7、AJAX

ASynchronous JavaScript And XML 异步的JavaScript 和 XML

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

1、简介

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

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

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

2、实现方式

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</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>
    
    
</head>
<body>

    <input type="button" value="发送异步请求" onclick="fun();">

    <input>
</body>
</html>

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<Person> ps = new ArrayList<Person>();
        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<String,Object> map = new HashMap<String,Object>();
        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、注册页面(关键)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>注册页面</title>
  <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>
</head>
<body>

<form action="">
  <input type="text" name="username" placeholder="请输入用户名" id="username"><span id="s_username"></span><br>
  <input type="password" name="username" placeholder="请输入密码"><br>
  <input type="submit" value="注册">
</form>
</body>
</html>
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<String, Object> 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<String, String> user = jedis.hgetAll("user");

        // keyset
        Set<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</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 = "<option name='"+ this.id +"'>"+ this.name +"</option>";
                    province.append(option);
                })
            })
        })
    </script>
</head>
<body>
<select name="province" id="province">
    <option>-请选择省份-</option>
</select>
</body>
</html>
ProvinceDaoImpl.java
public class ProvinceDaoImpl implements ProvinceDao {
    //声明成员变量 template
    private JdbcTemplate template = new JdbcTemplate(DruidPoolUtils.getDatasource());
    @Override
    public List<Province> findAll() {
        String sql = "select * from province";
        List<Province> list = template.query(sql, new BeanPropertyRowMapper<Province>(Province.class));
        return list;
    }
}
ProvinceServiceImpl.java
public class ProvinceServiceImpl implements ProvinceService {
    //声明dao
    private ProvinceDao dao = new ProvinceDaoImpl();
    @Override
    public List<Province> 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<Province> 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();
    }
}

posted @   猫的心情  阅读(190)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示