[TOC]

JavaWeb

什么是JavaWeb

  • Web:全球广域网,也称万维网(www),能够通过浏览器访问的网站
  • JavaWeb:使用 Java技术来解决相关web互联网领域的技术栈

JavaWeb图示

  1. 网页:展现数据
  2. 数据库:储存和管理数据
  3. JavaWeb程序:逻辑处理

image.png

JavaWeb学习路线

image.png


Maven

Maven简介

  • Apache Maven是一个项目管理和构建工具,它基于项目对象模型..(POM)的概念,通过一小段描述信息来管理项目的构建、报告和文档
  • 官网: http://maven.apache.org/
  • Maven是专门用于管理和构建Java项目的工具,它的主要功能有:
    • 提供了一套标准化的项目结构
    • 提供了一套标准化的构建流程(编译,测试,打包,发布…..)
    • 提供了一套依赖管理机制

常用的项目构建工具

image.png

Maven功能、作用

标准化的项目结构

  • eclipse
  • myeclipse
  • idea
  • 不同IDE之间,项目结构不一样,不通用

image.png

Maven提供了一套标准化的项目结构,所有IDE使用Maven构建的项目结构完全一样,所有IDE创建的Maven项目可以通用

标准化的构建流程

image.png

Maven提供了一套简单的命令来完成项目构建

依赖管理机制

  • 依赖管理其实就是管理你项目所依赖的第三方资源(jar包,插件……)

image.png
image.png


Maven模型

image.png

  • 项目对象模型(Project Object Model)
  • 依赖管理模型(Dependency)
  • 插件(Plugin)
  • 仓库
    image.png
    仓库分类
    • 本地仓库:自己计算机上的一个目录
    • 中央仓库:由Maven团队维护的全球唯一的仓库地址: https://repo1.maven.org/maven2/
    • 远程仓库(私服):一股由公司团队搭建的私有仓库(提升访问速度)

项目使用坐标引进对应依赖jar包:
当项目中使用坐标引入对应依赖jar包后,首先会查找本地仓库中是否有对应的jar包:

  • 如果有,则在项目直接引用;
  • 如果没有,则去中央仓库中下载对应的jar包到本地仓库。

还可以搭建远程仓库,将来jar包的查找顺序则变为:

  • 本地仓库→远程仓库→中央仓库

[

](https://www.baiducom)

Maven的安装配置

  1. 解压apache-maven-3.6.1.rar既安装完成
  2. 配置环境变量MAVEN_HOME为安装路径的bin目录
  3. 配置本地仓库:修改conf/settings.xml中的为一个指定目录
  4. 配置阿里云私服:修改conf/settings.xml中的标签
  • 解压 apache-maven-3.6.1.rar 既安装完成

image.png

  • 解压缩后的目录结构如下:

image.png

建议解压缩到没有中文、特殊字符的路径下。如解压缩到 D:\software 下。

  • bin目录 : 存放的是可执行命令。mvn 命令重点关注。
  • conf目录 :存放Maven的配置文件。settings.xml 配置文件后期需要修改。
  • lib目录 :存放Maven依赖的jar包。Maven也是使用java开发的,所以它也依赖其他的jar包。
    • 配置环境变量 MAVEN_HOME 为安装路径的bin目录
      此电脑 右键 —> 高级系统设置 —> 高级 —> 环境变量
      在系统变量处新建一个变量 MAVEN_HOME
    • image.png
      Path 中进行配置

image.png
打开命令提示符进行验证,出现如图所示表示安装成功
image.png

  • 配置本地仓库
    修改 conf/settings.xml 中的 为一个指定目录作为本地仓库,用来存储jar包。 image.png
  • 配置阿里云私服
    中央仓库在国外,所以下载jar包速度可能比较慢,而阿里里公司提供了一个远程仓库,里面基本也都有开源项目的jar包。
    修改 conf/settings.xml 中的 标签,为其添加如下子标签:
    <mirror>  
     <id>alimaven</id>  
     <name>aliyun maven</name>  
     <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
     <mirrorOf>central</mirrorOf>          
    </mirror>
    

image.png

Maven的基本使用

Maven的常用命令

  • compile :编译
  • clean:清理
  • test:测试
  • package:打包
  • install:安装

win10右键打开PowerShell

命令演示:

资料\代码\maven-project 提供了一个使用Maven构建的项目,项目结构如下:

image.png

而我们使用上面命令需要在磁盘上进入到项目的 pom.xml 目录下,打开命令提示符

image.png

编译命令演示
compile :编译

执行上述命令可以看到:

  • 从阿里云下载编译需要的插件的jar包,在本地仓库也能看到下载好的插件
  • 在项目下会生成一个 target 目录

image.png

同时在项目下会出现一个 target 目录,编译后的字节码文件就放在该目录下

image.png

清理命令演示
mvn clean

执行上述命令可以看到

  • 从阿里云下载清理需要的插件jar包
  • 删除项目下的 target 目录

image.png

打包命令演示
mvn package

执行上述命令可以看到:

  • 从阿里云下载打包需要的插件jar包
  • 在项目的 terget 目录下有一个jar包(将当前项目打成的jar包)

image.png

测试命令演示:

mvn test

该命令会执行所有的测试代码。执行上述命令效果如下

image.png

安装命令演示:

mvn install

该命令会将当前项目打成jar包,并安装到本地仓库。执行完上述命令后到本地仓库查看结果如下:

image.png

  • IDEA中也可以使用这些命令:

image.png

  • IDEA安装插件后

image.png

Maven的生命周期

Maven 构建项目生命周期描述的是一次构建过程经历经历了多少个事件

Maven 对项目构建的生命周期划分为3套

  • clean :清理工作。
  • default :核心工作,例如编译,测试,打包,安装等。
  • site : 产生报告,发布站点等。这套声明周期一般不会使用。

同一套生命周期内,执行后边的命令,前面的所有命令会自动执行。
例如默认(default)生命周期如下:

image.png

当我们执行 install(安装)命令时,它会先执行 compile命令,再执行 test 命令,再执行 package 命令,最后执行 install 命令。
当我们执行 package (打包)命令时,它会先执行 compile 命令,再执行 test 命令,最后执行 package 命令。

默认的生命周期也有对应的很多命令,其他的一般都不会使用,我们只关注常用的:
image.png

IDEA配置Maven

以后开发中我们肯定会在高级开发工具中使用Maven管理项目,而我们常用的高级开发工具是IDEA,所以接下来我们会讲解Maven在IDEA中的使用。

IDEA配置Maven环境

我们需要先在IDEA中配置Maven环境:

  • 选择 IDEA中 File —> Settings

image.png

  • 搜索 maven

image.png

  • 设置 IDEA 使用本地安装的 Maven,并修改配置文件路径

image.png

Maven 坐标详解

什么是坐标?

  • Maven 中的坐标是资源的唯一标识
  • 使用坐标来定义项目或引入项目中需要的依赖

Maven 坐标主要组成

  • groupId:定义当前Maven项目隶属组织名称(通常是域名反写,例如:com.itheima)
  • artifactId:定义当前Maven项目名称(通常是模块名称,例如 order-service、goods-service)
  • version:定义当前项目版本号

如下图就是使用坐标表示一个项目:
image.png
image.png

注意:

  • 上面所说的资源可以是插件、依赖、当前项目。
  • 我们的项目如果被其他的项目依赖时,也是需要坐标来引入的。

IDEA 创建 Maven项目

  • 打开项目结构

image.png

  • image.png

  • 创建模块,选择Maven,点击Next

image.png

  • 填写模块名称,坐标信息,点击finish,创建完成

image.png
创建好的项目目录结构如下:
image.png

  • 编写 HelloWorld,并运行

IDEA 导入 Maven项目

导入到自己的IDEA中。可以通过以下步骤进行项目的导入:

  • 选择右侧Maven面板,点击 + 号

image.png

  • 选中对应项目的pom.xml文件,双击即可

image.png

  • 如果没有Maven面板,选择
    View —> Appearance —> Tool Window Bars

image.png

IDEA使用Maven命令

可以通过下图所示进行命令的操作:

image.png

配置 Maven-Helper 插件

  • 选择 IDEA中 File —> Settings

image.png

  • 选择 Plugins

image.png

  • 搜索 Maven,选择第一个 Maven Helper,点击Install安装,弹出面板中点击Accept

image.png

  • 重启 IDEA

安装完该插件后可以通过 选中项目右键进行相关命令操作,如下图所示:

image.png

依赖管理

使用坐标引入jar包

使用坐标引入jar包的步骤:

  • 在项目的 pom.xml 中编写 标签
  • 在 标签中 使用 引入坐标
  • 定义坐标的 groupId,artifactId,version

image.png

  • 点击刷新按钮,使坐标生效

image.png

注意:

快捷方式导入jar包的坐标:

每次需要引入jar包,都去对应的网站进行搜索是比较麻烦的,接下来给大家介绍一种快捷引入坐标的方式

  • 在 pom.xml 中 按 alt + insert,选择 Dependency

image.png

  • 在弹出的面板中搜索对应坐标,然后双击选中对应坐标

image.png

  • 点击刷新按钮,使坐标生效

image.png

自动导入设置:

上面每次操作都需要点击刷新按钮,让引入的坐标生效。当然我们也可以通过设置让其自动完成

  • 选择 IDEA中 File —> Settings

image.png

  • 在弹出的面板中找到 Build Tools

image.png

  • 选择 Any changes,点击 ok 即可生效

依赖范围

通过设置坐标的依赖范围(scope),可以设置 对应jar包的作用范围:编译环境、测试环境、运行环境。

如下图所示给 junit 依赖通过 scope 标签指定依赖的作用范围。 那么这个依赖就只能作用在测试环境,其他环境下不能使用。
image.png

那么 scope 都可以有哪些取值呢?

依赖范围 编译classpath 测试classpath 运行classpath 例子
compile Y Y Y logback
test - Y - Junit
provided Y Y - servlet-api
runtime - Y Y jdbc驱动
system Y Y - 存储在本地的jar包
  • compile :作用于编译环境、测试环境、运行环境。
  • test : 作用于测试环境。典型的就是Junit坐标,以后使用Junit时,都会将scope指定为该值
  • provided :作用于编译环境、测试环境。我们后面会学习 servlet-api ,在使用它时,必须将 scope 设置为该值,不然运行时就会报错
  • runtime : 作用于测试环境、运行环境。jdbc驱动一般将 scope 设置为该值,当然不设置也没有任何问题

注意:

  • 如果引入坐标不指定 scope 标签时,默认就是 compile 值。以后大部分jar包都是使用默认值。

Mybatis

Mybatis概述

Mybatis概念

  • MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发
  • MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github
  • 官网:https://mybatis.org/mybatis-3/zh/index.html

持久层:

  • 负责将数据到保存到数据库的那一层代码。
    以后开发我们会将操作数据库的Java代码作为持久层。而Mybatis就是对jdbc代码进行了封装
  • JavaEE三层架构:表现层、业务层、持久层
    三层架构在后期会给大家进行讲解,今天先简单的了解下即可。

框架:

  • 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型
  • 在框架的基础之上构建软件编写更加高效、规范、通用、可扩展

举例给大家简单的解释一下什么是半成品软件。大家小时候应该在公园见过给石膏娃娃涂鸦
image.png

如下图所示有一个石膏娃娃,这个就是一个半成品。你可以在这个半成品的基础上进行不同颜色的涂鸦
image.png

了解了什么是Mybatis后,接下来说说以前 JDBC代码 的缺点以及Mybatis又是如何解决的。

JDBC 缺点

下面是 JDBC 代码,我们通过该代码分析都存在什么缺点:
image.png

  • 硬编码
    • 注册驱动、获取连接
      上图标1的代码有很多字符串,而这些是连接数据库的四个基本信息,以后如果要将Mysql数据库换成其他的关系型数据库的话,这四个地方都需要修改,如果放在此处就意味着要修改我们的源代码。
    • SQL语句
      上图标2的代码。如果表结构发生变化,SQL语句就要进行更改。这也不方便后期的维护。
  • 操作繁琐
    • 手动设置参数
    • 手动封装结果集
      上图标4的代码是对查询到的数据进行封装,而这部分代码是没有什么技术含量,而且特别耗费时间的。

Mybatis 优化

  • 硬编码可以配置到配置文件
  • 操作繁琐的地方mybatis都自动完成

如图所示
image.png

下图是持久层框架的使用占比。
image.png

Mybatis快速入门

需求:查询user表中所有的数据
步骤:

  1. 创建user表,添加数据
  2. 创建模块,导入坐标
  3. 编写MyBatis核心配置文件—>替换连接信息解决硬编码问题
  4. 编写SQL映射文件—>统一管理sql语句,解决硬编码问题
  5. 编码
    • 定义POJO类
    • 加载核心配置文件,获取 SqlSessionFactory对象
    • 获取 SqISession对象,执行SQL语句
    • 释放资源
  • 创建user表,添加数据
    ```sql create database mybatis; use mybatis;

drop table if exists tb_user;

create table tb_user( id int primary key auto_increment, username varchar(20), password varchar(20), gender char(1), addr varchar(30) );

INSERT INTO tb_user VALUES (1, ‘zhangsan’, ‘123’, ‘男’, ‘北京’); INSERT INTO tb_user VALUES (2, ‘李四’, ‘234’, ‘女’, ‘天津’); INSERT INTO tb_user VALUES (3, ‘王五’, ‘11’, ‘男’, ‘西安’);


-  创建模块,导入坐标<br />在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标 <br />注意:
   - logback除了三个坐标信息之外还需要一个配置文件
   - 需要在项目的 resources 目录下创建logback的配置文件 
```xml
<dependencies>
    <!--mybatis 依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>

    <!--mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>

    <!--junit 单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>

    <!-- 添加slf4j日志api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.20</version>
    </dependency>
    <!-- 添加logback-classic依赖 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!-- 添加logback-core依赖 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>
  • 编写 MyBatis 核心配置文件 — > 替换连接信息 解决硬编码问题
    在模块下的 resources 目录下创建mybatis的配置文件 mybatis-config.xml
  • 加载sql映射文件
  • 内容如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
          PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
          "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
      <typeAliases>
          <package name="com.itheima.pojo"/>
      </typeAliases>
    
      <!--
      environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
      -->
      <environments default="development">
          <environment id="development">
              <transactionManager type="JDBC"/>
              <dataSource type="POOLED">
                  <!--数据库连接信息-->
                  <property name="driver" value="com.mysql.jdbc.Driver"/>
                  <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
                  <property name="username" value="root"/>
                  <property name="password" value="1234"/>
              </dataSource>
          </environment>
      </environments>
      <mappers>
         <!--加载sql映射文件-->
         <mapper resource="UserMapper.xml"/>
      </mappers>
    </configuration>
    
  • 编写 SQL 映射文件 —> 统一管理sql语句,解决硬编码问题
    在模块的 resources 目录下创建映射配置文件 UserMapper.xml,(Mapper:映射)内容如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="test">
     <select id="selectAll" resultType="com.itheima.pojo.User">
         select * from tb_user;
     </select>
    </mapper>
    
  • 编码

    • com.itheima.pojo 包下创建 User类

      public class User {
      private int id;
      private String username;
      private String password;
      private String gender;
      private String addr;
      
      //省略了 setter 和 getter
      }
      
    • com.itheima 包下编写 MybatisDemo 测试类

      public class MyBatisDemo {
      
      public static void main(String[] args) throws IOException {
        //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      
        //2. 获取SqlSession对象,用它来执行sql
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //3. 执行sql
        List<User> users = sqlSession.selectList("test.selectAll"); //参数是一个字符串,该字符串必须是映射配置文件的namespace.id
        System.out.println(users);
        //4. 释放资源
        sqlSession.close();
      }
      }
      

解决SQL映射文件的警告提示:

在入门案例映射配置文件中存在报红的情况。问题如下:
image.png

  • 产生的原因:Idea和数据库没有建立连接,不识别表信息。但是大家一定要记住,它并不影响程序的执行。
  • 解决方式:在Idea中配置MySQL数据库连接。

IDEA中配置MySQL数据库连接

  • 点击IDEA右边框的 Database ,在展开的界面点击 + 选择 Data Source ,再选择 MySQL

    image.png

  • 在弹出的界面进行基本信息的填写

image.png
第一次使用需要下载相关驱动

  • 点击完成后就能看到如下界面

image.png
而此界面就和 navicat 工具一样可以进行数据库的操作。也可以编写SQL语句 (太tm智能了)
image.png

image.png

Mapper代理开发

Mapper代理开发概述

之前我们写的代码是基本使用方式,它也存在硬编码的问题,如下:
image.png

这里调用 selectList() 方法传递的参数是映射配置文件中的 namespace.id值。这样写也不便于后期的维护。如果使用 Mapper 代理方式(如下图)则不存在硬编码问题。
image.png

通过上面的描述可以看出 Mapper 代理方式的目的:

  • 解决原生方式中的硬编码
  • 简化后期执行SQL

Mybatis 官网也是推荐使用 Mapper 代理的方式。下图是截止官网的图片
image.png

使用Mapper代理要求

image.png

使用Mapper代理方式,必须满足以下要求:

  • 定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下。如下图:

image.png
在resources中创建文件夹时,不是能用.作为分级符,要是用\
image.png

  • ☆☆☆☆☆☆同时也要在mybatis-config (mybatis的配置文件) 更改sql映射文件的名字 ```xml


-  **设置SQL映射文件的namespace属性为Mapper接口全限定名 **

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1638364614241-778fcefc-6e81-4774-bfc9-845764385e3e.png#clientId=ub52b05e5-60e7-4&from=paste&height=155&id=u268ad0d4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=155&originWidth=645&originalType=binary&ratio=1&size=13020&status=done&style=none&taskId=u18f75c71-3454-4d8d-8285-95d40469e27&width=645)
> 

- ** 在 Mapper 接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致 **

![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1638364624090-de4ad451-c743-4412-8d0f-47cee2f5a822.png#clientId=ub52b05e5-60e7-4&from=paste&height=482&id=u4bb2a30b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=568&originWidth=863&originalType=binary&ratio=1&size=52215&status=done&style=none&taskId=u5f8ab9fc-bad7-4335-a9a8-beab87e4cce&width=732.9971313476562) 

<a name="91310298"></a>
#### 案例代码实现

-  在 `com.itheima.mapper` 包下创建 UserMapper接口,代码如下:  
```java
public interface UserMapper {
    List<User> selectAll();
    User selectById(int id);
}
  • resources 下创建 com/itheima/mapper 目录,并在该目录下创建 UserMapper.xml 映射配置文件

    <!--
     namespace:名称空间。必须是对应接口的全限定名
    -->
    <mapper namespace="com.itheima.mapper.UserMapper">
     <select id="selectAll" resultType="com.itheima.pojo.User">
         select *
         from tb_user;
     </select>
    </mapper>
    
  • com.itheima 包下创建 MybatisDemo2 测试类,代码如下:

    /**
    * Mybatis 代理开发
    */
    public class MyBatisDemo2 {
    
     public static void main(String[] args) throws IOException {
    
         //1. 加载mybatis的核心配置文件,获取 SqlSessionFactory
         String resource = "mybatis-config.xml";
         InputStream inputStream = Resources.getResourceAsStream(resource);
         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
         //2. 获取SqlSession对象,用它来执行sql
         SqlSession sqlSession = sqlSessionFactory.openSession();
         //3. 执行sql
         //3.1 获取UserMapper接口的代理对象
         UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
         List<User> users = userMapper.selectAll();
    
         System.out.println(users);
         //4. 释放资源
         sqlSession.close();
     }
    }
    

注意:

如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载 (可以处理同目录下所有)。也就是将核心配置文件的加载映射配置文件的配置修改为

<mappers>
    <!--加载sql映射文件-->
    <!-- <mapper resource="com/itheima/mapper/UserMapper.xml"/>-->

    <!--Mapper代理方式-->
    <package name="com.itheima.mapper"/>
</mappers>

核心配置文件mybatis-config

核心配置文件中现有的配置之前已经给大家进行了解释,而核心配置文件中还可以配置很多内容。我们可以通过查询官网看可以配置的内容
image.png

接下来我们先对里面的一些配置进行讲解。

多环境配置(environments)

在核心配置文件的 environments 标签中其实是可以配置多个 environment ,使用 id 给每段环境起名,在 environments 中使用 default='环境id' 来指定使用哪儿段配置。我们一般就配置一个 environment 即可。

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <!--数据库连接信息-->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
            <property name="username" value="root"/>
            <property name="password" value="1234"/>
        </dataSource>
    </environment>

    <environment id="test">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <!--数据库连接信息-->
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql:///mybatis?useSSL=false"/>
            <property name="username" value="root"/>
            <property name="password" value="1234"/>
        </dataSource>
    </environment>
</environments>=

类型别名(typeAliases)

在映射配置文件中的 resultType 属性需要配置数据封装的类型(类的全限定名)。而每次这样写是特别麻烦的,Mybatis 提供了 类型别名(typeAliases) 可以简化这部分的书写。

首先需要现在核心配置文件中配置类型别名,也就意味着给pojo包下所有的类起了别名(别名就是类名),不区分大小写。内容如下:

<typeAliases>
    <!--name属性的值是实体类所在包-->
    <package name="com.itheima.pojo"/> 
</typeAliases>

通过上述的配置,我们就可以简化映射配置文件中 resultType 属性值的编写

<mapper namespace="com.itheima.mapper.UserMapper">
    <select id="selectAll" resultType="user">
        select * from tb_user;
    </select>
</mapper>

映射器(mappers)

细节:配置各个标签时,需要遵守前后顺序

Mybatis练习

目标

  • 能够使用映射配置文件实现CRUD操作
  • 能够使用注解实现CRUD操作

配置文件实现CRUD

image.png

如上图所示产品原型,里面包含了品牌数据的 查询按条件查询添加删除批量删除修改 等功能,而这些功能其实就是对数据库表中的数据进行CRUD操作。接下来我们就使用Mybatis完成品牌数据的增删改查操作。以下是我们要完成功能列表:

  • 查询
    • 查询所有数据
    • 查询详情
    • 条件查询
  • 添加
  • 修改
    • 修改全部字段
    • 修改动态字段
  • 删除
    • 删除一个
    • 批量删除

我们先将必要的环境准备一下。

环境准备

  • 数据库表(tb_brand)及数据准备

    -- 删除tb_brand表
    drop table if exists tb_brand;
    -- 创建tb_brand表
    create table tb_brand
    (
     -- id 主键
     id           int primary key auto_increment,
     -- 品牌名称
     brand_name   varchar(20),
     -- 企业名称
     company_name varchar(20),
     -- 排序字段
     ordered      int,
     -- 描述信息
     description  varchar(100),
     -- 状态:0:禁用  1:启用
     status       int
    );
    -- 添加数据
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
        ('华为', '华为技术有限公司', 100, '华为致力于把数字世界带入每个人、每个家庭、每个组织,构建万物互联的智能世界', 1),
        ('小米', '小米科技有限公司', 50, 'are you ok', 1);
    
  • 实体类 Brand
    com.itheima.pojo 包下创建 Brand 实体类。

    public class Brand {
     // id 主键
     private Integer id;
     // 品牌名称
     private String brandName;
     // 企业名称
     private String companyName;
     // 排序字段
     private Integer ordered;
     // 描述信息
     private String description;
     // 状态:0:禁用  1:启用
     private Integer status;
    
     //省略 setter and getter。自己写时要补全这部分代码
    }
    
  • 编写测试用例
    测试代码需要在 test/java 目录下创建包及测试用例。项目结构如下:

image.png

  • 安装 MyBatisX 插件

    • MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
    • 主要功能
      • XML映射配置文件 和 接口方法 间相互跳转
      • 根据接口方法生成 statement
    • 安装方式
      点击 file ,选择 settings ,就能看到如下图所示界面
    • image.png

      注意:安装完毕后需要重启IDEA

    • 插件效果

    • image.png
      红色头绳的表示映射配置文件,蓝色头绳的表示mapper接口。在mapper接口点击红色头绳的小鸟图标会自动跳转到对应的映射配置文件,在映射配置文件中点击蓝色头绳的小鸟图标会自动跳转到对应的mapper接口。也可以在mapper接口中定义方法,自动生成映射配置文件中的 statement ,如图所示
      image.png

查询所有数据

image.png

如上图所示就页面上展示的数据,而这些数据需要从数据库进行查询。接下来我们就来讲查询所有数据功能,而实现该功能我们分以下步骤进行实现:

  • 编写接口方法:Mapper接口

    • 参数:无
      查询所有数据功能是不需要根据任何条件进行查询的,所以此方法不需要参数。

         ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1638419802690-54731994-71a9-4461-a6fd-997ac18a5ab7.png#clientId=u908855a4-5fd0-4&from=paste&height=45&id=u1b21a152&margin=%5Bobject%20Object%5D&name=image.png&originHeight=45&originWidth=479&originalType=binary&ratio=1&size=2847&status=done&style=none&taskId=u9eec3d30-f19c-4b03-8110-135a2c9599e&width=479) 
      
    • 结果:List
      我们会将查询出来的每一条数据封装成一个 Brand 对象,而多条数据封装多个 Brand 对象,需要将这些对象封装到List集合中返回。

          ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1638419842035-f7f7585f-cc59-424f-8b2d-ceceadffd6d7.png#clientId=u908855a4-5fd0-4&from=paste&height=129&id=u2f49a45d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=129&originWidth=479&originalType=binary&ratio=1&size=7952&status=done&style=none&taskId=ub9d5f5a9-16de-45aa-92f0-d72f427c4d8&width=479)
      
    • 执行方法、测试

1 编写接口方法

com.itheima.mapper 包写创建名为 BrandMapper 的接口。并在该接口中定义 List<Brand> selectAll() 方法。

public interface BrandMapper {

    /**
     * 查询所有
     */
    List<Brand> selectAll();
}

2 编写SQL语句

  • reources 下创建 com/itheima/mapper 目录结构,并在该目录下创建名为 BrandMapper.xml 的映射配置文件,mapper的namespace设置成接口名
  • 在接口方法中按住alt+enter自动生成(在mybatisx和已有相对于的xml映射文件条件下)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.itheima.mapper.BrandMapper">
    <select id="selectAll" resultType="brand">
        select *
        from tb_brand;
    </select>
</mapper>
  • 然后在mybatis核心配置中添加/更改 相应映射
    <mappers>
    <!--加载sql映射文件-->
    <mapper resource="com\itheima\mapper\BrandMapper.xml"/>
    </mappers>
    

3 编写测试方法

MybatisTest 类中编写测试查询所有的方法

@Test
public void testSelectAll() throws IOException {
    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);

    //4. 执行方法
    List<Brand> brands = brandMapper.selectAll();
    System.out.println(brands);

    //5. 释放资源
    sqlSession.close();

}

注意:现在我们感觉测试这部分代码写起来特别麻烦,我们可以先忍忍。以后我们只会写上面的第3步的代码,其他的都不需要我们来完成。

执行测试方法结果如下:

image.png

从上面结果我们看到了问题,有些数据封装成功了,而有些数据并没有封装成功。为什么这样呢?

这个问题可以通过两种方式进行解决:

  • 给字段起别名
  • 使用resultMap定义字段和属性的映射关系

4 起别名解决上述问题

从上面结果可以看到 brandNamecompanyName 这两个属性的数据没有封装成功,查询 实体类 和 表中的字段 发现,在实体类中属性名是 brandNamecompanyName ,而表中的字段名为 brand_namecompany_name,如下图所示 。那么我们只需要保持这两部分的名称一致这个问题就迎刃而解。

image.png

我们可以在写sql语句时给这两个字段起别名,将别名定义成和属性名一致即可。

<select id="selectAll" resultType="brand">
    select
    id, brand_name as brandName, company_name as companyName, ordered, description, status
    from tb_brand;
</select>

而上面的SQL语句中的字段列表书写麻烦,如果表中还有更多的字段,同时其他的功能也需要查询这些字段时就显得我们的代码不够精炼。Mybatis提供了**sql** 片段可以提高sql的复用性。

SQL片段:

  • 将需要复用的SQL片段抽取到 sql 标签中
    id属性值是唯一标识,引用时也是通过该值进行引用。

    <sql id="brand_column">
     id, brand_name as brandName, company_name as companyName, ordered, description, status
    </sql>
    
  • 在原sql语句中进行引用
    使用 **include** 标签引用上述的 SQL 片段,而 refid 指定上述 SQL 片段的id值。

    <select id="selectAll" resultType="brand">
     select
     <include refid="brand_column" />
     from tb_brand;
    </select>
    

5 使用resultMap解决上述问题

起别名 + sql片段的方式可以解决上述问题,但是它也存在问题。如果还有功能只需要查询部分字段,而不是查询所有字段,那么我们就需要再定义一个 SQL 片段,这就显得不是那么灵活。

那么我们也可以使用resultMap来定义字段和属性的映射关系的方式解决上述问题。

  • 在映射配置文件中使用resultMap定义 字段 和 属性 的映射关系

    • id:完成主键字段的映射
    • result:完成一般字段的映射
      <resultMap id="brandResultMap" type="brand">
      <!--
             id:完成主键字段的映射
                 column:表的列名
                 property:实体类的属性名
             result:完成一般字段的映射
                 column:表的列名
                 property:实体类的属性名
         -->
      <result column="brand_name" property="brandName"/>
      <result column="company_name" property="companyName"/>
      </resultMap>
      

      注意:在上面只需要定义 字段名 和 属性名 不一样的映射,而一样的则不需要专门定义出来。 在result标签后面加/ 就会自动省略后面的尾标签

  • SQL语句正常编写

    把resultType 改成 resultMap 再将 resultMap中的id赋值给 resultMap=”brandResultMap”

<select id="selectAll" resultMap="brandResultMap">
    select *
    from tb_brand;
</select>

6 小结

<!--
        数据库表的字段名称  和  实体类的属性名称不一样,则不能自动封装数据
            * 起别名:对不一样的列名起别名,让别名和实体类的属性名一样
                * 缺点:每次查询都要定义一次别名
                    * 解决:使用sql片段
                        * 缺点:不灵活,语句死板

            * resultMap:
                1. 定义<resultMap>标签
                2. 在<select>标签中使用resultMap属性替换 resultType属性
-->

实体类属性名 和 数据库表列名 不一致,不能自动封装数据

  • ==起别名:==在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
    • 可以定义 片段,提升复用性
  • ==resultMap:==定义 完成不一致的属性名和列名的映射

而我们最终选择使用 resultMap的方式。查询映射配置文件中查询所有的 statement 书写如下:

 <resultMap id="brandResultMap" type="brand">
     <!--
            id:完成主键字段的映射
                column:表的列名
                property:实体类的属性名
            result:完成一般字段的映射
                column:表的列名
                property:实体类的属性名
        -->
     <result column="brand_name" property="brandName"/>
     <result column="company_name" property="companyName"/>
</resultMap>



<select id="selectAll" resultMap="brandResultMap">
    select *
    from tb_brand;
</select>

查询详情(返回一行的查询)

image.png

有些数据的属性比较多,在页面表格中无法全部实现,而只会显示部分,而其他属性数据的查询可以通过 查看详情 来进行查询,如上图所示。

查看详情功能实现步骤:

  • 编写接口方法:Mapper接口

image.png

  • 参数:id
    查看详情就是查询某一行数据,所以需要根据id进行查询。而id以后是由页面传递过来。
  • 结果:Brand
    根据id查询出来的数据只要一条,而将一条数据封装成一个Brand对象即可
    • 编写SQL语句:SQL映射文件

image.png

  • 执行方法、进行测试

1 编写接口方法

BrandMapper 接口中定义根据id查询数据的方法

/**
  * 查看详情:根据Id查询
  */
Brand selectById(int id);

2 编写SQL语句

BrandMapper.xml 映射配置文件中编写 statement,使用 resultMap 而不是使用 resultType

<select id="selectById"  resultMap="brandResultMap">
    select *
    from tb_brand where id = #{id};
</select>

注意:上述SQL中的 #{id}先这样写,一会我们再详细讲解

3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

 @Test
public void testSelectById() throws IOException {
    //接收参数,该id以后需要传递过来
    int id = 1;

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);

    //4. 执行方法
    Brand brand = brandMapper.selectById(id);
    System.out.println(brand);

    //5. 释放资源
    sqlSession.close();
}

执行测试方法结果如下:

image.png

4 参数占位符

查询到的结果很好理解就是id为1的这行数据。而这里我们需要看控制台显示的SQL语句,能看到使用?进行占位。说明我们在映射配置文件中的写的 #{id} 最终会被?进行占位。接下来我们就聊聊映射配置文件中的参数占位符。

mybatis提供了两种参数占位符

  • {} :执行SQL时,会将 #{} 占位符替换为?,将来自动设置参数值。从上述例子可以看出使用#{} 底层使用的是 PreparedStatement

  • ${} :拼接SQL。底层使用的是 Statement,会存在SQL注入问题。如下图将 映射配置文件中的 #{} 替换成 ${} 来看效果
  • 使用时机
    • 参数传递时:#{}
    • 表名或者列名不固定的情况下,${} 会存在SQL注入问题


重新运行查看结果如下:

<select id="selectById"  resultMap="brandResultMap">
    select *
    from tb_brand where id = ${id};
</select>

image.png

<select id="selectById"  resultMap="brandResultMap">
    select *
    from tb_brand where id = #{id};
</select>

image.png

==注意:==从上面两个例子可以看出,以后开发我们使用 #{} 参数占位符。

5 parameterType使用

对于有参数的mapper接口方法,我们在映射配置文件中应该配置 ParameterType 来指定参数类型。只不过该属性都可以省略。如下:

<-- 参数类型parameterType可以省略 -->
<select id="selectById" parameterType="int" resultMap="brandResultMap">
    select *
    from tb_brand where id = ${id};
</select>

6 SQL语句中特殊字段处理

以后肯定会在SQL语句中写一下特殊字符,比如某一个字段大于某个值,如下图

image.png

可以看出报错了,因为映射配置文件是xml类型的问题,而 > < 等这些字符在xml中有特殊含义,所以此时我们需要将这些符号进行转义,可以使用以下两种方式进行转义

  • 转义字符(字符少时使用)
    下图的 &lt; 就是 < 的转义字符。

image.png

  • <![CDATA[内容]]>(字符多时使用)

image.png

7 总结

image.png

多条件查询

image.png

我们经常会遇到如上图所示的多条件查询,将多条件查询的结果展示在下方的数据列表中。而我们做这个功能需要分析最终的SQL语句应该是什么样,思考两个问题

  • 条件表达式
  • 如何连接

条件字段 企业名称品牌名称 需要进行模糊查询,所以条件应该是:

image.png

简单的分析后,我们来看功能实现的步骤:

  • 编写接口方法
    • 参数:所有查询条件
    • 结果:List
  • 在映射配置文件中编写SQL语句
  • 编写测试方法并执行

1 编写接口方法

BrandMapper 接口中定义多条件查询的方法。

而该功能有三个参数,我们就需要考虑定义接口时,参数应该如何定义。Mybatis针对多参数有多种实现

  • 使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位
    ```java /**
    • 条件查询参数接收
    • 1.散装参数 : 如果方法中有多个参数,需要使用@Param(“SQL参数占位符名称”)
    • 2.对象参数 : 对象的属性名称要和参数占位符名称一致
      1. map集合参数 *
    • @param status
    • @param companyName
    • @param brandName
    • @return */

List selectByCondition(@Param(“status”) int status, @Param(“companyName”) String companyName,@Param(“brandName”) String brandName);


-  将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 `#{内容}` 时,里面的内容必须和实体类属性名保持一致。  
```java
List<Brand> selectByCondition(Brand brand);
  • 将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致。
    List<Brand> selectByCondition(Map map);
    

2 编写SQL语句

BrandMapper.xml 映射配置文件中编写 statement,使用 resultMap 而不是使用 resultType,(在前面定义了resultMap标签)

<select id="selectByCondition" resultMap="brandResultMap">
    select *
    from tb_brand
    where status = #{status}
    and company_name like #{companyName}
    and brand_name like #{brandName}
</select>

3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

@Test
public void testSelectByCondition() throws IOException {
    //接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";

    // 处理参数(这里是模糊查询)
    companyName = "%" + companyName + "%";
    brandName = "%" + brandName + "%";

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);

    //4. 执行方法
    //方式一 :接口方法参数使用 @Param 方式调用的方法
    //List<Brand> brands = brandMapper.selectByCondition(status, companyName, brandName);
    //方式二 :接口方法参数是 实体类对象 方式调用的方法
     //封装对象
    /* Brand brand = new Brand();
        brand.setStatus(status);
        brand.setCompanyName(companyName);
        brand.setBrandName(brandName);*/

    //List<Brand> brands = brandMapper.selectByCondition(brand);

    //方式三 :接口方法参数是 map集合对象 方式调用的方法
    Map map = new HashMap();
    map.put("status" , status);
    map.put("companyName", companyName);
    map.put("brandName" , brandName);
    List<Brand> brands = brandMapper.selectByCondition(map);
    System.out.println(brands);

    //5. 释放资源
    sqlSession.close();
}

4 动态SQL

上述功能实现存在很大的问题。用户在输入条件时,肯定不会所有的条件都填写,这个时候我们的SQL语句就不能那样写的

例如用户只输入 当前状态 时,SQL语句就是

select * from tb_brand where status = #{status}

而用户如果只输入企业名称时,SQL语句就是

select * from tb_brand where company_name like #{companName}

而用户如果输入了 当前状态企业名称 时,SQL语句又不一样

select * from tb_brand where status = #{status} and company_name like #{companName}

针对上述的需要,Mybatis对动态SQL有很强大的支撑:

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

我们先学习 if 标签和 where 标签:

  • if 标签:条件判断

    • test 属性:逻辑表达式(判断映射列名的值)
      <select id="selectByCondition" resultMap="brandResultMap">
      select *
      from tb_brand
      where
         <if test="status != null">
             and status = #{status}
         </if>
         <if test="companyName != null and companyName != '' ">
             and company_name like #{companyName}
         </if>
         <if test="brandName != null and brandName != '' ">
             and brand_name like #{brandName}
         </if>
      </select>
      
      如上的这种SQL语句就会根据传递的参数值进行动态的拼接。如果此时status和companyName有值那么就会值拼接这两个条件。
      执行结果如下:
      image.png
      但是它也存在问题,如果此时给的参数值是
      Map map = new HashMap();
      // map.put("status" , status);
      map.put("companyName", companyName);
      map.put("brandName" , brandName);
      
      拼接的SQL语句就变成了
      select * from tb_brand where and company_name like ? and brand_name like ?
      
      而上面的语句中 where 关键后直接跟 and 关键字,这就是一条错误的SQL语句。
      解决方法:
  • 恒等式

    <select id="selectByCondition" resultMap="brandResultMap">
      select *
      from tb_brand
      where 1 = 1 
          <if test="status != null">
              and status = #{status}
          </if>
          <if test="companyName != null and companyName != '' ">
              and company_name like #{companyName}
          </if>
          <if test="brandName != null and brandName != '' ">
              and brand_name like #{brandName}
          </if>
    </select>
    

也可以使用 where 标签解决

  • where 标签 (智能语法)
    • 作用:
      • 替换where关键字
      • 动态的去掉第一个条件前的 and
      • 如果所有的参数没有值则不加where关键字
        <select id="selectByCondition" resultMap="brandResultMap">
        select *
        from tb_brand
        <where>
        <if test="status != null">
            and status = #{status}
        </if>
        <if test="companyName != null and companyName != '' ">
            and company_name like #{companyName}
        </if>
        <if test="brandName != null and brandName != '' ">
            and brand_name like #{brandName}
        </if>
        </where>
        </select>
        

        注意:需要给每个条件前都加上 and 关键字

单个条件(动态SQL)

image.png

如上图所示,在查询时只能选择 品牌名称当前状态企业名称 这三个条件中的一个,但是用户到底选择哪儿一个,我们并不能确定。这种就属于单个条件的动态SQL语句

这种需求需要使用到 choose(when,otherwise)标签 实现, 而 choose 标签类似于Java 中的switch语句。

通过一个案例来使用这些标签

1 编写接口方法

BrandMapper 接口中定义单条件查询的方法。

/**
  * 单条件动态查询
  * @param brand
  * @return
  */
List<Brand> selectByConditionSingle(Brand brand);

2 编写SQL语句

BrandMapper.xml 映射配置文件中编写 statement,使用 resultMap 而不是使用 resultType

<select id="selectByConditionSingle" resultMap="brandResultMap">
    select *
    from tb_brand
    where
        <choose><!--相当于switch-->
            <when test="status != null"><!--相当于case-->
                status = #{status}
            </when>
            <when test="companyName != null and companyName != '' "><!--相当于case-->
                company_name like #{companyName}
            </when>
            <when test="brandName != null and brandName != ''"><!--相当于case-->
                brand_name like #{brandName}
            </when>
        </choose>
</select>
  • 如果用户一个都不查询,即没有查询条件
  • 使用<otherwise> 1 = 1 </otherwise>,即使等式恒成立

    <select id="selectByConditionSingle" resultMap="brandResultMap">
      select *
      from tb_brand
      where
          <choose><!--相当于switch-->
              <when test="status != null"><!--相当于case-->
                  status = #{status}
              </when>
              <when test="companyName != null and companyName != '' "><!--相当于case-->
                  company_name like #{companyName}
              </when>
              <when test="brandName != null and brandName != ''"><!--相当于case-->
                  brand_name like #{brandName}
              </when>
    
            <otherwise>
                  1 = 1
            </otherwise>
    
          </choose>
    </select>
    
  • 也可以使用where标签(智能语法)

    <select id="selectByConditionSingle" resultMap="brandResultMap">
      select *
      from tb_brand
      <where>
          <choose><!--相当于switch-->
              <when test="status != null"><!--相当于case-->
                  status = #{status}
              </when>
              <when test="companyName != null and companyName != '' "><!--相当于case-->
                  company_name like #{companyName}
              </when>
              <when test="brandName != null and brandName != ''"><!--相当于case-->
                  brand_name like #{brandName}
              </when>
          </choose>
        </where>
    </select>
    

    3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

@Test
public void testSelectByConditionSingle() throws IOException {
    //接收参数
    int status = 1;
    String companyName = "华为";
    String brandName = "华为";

    // 处理参数
    companyName = "%" + companyName + "%";
    brandName = "%" + brandName + "%";

    //封装对象
    Brand brand = new Brand();
    //brand.setStatus(status);
    brand.setCompanyName(companyName);
    //brand.setBrandName(brandName);

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    List<Brand> brands = brandMapper.selectByConditionSingle(brand);
    System.out.println(brands);

    //5. 释放资源
    sqlSession.close();
}

执行测试方法结果如下:

image.png

  • 注意:

如果是两个条件,成立的是第一个when

Brand brand = new Brand();
brand.setStatus(status);
//brand.setCompanyName(companyName);
brand.setBrandName(brandName);
List<Brand> brands = brandMapper.selectByConditionSingle(brand);

image.png

添加数据

image.png

如上图是我们平时在添加数据时展示的页面,而我们在该页面输入想要的数据后添加 提交 按钮,就会将这些数据添加到数据库中。接下来我们就来实现添加数据的操作。

  • 编写接口方法

image.png
参数:除了id之外的所有的数据。id对应的是表中主键值,而主键我们是 自动增长 生成的。

  • 编写SQL语句

image.png

  • 编写测试方法并执行

明确了该功能实现的步骤后,接下来我们进行具体的操作。

1 编写接口方法

BrandMapper 接口中定义添加方法。

 /**
   * 添加
   */
void add(Brand brand);

2 编写SQL语句

BrandMapper.xml 映射配置文件中编写添加数据的 statement

<insert id="add">
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>

3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法,需要手动提交事务
提交事务:

  • 使用sqlSession.commit();
  • 或者在创建SqlSession对象时,SqlSession sqlSession = sqlSessionFactory.openSession(true);//设置自动提交事务
@Test
public void testAdd() throws IOException {
    //接收参数
    int status = 1;
    String companyName = "波导手机";
    String brandName = "波导";
    String description = "手机中的战斗机";
    int ordered = 100;

    //封装对象
    Brand brand = new Brand();
    brand.setStatus(status);
    brand.setCompanyName(companyName);
    brand.setBrandName(brandName);
    brand.setDescription(description);
    brand.setOrdered(ordered);

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //SqlSession sqlSession = sqlSessionFactory.openSession(true); //设置自动提交事务,这种情况不需要手动提交事务了
    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    brandMapper.add(brand);
    //需要提交事务
    sqlSession.commit();
    //5. 释放资源
    sqlSession.close();
}

执行结果如下:
image.png

4 添加-主键返回

在数据添加成功后,有时候需要获取插入数据库数据的主键(主键是自增长)。

比如:添加订单和订单项,如下图就是京东上的订单

image.png

订单数据存储在订单表中,订单项存储在订单项表中。

  • 添加订单数据

image.png

  • 添加订单项数据,订单项中需要设置所属订单的id

image.png

明白了什么时候 主键返回 。接下来简单模拟一下,在添加完数据后打印id属性值,能打印出来说明已经获取到了。

我们将上面添加品牌数据的案例中映射配置文件里 **statement** 进行修改,如下

<insert id="add" useGeneratedKeys="true" keyProperty="id">
    insert into tb_brand (brand_name, company_name, ordered, description, status)
    values (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>

在 insert 标签上添加如下属性:

  • useGeneratedKeys:是够获取自动增长的主键值。true表示获取
  • keyProperty :指定将获取到的主键值封装到哪儿个属性里

接着修改方法,在方法中写入如下代码,即可获得绑定的表中id属性:

Integer id = brand.getId();
System.out.println(id);

结果:
image.png

修改

image.png

如图所示是修改页面,用户在该页面书写需要修改的数据,点击 提交 按钮,就会将数据库中对应的数据进行修改。注意一点,如果哪儿个输入框没有输入内容,我们是将表中数据对应字段值替换为空白还是保留字段之前的值?答案肯定是保留之前的数据。

接下来我们就具体来实现

1 编写接口方法

BrandMapper 接口中定义修改方法。

 /**
   * 修改
   */
void update(Brand brand);
//int  update(Brand brand);

上述方法参数 Brand 就是封装了需要修改的数据,而id肯定是有数据的,这也是和添加方法的区别。

2 编写SQL语句

  • BrandMapper.xml 映射配置文件中编写修改数据的 statement

    <update id="update">
          update tb_brand
          set brand_name   = #{brandName},
              company_name = #{companyName},
              ordered      = #{ordered},
              description  = #{description},
              status       = #{status}
          where id = #{id};
      </update>
    
  • 用户在修改数据时,不一定是把所有数据都修改了,所以要采用 标签 if判断,动态sql语句

  • 使用标签,规避语法问题
    <update id="update">
      update tb_brand
      <set>
          <if test="brandName != null and brandName != ''">
              brand_name = #{brandName},
          </if>
          <if test="companyName != null and companyName != ''">
              company_name = #{companyName},
          </if>
          <if test="ordered != null">
              ordered = #{ordered},
          </if>
          <if test="description != null and description != ''">
              description = #{description},
          </if>
          <if test="status != null">
              status = #{status}
          </if>
      </set>
      where id = #{id};
    </update>
    

set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。

3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

@Test
public void testUpdate() throws IOException {
    //接收参数
    int status = 0;
    String companyName = "波导手机";
    String brandName = "波导";
    String description = "波导手机,手机中的战斗机";
    int ordered = 200;
    int id = 6;

    //封装对象
    Brand brand = new Brand();
    brand.setStatus(status);
    //        brand.setCompanyName(companyName);
    //        brand.setBrandName(brandName);
    //        brand.setDescription(description);
    //        brand.setOrdered(ordered);
    brand.setId(id);

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    int count = brandMapper.update(brand);
    System.out.println(count);
    //提交事务
    sqlSession.commit();
    //5. 释放资源
    sqlSession.close();
}

执行测试方法结果如下:
image.png

从结果中SQL语句可以看出,只修改了 status 字段值,因为我们给的数据中只给Brand实体对象的 status 属性设置值了。这就是 set 标签的作用。

删除一行数据

image.png

如上图所示,每行数据后面都有一个 删除 按钮,当用户点击了该按钮,就会将改行数据删除掉。那我们就需要思考,这种删除是根据什么进行删除呢?
是通过主键id删除,因为id是表中数据的唯一标识。

接下来就来实现该功能。

1 编写接口方法

BrandMapper 接口中定义根据id删除方法。

/**
  * 根据id删除
  */
void deleteById(int id);

2 编写SQL语句

BrandMapper.xml 映射配置文件中编写删除一行数据的 statement

<delete id="deleteById">
    delete from tb_brand where id = #{id};
</delete>

3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

 @Test
public void testDeleteById() throws IOException {
    //接收参数
    int id = 6;

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    brandMapper.deleteById(id);
    //提交事务
    sqlSession.commit();
    //5. 释放资源
    sqlSession.close();
}

运行过程只要没报错,直接到数据库查询数据是否还存在。
运行结果
image.png

批量删除

image.png

如上图所示,用户可以选择多条数据,然后点击上面的 删除 按钮,就会删除数据库中对应的多行数据。

1 编写接口方法

BrandMapper 接口中定义删除多行数据的方法。

/**
  * 批量删除
  */
void deleteByIds(int[] ids); //对应foreach collection="array"
//void deleteByIds((@Param("ids") int[] ids);  //对应foreach collection="ids"

参数是一个数组,数组中存储的是多条数据的id

2 编写SQL语句

BrandMapper.xml 映射配置文件中编写删除多条数据的 statement

编写SQL时需要遍历数组来拼接SQL语句。Mybatis 提供了 foreach 标签供我们使用

foreach 标签

用来迭代任何可迭代的对象(如数组,集合)。

  • collection 属性:
    • mybatis会将数组参数,封装为一个Map集合。
      • 默认:array(key) = 数组(value)
      • 使用@Param注解改变map集合的默认key的名称
  • item 属性:本次迭代获取到的元素。
  • separator 属性:集合项迭代之间的分隔符。foreach 标签不会错误地添加多余的分隔符。也就是最后一次迭代不会加分隔符。
  • open 属性:该属性值是在拼接SQL语句之前拼接的语句,只会拼接一次
  • close 属性:该属性值是在拼接SQL语句拼接后拼接的语句,只会拼接一次
<delete id="deleteByIds">
    delete from tb_brand where id
    in
    <foreach collection="array" item="id" separator="," open="(" close=")">  <!-- void deleteByIds((@Param("ids") int[] ids);  //对应foreach collection="ids" -->
        #{id}
    </foreach>
    ;
</delete>

假如数组中的id数据是{1,2,3},那么拼接后的sql语句就是:

delete from tb_brand where id in (1,2,3);

image.png

3 编写测试方法

test/java 下的 com.itheima.mapper 包下的 MybatisTest类中 定义测试方法

@Test
public void testDeleteByIds() throws IOException {
    //接收参数
    int[] ids = {5,7,8};

    //1. 获取SqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //2. 获取SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //SqlSession sqlSession = sqlSessionFactory.openSession(true);
    //3. 获取Mapper接口的代理对象
    BrandMapper brandMapper = sqlSession.getMapper(BrandMapper.class);
    //4. 执行方法
    brandMapper.deleteByIds(ids);
    //提交事务
    sqlSession.commit();
    //5. 释放资源
    sqlSession.close();
}

运行后:
image.png

Mybatis参数传递

Mybatis 接口方法中可以接收各种各样的参数,如下:

  • 多个参数
  • 单个参数:单个参数又可以是如下类型
    • POJO 类型
    • Map 集合类型
    • Collection 集合类型
    • List 集合类型
    • Array 类型
    • 其他类型

1 多个参数

如下面的代码,就是接收两个参数,而接收多个参数需要使用 @Param 注解,那么为什么要加该注解呢?这个问题要弄明白就必须来研究Mybatis 底层对于这些参数是如何处理的。

User select(@Param("username") String username,@Param("password") String password);
<select id="select" resultType="user">
    select *
    from tb_user
    where 
        username=#{username}
        and password=#{password}
</select>

我们在接口方法中定义多个参数,Mybatis 会将这些参数封装成 Map 集合对象,值就是参数值,而键在没有使用 @Param 注解时有以下命名规则:

  • 以 arg 开头 :第一个参数就叫 arg0,第二个参数就叫 arg1,以此类推。如:

    map.put(“arg0”,参数值1);

    map.put(“arg1”,参数值2);

  • 以 param 开头 : 第一个参数就叫 param1,第二个参数就叫 param2,依次类推。如:

    map.put(“param1”,参数值1);

    map.put(“param2”,参数值2);

代码验证:

  • UserMapper 接口中定义如下方法

    User select(String username,String password);
    
  • UserMapper.xml 映射配置文件中定义SQL
    或者

    <select id="select" resultType="user">
     select *
     from tb_user
     where 
         username=#{arg0}
         and password=#{arg1}
    </select>
    
    <select id="select" resultType="user">
     select *
     from tb_user
     where 
         username=#{param1}
         and password=#{param2}
    </select>
    
  • 运行代码结果如下

image.png
在映射配合文件的SQL语 句中 使用用 arg 开头的和 param 书写,代码的可读性会变的特别差,此时可以使用 @Param 注解。

在接口方法参数上使用 @Param 注解,Mybatis 会将 arg 开头的键名替换为对应注解的属性值。
代码验证:

  • UserMapper 接口中定义如下方法,在 username 参数前加上 @Param 注解
    Mybatis 在封装 Map 集合时,键名就会变成如下:

    User select(@Param("username") String username, String password);
    

    map.put(“username”,参数值1);

    map.put(“arg1”,参数值2);

    map.put(“param1”,参数值1);

    map.put(“param2”,参数值2);

  • UserMapper.xml 映射配置文件中定义SQL

    <select id="select" resultType="user">
     select *
     from tb_user
     where 
         username=#{username}
         and password=#{param2}
    </select>
    
  • 运行程序结果没有报错。而如果将 #{} 中的 username 还是写成 arg0

    <select id="select" resultType="user">
     select *
     from tb_user
     where 
         username=#{arg0}
         and password=#{param2}
    </select>
    
  • 运行程序则可以看到错误,此时arg0已经被username替换
    image.png

结论:以后接口参数是多个时,在每个参数上都使用 注解。这样代码的可读性更高。@Param

2 单个参数

  • POJO 类型
    直接使用。要求 属性名参数占位符名称 一致
  • Map 集合类型
    直接使用。要求 map集合的键名参数占位符名称 一致
  • Collection 集合类型
    Mybatis 会将集合封装到 map 集合中,如下:
    可以使用 注解替换map集合中默认的 arg 键名。@Param

    map.put(“arg0”,collection集合);

    map.put(“collection”,collection集合;

  • List 集合类型
    Mybatis 会将集合封装到 map 集合中,如下:
    可以使用 注解替换map集合中默认的 arg 键名。@Param

    map.put(“arg0”,list集合);

    map.put(“collection”,list集合);

    map.put(“list”,list集合);

  • Array 类型
    Mybatis 会将集合封装到 map 集合中,如下:
    可以使用 注解替换map集合中默认的 arg 键名。@Param

    map.put(“arg0”,数组);

    map.put(“array”,数组);

  • 其他类型
    比如int类型,参数占位符名称 叫什么都可以。

  • 尽量做到见名知意
    <select id="select" resultType="user">
      select *
      from tb_user
      where 
          username=#{name}
    </select>
    

3 Mybatis参数传递源码

public Object getNamedParams(Object[] args) {
    int paramCount = this.names.size();
    if (args != null && paramCount != 0) {
        if (!this.hasParamAnnotation && paramCount == 1) {
            Object value = args[(Integer)this.names.firstKey()];
            return wrapToMapIfCollection(value, this.useActualParamName ? (String)this.names.get(0) : null);
        } else {
            Map<String, Object> param = new ParamMap();
            int i = 0;

            for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                Entry<Integer, String> entry = (Entry)var5.next();
                param.put(entry.getValue(), args[(Integer)entry.getKey()]);
                String genericParamName = "param" + (i + 1);
                if (!this.names.containsValue(genericParamName)) {
                    param.put(genericParamName, args[(Integer)entry.getKey()]);
                }
            }

            return param;
        }
    } else {
        return null;
    }
}
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
    ParamMap map;
    if (object instanceof Collection) {
        map = new ParamMap();
        map.put("collection", object);
        if (object instanceof List) {
            map.put("list", object);
        }

        Optional.ofNullable(actualParamName).ifPresent((name) -> {
            map.put(name, object);
        });
        return map;
    } else if (object != null && object.getClass().isArray()) {
        map = new ParamMap();
        map.put("array", object);
        Optional.ofNullable(actualParamName).ifPresent((name) -> {
            map.put(name, object);
        });
        return map;
    } else {
        return object;
    }
}

注解实现CRUD

使用注解开发会比配置文件开发更加方便。如下就是使用注解进行开发

@Select(value = "select * from tb_user where id = #{id}")
public User select(int id);

注意:

  • 注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的 statement

Mybatis 针对 CURD 操作都提供了对应的注解,已经做到见名知意。如下:

接下来我们做一个案例来使用 Mybatis 的注解开发

代码实现:

  • 将之前案例中 UserMapper.xml 中的 根据id查询数据 的 statement 注释掉

image.png

  • UserMapper 接口的 selectById 方法上添加注解

image.png

  • 运行测试程序也能正常查询到数据

我们课程上只演示这一个查询的注解开发,其他的同学们下来可以自己实现,都是比较简单。

==注意:==在官方文档中 入门 中有这样的一段话:
image.png

所以,注解完成简单功能,配置文件完成复杂功能。

而我们之前写的动态 SQL 就是复杂的功能,如果用注解使用的话,就需要使用到 Mybatis 提供的SQL构建器来完成,而对应的代码如下:
image.png

上述代码将java代码和SQL语句融到了一块,使得代码的可读性大幅度降低。

1,HTML

image.png

1.1 介绍

HTML 是一门语言,所有的网页都是用HTML 这门语言编写出来的,也就是HTML是用来写网页的,像京东,12306等网站有很多网页。

image.png

image.png

这些都是网页展示出来的效果。而HTML也有专业的解释

HTML(HyperText Markup Language):超文本标记语言:

  • 超文本:超越了文本的限制,比普通文本更强大。除了文字信息,还可以定义图片、音频、视频等内容
    如上图看到的页面,我们除了能看到一些文字,同时也有大量的图片展示;有些网页也有视频,音频等。这种展示效果超越了文本展示的限制。
  • 标记语言:由标签构成的语言
    之前学习的XML就是标记语言,由一个一个的标签组成,HTML 也是由标签组成 。我们在浏览器页面右键可以查看页面的源代码,如下
    image.png
    可以看到如下内容,就是由一个一个的标签组成的

image.png

这些标签不像XML那样可以自定义,==HTML中的标签都是预定义好的,运行在浏览器上并由浏览器解析,==然后展示出对应的效果。例如我们想在浏览器上展示出图片就需要使用预定义的 img 标签;想展示可以点击的链接的效果就可以使用预定义的 a 标签等。

HTML 预定义了很多标签,由于我们是Java工程师、是做后端开发,所以不会每个都学习,页面开发是有专门的前端工程来开发。那为什么我们还要学习呢?在公司中或多或少大家也会涉及到前端开发。

简单的给大家聊一下开发流程:

以后我们是通过Java程序从数据库中查询出来数据,然后交给页面进行展示,这样用户就能通过在浏览器通过页面看到数据。

W3C标准:

W3C是万维网联盟,这个组成是用来定义标准的。他们规定了一个网页是由三部分组成,分别是:

  • 结构:对应的是 HTML 语言
  • 表现:对应的是 CSS 语言
  • 行为:对应的是 JavaScript 语言

HTML定义页面的整体结构;CSS是用来美化页面,让页面看起来更加美观;JavaScript可以使网页动起来,比如轮播图也就是多张图片自动的进行切换等效果。

为了更好的给大家表述这三种语言的作用。我们通过具体的页面给大家说明。

如下只是使用HTML语言编写的页面的结构:

image.png

可以看到页面是比较丑的,但是每一部分其实都已经包含了。接下来咱们加上 CSS 进行美化看到的效果如下:

image.png

瞬间感觉好看多了,这就是CSS的作用,用来美化页面的。接下来再加上JavaScript试试

image.png

在上图中可以看到多了轮播图,在浏览器上它是会自动切换图片的,并且切换的动态效果是很不错的。

看到了前端编写的这三个技术效果后,我们今天学习的是HTML,学习HTML其实就是学习预定义的这些标签。

1.2 快速入门

需求:编写如下图效果的页面

image.png

要实现这个页面,我们需要从以下三步进行实现

  • 新建文本文件,后缀名改为 .html
    页面文件的后缀名是 .html,所以需要该后缀名
  • 编写 HTML 结构标签
    HTML 是由一个一个的标签组成的,但是它也用于表示结构的标签
    html标签是根标签,下面有 head 标签和 body 标签这两个子标签。而 head 标签的 title 子标签是用来定义页面标题名称的,它定义的内容会展示在浏览器的标题位置,如下图红框标记

image.png
body 标签的内容会被展示在内容区中,如下图红框标记
image.png

<html>
    <head>
        <title> </title>
    </head>
    <body>

    </body>
</html>
  • 在中定义文字

代码如下:

<html>
    <head>
        <title>html 快速入门</title>
    </head>
    <body>
        乾坤未定,你我皆是黑马~
    </body>
</html>

同学们在访问其他网站页面时会看到字体颜色是五颜六色的,我们可以该字体颜色吗?当然可以了

font 标签就可以使用,该标签有一个 color 属性可以设置字体颜色,如: 就是将文字设置成了红颜色。那么我们只需要将需要变成红色的文字放在标签体部分就可以了,如下:

<html>
    <head>
        <title>html 快速入门</title>
    </head>
    <body>
        <font color='red'>乾坤未定,你我皆是黑马~</font>
    </body>
</html>

总结:

  • HTML 文件以.htm或.html为扩展名
  • HTML 结构标签

image.png

  • HTML 标签不区分大小写
    如上案例中的 font 写成 Font 也是一样可以展示出对应的效果的。
  • HTML 标签属性值 单双引皆可
    如上案例中的color属性值使用双引号也是可以的。
  • HTML 语法松散
    比如 font 标签不加结束标签也是可以展示出效果的。但是建议同学们在写的时候还是不要这样做,严格按照要求去写。

1.3 基础标签

基础标签就是一些和文字相关的标签,如下:

image.png

接下来我们挨个进行讲解

1.3.1 标题标签

  • 创建模块
    在 Idea 中创建模块,而我们现在不需要写java代码,所以 src 目录就可以删除掉。在模块下创建一个html文件夹,该我们今天的所以的页面文件所部放在该文件夹下。模块目录如下
    image.png
  • 创建页面文件
    选中 html 文件夹右键创建页面文件(01-基础标签.html)

image.png
创建好后 idea 会自动加上结构标签,如下
image.png
我们只需要在 body 标签中书写标签。

  • 书写标题标签
    标题标签中 h1最大,h6最小。

    <h1>我是标题 h1</h1>
    <h2>我是标题 h2</h2>
    <h3>我是标题 h3</h3>
    <h4>我是标题 h4</h4>
    <h5>我是标题 h5</h5>
    <h6>我是标题 h6</h6>
    
  • 通过浏览器查看效果
    idea 提供了快捷的打开方式,如下图

image.png
浏览器展示效果如下:
image.png

1.3.2 hr标签

hr 标签在浏览器中呈现出 横线 的效果。

在页面文件中书写 hr 标签

<hr>

效果如下:
image.png

1.3.3 字体标签

font:字体标签

  • face 属性:用来设置字体。如 “楷体”、”宋体”等
  • color 属性:设置文字颜色。颜色有三种表示方式
    • 英文单词:red,pink,blue…
      这种方式表示的颜色特别有限,所以一般不用。
    • rgb(值1,值2,值3):值的取值范围:0~255
      此种方式也就是三原色(红绿蓝)设置方式。 例如: rgb(255,0,0)。
      这种书写起来比较麻烦,一般不用。
    • #值1值2值3:值的范围:00~FF
      这种方式是rgb方式的简化写法,以后基本都用此方式。
      值1表示红色的范围,值2表示绿色的范围,值3表示蓝色范围。例如: #ff0000
  • size 属性:设置文字大小

代码演示:

<font face="楷体" size="5" color="#ff0000">传智教育</font>

效果如下:
image.png

注意:

font 标签已经不建议使用了,以后如果要改变文字字体,大小,颜色可以使用 CSS 进行设置。

1.3.4 换行标签

在页面文件中书写如下内容

刚察草原绿草如茵,沙柳河水流淌入湖。藏族牧民索南才让家中,茶几上摆着馓子、麻花和水果,炉子上刚煮开的奶茶香气四溢……

6月8日下午,习近平总书记来到青海省海北藏族自治州刚察县沙柳河镇果洛藏贡麻村,走进牧民索南才让家中,看望慰问藏族群众。

在浏览器展示的效果如下:
image.png

我们可以看到并没有换行。如果要实现换行效果,需要使用 换行标签(br标签)。

修改页面文件内容如下:

刚察草原绿草如茵,沙柳河水流淌入湖。藏族牧民索南才让家中,茶几上摆着馓子、麻花和水果,炉子上刚煮开的奶茶香气四溢……<br>

6月8日下午,习近平总书记来到青海省海北藏族自治州刚察县沙柳河镇果洛藏贡麻村,走进牧民索南才让家中,看望慰问藏族群众。

浏览器打开效果如下:
image.png

现在就有换行效果了。

1.3.5 段落标签

上面文字展示的效果还是不太好,我们想让每一段上下都加空行。此时就需要使用段落标签(p标签)

在页面文件中书写如下内容:

<p>
刚察草原绿草如茵,沙柳河水流淌入湖。藏族牧民索南才让家中,茶几上摆着馓子、麻花和水果,炉子上刚煮开的奶茶香气四溢……
</p>
<p>
6月8日下午,习近平总书记来到青海省海北藏族自治州刚察县沙柳河镇果洛藏贡麻村,走进牧民索南才让家中,看望慰问藏族群众。
</p>

在浏览器展示的效果如下:
image.png

这种效果就会比之前的效果好一些,呈现出段落的效果。

1.3.6 加粗、斜体、下划线标签

  • b:加粗标签
  • i:斜体标签
  • u:下划线标签,在文字的下方有一条横线

代码如下:

<b>沙柳河水流淌</b><br>
<i>沙柳河水流淌</i><br>
<u>沙柳河水流淌</u><br>

在浏览器展示的效果如下:
image.png

1.3.7 居中标签

center :文本居中

代码如下:

<hr>
<center>
    <b>沙柳河水流淌</b>
</center>

在浏览器效果如下:
image.png

1.3.8 案例

实现如下图所示页面效果:
image.png

此案例同学们自己实现,用我们学过的基础标签。

注意:在上图页面中版权所有里有特殊字符,需要使用转义字符。有如下转义字符: image.png

1.4 图片、音频、视频标签

image.png

  • img:定义图片
    • src:规定显示图像的 URL(统一资源定位符)
    • height:定义图像的高度
    • width:定义图像的宽度
  • audio:定义音频。支持的音频格式:MP3、WAV、OGG
    • src:规定音频的 URL
    • controls:显示播放控件
  • video:定义视频。支持的音频格式:MP4, WebM、OGG
    • src:规定视频的 URL
    • controls:显示播放控件

尺寸单位:

height属性和width属性有两种设置方式:

  • 像素:单位是px
  • 百分比:占父标签的百分比。例如宽度设置为 50%,意思就是占它的父标签宽度的一般(50%)

资源路径:

图片,音频,视频标签都有src属性,而src是用来指定对应的图片,音频,视频文件的路径。此处的图片,音频,视频就称为资源。资源路径有如下两种设置方式:

  • 绝对路径:完整路径
    这里的绝对路径是网络中的绝对路径。 格式为: 协议://ip地址:端口号/资源名称。
    如:
    这里src属性的值就是网络中的绝对路径。

    <img src="https://th.bing.com/th/id/R33674725d9ae34f86e3835ae30b20afe?rik=Pb3C9e5%2b%2b3a9Vw&riu=http%3a%2f%2fwww.desktx.com%2fd%2ffile%2fwallpaper%2fscenery%2f20180626%2f4c8157d07c14a30fd76f9bc110b1314e.jpg&ehk=9tpmnrrRNi0eBGq3CnhwvuU8PPmKuy1Yma0zL%2ba14T0%3d&risl=&pid=ImgRaw" width="300" height="400">
    
  • 相对路径:相对位置关系
    找页面和其他资源的相对路径。
    如模块目录结构如下:

image.png
01-基础标签.html 里的标签中找不同的图片,路径写法不同

./ 表示当前路径

../ 表示上一级路径

../../ 表示上两级路径

<!--在该页面找a.jpg,就需要先回到上一级目录,该级目录有img目录,进入该目录就可以找到 a.jpg图片-->
<img src="../img/a.jpg" width="300" height="400">
<!--该页面和aa.jpg 是在同一级下,所以可以直接写 图片的名称,也可以写成  ./aa.jpg-->
<img src="aa.jpg" width="300" height="400">

使用这些标签的代码如下:

<img src="../img/a.jpg" width="300" height="400">
<audio src="b.mp3" controls></audio>
<video src="c.mp4" controls width="500" height="300"></video>

在浏览器展示的效果如下:

image.png

1.5 超链接标签

在网页中可以看到很多超链接标签,如下

image.png

上图红框中的都是超链接,当我们点击这些超链接时会跳转到其他的页面或者资源。而超链接使用的是 a 标签。
image.png

a 标签属性:

  • href:指定访问资源的URL
  • target:指定打开资源的方式
    • _self:默认值,在当前页面打开
    • _blank:在空白页面打开

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="https://www.itcast.cn" target="_self">点我有惊喜</a>
</body>
</html>

效果图示:
image.png

当我们将 target 属性值设置为 _blank,效果图示:
image.png

1.6 列表标签

HTML 中列表分为

  • 有序列表
    如下图,页面效果中是有标号对每一项进行标记的。

image.png

  • 无序列表
    如下图,页面效果中没有标号对每一项进行标记,而是使用 点 进行标记。

image.png

标签说明:

image.png

有序列表中的 type 属性用来指定标记的标号的类型(数字、字母、罗马数字等)

无序列表中的 type 属性用来指定标记的形状

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ol type="A">
        <li>咖啡</li>
        <li>茶</li>
        <li>牛奶</li>
    </ol>

    <ul type="circle">
        <li>咖啡</li>
        <li>茶</li>
        <li>牛奶</li>
    </ul>
</body>
</html>

1.7 表格标签

image.png

如上图就是一个表格,表格可以使用如下标签定义

  • table :定义表格
    • border:规定表格边框的宽度
    • width :规定表格的宽度
    • cellspacing:规定单元格之间的空白
  • tr :定义行
    • align:定义表格行的内容对齐方式
  • td :定义单元格
    • rowspan:规定单元格可横跨的行数
    • colspan:规定单元格可横跨的列数
  • th:定义表头单元格

代码演示:

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

<table border="1" cellspacing="0" width="500">
    <tr>
        <th>序号</th>
        <th>品牌logo</th>
        <th>品牌名称</th>
        <th>企业名称</th>
    </tr>
    <tr align="center">
        <td>010</td>
        <td><img src="../img/三只松鼠.png" width="60" height="50"></td>
        <td>三只松鼠</td>
        <td>三只松鼠</td>
    </tr>

    <tr align="center">
        <td>009</td>
        <td><img src="../img/优衣库.png" width="60" height="50"></td>
        <td>优衣库</td>
        <td>优衣库</td>
    </tr>

    <tr align="center">
        <td>008</td>
        <td><img src="../img/小米.png" width="60" height="50"></td>
        <td>小米</td>
        <td>小米科技有限公司</td>
    </tr>
</table>
</body>
</html>

1.8 布局标签

image.png

这两个标签,一般都是和css结合到一块使用来实现页面的布局。

div标签 在浏览器上会有换行的效果,而 span 标签在浏览器上没有换行效果。

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>我是div</div>
    <div>我是div</div>
    <span>我是span</span>
    <span>我是span</span>
</body>
</html>

浏览器效果如下:
image.png

1.9 表单标签

表单标签效果大家其实都不陌生,像登陆页面、注册页面等都是表单。

image.png

像这样的表单就是用来采集用户输入的数据,然后将数据发送到服务端,服务端会对数据库进行操作,比如注册就是将数据保存到数据库中,而登陆就是根据用户名和密码进行数据库的查询操作。

表单是很重要的标签,需要大家重点来学习。

1.9.1 表单标签概述

表单:在网页中主要负责数据采集功能,使用标签定义表单

表单项(元素):不同类型的 input 元素、下拉列表、文本域等

image.png

form 是表单标签,它在页面上没有任何展示的效果。需要借助于表单项标签来展示不同的效果。如下图就是不同的表单项标签展示出来的效果。

image.png

1.9.2 form标签属性

  • action:规定当提交表单时向何处发送表单数据,该属性值就是URL
    以后会将数据提交到服务端,该属性需要书写服务端的URL。而今天我们可以书写 # ,表示提交到当前页面来看效果。

表单项数据要向被提交,则必须指定其name属性

  • method :规定用于发送表单数据的方式
    method取值有如下两种:
    • get:默认值。如果不设置method属性则默认就是该值
      • 请求参数会拼接在URL后边
      • url的长度有限制 4KB
    • post:
      • 浏览器会将数据放到http请求消息体中
      • 请求参数无限制的

1.9.3 代码演示

由于表单标签在页面上没有任何展示的效果,所以在演示的过程是会先使用 input 这个表单项标签展示输入框效果。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form>
        <input type="text">
        <input type="submit">
    </form>
</body>
</html>

浏览器展示效果如下:
image.png

从效果可以看到页面有一个输入框,用户可以在数据框中输入自己想输入的内容,点击提交按钮以后会将数据发送到服务端,当然现在肯定不能实现。现在我们可以将 form 标签的 action 属性值设置为 # ,将其将数据提交到当前页面。还需要注意一点,要想提交数据,input 输入框必须设置 name 属性。代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="#">
        <input type="text" name="username">
        <input type="submit">
    </form>
</body>
</html>

浏览器展示效果如下:
image.png

在输入框输入 hehe ,然后点击 提交 按钮,就能看到如下效果
image.png

我们可以看到在浏览器的地址栏的URL后拼接了我们提交的数据。username 就是输入框 name 属性值,而 hehe 就是我们在输入框输入的内容。

接下来我们来聊 method 属性,默认是 method = 'get',所以该取值就会将数据拼接到URL的后面。那我们将 method 属性值设置为 post,浏览器的效果如下:
image.png

从上图可以看出数据并没有拼接到 URL 后,那怎么看提交的数据呢?我们可以使用浏览器的开发者工具来查看
image.png

按照如上步骤操作能看到如下页面
image.png

重新提交数据后,可以看到提交的数据,如下图
image.png

1.10 表单项标签

表单项标签有很多,不同的表单项标签有不同的展示效果。表单项标签可以分为以下三个:

  • :表单项,通过type属性控制输入形式
    input 标签有个 type 属性。 type 属性的取值不同,展示的效果也不一样

image.png


  • 状态: 禁用 启用
    ``` ##### 8.3.5 编写servlet 在 `web` 包下创建 `AddServlet` 的 `servlet`,该 `servlet` 的逻辑如下: - 设置处理post请求乱码的字符集 - 接收客户端提交的数据 - 将接收到的数据封装到 `Brand` 对象中 - 调用 `BrandService` 的`add()` 方法进行添加的业务逻辑处理 - 跳转到 `selectAllServlet` 资源重新查询数据 具体的代码如下: ```java @WebServlet("/addServlet") public class AddServlet extends HttpServlet { private BrandService service = new BrandService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //处理POST请求的乱码问题 request.setCharacterEncoding("utf-8"); //1. 接收表单提交的数据,封装为一个Brand对象 String brandName = request.getParameter("brandName"); String companyName = request.getParameter("companyName"); String ordered = request.getParameter("ordered"); String description = request.getParameter("description"); String status = request.getParameter("status"); //封装为一个Brand对象 Brand brand = new Brand(); brand.setBrandName(brandName); brand.setCompanyName(companyName); brand.setOrdered(Integer.parseInt(ordered)); brand.setDescription(description); brand.setStatus(Integer.parseInt(status)); //2. 调用service 完成添加 service.add(brand); //3. 转发到查询所有Servlet request.getRequestDispatcher("/selectAllServlet").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } ``` ##### 8.3.6 测试 点击 `brand.jsp` 页面的 `新增` 按钮,会跳转到 `addBrand.jsp`页面
    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384638419-8aa72e79-d560-4fd3-a66c-1b4596286a4d.png#clientId=u62e4f2d3-f413-4&from=paste&height=487&id=u0467fe61&margin=%5Bobject%20Object%5D&name=image.png&originHeight=487&originWidth=737&originalType=binary&ratio=1&size=27140&status=done&style=none&taskId=ub3c98ae1-3c36-492e-9354-4e16a2535aa&width=737) 点击 `提交` 按钮,就能看到如下页面,里面就包含我们刚添加的数据
    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384647194-c78179fc-ea29-4c84-b0e4-2d3aadb69709.png#clientId=u62e4f2d3-f413-4&from=paste&height=231&id=u82271529&margin=%5Bobject%20Object%5D&name=image.png&originHeight=231&originWidth=1549&originalType=binary&ratio=1&size=48302&status=done&style=none&taskId=u96ed9b88-7900-415a-9577-f6d2da88ed5&width=1549) #### 8.4 修改 ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384654144-cb70e2bc-f7dd-4eb8-b292-677abaa6431b.png#clientId=u62e4f2d3-f413-4&from=paste&height=475&id=uef82b813&margin=%5Bobject%20Object%5D&name=image.png&originHeight=475&originWidth=1904&originalType=binary&ratio=1&size=78580&status=done&style=none&taskId=u1f1a0e63-5eb5-4aef-ba70-ba040a24511&width=1904) 点击每条数据后面的 `编辑` 按钮会跳转到修改页面,如下图:
    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384661667-23875c41-9f26-423c-9272-216558d7e95d.png#clientId=u62e4f2d3-f413-4&from=paste&height=571&id=ua8b768ff&margin=%5Bobject%20Object%5D&name=image.png&originHeight=571&originWidth=1894&originalType=binary&ratio=1&size=83471&status=done&style=none&taskId=u9125088e-e09b-492c-a2f9-26d3848c6d1&width=1894) 在该修改页面我们可以看到将 `编辑` 按钮所在行的数据 回显 到表单,然后需要修改那个数据在表单中进行修改,然后点击 `提交` 的按钮将数据提交到后端,后端再将数据存储到数据库中。 从上面的例子我们知道 `修改` 功能需要从两方面进行实现,数据回显和修改操作。 ##### 8.4.1 回显数据 ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384668917-d129a4cc-0f0d-4f8b-af9e-871e4a2f4c92.png#clientId=u62e4f2d3-f413-4&from=paste&height=249&id=u1fc2a58f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=249&originWidth=983&originalType=binary&ratio=1&size=65579&status=done&style=none&taskId=ude890b3f-d0f7-4485-80a7-0805adf6fc4&width=983) 上图就是回显数据的效果。要实现这个效果,那当点击 `修改` 按钮时不能直接跳转到 `update.jsp` 页面,而是**需要先带着当前行数据的 **`**id**`** 请求后端程序,后端程序根据 **`**id**`** 查询数据,将数据存储到域对象中跳转到 **`**update.jsp**`** 页面进行数据展示**。整体流程如下 ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384685358-94f80b89-9ce4-4033-8afa-7ee1d56db6f8.png#clientId=u62e4f2d3-f413-4&from=paste&height=311&id=u9b18e5b4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=396&originWidth=1406&originalType=binary&ratio=1&size=92937&status=done&style=none&taskId=u8cee2dc4-59bd-4398-8cca-16af0861f3d&width=1105.1817626953125) `brand.jsp` ```java 修改 删除 ``` ###### 8.4.1.1 编写BrandMapper方法 在 `BrandMapper` 接口,在接口中定义 `selectById(int id)` 方法 ```java /** * 根据id查询 * @param id * @return */ @Select("select * from tb_brand where id = #{id}") @ResultMap("brandResultMap") Brand selectById(int id); ``` ###### 8.4.1.2 编写BrandService方法 在 `BrandService` 类中定义根据id查询数据方法 `selectById(int id)` ```java /** * 根据id查询 * @return */ public Brand selectById(int id){ //调用BrandMapper.selectAll() //2. 获取SqlSession SqlSession sqlSession = factory.openSession(); //3. 获取BrandMapper BrandMapper mapper = sqlSession.getMapper(BrandMapper.class); //4. 调用方法 Brand brand = mapper.selectById(id); sqlSession.close(); return brand; } ``` ###### 8.4.1.3 编写servlet 在 `web` 包下创建 `SelectByIdServlet` 的 `servlet`,该 `servlet` 的逻辑如下: - 获取请求数据 `id` - 调用 `BrandService` 的 `selectById()` 方法进行数据查询的业务逻辑 - 将查询到的数据存储到 request 域对象中 - 跳转到 `update.jsp` 页面进行数据真实 具体代码如下: ```java @WebServlet("/selectByIdServlet") public class SelectByIdServlet extends HttpServlet { private BrandService service = new BrandService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收id String id = request.getParameter("id"); //2. 调用service查询 Brand brand = service.selectById(Integer.parseInt(id)); //3. 存储到request中 request.setAttribute("brand",brand); //4. 转发到update.jsp request.getRequestDispatcher("/update.jsp").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } ``` ###### 8.4.1.4 编写update.jsp页面 拷贝 `addBrand.jsp` 页面,改名为 `update.jsp` 并做出以下修改: - `title` 标签内容改为 `修改品牌` - `form` 标签的 `action` 属性值改为 `/brand-demo/updateServlet` - `input` 标签要进行数据回显,需要设置 `value` 属性 ```jsp 品牌名称:
    企业名称:
    排序:
    ``` - `textarea` 标签要进行数据回显,需要在标签体中使用 `EL表达式` ```jsp 描述信息:
    ``` - 单选框使用 `if` 标签需要判断 `brand.status` 的值是 1 还是 0 在指定的单选框上使用 `checked` 属性,表示被选中状态 ```jsp 状态: 禁用 启用
    禁用 启用
    ``` 综上,`update.jsp` 代码如下: ```jsp <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

    修改品牌

    品牌名称:
    企业名称:
    排序:
    描述信息:
    状态: 禁用 启用
    禁用 启用
    ``` ##### 8.4.2 修改数据 做完回显数据后,接下来我们要做修改数据了,而下图是修改数据的效果: ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384705872-8611f7d2-6088-4090-8c60-8f0006ebf1de.png#clientId=u62e4f2d3-f413-4&from=paste&height=242&id=u3818703c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=242&originWidth=1028&originalType=binary&ratio=1&size=68247&status=done&style=none&taskId=u883fd148-1f95-47af-b037-783045ceb46&width=1028) 在修改页面进行数据修改,点击 `提交` 按钮,会将数据提交到后端程序,后端程序会对表中的数据进行修改操作,然后重新进行数据的查询操作。整体流程如下:
    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639384712717-b9bf19b0-8c11-4b34-8953-e6c57771d0e2.png#clientId=u62e4f2d3-f413-4&from=paste&height=331&id=u0549c325&margin=%5Bobject%20Object%5D&name=image.png&originHeight=388&originWidth=1413&originalType=binary&ratio=1&size=96122&status=done&style=none&taskId=u3ef6e32c-0d98-48bb-9d0f-36df2b3d583&width=1204.1817626953125) ###### 8.4.2.1 编写BrandMapper方法 在 `BrandMapper` 接口,在接口中定义 `update(Brand brand)` 方法 ```java /** * 修改 * @param brand */ @Update("update tb_brand set brand_name = #{brandName},company_name = #{companyName},ordered = #{ordered},description = #{description},status = #{status} where id = #{id}") void update(Brand brand); ``` ###### 8.4.2.2 编写BrandService方法 在 `BrandService` 类中定义根据id查询数据方法 `update(Brand brand)` ```java /** * 修改 * @param brand */ public void update(Brand brand){ //2. 获取SqlSession SqlSession sqlSession = factory.openSession(); //3. 获取BrandMapper BrandMapper mapper = sqlSession.getMapper(BrandMapper.class); //4. 调用方法 mapper.update(brand); //提交事务 sqlSession.commit(); //释放资源 sqlSession.close(); } ``` ###### 8.4.2.3 编写servlet 在 `web` 包下创建 `AddServlet` 的 `servlet`,该 `servlet` 的逻辑如下: - 设置处理post请求乱码的字符集 - 接收客户端提交的数据 - 将接收到的数据封装到 `Brand` 对象中 - 调用 `BrandService` 的`update()` 方法进行添加的业务逻辑处理 - 跳转到 `selectAllServlet` 资源重新查询数据 具体的代码如下: ```java @WebServlet("/updateServlet") public class UpdateServlet extends HttpServlet { private BrandService service = new BrandService(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //处理POST请求的乱码问题 request.setCharacterEncoding("utf-8"); //1. 接收表单提交的数据,封装为一个Brand对象 String id = request.getParameter("id"); String brandName = request.getParameter("brandName"); String companyName = request.getParameter("companyName"); String ordered = request.getParameter("ordered"); String description = request.getParameter("description"); String status = request.getParameter("status"); //封装为一个Brand对象 Brand brand = new Brand(); brand.setId(Integer.parseInt(id)); brand.setBrandName(brandName); brand.setCompanyName(companyName); brand.setOrdered(Integer.parseInt(ordered)); brand.setDescription(description); brand.setStatus(Integer.parseInt(status)); //2. 调用service 完成修改 service.update(brand); //3. 转发到查询所有Servlet request.getRequestDispatcher("/selectAllServlet").forward(request,response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } ``` 存在问题:update.jsp 页面提交数据时是没有携带主键数据的,而后台修改数据需要根据主键进行修改。 针对这个问题,我们不希望页面将主键id展示给用户看,但是又希望在提交数据时能将主键id提交到后端。此时我们就想到了在学习 HTML 时学习的隐藏域,在 `update.jsp` 页面的表单中添加如下代码: ```jsp <%--隐藏域,提交id--%> ``` `update.jsp` 页面的最终代码如下: ```java <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

    修改品牌

    <%--隐藏域,提交id--%> 品牌名称:
    企业名称:
    排序:
    描述信息:
    状态: 禁用 启用
    禁用 启用
    ``` #### 8.5 删除 `brand.jsp` ```java 修改 删除 ``` `BrandMapper.java` ```java /** * 根据id删除对应记录 * @param id */ @Delete("delete from tb_brand where id = #{id}") void deleteById(int id); ``` `BrandService.java` ```java /** * 根据id删除对应记录 */ public void deleteById(int id){ SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory(); //获取SqlSession SqlSession sqlSession = factory.openSession(); //获取BrandMapper BrandMapper mapper = sqlSession.getMapper(BrandMapper.class); //调用方法 mapper.deleteById(id); //增删改需提交事务 sqlSession.commit(); //关闭资源 sqlSession.close(); } ``` `DeleteByIdServlet.java` ```java package com.wexiao.web; import com.wexiao.service.BrandService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * @author 微咲 * @version 1.0 */ @WebServlet("/deleteByIdServlet") public class DeleteByIdServlet extends HttpServlet { private BrandService service = new BrandService(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接受页面传输来的id String id = request.getParameter("id"); //2. 删除id对应的数据 service.deleteById(Integer.parseInt(id)); //3. 转发到查询所有Servlet request.getRequestDispatcher("/selectAllServlet").forward(request,response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doGet(request, response); } } ``` ##
    会话技术 request只有一次请求,Session可以多次
    **目标** - 理解什么是会话跟踪技术 - 掌握Cookie的使用 - 掌握Session的使用 - 完善用户登录注册案例的功能 ### 1,会话跟踪技术的概述 对于会话跟踪这四个词,我们需要拆开来进行解释,首先要理解什么是会话,然后再去理解什么是会话跟踪: - 会话:用户打开浏览器,访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。 - 从浏览器发出请求到服务端响应数据给前端之后,一次会话(在浏览器和服务器之间)就被建立了 - 会话被建立后,如果浏览器或服务端都没有被关闭,则会话就会持续建立着 - 浏览器和服务器就可以继续使用该会话进行请求发送和响应,上述的整个过程就被称之为会话。 用实际场景来理解下会话,比如在我们访问京东的时候,当打开浏览器进入京东首页后,浏览器和京东的服务器之间就建立了一次会话,后面的搜索商品,查看商品的详情,加入购物车等都是在这一次会话中完成。思考:下图中总共建立了几个会话?![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491110290-cef12ec8-cd1e-4c7b-bc86-1b182c584620.png#from=url&id=G14x8&margin=%5Bobject%20Object%5D&originHeight=470&originWidth=679&originalType=binary&ratio=1&status=done&style=none)
    每个浏览器都会与服务端建立了一个会话,加起来总共是3个会话。 - 会话跟踪:**一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间****共享数据。** - 服务器会收到多个请求,这多个请求可能来自多个浏览器,如上图中的6个请求来自3个浏览器 - 服务器需要用来识别请求是否来自同一个浏览器 - **服务器用来识别浏览器的过程,这个过程就是****会话跟踪** - 服务器识别浏览器后就可以在同一个会话中多次请求之间来共享数据 那么我们又有一个问题需要思考,一个会话中的多次请求为什么要共享数据呢?有了这个数据共享功能后能实现哪些功能呢? - **购物车:****加入购物车****和****去购物车结算****是两次请求,但是后面这次请求要想展示前一次请求所添加的商品,就需要用到数据共享。**![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491122503-3750903b-301e-44c0-9108-01b317292fbf.png#from=url&id=UJV4R&margin=%5Bobject%20Object%5D&originHeight=626&originWidth=1310&originalType=binary&ratio=1&status=done&style=none) - **页面展示用户登录信息:**很多网站,登录后访问多个功能发送多次请求后,浏览器上都会有当前登录用户的信息[用户名],比如百度、京东、码云等。![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491156652-51d0e983-3fcd-4d15-b7ee-34cffb2f6592.png#from=url&id=zENqC&margin=%5Bobject%20Object%5D&originHeight=227&originWidth=422&originalType=binary&ratio=1&status=done&style=none) - **网站登录页面的****记住我****功能:**当用户登录成功后,勾选记住我按钮后下次再登录的时候,网站就会自动填充用户名和密码,简化用户的登录操作,多次登录就会有多次请求,他们之间也涉及到共享数据![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491163470-fad375ed-a04d-4162-b7a9-9059f9796ef1.png#from=url&id=M9gkN&margin=%5Bobject%20Object%5D&originHeight=286&originWidth=422&originalType=binary&ratio=1&status=done&style=none) - **登录页面的验证码功能:**生成验证码和输入验证码点击注册这也是两次请求,这两次请求的数据之间要进行对比,相同则允许注册,不同则拒绝注册,该功能的实现也需要在同一次会话中共享数据。![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491170504-0b94bed4-f198-4215-9776-569611169867.png#from=url&id=uZnpy&margin=%5Bobject%20Object%5D&originHeight=336&originWidth=284&originalType=binary&ratio=1&status=done&style=none) 通过这几个例子的讲解,相信大家对会话追踪技术已经有了一定的理解,该技术在实际开发中也非常重要。那么接下来我们就需要去学习下会话跟踪技术,在学习这些技术之前,我们需要思考**:为什么现在浏览器和服务器不支持数据共享呢?** - 浏览器和服务器之间使用的是HTTP请求来进行数据传输 - **HTTP协议是****无状态的,每次浏览器向服务器请求时,服务器都会将该请求视为新的请求** - HTTP协议设计成无状态的目的是让每次请求之间相互独立,互不影响 - 请求与请求之间独立后,就无法实现多次请求之间的数据共享 分析完具体的原因后,那么该如何实现会话跟踪技术呢? 具体的实现方式有:
    (1)客户端会话跟踪技术:Cookie
    (2)服务端会话跟踪技术:Session
    这两个技术都可以实现会话跟踪,它们之间最大的区别:Cookie是存储在**浏览器端**而Session是存储在**服务器端**
    具体的学习思路为: - CooKie的基本使用、原理、使用细节 - Session的基本使用、原理、使用细节 - Cookie和Session的综合案例 **小结**
    在这节中,我们主要介绍了下什么是会话和会话跟踪技术,需要注意的是: - HTTP协议是无状态的,靠HTTP协议是无法实现会话跟踪 - 想要实现会话跟踪,就需要用到Cookie和Session 这个Cookie和Session具体该如何使用,接下来就先从Cookie来学起。 ### 2,Cookie 学习Cookie,我们主要解决下面几个问题: - 什么是Cookie? - Cookie如何来使用? - Cookie是如何实现的? - Cookie的使用注意事项有哪些? #### 2.1 Cookie的基本使用 **1.概念**
    Cookie:客户端会话技术,将数据保存到客户端,以后每次请求都携带Cookie数据进行访问。
    **2.Cookie的工作流程**
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491184912-5ca8b4c6-90ba-44f1-b22a-6aeef093a09a.png#from=url&id=PPH7n&margin=%5Bobject%20Object%5D&originHeight=486&originWidth=750&originalType=binary&ratio=1&status=done&style=none) - 服务端提供了两个Servlet,分别是ServletA和ServletB - 浏览器发送HTTP请求1给服务端,服务端ServletA接收请求并进行业务处理 - 服务端ServletA在处理的过程中可以创建一个Cookie对象并将name=zs的数据存入Cookie - 服务端ServletA在响应数据的时候,会把Cookie对象响应给浏览器 - 浏览器接收到响应数据,会把Cookie对象中的数据存储在浏览器内存中,此时浏览器和服务端就建立了一次会话 - 在同一次会话中浏览器再次发送HTTP请求2给服务端ServletB,浏览器会携带Cookie对象中的所有数据 - ServletB接收到请求和数据后,就可以获取到存储在Cookie对象中的数据,这样同一个会话中的多次请求之间就实现了数据共享 **3.Cookie的基本使用**
    对于Cookie的使用,我们更关注的应该是后台代码如何操作Cookie,对于Cookie的操作主要分两大类,本别是发送Cookie和获取Cookie,对于上面这两块内容,分别该如何实现呢?
    3.1 **发送Cookie** - 创建Cookie对象,并设置数据 Cookie cookie = new Cookie("key","value"); - 发送Cookie到客户端:使用response对象 response.addCookie(cookie);
    介绍完发送Cookie对应的步骤后,接下面通过一个案例来完成Cookie的发送,具体实现步骤为:
    需求:在Servlet中生成Cookie对象并存入数据,然后将数据发送给浏览器
    1.创建Maven项目,项目名称为cookie-demo,并在pom.xml添加依赖
    2.编写Servlet类,名称为AServlet
    3.在AServlet中创建Cookie对象,存入数据,发送给前端
    4.启动测试,在浏览器查看Cookie对象中的值
    (1)创建Maven项目cookie-demo,并在pom.xml添加依赖

    8
    8



    javax.servlet
    javax.servlet-api
    3.1.0
    provided



    javax.servlet.jsp
    jsp-api
    2.2
    provided



    jstl
    jstl
    1.2


    taglibs
    standard
    1.1.2





    org.apache.tomcat.maven
    tomcat7-maven-plugin
    2.2



    (2)编写Servlet类,名称为AServlet
    @WebServlet("/aServlet")
    public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (3)在Servlet中创建Cookie对象,存入数据,发送给前端
    @WebServlet("/aServlet")
    public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //发送Cookie
    //1. 创建Cookie对象
    Cookie cookie = new Cookie("username","zs");
    //2. 发送Cookie,response
    response.addCookie(cookie);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (4)启动测试,在浏览器查看Cookie对象中的值
    访问http://localhost:8080/cookie-demo/aServlet
    chrome浏览器查看Cookie的值,有两种方式,分布式:
    方式一:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491219441-099ab82e-be4e-42ef-967a-ae789ded74ec.png#from=url&id=HVGof&margin=%5Bobject%20Object%5D&originHeight=3157&originWidth=1611&originalType=binary&ratio=1&status=done&style=none)
    方式二:选中打开开发者工具或者 使用快捷键F12 或者 Ctrl+Shift+I
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491218315-57382e1e-0f1b-4894-873f-c3e8f97465e0.png#from=url&id=FdFxn&margin=%5Bobject%20Object%5D&originHeight=1462&originWidth=1477&originalType=binary&ratio=1&status=done&style=none)
    3.2 **获取Cookie** - 获取客户端携带的所有Cookie,使用request对象 Cookie[] cookies = request.getCookies(); - 遍历数组,获取每一个Cookie对象:for - 使用Cookie对象方法获取数据 cookie.getName();
    cookie.getValue();
    介绍完获取Cookie对应的步骤后,接下面再通过一个案例来完成Cookie的获取,具体实现步骤为:
    需求:在Servlet中获取前一个案例存入在Cookie对象中的数据
    1.编写一个新Servlet类,名称为BServlet
    2.在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
    3.启动测试,在控制台打印出获取的值
    (1)编写一个新Servlet类,名称为BServlet
    @WebServlet("/bServlet")
    public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (2)在BServlet中使用request对象获取Cookie数组,遍历数组,从数据中获取指定名称对应的值
    @WebServlet("/bServlet")
    public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取Cookie
    //1. 获取Cookie数组
    Cookie[] cookies = request.getCookies();
    //2. 遍历数组
    for (Cookie cookie : cookies) {
    //3. 获取数据
    String name = cookie.getName();
    if("username".equals(name)){
    String value = cookie.getValue();
    System.out.println(name+":"+value);
    break;
    }
    } } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (3)启动测试,在控制台打印出获取的值
    访问http://localhost:8080/cookie-demo/bServlet
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491232551-9eeaa125-bea1-4481-b098-b25c074b1c6a.png#from=url&id=asFRo&margin=%5Bobject%20Object%5D&originHeight=266&originWidth=1029&originalType=binary&ratio=1&status=done&style=none)
    在IDEA控制台就能看到输出的结果:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491242259-e2f90679-923f-4c8e-a56f-7b738ab4e442.png#from=url&id=xCTxJ&margin=%5Bobject%20Object%5D&originHeight=222&originWidth=1257&originalType=binary&ratio=1&status=done&style=none)
    **完成了一次对话两次请求之间的共享数据**
    思考:测试的时候 - 在访问AServlet和BServlet的中间把关闭浏览器,重启浏览器后访问BServlet能否获取到Cookie中的数据? 这个问题,我们会在Cookie的使用细节中讲,大家可以动手先试下。
    **小结**
    在这节中,我们主要讲解了Cookie的基本使用,包含两部分内容 - **发送Cookie:** - **创建Cookie对象,并设置值:Cookie cookie = new Cookie("key","value");** - **发送Cookie到客户端使用的是Reponse对象:response.addCookie(cookie);** - **获取Cookie:** - **使用Request对象获取Cookie数组:Cookie[] cookies = request.getCookies();** - **遍历数组** - **获取数组中每个Cookie对象的值:cookie.getName()和cookie.getValue()** 介绍完Cookie的基本使用之后,那么Cookie的底层到底是如何实现一次会话两次请求之间的数据共享呢? #### 2.2 Cookie的原理分析 对于Cookie的实现原理是基于HTTP协议的,其中设计到HTTP协议中的两个请求头信息: - **响应头:set-cookie** - **请求头: cookie** ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491248605-2352a98f-21d1-44f7-9a74-2a1daec98e54.png#from=url&id=elOQc&margin=%5Bobject%20Object%5D&originHeight=304&originWidth=876&originalType=binary&ratio=1&status=done&style=none) - 前面的案例中已经能够实现,AServlet给前端发送Cookie,BServlet从request中获取Cookie的功能 - 对于AServlet响应数据的时候,Tomcat服务器都是基于HTTP协议来响应数据 - **当Tomcat发现后端要返回的是一个Cookie对象之后,Tomcat就会在响应头中添加一行数据****Set-Cookie:username=zs** - 浏览器获取到响应结果后,从响应头中就可以获取到Set-Cookie对应值username=zs,并将数据**存储在浏览器的内存中** - **浏览器再次发送请求给BServlet的时候,浏览器会自动在请求头中添加****Cookie: username=zs****发送给服务端BServlet** - Request对象会把请求头中cookie对应的值封装成一个个Cookie对象,最终形成一个数组 - BServlet通过Request对象获取到Cookie[]后,就可以从中获取自己需要的数据 接下来,使用刚才的案例,把上述结论验证下:
    (1)访问AServlet对应的地址http://localhost:8080/cookie-demo/aServlet
    使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行查看响应头中的数据
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491257063-ae070a24-bfae-4587-8a6a-34200e463e9d.png#from=url&id=d3MJ0&margin=%5Bobject%20Object%5D&originHeight=941&originWidth=901&originalType=binary&ratio=1&status=done&style=none)
    (2)访问BServlet对应的地址http://localhost:8080/cookie-demo/bServlet
    使用Chrom浏览器打开开发者工具(F12或Crtl+Shift+I)进行查看请求头中的数据
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491265298-fca9dd61-1ddf-46ee-82da-603e0cbe731a.png#from=url&id=pgtuG&margin=%5Bobject%20Object%5D&originHeight=922&originWidth=946&originalType=binary&ratio=1&status=done&style=none) #### 2.3 Cookie的使用细节 在这节我们主要讲解两个知识,第一个是Cookie的存活时间,第二个是Cookie如何存储中文,首先来学习下Cookie的存活时间。 ##### 2.3.1 Cookie的存活时间 前面让大家思考过一个问题:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491294161-c4d3c6dd-ed0e-4130-af2f-b6b977ffef53.png#from=url&id=iUGUi&margin=%5Bobject%20Object%5D&originHeight=289&originWidth=848&originalType=binary&ratio=1&status=done&style=none)
    (1)浏览器发送请求给AServlet,AServlet会响应一个存有usernanme=zs的Cookie对象给浏览器
    (2)浏览器接收到响应数据将cookie存入到浏览器内存中
    (3)当浏览器再次发送请求给BServlet,BServlet就可以使用Request对象获取到Cookie数据
    (4)在发送请求到BServlet之前,如果把浏览器关闭再打开进行访问,BServlet能否获取到Cookie数据?
    注意:浏览器关闭再打开不是指打开一个新的选显卡,而且必须是先关闭再打开,顺序不能变。
    针对上面这个问题,通过演示,会发现,BServlet中无法再获取到Cookie数据,这是为什么呢? - **默认情况下,Cookie存储在浏览器内存中,当浏览器关闭,内存释放,则Cookie被销毁** 这个结论就印证了上面的演示效果,但是如果使用这种默认情况下的Cookie,有些需求就无法实现,比如:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491302550-c9ef982f-4dd3-42dc-84fe-48862f210b24.png#from=url&id=B4B2Z&margin=%5Bobject%20Object%5D&originHeight=659&originWidth=1548&originalType=binary&ratio=1&status=done&style=none)
    上面这个网站的登录页面上有一个记住我的功能,这个功能大家都比较熟悉 - 第一次输入用户名和密码并勾选记住我然后进行登录 - 下次再登陆的时候,用户名和密码就会被自动填充,不需要再重新输入登录 - 比如记住我这个功能需要记住用户名和密码一个星期,那么使用默认情况下的Cookie就会出现问题 - 因为默认情况,浏览器一关,Cookie就会从浏览器内存中删除,对于记住我功能就无法实现 所以我们现在就遇到一个难题是如何将Cookie持久化存储?
    Cookie其实已经为我们提供好了对应的API来完成这件事,这个API就是setMaxAge, - 设置Cookie存活时间 setMaxAge(int seconds)
    参数值为:
    **1.正数:将Cookie写入浏览器所在电脑的硬盘,****持久化存储。到时间自动删除**
    **2.负数:默认值,Cookie在当前浏览器内存中,当****浏览器关闭,则Cookie被销毁**
    **3.零:****删除对应Cookie**
    接下来,咱们就在AServlet中去设置Cookie的存活时间。
    @WebServlet("/aServlet")
    public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //发送Cookie
    //1. 创建Cookie对象
    Cookie cookie = new Cookie("username","zs");
    //设置存活时间 ,1周 7天
    cookie.setMaxAge(60*60*24*7); //易阅读,需程序计算
    //cookie.setMaxAge(604800); //不易阅读(可以使用注解弥补),程序少进行一次计算
    //2. 发送Cookie,response
    response.addCookie(cookie);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    修改完代码后,启动测试,访问http://localhost:8080/cookie-demo/aServlet - 访问一个AServlet后,把浏览器关闭重启后,再去访问http://localhost:8080/cookie-demo/bServet,能在控制台打印出username:zs,说明Cookie没有随着浏览器关闭而被销毁 - 通过浏览器查看Cookie的内容,会发现Cookie的相关信息 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491330817-25fd6084-f6e4-4e32-8977-f2c9d06f0c1d.png#from=url&id=Tz7Ak&margin=%5Bobject%20Object%5D&originHeight=981&originWidth=1241&originalType=binary&ratio=1&status=done&style=none) ##### 2.3.2 Cookie存储中文 首先,先来演示一个效果,将之前username=zs的值改成username=张三,把汉字张三存入到Cookie中,看是什么效果:
    @WebServlet("/aServlet")
    public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //发送Cookie
    String value = "张三";
    Cookie cookie = new Cookie("username",value);
    //设置存活时间 ,1周 7天
    cookie.setMaxAge(60*60*24*7);
    //2. 发送Cookie,response
    response.addCookie(cookie);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    启动访问测试,访问http://localhost:8080/cookie-demo/aServlet会发现浏览器会提示错误信息
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491333365-b13e82db-b894-4c98-9cba-605bf7004075.png#from=url&id=kELNG&margin=%5Bobject%20Object%5D&originHeight=642&originWidth=953&originalType=binary&ratio=1&status=done&style=none)
    通过上面的案例演示,我们得到一个结论: - **Cookie不能直接存储中文** Cookie不能存储中文,但是如果有这方面的需求,这个时候该如何解决呢?
    这个时候,我们可以使用之前学过的一个知识点叫URL编码,所以如果需要存储中文,就需要进行转码,具体的实现思路为:
    **1.在AServlet中对中文进行URL编码,采用URLEncoder.encode(),将编码后的值存入Cookie中**
    **2.在BServlet中获取Cookie中的值,获取的值为URL编码后的值**
    **3.将获取的值在进行URL解码,采用URLDecoder.decode(),就可以获取到对应的中文值**
    (1)在AServlet中对中文进行URL编码
    @WebServlet("/aServlet")
    public class AServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //发送Cookie
    String value = "张三";
    //对中文进行URL编码
    value = URLEncoder.encode(value, "UTF-8");
    System.out.println("存储数据:"+value);
    //将编码后的值存入Cookie中
    Cookie cookie = new Cookie("username",value);
    //设置存活时间 ,1周 7天
    cookie.setMaxAge(60*60*24*7);
    //2. 发送Cookie,response
    response.addCookie(cookie);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (2)在BServlet中获取值,并对值进行解码
    @WebServlet("/bServlet")
    public class BServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取Cookie
    //1. 获取Cookie数组
    Cookie[] cookies = request.getCookies();
    //2. 遍历数组
    for (Cookie cookie : cookies) {
    //3. 获取数据
    String name = cookie.getName();
    if("username".equals(name)){
    String value = cookie.getValue();//获取的是URL编码后的值 %E5%BC%A0%E4%B8%89
    //URL解码
    value = URLDecoder.decode(value,"UTF-8");
    System.out.println(name+":"+value);//value解码后为 张三
    break;
    }
    } } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    至此,我们就可以将中文存入Cookie中进行使用。
    **小结**
    Cookie的使用细节中,我们讲了Cookie的存活时间和存储中文: - **存活时间,需要掌握setMaxAage()API的使用** - **存储中文,需要掌握URL编码和解码的使用** ### 3,Session Cookie已经能完成一次会话多次请求之间的数据共享,之前我们还提到过Session也可以实现,那么: - 什么是Session? - Session如何来使用? - Session是如何实现的? - Session的使用注意事项有哪些? #### 3.1 Session的基本使用 **1.概念**
    Session:服务端会话跟踪技术:将数据保存到服务端。 - Session是存储在服务端而Cookie是存储在客户端 - 存储在客户端的数据容易被窃取和截获,存在很多不安全的因素 - 存储在服务端的数据相比于客户端来说就更安全 **2.Session的工作流程**
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491352778-26de7ef3-164a-4719-a985-a2e6e6191d75.png#from=url&id=PoSZU&margin=%5Bobject%20Object%5D&originHeight=303&originWidth=847&originalType=binary&ratio=1&status=done&style=none) - 在服务端的AServlet获取一个Session对象,把数据存入其中 - 在服务端的BServlet获取到相同的Session对象,从中取出数据 - 就可以**实现一次会话中多次请求之间的数据共享了** - 现在最大的问题是_**如何保证AServlet和BServlet使用的是同一个Session对象**_(在原理分析会讲解)? **3.Session的基本使用**
    在JavaEE中提供了HttpSession接口,来实现一次会话的多次请求之间数据共享功能。
    具体的使用步骤为: - 获取Session对象,使用的是request对象 HttpSession session = request.getSession(); - Session对象提供的功能: - 存储数据到 session 域中 void setAttribute(String name, Object o) - 根据 key,获取值 Object getAttribute(String name) - 根据 key,删除该键值对 void removeAttribute(String name)
    介绍完Session相关的API后,接下来通过一个案例来完成对Session的使用,具体实现步骤为:
    需求:在一个Servlet中往Session中存入数据,在另一个Servlet中获取Session中存入的数据
    1.创建名为SessionDemo1的Servlet类
    2.创建名为SessionDemo2的Servlet类
    3.在SessionDemo1的方法中:获取Session对象、存储数据
    4.在SessionDemo2的方法中:获取Session对象、获取数据
    5.启动测试
    (1)创建名为SessionDemo1的Servlet类
    @WebServlet("/demo1")
    public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (2)创建名为SessionDemo2的Servlet类
    @WebServlet("/demo2")
    public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (3)SessionDemo1:获取Session对象、存储数据
    @WebServlet("/demo1")
    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("username","zs");
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (4)SessionDemo2:获取Session对象、获取数据
    @WebServlet("/demo2")
    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 username = session.getAttribute("username");
    System.out.println(username);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (5)启动测试, - 先访问http://localhost:8080/cookie-demo/demo1,将数据存入Session - 在访问http://localhost:8080/cookie-demo/demo2,从Session中获取数据 - 查看控制台 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491353502-a92cdf2f-e3ee-4568-a987-32d83a9a6003.png#from=url&id=OC2ft&margin=%5Bobject%20Object%5D&originHeight=408&originWidth=948&originalType=binary&ratio=1&status=done&style=none)
    通过案例的效果,能看到Session是能够在一次会话中两次请求之间共享数据。
    **小结**
    至此Session的基本使用就已经完成了,重点要掌握的是: - Session的获取 HttpSession session = request.getSession(); - Session常用方法的使用 **注意:**Session中可以存储的是一个Object类型的数据,也就是说Session中可以存储任意数据类型。 void setAttribute(String name, Object o)
    Object getAttribute(String name)
    介绍完Session的基本使用之后,那么Session的底层到底是如何实现一次会话两次请求之间的数据共享呢? #### 3.2 Session的原理分析 - Session是基于Cookie实现的 这句话其实不太能详细的说明Session的底层实现,接下来,咱们一步步来分析下Session的具体实现原理:
    (1)前提条件
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491404051-a47582b0-57ec-4b47-ab9f-a3a055d6cf62.png#from=url&id=jhRIq&margin=%5Bobject%20Object%5D&originHeight=306&originWidth=1222&originalType=binary&ratio=1&status=done&style=none)
    **Session要想实现一次会话多次请求之间的数据共享,就必须要****保证多次请求获取Session的对象是同一个。**
    那么它们是一个对象么?要验证这个结论也很简单,只需要在上面案例中的两个Servlet中分别打印下Session对象
    SessionDemo1
    @WebServlet("/demo1")
    public class SessionDemo1 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //存储到Session中
    //1. 获取Session对象
    HttpSession session = request.getSession();
    System.out.println(session);
    //2. 存储数据
    session.setAttribute("username","zs");
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    SessionDemo2
    @WebServlet("/demo2")
    public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取数据,从session中
    //1. 获取Session对象
    HttpSession session = request.getSession();
    System.out.println(session);
    //2. 获取数据
    Object username = session.getAttribute("username");
    System.out.println(username);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    启动测试,分别访问
    http://localhost:8080/cookie-demo/demo1
    http://localhost:8080/cookie-demo/demo2
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491413061-3522dcbb-027a-4087-9151-26089cc41b7f.png#from=url&id=fekuP&margin=%5Bobject%20Object%5D&originHeight=410&originWidth=1254&originalType=binary&ratio=1&status=done&style=none)
    通过打印可以得到如下结论: - **两个Servlet类中获取的Session对象是同一个** - **把demo1和demo2请求刷新多次,控制台最终打印的结果都是同一个** 那么问题又来了_**,如果新开一个浏览器,访问demo1或者demo2,打印在控制台的Session还是同一个对象么?**_
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491405954-05666888-0fe7-46f9-9032-9a814aa6d74e.png#from=url&id=daHoW&margin=%5Bobject%20Object%5D&originHeight=391&originWidth=1219&originalType=binary&ratio=1&status=done&style=none)
    注意:在一台电脑上演示的时候,如果是相同的浏览器必须要把浏览器全部关掉重新打开,才算新开的一个浏览器。
    当然也可以使用不同的浏览器进行测试,就不需要把之前的浏览器全部关闭。
    测试的结果:如果是不同浏览器或者重新打开浏览器后,打印的Session就不一样了。
    所以Session实现的也是一次会话中的多次请求之间的数据共享。
    那么最主要的问题就来了,Session是如何保证在一次会话中获取的Session对象是同一个呢?
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491406348-86f6eed7-2966-46bd-98ff-588809882050.png#from=url&id=PTdVA&margin=%5Bobject%20Object%5D&originHeight=429&originWidth=1246&originalType=binary&ratio=1&status=done&style=none)
    (1)demo1在第一次获取session对象的时候,session对象会有一个唯一的标识,假如是id:10
    (2)demo1在**session中存入其他数据并处理完成所有业务后,需要通过Tomcat服务器响应结果给浏览器**
    (3)**Tomcat服务器发现业务处理中使用了session对象,就会把session的唯一标识****id:10****当做一个cookie,添加****Set-Cookie:JESSIONID=10****到响应头中,并响应给浏览器**
    (4)浏览器接收到响应结果后,会把**响应头中的coookie数据存储到浏览器的内存中**
    (5)浏览器**在同一会话中访问demo2的时候,会把cookie中的数据按照****cookie: JESSIONID=10****的格式添加到请求头中并发送给服务器Tomcat**
    (6)demo2获取到请求后,**从请求头中就读取cookie中的JSESSIONID值为10**,然后就会到服务器内存中寻找id:10的session对象,如果找到了,就直接返回该对象,如果没有则新创建一个session对象
    (7)关闭打开浏览器后,因为浏览器的cookie已被销毁,所以就没有JESSIONID的数据,服务端获取到的session就是一个全新的session对象
    至此,Session是基于Cookie来实现的这就话,我们就解释完了,接下来通过实例来演示下:
    (1)使用chrome浏览器访问http://localhost:8080/cookie-demo/demo1 打开开发者模式(F12或Ctrl+Shift+I),查看==响应头(Response Headers)==数据:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491406944-b7cb6a00-4220-46a8-b162-15edf4a7cdd2.png#from=url&id=WJvX4&margin=%5Bobject%20Object%5D&originHeight=892&originWidth=1097&originalType=binary&ratio=1&status=done&style=none)
    (2)使用chrome浏览器再次访问http://localhost:8080/cookie-demo/demo2,查看==请求头(Request Headers)==数据:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491413144-6abfa6e5-2a5c-496a-a561-109aa00c9467.png#from=url&id=IV0mz&margin=%5Bobject%20Object%5D&originHeight=903&originWidth=1278&originalType=binary&ratio=1&status=done&style=none)
    **小结**
    介绍完Session的原理,我们只需要记住 - **Session是基于Cookie来实现的** #### 3.3 Session的使用细节 这节我们会主要讲解两个知识,第一个是Session的钝化和活化,第二个是Session的销毁,首先来学习什么是Session的钝化和活化? ##### 3.3.1 Session钝化与活化 首先需要大家思考的问题是: - 服务器重启后,Session中的数据是否还在? 要想回答这个问题,我们可以先看下下面这幅图,
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491412430-67b1c68a-aa2f-4a9a-b0da-abebbc1a843f.png#from=url&id=r6JyM&margin=%5Bobject%20Object%5D&originHeight=327&originWidth=922&originalType=binary&ratio=1&status=done&style=none)
    **(1)服务器端AServlet和BServlet共用的session对象应该是存储在服务器的内存中**
    (2)服务器重新启动后,内存中的数据应该是已经被释放,对象也应该都销毁了
    所以session数据应该也已经不存在了。但是如果session不存在会引发什么问题呢?
    举个例子说明下,
    (1)用户把需要购买的商品添加到购物车,因为要实现同一个会话多次请求数据共享,所以假设把数据存入Session对象中
    (2)用户正要付钱的时候接到一个电话,付钱的动作就搁浅了
    (3)正在用户打电话的时候,购物网站因为某些原因需要重启
    (4)重启后session数据被销毁,购物车中的商品信息也就会随之而消失
    (5)用户想再次发起支付,就会出为问题
    所以说对于session的数据,我们应该做到就算服务器重启了,也应该能把数据保存下来才对。
    分析了这么多,那么**Tomcat服务器在重启的时候,session数据到底会不会保存以及是如何保存的**,我们可以通过实际案例来演示下:
    注意:这里所说的关闭和启动应该要确保是正常的关闭和启动。
    那如何才是**正常关闭Tomcat服务器**呢?
    需要使用命令行的方式来启动和停止Tomcat服务器:
    启动:进入到项目pom.xml所在目录,执行tomcat7:run
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491414406-66ab15b7-90fd-4353-8761-1aba3639bb83.png#from=url&id=uzxjb&margin=%5Bobject%20Object%5D&originHeight=537&originWidth=1032&originalType=binary&ratio=1&status=done&style=none)
    停止:在启动的命令行界面,输入ctrl+c
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491414570-13eb58d9-d211-4095-b466-48636404be92.png#from=url&id=CAkaQ&margin=%5Bobject%20Object%5D&originHeight=292&originWidth=1410&originalType=binary&ratio=1&status=done&style=none)
    有了上述两个正常启动和关闭的方式后,接下来的测试流程是:
    (1)先启动Tomcat服务器
    (2)访问http://localhost:8080/cookie-demo/demo1将数据存入session中
    (3)正确停止Tomcat服务器
    (4)再次重新启动Tomcat服务器
    (5)访问http://localhost:8080/cookie-demo/demo2 查看是否能获取到session中的数据
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491424112-b88b982d-a684-43de-be21-8d7d20d63fc7.png#from=url&id=YRhJH&margin=%5Bobject%20Object%5D&originHeight=261&originWidth=1444&originalType=binary&ratio=1&status=done&style=none)
    经过测试,会发现只要服务器是正常关闭和启动,session中的数据是可以被保存下来的。
    那么Tomcat服务器到底是如何做到的呢?
    具体的原因就是:Session的钝化和活化: - 钝化:在服务器正常关闭后,Tomcat会自动将Session数据写入硬盘的文件中 - 钝化的数据路径为:项目目录\target\tomcat\work\Tomcat\localhost\项目名称\SESSIONS.ser![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491438413-46c014f0-069e-4cec-bca6-2b65678de82d.png#from=url&id=SszRT&margin=%5Bobject%20Object%5D&originHeight=147&originWidth=1139&originalType=binary&ratio=1&status=done&style=none) - 活化:再次启动服务器后,从文件中加载数据到Session中 - **数据加载到Session中后,路径中的****SESSIONS.ser****文件会被删除掉** 对于上述的整个过程,大家只需要了解下即可。因为所有的过程都是Tomcat自己完成的,不需要我们参与。
    **小结**
    Session的钝化和活化介绍完后,需要我们注意的是: - **session数据存储在服务端,服务器重启后,session数据会被保存** - **浏览器被关闭启动后,重新建立的连接就已经是一个全新的会话,获取的session数据也是一个新的对象** - **session的数据要想共享,浏览器不能关闭,所以session数据不能长期保存数据** - **cookie是存储在客户端,是可以长期保存** ##### 3.3.2 Session销毁 session的销毁会有两种方式: - **默认情况下,无操作,30分钟自动销毁** - 对于这个失效时间,是可以通过配置进行修改的 - 在项目的web.xml中配置
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    100

    - 如果没有配置,默认是30分钟,默认值是在Tomcat的web.xml配置文件中写死的![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491502202-efe6ffdd-155a-4da5-93d1-dcfeb9b0684e.png#from=url&id=sJ6g9&margin=%5Bobject%20Object%5D&originHeight=694&originWidth=1645&originalType=binary&ratio=1&status=done&style=none) - **调用Session对象的invalidate()进行销毁** - 在SessionDemo2类中添加session销毁的方法 - session.invalidate(); @WebServlet("/demo2")
    public class SessionDemo2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取数据,从session中 //1. 获取Session对象
    HttpSession session = request.getSession();
    System.out.println(session); // 销毁
    session.invalidate();
    //2. 获取数据
    Object username = session.getAttribute("username");
    System.out.println(username);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    } - 启动访问测试,先访问demo1将数据存入到session,再次访问demo2从session中获取数据![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491509305-34b1627c-b6a4-4226-aed7-a8586d8d86c0.png#from=url&id=zoL1d&margin=%5Bobject%20Object%5D&originHeight=571&originWidth=1004&originalType=binary&ratio=1&status=done&style=none) - 该销毁方法一般会在用户退出的时候,需要将session销毁掉。 **Cookie和Session小结** - Cookie 和 Session 都是来完成**一次会话内多次请求**间数据共享的。 所需两个对象放在一块,就需要思考:
    **Cookie和Session的区别是什么?**
    **Cookie和Session的应用场景分别是什么?** - **区别:** - 存储位置:Cookie 是将数据存储在客户端,Session 将数据存储在服务端 - 安全性:Cookie不安全,Session安全 - 数据大小:Cookie最大3KB,Session无大小限制 - 存储时间:Cookie可以通过setMaxAge()长期存储,Session默认30分钟 - 服务器性能:Cookie不占服务器资源,Session占用服务器资源 - **应用场景:** - 购物车:使用Cookie来存储 - 以登录用户的名称展示:使用Session来存储 - 记住我功能:使用Cookie来存储 - 验证码:使用session来存储 - 结论 - Cookie是用来保证用户在未登录情况下的身份识别 - Session是用来保存用户登录后的数据 介绍完Cookie和Session以后,具体用哪个还是需要根据具体的业务进行具体分析。 ### 4,用户登录注册案例 #### 4.1 需求分析 需求说明: 1. 完成用户登录功能,如果用户勾选“记住用户” ,则下次访问登录页面自动填充用户名密码 1. 完成注册功能,并实现验证码功能 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491520670-dbe80683-4341-4de7-b401-d772bcb77991.png#from=url&id=CDtvV&margin=%5Bobject%20Object%5D&originHeight=394&originWidth=924&originalType=binary&ratio=1&status=done&style=none) #### 4.2 用户登录功能 1. 需求: ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491526158-a947887c-09e5-446d-a213-7f14f119e6f9.png#from=url&id=anE9V&margin=%5Bobject%20Object%5D&originHeight=321&originWidth=753&originalType=binary&ratio=1&status=done&style=none) - 用户登录成功后,跳转到列表页面,并在页面上展示当前登录的用户名称 - 用户登录失败后,跳转回登录页面,并在页面上展示对应的错误信息 1. 实现流程分析 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491531370-bb036235-3fe9-4d72-be9a-56ea38cbe7b8.png#from=url&id=TpPFi&margin=%5Bobject%20Object%5D&originHeight=453&originWidth=1592&originalType=binary&ratio=1&status=done&style=none)
    (1)前端通过表单发送请求和数据给Web层的LoginServlet
    (2)在LoginServlet中接收请求和数据[用户名和密码]
    (3)LoginServlet接收到请求和数据后,调用Service层完成根据用户名和密码查询用户对象
    (4)在Service层需要编写UserService类,在类中实现login方法,方法中调用Dao层的UserMapper
    (5)在UserMapper接口中,声明一个根据用户名和密码查询用户信息的方法
    (6)Dao层把数据查询出来以后,将返回数据封装到User对象,将对象交给Service层
    (7)Service层将数据返回给Web层
    (8)Web层获取到User对象后,判断User对象,如果为Null,则将错误信息响应给登录页面,如果不为Null,则跳转到列表页面,并把当前登录用户的信息存入Session携带到列表页面。 1. 具体实现 (1)完成Dao层的代码编写
    (1.1)将04-资料\1. 登录注册案例\2. MyBatis环境\UserMapper.java放到com.itheima.mapper`包下:
    public interface UserMapper {
    /**
    * 根据用户名和密码查询用户对象
    * @param username
    * @param password
    * @return
    */
    @Select("select * from tb_user where username = #{username} and password = #{password}")
    User select(@Param("username") String username,@Param("password") String password); /**
    * 根据用户名查询用户对象
    * @param username
    * @return
    */
    @Select("select * from tb_user where username = #{username}")
    User selectByUsername(String username); /**
    * 添加用户
    * @param user
    */
    @Insert("insert into tb_user values(null,#{username},#{password})")
    void add(User user);
    }
    (1.2)将04-资料\1. 登录注册案例\2. MyBatis环境\User.java放到com.itheima.pojo包下:
    public class User { private Integer id;
    private String username;
    private String password; public Integer getId() {
    return id;
    } public void setId(Integer 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;
    } @Override
    public String toString() {
    return "User{" +
    "id=" + id +
    ", username='" + username + '\'' +
    ", password='" + password + '\'' +
    '}';
    }
    }
    (1.3)将04-资料\1. 登录注册案例\2. MyBatis环境\UserMapper.xml放入到resources/com/itheima/mapper`目录下:

    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

    (2)完成Service层的代码编写
    (2.1)在com.itheima.service包下,创建UserService类
    public class UserService {
    //1.使用工具类获取SqlSessionFactory
    SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
    /**
    * 登录方法
    * @param username
    * @param password
    * @return
    */
    public User login(String username,String password){
    //2. 获取SqlSession
    SqlSession sqlSession = factory.openSession();
    //3. 获取UserMapper
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //4. 调用方法
    User user = mapper.select(username, password);
    //释放资源
    sqlSession.close(); return user;
    }
    }
    (3)完成页面和Web层的代码编写
    (3.1)将04-资料\1. 登录注册案例\1. 静态页面拷贝到项目的webapp目录下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491540584-e410ab7b-31cc-495c-b58f-0cfca44ffccd.png#from=url&id=Aat59&margin=%5Bobject%20Object%5D&originHeight=416&originWidth=411&originalType=binary&ratio=1&status=done&style=none)
    (3.2)将login.html内容修改成login.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>








    LOGIN IN


    用户名或密码不正确

    Username:


    Password:


    Remember:







    (3.3)创建LoginServlet类
    @WebServlet("/loginServlet")
    public class LoginServlet extends HttpServlet {
    private UserService service = new UserService(); @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取用户名和密码
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    //2. 调用service查询
    User user = service.login(username, password); //3. 判断
    if(user != null){
    //登录成功,跳转到查询所有的BrandServlet

    //将登陆成功后的user对象,存储到session
    HttpSession session = request.getSession();
    session.setAttribute("user",user);

    String contextPath = request.getContextPath();
    response.sendRedirect(contextPath+"/selectAllServlet");
    }else {
    // 登录失败,
    // 存储错误信息到request
    request.setAttribute("login_msg","用户名或密码错误");
    // 跳转到login.jsp
    request.getRequestDispatcher("/login.jsp").forward(request,response); }
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (3.4)在brand.jsp中标签下添加欢迎当前用户的提示信息:

    ${user.username},欢迎您


    (3.5) 修改login.jsp,将错误信息使用EL表达式来获取
    修改前内容:
    用户名或密码不正确

    修改后内容:
    ${login_msg}

    (4)启动,访问测试
    (4.1) 进入登录页面,输入错误的用户名或密码
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491549110-99975f4b-1400-4457-ba02-6b0b9af7b6fb.png#from=url&id=IDeWJ&margin=%5Bobject%20Object%5D&originHeight=812&originWidth=1356&originalType=binary&ratio=1&status=done&style=none)
    (4.2)输入正确的用户和密码信息
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491555900-76200e72-d968-43aa-bf1f-524521a9c7db.png#from=url&id=MzFnt&margin=%5Bobject%20Object%5D&originHeight=497&originWidth=1554&originalType=binary&ratio=1&status=done&style=none)
    **小结** - 在LoginServlet中,将登录成功的用户数据存入session中,方法在列表页面中获取当前登录用户信息进行展示 - 在LoginServlet中,将登录失败的错误信息存入到request中,如果存入到session中就会出现这次会话的所有请求都有登录失败的错误信息,这个是不需要的,所以不用存入到session中 #### 4.3 记住我-设置Cookie 1. 需求: 如果用户勾选“记住用户” ,则下次访问登陆页面自动填充用户名密码。这样可以提升用户的体验。
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491558296-bf217a9d-d567-4b8c-b64c-19a2031e09f7.png#from=url&id=YeRwo&margin=%5Bobject%20Object%5D&originHeight=277&originWidth=717&originalType=binary&ratio=1&status=done&style=none)
    对应上面这个需求,最大的问题就是: 如何自动填充用户名和密码? 1. 实现流程分析 因为记住我功能要实现的效果是,就算用户把浏览器关闭过几天再来访问也能自动填充,所以需要将登陆信息存入一个可以长久保存,并且能够在浏览器关闭重新启动后依然有效的地方,就是我们前面讲的Cookie,所以: - 将用户名和密码写入Cookie中,并且持久化存储Cookie,下次访问浏览器会自动携带Cookie - 在页面获取Cookie数据后,设置到用户名和密码框中 - 何时写入Cookie? - 用户必须登陆成功后才需要写 - 用户必须在登录页面勾选了记住我的复选框 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491565405-0dac60bd-005d-4ac4-b754-ce8158938209.png#from=url&id=foMwS&margin=%5Bobject%20Object%5D&originHeight=478&originWidth=621&originalType=binary&ratio=1&status=done&style=none)
    (1)前端需要在发送请求和数据的时候,多携带一个用户是否勾选Remember的数据
    (2)LoginServlet获取到数据后,调用Service完成用户名和密码的判定
    (3)登录成功,并且用户在前端勾选了记住我,需要往Cookie中写入用户名和密码的数据,并设置Cookie存活时间
    (4)设置成功后,将数据响应给前端 1. 具体实现 (1)在login.jsp为复选框设置值
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>








    LOGIN IN


    ${login_msg}

    Username:


    Password:


    Remember:







    (2)在LoginServlet获取复选框的值并在登录成功后进行设置Cookie
    @WebServlet("/loginServlet")
    public class LoginServlet extends HttpServlet {
    private UserService service = new UserService(); @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取用户名和密码
    String username = request.getParameter("username");
    String password = request.getParameter("password"); //获取复选框数据
    String remember = request.getParameter("remember"); //2. 调用service查询
    User user = service.login(username, password); //3. 判断
    if(user != null){
    //登录成功,跳转到查询所有的BrandServlet //判断用户是否勾选记住我,字符串写前面是为了避免出现空指针异常
    if("1".equals(remember)){
    //勾选了,发送Cookie
    //1. 创建Cookie对象
    Cookie c_username = new Cookie("username",username);
    Cookie c_password = new Cookie("password",password);
    // 设置Cookie的存活时间
    c_username.setMaxAge( 60 * 60 * 24 * 7);
    c_password.setMaxAge( 60 * 60 * 24 * 7);
    //2. 发送
    response.addCookie(c_username);
    response.addCookie(c_password);
    } //将登陆成功后的user对象,存储到session
    HttpSession session = request.getSession();
    session.setAttribute("user",user); String contextPath = request.getContextPath();
    response.sendRedirect(contextPath+"/selectAllServlet");
    }else {
    // 登录失败, // 存储错误信息到request
    request.setAttribute("login_msg","用户名或密码错误"); // 跳转到login.jsp
    request.getRequestDispatcher("/login.jsp").forward(request,response); }
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (3)启动访问测试,
    只有当前用户名和密码输入正确,并且勾选了Remeber的复选框,在响应头中才可以看得cookie的相关数据
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491575703-3eefc60e-1786-4b8d-9151-74ddf2a64f4f.png#from=url&id=C1oW1&margin=%5Bobject%20Object%5D&originHeight=944&originWidth=1011&originalType=binary&ratio=1&status=done&style=none) #### 4.4 记住我-获取Cookie 1. 需求 登录成功并勾选了Remeber后,后端返回给前端的Cookie数据就已经存储好了,接下来就需要在页面获取Cookie中的数据,并把数据设置到登录页面的用户名和密码框中。
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491578875-f8d65a7d-ea9d-41d8-946e-f1ba6e06f7ef.png#from=url&id=Omcth&margin=%5Bobject%20Object%5D&originHeight=297&originWidth=364&originalType=binary&ratio=1&status=done&style=none)
    如何在页面直接获取Cookie中的值呢? 1. 实现流程分析 **在页面可以使用EL表达式,${cookie.****key.value}**
    key:指的是存储在cookie中的键名称
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491586403-4736f4bb-7002-4270-9669-f0c8d421f322.png#from=url&id=KapuR&margin=%5Bobject%20Object%5D&originHeight=443&originWidth=655&originalType=binary&ratio=1&status=done&style=none)
    **(1)在login.jsp用户名的表单输入框使用value值给表单元素添加默认值,value可以使用****${cookie.username.value}**
    **(2)在login.jsp密码的表单输入框使用value值给表单元素添加默认值,value可以使用****${cookie.password.value}** 1. 具体实现 (1)修改login.jsp页面
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>








    LOGIN IN


    ${login_msg}

    Username:

    Password:


    Remember:






    1. 访问测试,重新访问登录页面,就可以看得用户和密码已经被填充。 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491597346-7bfcff87-5d31-448c-bfc2-72e8de198193.png#from=url&id=JzvjS&margin=%5Bobject%20Object%5D&originHeight=699&originWidth=1218&originalType=binary&ratio=1&status=done&style=none) #### 4.5 用户注册功能 1. 需求 - 注册功能:保存用户信息到数据库 - 验证码功能 - 展示验证码:展示验证码图片,并可以点击切换 - 校验验证码:验证码填写不正确,则注册失败 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491603124-d640f2ab-1cf1-480f-8c06-eec3b0bbd036.png#from=url&id=m0SWS&margin=%5Bobject%20Object%5D&originHeight=591&originWidth=499&originalType=binary&ratio=1&status=done&style=none) 1. 实现流程分析 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491607974-43af58ee-2f0d-4d9f-b7b3-2b8b2db257bd.png#from=url&id=VVtIw&margin=%5Bobject%20Object%5D&originHeight=449&originWidth=1603&originalType=binary&ratio=1&status=done&style=none)
    (1)前端通过表单发送请求和数据给Web层的RegisterServlet
    (2)在RegisterServlet中接收请求和数据[用户名和密码]
    (3)RegisterServlet接收到请求和数据后,调用Service层完成用户信息的保存
    (4)在Service层需要编写UserService类,在类中实现register方法,需要判断用户是否已经存在,如果不存在,则完成用户数据的保存
    (5)在UserMapper接口中,声明两个方法,一个是根据用户名查询用户信息方法,另一个是保存用户信息方法
    (6)在UserService类中保存成功则返回true,失败则返回false,将数据返回给Web层
    (7)Web层获取到结果后,如果返回的是true,则提示注册成功,并转发到登录页面,如果返回false则提示用户名已存在并转发到注册页面 1. 具体实现 (1)Dao层代码参考资料中的内容完成
    (2)编写Service层代码
    public class UserService {
    //1.使用工具类获取SqlSessionFactory
    SqlSessionFactory factory = SqlSessionFactoryUtils.getSqlSessionFactory();
    /**
    * 注册方法
    * @return
    */ public boolean register(User user){
    //2. 获取SqlSession
    SqlSession sqlSession = factory.openSession();
    //3. 获取UserMapper
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    //4. 判断用户名是否存在
    User u = mapper.selectByUsername(user.getUsername()); if(u == null){
    // 用户名不存在,注册
    mapper.add(user);
    sqlSession.commit();
    }
    sqlSession.close(); return u == null; }
    }
    (3)完成页面和Web层的代码编写
    (3.1)将register.html内容修改成register.jsp
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>










    欢迎注册


    已有帐号? 登录















    用户名




    密码




    验证码

    JavaWeb - 图169
    看不清?










    (3.2)编写RegisterServlet
    @WebServlet("/registerServlet")
    public class RegisterServlet extends HttpServlet {
    private UserService service = new UserService(); @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取用户名和密码数据
    String username = request.getParameter("username");
    String password = request.getParameter("password");

    User user = new User();
    user.setUsername(username);
    user.setPassword(password); //2. 调用service 注册
    boolean flag = service.register(user);
    //3. 判断注册成功与否
    if(flag){
    //注册功能,跳转登陆页面
    request.setAttribute("register_msg","注册成功,请登录");
    request.getRequestDispatcher("/login.jsp").forward(request,response);
    }else {
    //注册失败,跳转到注册页面 request.setAttribute("register_msg","用户名已存在");
    request.getRequestDispatcher("/register.jsp").forward(request,response);
    } } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (3.3)需要在页面上展示后台返回的错误信息,需要修改register.jsp
    修改前:
    修改后:${register_msg}
    (3.4)如果注册成功,需要把成功信息展示在登录页面,所以也需要修改login.jsp
    修改前:
    ${login_msg}

    修改后:
    ${login_msg} ${register_msg}

    (3.5)修改login.jsp,将注册跳转地址修改为register.jsp
    修改前:没有账号?
    修改后: 没有账号?
    (3.6)启动测试,
    如果是注册的用户信息已经存在:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491619758-e963ffb3-1e1f-4858-af81-bc7b35dbd42f.png#from=url&id=vE91G&margin=%5Bobject%20Object%5D&originHeight=696&originWidth=770&originalType=binary&ratio=1&status=done&style=none)
    如果注册的用户信息不存在,注册成功:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491625651-8589fbdd-886c-400d-8995-244c35dae5f7.png#from=url&id=ckaQi&margin=%5Bobject%20Object%5D&originHeight=456&originWidth=752&originalType=binary&ratio=1&status=done&style=none) #### 4.6 验证码-展示 1. 需求分析 展示验证码:展示验证码图片,并可以点击切换
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491632425-357cfbc6-f391-49a3-9ede-15b8ccaffa6f.png#from=url&id=ZJaZl&margin=%5Bobject%20Object%5D&originHeight=432&originWidth=377&originalType=binary&ratio=1&status=done&style=none)
    验证码的生成是通过工具类来实现的,具体的工具类参考
    04-资料\1. 登录注册案例\CheckCodeUtil.java
    在该工具类中编写main方法进行测试:
    public static void main(String[] args) throws IOException {
    //生成验证码的图片位置
    OutputStream fos = new FileOutputStream("d://a.jpg");
    //checkCode为最终验证码的数据
    String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, fos, 4);
    System.out.println(checkCode);
    }
    生成完验证码以后,我们就可以知晓: - 验证码就是使用Java代码生成的一张图片 - 验证码的作用:防止机器自动注册,攻击服务器 1. 实现流程分析 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491638734-b819adb6-500d-4828-b979-134290b68fbd.png#from=url&id=NI52M&margin=%5Bobject%20Object%5D&originHeight=506&originWidth=647&originalType=binary&ratio=1&status=done&style=none)
    (1)前端发送请求给CheckCodeServlet
    (2)CheckCodeServlet接收到请求后,生成验证码图片,将图片用Reponse对象的输出流写回到前端
    思考:如何将图片写回到前端浏览器呢?
    (1)Java中已经有工具类生成验证码图片,测试类中只是把图片生成到磁盘上(2)生成磁盘的过程中使用的是OutputStream流,如何把这个图片生成在页面呢?(3)前面在将Reponse对象的时候,它有一个方法可以获取其字节输出流,getOutputStream()(4)综上所述,我们可以把写往磁盘的流对象更好成Response的字节流,即可完成图片响应给前端 1. 具体实现 (1)修改Register.jsp页面,将验证码的图片从后台获取

    验证码


    JavaWeb - 图170
    看不清?


    (2)编写CheckCodeServlet类,用来接收请求生成验证码
    @WebServlet("/checkCodeServlet")
    public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 生成验证码
    ServletOutputStream os = response.getOutputStream();
    String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    } #### 4.7验证码-校验 1. 需求 - 判断程序生成的验证码 和 用户输入的验证码 是否一样,如果不一样,则阻止注册 - 验证码图片访问和提交注册表单是两次请求,所以要将程序生成的验证码存入Session中 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491646507-a988f724-ce34-4cd7-936b-860b3093f8e0.png#from=url&id=ST50e&margin=%5Bobject%20Object%5D&originHeight=432&originWidth=409&originalType=binary&ratio=1&status=done&style=none)
    思考:为什么要把验证码数据存入到Session中呢? - 生成验证码和校验验证码是两次请求,此处就需要在一个会话的两次请求之间共享数据 - 验证码属于安全数据类的,所以我们选中Session来存储验证码数据。 1. 实现流程分析 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639491650414-f17be6ae-a363-48fa-8e3d-f82be0a65c23.png#from=url&id=PCX6b&margin=%5Bobject%20Object%5D&originHeight=459&originWidth=592&originalType=binary&ratio=1&status=done&style=none)
    (1)在CheckCodeServlet中生成验证码的时候,将验证码数据存入Session对象
    (2)前端将验证码和注册数据提交到后台,交给RegisterServlet类
    (3)RegisterServlet类接收到请求和数据后,其中就有验证码,和Session中的验证码进行对比
    (4)如果一致,则完成注册,如果不一致,则提示错误信息 1. 具体实现 (1)修改CheckCodeServlet类,将验证码存入Session对象
    @WebServlet("/checkCodeServlet")
    public class CheckCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 生成验证码
    ServletOutputStream os = response.getOutputStream();
    String checkCode = CheckCodeUtil.outputVerifyImage(100, 50, os, 4); // 存入Session
    HttpSession session = request.getSession();
    session.setAttribute("checkCodeGen",checkCode); } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    (2)在RegisterServlet中,获取页面的和session对象中的验证码,进行对比
    package com.itheima.web; import com.itheima.pojo.User;
    import com.itheima.service.UserService; import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.*;
    import java.io.IOException; @WebServlet("/registerServlet")
    public class RegisterServlet extends HttpServlet {
    private UserService service = new UserService(); @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 获取用户名和密码数据
    String username = request.getParameter("username");
    String password = request.getParameter("password"); User user = new User();
    user.setUsername(username);
    user.setPassword(password); // 获取用户输入的验证码
    String checkCode = request.getParameter("checkCode"); // 程序生成的验证码,从Session获取
    HttpSession session = request.getSession();
    String checkCodeGen = (String) session.getAttribute("checkCodeGen"); // 比对
    if(!checkCodeGen.equalsIgnoreCase(checkCode)){ request.setAttribute("register_msg","验证码错误");
    request.getRequestDispatcher("/register.jsp").forward(request,response); // 不允许注册
    return;
    }
    //2. 调用service 注册
    boolean flag = service.register(user);
    //3. 判断注册成功与否
    if(flag){
    //注册功能,跳转登陆页面 request.setAttribute("register_msg","注册成功,请登录");
    request.getRequestDispatcher("/login.jsp").forward(request,response);
    }else {
    //注册失败,跳转到注册页面 request.setAttribute("register_msg","用户名已存在");
    request.getRequestDispatcher("/register.jsp").forward(request,response);
    } } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    }
    至此,用户的注册登录功能就已经完成了。 ##
    1,Filter ### 1.1 Filter概述 Filter 表示过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。Servlet 我们之前都已经学习过了,Filter和Listener 我们今天都会进行学习。
    **过滤器可以把对资源的请求****拦截下来,从而实现一些特殊的功能。**
    如下图所示,浏览器可以访问服务器上的所有的资源(servlet、jsp、html等)
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652696178-40020c22-1baf-4944-befa-455eb35e4152.png#from=url&id=wDpam&margin=%5Bobject%20Object%5D&originHeight=433&originWidth=1063&originalType=binary&ratio=1&status=done&style=none)
    而在访问到这些资源之前可以使过滤器拦截来下,也就是说在访问资源之前会先经过 Filter,如下图
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652706139-f49e646d-67e5-47dd-a2f6-7151a37b5343.png#from=url&id=P83Ii&margin=%5Bobject%20Object%5D&originHeight=379&originWidth=943&originalType=binary&ratio=1&status=done&style=none)
    拦截器拦截到后可以做什么功能呢?
    ==过滤器一般完成一些通用的操作。==比如每个资源都要写一些代码完成某个功能,我们总不能在每个资源中写这样的代码吧,而此时我们**可以将这些代码写在过滤器中,因为请求每一个资源都要经过过滤器。**
    **我们之前做的品牌数据管理的案例中就已经做了登陆的功能,而如果我们不登录能不能访问到数据呢?我们可以在浏览器直接访问首页 ,可以看到****查询所有****的超链接**
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652770159-0ece66ea-5327-497b-af80-9dc73c0c0547.png#from=url&id=P6F64&margin=%5Bobject%20Object%5D&originHeight=171&originWidth=623&originalType=binary&ratio=1&status=done&style=none)
    当我点击该按钮,居然可以看到品牌的数据
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652775612-62d77a8c-2b1e-45ac-b07b-e1d40d315730.png#from=url&id=kbhP7&margin=%5Bobject%20Object%5D&originHeight=306&originWidth=1542&originalType=binary&ratio=1&status=done&style=none)
    这显然和我们的要求不符。我们希望实现的效果是用户如果登陆过了就跳转到品牌数据展示的页面;如果没有登陆就跳转到登陆页面让用户进行登陆,要实现这个效果需要在每一个资源中都写上这段逻辑,而像这种通用的操作,我们就可以放在过滤器中进行实现。这个就是权限控制,以后我们还会进行细粒度权限控制。过滤器还可以做 统一编码处理、 敏感字符处理 等等… ### 1.2 Filter快速入门 #### 1.2.1 开发步骤 进行 Filter 开发分成以下三步实现 - 定义类,实现 Filter接口,并重写其所有方法 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652797923-b8e9e3ad-440d-43b6-ab7e-fc30aaf8ff85.png#from=url&id=SwYrT&margin=%5Bobject%20Object%5D&originHeight=228&originWidth=812&originalType=binary&ratio=1&status=done&style=none) - 配置Filter拦截资源的路径:在类上定义 @WebFilter 注解。而注解的 value 属性值 /* 表示拦截所有的资源 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652982199-cd08634a-5743-409f-a0b8-d25dc091d282.png#from=url&id=YobNJ&margin=%5Bobject%20Object%5D&originHeight=96&originWidth=696&originalType=binary&ratio=1&status=done&style=none) - **在doFilter方法中输出一句话,并放行** ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652989749-e0c8be2d-7fdf-48e6-b543-9cd92ac596ae.png#from=url&id=pyOrY&margin=%5Bobject%20Object%5D&originHeight=237&originWidth=887&originalType=binary&ratio=1&status=done&style=none)
    上述代码中的 **chain.doFilter(request,response);****就是放行**,也就是让其访问本该访问的资源。 #### 1.2.2 代码演示 创建一个项目,项目下有一个 hello.jsp 页面,项目结构如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639652996220-acac5a8b-da08-4c81-9536-08dc25648af8.png#from=url&id=PyMsu&margin=%5Bobject%20Object%5D&originHeight=352&originWidth=403&originalType=binary&ratio=1&status=done&style=none)
    pom.xml 配置文件内容如下:

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4.0.0 org.example
    filter-demo
    1.0-SNAPSHOT
    war
    8
    8


    javax.servlet
    javax.servlet-api
    3.1.0
    provided




    org.apache.tomcat.maven
    tomcat7-maven-plugin
    2.2

    80





    hello.jsp 页面内容如下:
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>





    hello JSP~




    我们现在在浏览器输入 http://localhost/filter-demo/hello.jsp 访问 hello.jsp 页面,这里是可以访问到 hello.jsp 页面内容的。
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653003893-ee6cc2c8-07c6-49fc-8d91-b72dae5fe136.png#from=url&id=hbYl6&margin=%5Bobject%20Object%5D&originHeight=191&originWidth=629&originalType=binary&ratio=1&status=done&style=none)
    接下来编写过滤器。过滤器是 Web 三大组件之一,所以我们将 filter 创建在 com.itheima.web.filter 包下,起名为 FilterDemo
    javax.servlet`下的`filter
    @WebFilter("/*")
    public class FilterDemo implements Filter { @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("FilterDemo...");
    } @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    } @Override
    public void destroy() {
    }
    }
    重启启动服务器,再次重新访问 hello.jsp 页面,这次发现页面没有任何效果,但是在 idea 的控制台可以看到如下内容
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653009749-70dce179-ad7e-4188-95f5-d0d5b7524eeb.png#from=url&id=xAIlx&margin=%5Bobject%20Object%5D&originHeight=223&originWidth=1295&originalType=binary&ratio=1&status=done&style=none)
    上述效果说明 FilterDemo 这个过滤器的 doFilter() 方法执行了,但是为什么在浏览器上看不到 hello.jsp 页面的内容呢?这是因为在 doFilter() 方法中添加放行的方法才能访问到 hello.jsp 页面。那就在 doFilter() 方法中添加放行的代码
    //放行
    chain.doFilter(request,response);
    再次重启服务器并访问 hello.jsp 页面,发现这次就可以在浏览器上看到页面效果。
    **FilterDemo****过滤器完整代码如下:**
    @WebFilter("/*")
    public class FilterDemo implements Filter { @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    System.out.println("1.FilterDemo...");
    //放行
    chain.doFilter(request,response);
    } @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    } @Override
    public void destroy() {
    }
    } ### 1.3 Filter执行流程 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653015565-18b24f1f-2ac5-49dc-ad85-c262d3b7e7cb.png#from=url&id=PBMUN&margin=%5Bobject%20Object%5D&originHeight=386&originWidth=929&originalType=binary&ratio=1&status=done&style=none)
    如上图是使用过滤器的流程,我们通过以下问题来研究过滤器的执行流程: - 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?从上图就可以看出肯定 会 回到Filter中 - 如果回到Filter中,是重头执行还是执行放行后的逻辑呢?如果是重头执行的话,就意味着 放行前逻辑 会被执行两次,肯定不会这样设计了;所以访问完资源后,会回到 放行后逻辑,执行该部分代码。 通过上述的说明,我们就可以总结**Filter的执行流程如下:**
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653020691-5dc218a2-ab29-466c-b0b6-ac101ccc3f04.png#from=url&id=YKqoE&margin=%5Bobject%20Object%5D&originHeight=66&originWidth=994&originalType=binary&ratio=1&status=done&style=none)
    接下来我们通过代码验证一下,在 doFilter() 方法前后都加上输出语句,如下
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653027096-647449b9-53cb-4f01-bbac-8e33049e5a4d.png#from=url&id=StNrw&margin=%5Bobject%20Object%5D&originHeight=339&originWidth=1075&originalType=binary&ratio=1&status=done&style=none)
    同时在 hello.jsp 页面加上输出语句,如下
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653032167-e29d8871-3475-40af-b384-3c7719102621.png#from=url&id=NkRx9&margin=%5Bobject%20Object%5D&originHeight=195&originWidth=579&originalType=binary&ratio=1&status=done&style=none)
    执行访问该资源打印的顺序是按照我们标记的标号进行打印的话,说明我们上边总结出来的流程是没有问题的。启动服务器访问 hello.jsp 页面,在控制台打印的内容如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653038425-7df6c643-e8e0-462c-8867-493258fca266.png#from=url&id=UmsqN&margin=%5Bobject%20Object%5D&originHeight=192&originWidth=1132&originalType=binary&ratio=1&status=done&style=none)
    以后我们可以将对请求进行处理的代码放在放行之前进行处理,而如果请求完资源后还要对响应的数据进行处理时可以在放行后进行逻辑处理。 ### 1.4 Filter拦截路径配置 **拦截路径表示 Filter 会对请求的哪些资源进行拦截****,使用** **@WebFilter****注解进行配置****。如:****@WebFilter("拦截路径")**
    拦截路径有如下四种配置方式: - **拦截具体的资源****:/index.jsp:只有访问index.jsp时才会被拦截** - **目录拦截****:/user/*:访问/user下的所有资源,都会被拦截** - **后缀名拦截****:*.jsp:访问后缀名为jsp的资源,都会被拦截** - **拦截所有****:/*:访问所有资源,都会被拦截** 通过上面拦截路径的学习,大家会发现拦截路径的配置方式和 Servlet 的请求资源路径配置方式一样,但是表示的含义不同。 ### 1.5 过滤器链 #### 1.5.1 概述 过滤器链是指在一个Web应用,可以配置多个过滤器,这多个过滤器称为过滤器链。
    如下图就是一个过滤器链,我们学习过滤器链主要是学习过滤器链执行的流程
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653063941-d411bd3f-b61f-4a6c-a32d-094e309f3e06.png#from=url&id=Qb2co&margin=%5Bobject%20Object%5D&originHeight=350&originWidth=1155&originalType=binary&ratio=1&status=done&style=none)
    上图中的过滤器链执行是按照以下流程执行: 1. 执行 Filter1 的放行前逻辑代码 1. 执行 Filter1 的放行代码 1. 执行 Filter2 的放行前逻辑代码 1. 执行 Filter2 的放行代码 1. 访问到资源 1. 执行 Filter2 的放行后逻辑代码 1. 执行 Filter1 的放行后逻辑代码 以上流程串起来就像一条链子,故称之为过滤器链。 #### 1.5.2 代码演示 - 编写第一个过滤器 FilterDemo ,配置成拦截所有资源 @WebFilter("/*")
    public class FilterDemo implements Filter { @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //1. 放行前,对 request数据进行处理
    System.out.println("1.FilterDemo...");
    //放行
    chain.doFilter(request,response);
    //2. 放行后,对Response 数据进行处理
    System.out.println("3.FilterDemo...");
    } @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    } @Override
    public void destroy() {
    }
    } - 编写第二个过滤器 FilterDemo2 ,配置成拦截所有资源 @WebFilter("/*")
    public class FilterDemo2 implements Filter { @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //1. 放行前,对 request数据进行处理
    System.out.println("2.FilterDemo...");
    //放行
    chain.doFilter(request,response);
    //2. 放行后,对Response 数据进行处理
    System.out.println("4.FilterDemo...");
    } @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    } @Override
    public void destroy() {
    }
    } - 修改 hello.jsp 页面中脚本的输出语句 <%@ page contentType="text/html;charset=UTF-8" language="java" %>





    hello JSP~


    <%
    System.out.println("3.hello jsp");
    %>

    - 启动服务器,在浏览器输入 http://localhost/filter-demo/hello.jsp 进行测试,在控制台打印内容如下 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653083700-4826e54f-dc9f-4011-9ef4-4b337f32f4c9.png#from=url&id=vxlxs&margin=%5Bobject%20Object%5D&originHeight=279&originWidth=1158&originalType=binary&ratio=1&status=done&style=none) 从结果可以看到确实是按照我们之前说的执行流程进行执行的。 #### 1.5.3 问题 上面代码中**为什么是先执行****FilterDemo****,后执行****FilterDemo2****呢**?
    我们现在使用的是**注解配置Filter,而这种配置方式的优先级是****按照过滤器类名(字符串)的自然排序。**
    比如有如下两个名称的过滤器 : **BFilterDemo****和****AFilterDemo****。那****一定是** **AFilterDemo****过滤器先执行****。** ### 1.6 案例 #### 1.6.1 需求 访问服务器资源时,需要先进行登录验证,如果没有登录,则自动跳转到登录页面 #### 1.6.2 分析 我们要实现该功能是在每一个资源里加入登陆状态校验的代码吗?显然是不需要的,只需要写一个 Filter ,在该过滤器中进行登陆状态校验即可。而在该 Filter 中逻辑如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653088462-ed520aa4-6f4a-4a0b-8ba0-d974e5c73391.png#from=url&id=ixjFh&margin=%5Bobject%20Object%5D&originHeight=357&originWidth=1261&originalType=binary&ratio=1&status=done&style=none) #### 1.6.3 代码实现 ##### 1.6.3.1 创建Filter 在 brand-demo 工程创建 com.itheima.web.filter 包,在该下创建名为 LoginFilter 的过滤器
    @WebFilter("/*")
    public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {

    } public void init(FilterConfig config) throws ServletException {
    } public void destroy() {
    }
    } ##### 1.6.3.2 编写逻辑代码 在 doFilter() 方法中编写登陆状态校验的逻辑代码。
    我们首先需要从 session 对象中获取用户信息,但是 ServletRequest 类型的 requset 对象没有获取 session 对象的方法,所以此时需要将 request对象强转成 HttpServletRequest 对象。
    HttpServletRequest req = (HttpServletRequest) request;
    然后完成以下逻辑 - 获取Session对象 - 从Session对象中获取名为 user 的数据 - 判断获取到的数据是否是 null - 如果不是,说明已经登陆,放行 - 如果是,说明尚未登陆,将提示信息存储到域对象中并跳转到登陆页面 代码如下:
    @WebFilter("/*")
    public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest req = (HttpServletRequest) request;

    //1. 判断session中是否有user
    HttpSession session = req.getSession();
    Object user = session.getAttribute("user"); //2. 判断user是否为null
    if(user != null){
    // 登录过了
    //放行
    chain.doFilter(request, response);
    }else {
    // 没有登陆,存储提示信息,跳转到登录页面 req.setAttribute("login_msg","您尚未登陆!");
    req.getRequestDispatcher("/login.jsp").forward(req,response);
    }
    } public void init(FilterConfig config) throws ServletException {
    } public void destroy() {
    }
    } ##### 1.6.3.3 测试并抛出问题 在浏览器上输入 http://localhost:8080/brand-demo/ ,可以看到如下页面效果
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653096969-e8cac6b5-6c46-475d-9231-4991fab2091d.png#from=url&id=rHZly&margin=%5Bobject%20Object%5D&originHeight=459&originWidth=620&originalType=binary&ratio=1&status=done&style=none)
    从上面效果可以看出没有登陆确实是跳转到登陆页面了,但是登陆页面为什么展示成这种效果了呢? ##### 1.6.3.4 问题分析及解决 因为登陆页面需要 css/login.css 这个文件进行样式的渲染,下图是登陆页面引入的css文件图解
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639653101958-4852e9cf-0fc2-400d-8e3e-2f4f281bf15d.png#from=url&id=UseRf&margin=%5Bobject%20Object%5D&originHeight=136&originWidth=698&originalType=binary&ratio=1&status=done&style=none)
    而在请求这个css资源时被过滤器拦截,就相当于没有加载到样式文件导致的。解决这个问题,只需要对所以的登陆相关的资源进行放行即可。还有一种情况就是当我没有用户信息时需要进行注册,而注册时也希望被过滤器放行。
    综上,我们需要在判断session中是否包含用户信息之前,应该加上对登陆及注册相关资源放行的逻辑处理
    //判断访问资源路径是否和登录注册相关
    //1,在数组中存储登陆和注册相关的资源路径
    String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
    //2,获取当前访问的资源路径
    String url = req.getRequestURL().toString(); //3,遍历数组,获取到每一个需要放行的资源路径
    for (String u : urls) {
    //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
    /*
    比如当前访问的资源路径是 /brand-demo/login.jsp
    而字符串 /brand-demo/login.jsp 包含了 字符串 /login.jsp ,所以这个字符串就需要放行
    */
    if(url.contains(u)){
    //找到了,放行
    chain.doFilter(request, response);
    //break;
    return;
    }
    } ##### 1.6.3.5 过滤器完整代码 @WebFilter("/*")
    public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
    HttpServletRequest req = (HttpServletRequest) request;

    //判断访问资源路径是否和登录注册相关
    //1,在数组中存储登陆和注册相关的资源路径
    String[] urls = {"/login.jsp","/imgs/","/css/","/loginServlet","/register.jsp","/registerServlet","/checkCodeServlet"};
    //2,获取当前访问的资源路径
    String url = req.getRequestURL().toString(); //3,遍历数组,获取到每一个需要放行的资源路径
    for (String u : urls) {
    //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
    /*
    比如当前访问的资源路径是 /brand-demo/login.jsp
    而字符串 /brand-demo/login.jsp 包含了 字符串 /login.jsp ,所以这个字符串就需要放行
    */
    if(url.contains(u)){
    //找到了,放行
    chain.doFilter(request, response);
    //break;
    return;
    }
    }

    //1. 判断session中是否有user
    HttpSession session = req.getSession();
    Object user = session.getAttribute("user"); //2. 判断user是否为null
    if(user != null){
    // 登录过了
    //放行
    chain.doFilter(request, response);
    }else {
    // 没有登陆,存储提示信息,跳转到登录页面 req.setAttribute("login_msg","您尚未登陆!");
    req.getRequestDispatcher("/login.jsp").forward(req,response);
    }
    } public void init(FilterConfig config) throws ServletException {
    } public void destroy() {
    }
    } ## 2,Listener ### 2.1 概述 - Listener 表示监听器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一。 - 监听器可以监听就是在 application,**session****,****request** 三个对象创建、销毁或者往其中添加修改删除属性时自动执行代码的功能组件。request 和 session 我们学习过。而 application 是 ServletContext 类型的对象。ServletContext 代表整个web应用,在服务器启动的时候,tomcat会自动创建该对象。在服务器关闭时会自动销毁该对象。 ### 2.2 分类 JavaWeb 提供了8个监听器:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639659746138-14eb9760-51b7-4cd9-8be9-40eb0f8c43e9.png#from=url&id=qkaQ8&margin=%5Bobject%20Object%5D&originHeight=400&originWidth=1115&originalType=binary&ratio=1&status=done&style=none)
    这里面只有 ServletContextListener 这个监听器后期我们会接触到,ServletContextListener 是用来监听 ServletContext 对象的创建和销毁。
    ServletContextListener 接口中有以下两个方法 - void contextInitialized(ServletContextEvent sce):ServletContext 对象被创建了会自动执行的方法 - void contextDestroyed(ServletContextEvent sce):ServletContext 对象被销毁时会自动执行的方法 ### 2.3 代码演示 我们只演示一下 ServletContextListener 监听器 - 定义一个类,实现ServletContextListener 接口 - 重写所有的抽象方法 - 使用 @WebListener 进行配置 代码如下:
    @WebListener
    public class ContextLoaderListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
    //加载资源
    System.out.println("ContextLoaderListener...");
    } @Override
    public void contextDestroyed(ServletContextEvent sce) {
    //释放资源
    }
    }
    启动服务器,就可以在启动的日志信息中看到 contextInitialized() 方法输出的内容,同时也说明了 ServletContext 对象在服务器启动的时候被创建了。 ## 3,Ajax ### 3.1 概述 (Asynchronous JavaScript And XML):异步的 JavaScript 和 XML。AJAX
    我们先来说概念中的 JavaScript 和 XML,JavaScript 表明该技术和前端相关;XML**是指以此进行数据交换**。而这两个我们之前都学习过。 #### 3.1.1 作用 AJAX 作用有以下两方面: 1. **与服务器进行数据交换**:通过AJAX可以给服务器发送请求,服务器将数据直接响应回给浏览器。如下图 我们先来看之前做功能的流程,如下图:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660047365-c35c51e5-59f3-4134-8dc3-f89a10b0e75f.png#from=url&id=xlTl2&margin=%5Bobject%20Object%5D&originHeight=242&originWidth=1325&originalType=binary&ratio=1&status=done&style=none)
    如上图,Servlet 调用完业务逻辑层后将数据存储到域对象中,然后跳转到指定的 jsp 页面,在 页面上使用 EL表达式 和 JSTL 标签库进行数据的展示。
    而我们学习了AJAX 后,就可以使用AJAX和服务器进行通信,以达到使用 **HTML+AJAX****来替换JSP**页面了。如下图,浏览器发送请求servlet,servlet 调用完业务逻辑层后将数据直接响应回给浏览器页面,页面使用 HTML 来进行数据展示。
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660055902-aee2ee78-6fe8-46d4-af9f-033aa9e84a4c.png#from=url&id=iyOgd&margin=%5Bobject%20Object%5D&originHeight=246&originWidth=1309&originalType=binary&ratio=1&status=done&style=none) 1. **异步交互**:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用校验,等等… ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660060868-17fa55e3-a4b7-4664-8352-9559180da51e.png#from=url&id=Kuxzl&margin=%5Bobject%20Object%5D&originHeight=272&originWidth=622&originalType=binary&ratio=1&status=done&style=none)
    上图所示的效果我们经常见到,在我们输入一些关键字(例如 奥运)后就会在下面联想出相关的内容,而联想出来的这部分数据肯定是存储在百度的服务器上,而我们并没有看出页面重新刷新,这就是 更新局部页面 的效果。再如下图:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660065713-394bb7a5-f83f-4a9b-a1c9-5eee70b54ed2.png#from=url&id=jwLOy&margin=%5Bobject%20Object%5D&originHeight=236&originWidth=471&originalType=binary&ratio=1&status=done&style=none)
    我们在用户名的输入框输入用户名,当输入框一失去焦点,如果用户名已经被占用就会在下方展示提示的信息;在这整个过程中也没有页面的刷新,只是在局部展示出了提示信息,这就是 更新局部页面 的效果。 #### 3.1.2 同步和异步 知道了局部刷新后,接下来我们再聊聊同步和异步: - **同步发送请求过程如下** ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660070903-de008b59-cbd1-4440-ba13-c25eb92d2a08.png#from=url&id=MEBlh&margin=%5Bobject%20Object%5D&originHeight=199&originWidth=972&originalType=binary&ratio=1&status=done&style=none)
    浏览器页面在发送请求给服务器,在服务器处理请求的过程中,浏览器页面不能做其他的操作。只能等到服务器响应结束后才能,浏览器页面才能继续做其他的操作。 - **异步发送请求过程如下** ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660079374-a879a758-5b9b-4ba1-8625-1c2cb9da52e9.png#from=url&id=yPyZp&margin=%5Bobject%20Object%5D&originHeight=211&originWidth=962&originalType=binary&ratio=1&status=done&style=none) 浏览器页面发送请求给服务器,在服务器处理请求的过程中,浏览器页面还可以做其他的操作。 ### 3.2 快速入门 #### 3.2.1 服务端实现 在项目的创建 com.itheima.web.servlet ,并在该包下创建名为 AjaxServlet 的servlet
    @WebServlet("/ajaxServlet")
    public class AjaxServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 响应数据
    response.getWriter().write("hello ajax~");
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    } #### 3.2.2 客户端实现 在 webapp 下创建名为 01-ajax-demo1.html 的页面,在该页面书写 ajax 代码 - 创建核心对象,不同的浏览器创建的对象是不同的 var xhttp;
    if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
    } else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    } - 发送请求 //建立连接
    xhttp.open("GET", "http://localhost:8080/ajax-demo/ajaxServlet");
    //发送请求
    xhttp.send(); - 获取响应 xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    // 通过 this.responseText 可以获取到服务端响应的数据
    alert(this.responseText);
    }
    };
    **完整代码如下:**








    #### 3.2.3 测试 在浏览器地址栏输入 http://localhost:8080/ajax-demo/01-ajax-demo1.html ,在 01-ajax-demo1.html加载的时候就会发送 ajax 请求,效果如下
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660093416-e4381187-6b81-4fc9-ac2d-e15c83071852.png#from=url&id=GUWzQ&margin=%5Bobject%20Object%5D&originHeight=357&originWidth=794&originalType=binary&ratio=1&status=done&style=none)
    我们可以通过 开发者模式 查看发送的 AJAX 请求。在浏览器上按 F12 快捷键
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660098734-17a22fca-dd3e-4233-815e-82d2cc7bd5db.png#from=url&id=ICBDJ&margin=%5Bobject%20Object%5D&originHeight=317&originWidth=1223&originalType=binary&ratio=1&status=done&style=none)
    这个是查看所有的请求,如果我们只是想看 异步请求的话,点击上图中 All 旁边的 XHR,会发现只展示 Type 是 xhr 的请求。如下图:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660103394-c59847e7-0165-4f54-b95c-65a4cb5bc9d4.png#from=url&id=yiCEB&margin=%5Bobject%20Object%5D&originHeight=281&originWidth=1190&originalType=binary&ratio=1&status=done&style=none) ### 3.3 案例 需求:在完成用户注册时,当用户名输入框失去焦点时,校验用户名是否在数据库已存在
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660107751-f3dbe0ef-ae15-4890-b685-4f17a70b0f50.png#from=url&id=Y0fr1&margin=%5Bobject%20Object%5D&originHeight=639&originWidth=1365&originalType=binary&ratio=1&status=done&style=none) #### 3.3.1 分析 - **前端完成的逻辑** 1. 给用户名输入框绑定光标失去焦点事件 onblur 1. 发送 ajax请求,携带username参数 1. 处理响应:是否显示提示信息 - **后端完成的逻辑** 1. 接收用户名 1. 调用service查询User。此案例是为了演示前后端异步交互,所以此处我们不做业务逻辑处理 1. 返回标记 整体流程如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660112539-6c7f4960-5e7b-4f54-a904-3ed841918210.png#from=url&id=H3uLe&margin=%5Bobject%20Object%5D&originHeight=294&originWidth=692&originalType=binary&ratio=1&status=done&style=none) #### 3.3.2 后端实现 在 com.ithiema.web.servlet 包中定义名为 SelectUserServlet 的servlet。代码如下:
    @WebServlet("/selectUserServlet")
    public class SelectUserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 接收用户名
    String username = request.getParameter("username");
    //2. 调用service查询User对象,此处不进行业务逻辑处理,直接给 flag 赋值为 true,表明用户名占用
    boolean flag = true;
    //3. 响应标记
    response.getWriter().write("" + flag);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    } #### 3.3.3 前端实现 将 04-资料\1. 验证用户名案例\1. 静态页面 下的文件整体拷贝到项目下 webapp 下。并在 register.html 页面的 body 结束标签前编写 script 标签,在该标签中实现如下逻辑
    **第一步:给用户名输入框绑定光标失去焦点事件****onblur**
    //1. 给用户名输入框绑定 失去焦点事件
    document.getElementById("username").onblur = function () {

    }
    **第二步:发送 ajax请求,携带username参数**
    在 第一步 绑定的匿名函数中书写发送 ajax 请求的代码
    //2. 发送ajax请求
    //2.1. 创建核心对象
    var xhttp;
    if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
    } else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    //2.2. 发送请求
    xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet);
    xhttp.send(); //2.3. 获取响应
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    //处理响应的结果
    }
    };
    由于我们发送的是 GET 请求,所以需要在 URL 后拼接从输入框获取的用户名数据。而我们在 第一步 绑定的匿名函数中通过以下代码可以获取用户名数据
    // 获取用户名的值
    var username = this.value; //this : 给谁绑定的事件,this就代表谁
    而携带数据需要将 URL 修改为:
    xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username="+username);
    **第三步:处理响应:是否显示提示信息**
    当 this.readyState == 4 && this.status == 200 条件满足时,说明已经成功响应数据了。
    此时需要判断响应的数据是否是 "true" 字符串,如果是说明用户名已经占用给出错误提示;如果不是说明用户名未被占用清除错误提示。代码如下
    //判断
    if(this.responseText == "true"){
    //用户名存在,显示提示信息
    document.getElementById("username_err").style.display = '';
    }else {
    //用户名不存在 ,清楚提示信息
    document.getElementById("username_err").style.display = 'none';
    }
    **综上所述,前端完成代码如下:**
    //1. 给用户名输入框绑定 失去焦点事件
    document.getElementById("username").onblur = function () {
    //2. 发送ajax请求
    // 获取用户名的值
    var username = this.value; //2.1. 创建核心对象
    var xhttp;
    if (window.XMLHttpRequest) {
    xhttp = new XMLHttpRequest();
    } else {
    // code for IE6, IE5
    xhttp = new ActiveXObject("Microsoft.XMLHTTP");
    }
    //2.2. 发送请求
    xhttp.open("GET", "http://localhost:8080/ajax-demo/selectUserServlet?username="+username);
    xhttp.send(); //2.3. 获取响应
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    //alert(this.responseText);
    //判断
    if(this.responseText == "true"){
    //用户名存在,显示提示信息
    document.getElementById("username_err").style.display = '';
    }else {
    //用户名不存在 ,清楚提示信息
    document.getElementById("username_err").style.display = 'none';
    }
    }
    };
    } ## 4,Axios Axios 对原生的AJAX进行封装,简化书写。
    Axios官网是:https://www.axios-http.cn ### 4.1 基本使用 axios 使用是比较简单的,分为以下两步: - 引入 axios 的 js 文件
    • 使用axios 发送请求,并获取响应结果
      • 发送 get 请求

    axios({
    method:”get”,
    url:”http://localhost:8080/ajax-demo1/aJAXDemo1?username=zhangsan
    }).then(function (resp){
    alert(resp.data);
    })

    • 发送 post 请求

    axios({
    method:”post”,
    url:”http://localhost:8080/ajax-demo1/aJAXDemo1“,
    data:”username=zhangsan”
    }).then(function (resp){
    alert(resp.data);
    });
    axios() 是用来发送异步请求的,小括号中使用 js 对象传递请求相关的参数:

    • method 属性:用来设置请求方式的。取值为 get 或者 post。
    • url 属性:用来书写请求的资源路径。如果是 get 请求,需要将请求参数拼接到路径的后面,格式为: url?参数名=参数值&参数名2=参数值2。
    • data 属性:作为请求体被发送的数据。也就是说如果是 post 请求的话,数据需要作为 data 属性的值。

    then() 需要传递一个匿名函数。我们将 then() 中传递的匿名函数称为 回调函数,意思是该匿名函数在发送请求时不会被调用,而是在成功响应后调用的函数。而该回调函数中的 resp 参数是对响应的数据进行封装的对象,通过 resp.data 可以获取到响应的数据。

    4.2 快速入门

    4.2.1 后端实现

    定义一个用于接收请求的servlet,代码如下:
    @WebServlet(“/axiosServlet”)
    public class AxiosServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println(“get…”);
    //1. 接收请求参数
    String username = request.getParameter(“username”);
    System.out.println(username);
    //2. 响应数据
    response.getWriter().write(“hello Axios~”);
    }

    @Override<br />    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {<br />        System.out.println("post...");<br />        this.doGet(request, response);<br />    }<br />}
    

    4.2.2 前端实现

    • 引入 js 文件
    • 发送 ajax 请求
      • get 请求

    axios({
    method:”get”,
    url:”http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan
    }).then(function (resp) {
    alert(resp.data);
    })

    • post 请求

    axios({
    method:”post”,
    url:”http://localhost:8080/ajax-demo/axiosServlet“,
    data:”username=zhangsan”
    }).then(function (resp) {
    alert(resp.data);
    })
    整体页面代码如下:
    <!DOCTYPE html>








    ### 4.3 请求方法别名 为了方便起见, Axios 已经为所有支持的请求方法提供了别名。如下: - get 请求 : axios.get(url[,config]) - delete 请求 : axios.delete(url[,config]) - head 请求 : axios.head(url[,config]) - options 请求 : axios.option(url[,config]) - post 请求:axios.post(url[,data[,config]) - put 请求:axios.put(url[,data[,config]) - patch 请求:axios.patch(url[,data[,config]) 而我们只关注 get 请求和 post 请求。
    入门案例中的 get 请求代码可以改为如下:
    axios.get("http://localhost:8080/ajax-demo/axiosServlet?username=zhangsan").then(function (resp) {
    alert(resp.data);
    });
    入门案例中的 post 请求代码可以改为如下:
    axios.post("http://localhost:8080/ajax-demo/axiosServlet","username=zhangsan").then(function (resp) {
    alert(resp.data);
    }) ## 5,JSON ### 5.1 概述 概念:。JavaScript 对象表示法.JavaScript Object Notation
    如下是 JavaScript 对象的定义格式:
    {
    name:"zhangsan",
    age:23,
    city:"北京"
    }
    接下来我们再看看 JSON 的格式:
    {
    "name":"zhangsan",
    "age":23,
    "city":"北京"
    }
    通过上面 js 对象格式和 json 格式进行对比,发现两个格式特别像。只不过 js 对象中的属性名可以使用引号(可以是单引号,也可以是双引号);而 json 格式中的键要求必须使用双引号括起来,这是 json 格式的规定。json 格式的数据有什么作用呢?
    作用:由于其语法格式简单,层次结构鲜明,现多用于作为数据载体,在网络中进行数据传输。如下图所示就是服务端给浏览器响应的数据,这个数据比较简单,如果现需要将 JAVA 对象中封装的数据响应回给浏览器的话,应该以何种数据传输呢?
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660158334-5bbb01a7-34c6-4fab-b7d1-ddbf2895be2e.png#from=url&id=uUGmN&margin=%5Bobject%20Object%5D&originHeight=81&originWidth=576&originalType=binary&ratio=1&status=done&style=none)
    大家还记得 ajax 的概念吗? 是 异步的 JavaScript 和 xml。这里的 xml就是以前进行数据传递的方式,如下:

    张三
    23
    北京

    再看 json 描述以上数据的写法:
    {
    "name":"张三",
    "age":23,
    "city":"北京"
    }
    上面两种格式进行对比后就会发现 json 格式数据的简单,以及所占的字节数少等优点。 ### 5.2 JSON 基础语法 #### 5.2.1 定义格式 JSON 本质就是一个字符串,但是该字符串内容是有一定的格式要求的。 定义格式如下:
    var 变量名 = '{"key":value,"key":value,...}';
    JSON 串的键要求必须使用双引号括起来,而值根据要表示的类型确定。value 的数据类型分为如下 - 数字(整数或浮点数) - 字符串(使用双引号括起来) - 逻辑值(true或者false) - 数组(在方括号中) - 对象(在花括号中) - null 示例:
    var jsonStr = '{"name":"zhangsan","age":23,"addr":["北京","上海","西安"]}' #### 5.2.2 代码演示 创建一个页面,在该页面的


    通过浏览器打开,页面效果如下图所示
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660148020-71f37669-d8df-4e47-aa81-184653dcebd2.png#from=url&id=xs2Sw&margin=%5Bobject%20Object%5D&originHeight=216&originWidth=481&originalType=binary&ratio=1&status=done&style=none)
    现在我们需要获取到该 JSON 串中的 name 属性值,应该怎么处理呢?
    如果它是一个 js 对象,我们就可以通过 js对象.属性名 的方式来获取数据。JS 提供了一个对象 JSON ,该对象有如下两个方法: - parse(str) :将 JSON串转换为 js 对象。使用方式是: var jsObject = JSON.parse(jsonStr); - stringify(obj) :将 js 对象转换为 JSON 串。使用方式是:var jsonStr = JSON.stringify(jsObject) 代码演示:









    #### 5.2.3 发送异步请求携带参数 后面我们使用 axios 发送请求时,如果要携带复杂的数据时都会以 JSON 格式进行传递,如下
    axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data:"username=zhangsan"
    }).then(function (resp) {
    alert(resp.data);
    })
    请求参数不可能由我们自己拼接字符串吧?肯定不用,可以提前定义一个 js 对象,用来封装需要提交的参数,然后使用 JSON.stringify(js对象) 转换为 JSON 串,再将该 JSON 串作为 axios 的 data 属性值进行请求参数的提交。如下:
    var jsObject = {name:"张三"}; axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data: JSON.stringify(jsObject)
    }).then(function (resp) {
    alert(resp.data);
    })
    而 axios 是一个很强大的工具。我们只需要将需要提交的参数封装成 js 对象,并将该 js 对象作为 axios 的 data 属性值进行,它会自动将 js 对象转换为 JSON 串进行提交。如下:
    var jsObject = {name:"张三"}; axios({
    method:"post",
    url:"http://localhost:8080/ajax-demo/axiosServlet",
    data:jsObject //这里 axios 会将该js对象转换为 json 串的
    }).then(function (resp) {
    alert(resp.data);
    })
    注意: - js 提供的 JSON 对象我们只需要了解一下即可。因为 axios 会自动对 js 对象和 JSON 串进行想换转换。 - 发送异步请求时,如果请求参数是 JSON 格式,那请求方式必须是 POST。因为 JSON 串需要放在请求体中。 ### 5.3 JSON串和Java对象的相互转换 学习完 json 后,接下来聊聊 json 的作用。以后我们会以 json 格式的数据进行前后端交互。前端发送请求时,如果是复杂的数据就会以 json 提交给后端;而后端如果需要响应一些复杂的数据时,也需要以 json 格式将数据响应回给浏览器。
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660167756-04c035bc-e5aa-427e-8033-50b83913f612.png#from=url&id=Eq3h9&margin=%5Bobject%20Object%5D&originHeight=354&originWidth=780&originalType=binary&ratio=1&status=done&style=none)
    在后端我们就需要重点学习以下两部分操作: - 请求数据:JSON字符串转为Java对象 - 响应数据:Java对象转为JSON字符串 接下来给大家介绍一套 API,可以实现上面两部分操作。这套 API 就是 Fastjson #### 5.3.1 Fastjson 概述 Fastjson 是阿里巴巴提供的一个Java语言编写的高性能功能完善的 JSON 库,是目前Java语言中最快的 JSON 库,可以实现 Java 对象和 JSON 字符串的相互转换。 #### 5.3.2 Fastjson 使用 Fastjson 使用也是比较简单的,分为以下三步完成 1. **导入坐标**
    com.alibaba
    fastjson
    1.2.62
    1. **Java对象转JSON**将 Java 对象转换为 JSON 串,只需要使用 Fastjson 提供的 JSON 类中的 toJSONString() 静态方法即可。 String jsonStr = JSON.toJSONString(obj); 1. **JSON字符串转Java对象**将 json 转换为 Java 对象,只需要使用 Fastjson 提供的 JSON 类中的 parseObject() 静态方法即可。 User user = JSON.parseObject(jsonStr, User.class); #### 5.3.3 代码演示 - 引入坐标 - 创建一个类,专门用来测试 Java 对象和 JSON 串的相互转换,代码如下: public class FastJsonDemo { public static void main(String[] args) {
    //1. 将Java对象转为JSON字符串
    User user = new User();
    user.setId(1);
    user.setUsername("zhangsan");
    user.setPassword("123"); String jsonString = JSON.toJSONString(user);
    System.out.println(jsonString);//{"id":1,"password":"123","username":"zhangsan"} //2. 将JSON字符串转为Java对象
    User u = JSON.parseObject("{\"id\":1,\"password\":\"123\",\"username\":\"zhangsan\"}", User.class);
    System.out.println(u);
    }
    } ## 6,案例 ### 6.1 需求 使用Axios + JSON 完成品牌列表数据查询和添加。页面效果还是下图所示:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660175033-718f03f7-c26f-418a-844b-687f6d04e9d8.png#from=url&id=HumIA&margin=%5Bobject%20Object%5D&originHeight=287&originWidth=1312&originalType=binary&ratio=1&status=done&style=none) ### 6.2 查询所有功能 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660179253-a196f299-b32b-4ebe-be32-0811517cb57d.png#from=url&id=xE1J7&margin=%5Bobject%20Object%5D&originHeight=407&originWidth=1431&originalType=binary&ratio=1&status=done&style=none)
    如上图所示就该功能的整体流程。前后端需以 JSON 格式进行数据的传递;由于此功能是查询所有的功能,前端发送 ajax 请求不需要携带参数,而后端响应数据需以如下格式的 json 数据
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660183938-1e2dd1a0-e560-49ba-a15d-a8d23ae781ec.png#from=url&id=dTkeS&margin=%5Bobject%20Object%5D&originHeight=90&originWidth=1900&originalType=binary&ratio=1&status=done&style=none) #### 6.2.1 环境准备 将 02-AJAX\04-资料\3. 品牌列表案例\初始工程 下的 brand-demo 工程拷贝到我们自己 工作空间 ,然后再将项目导入到我们自己的 Idea 中。工程目录结构如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660188649-561068ce-0900-4828-87c4-24803c3b447b.png#from=url&id=NxIMq&margin=%5Bobject%20Object%5D&originHeight=702&originWidth=404&originalType=binary&ratio=1&status=done&style=none)
    注意: - 在给定的原始工程中已经给定一些代码。而在此案例中我们只关注前后端交互代码实现 - 要根据自己的数据库环境去修改连接数据库的信息,在 mybatis-config.xml 核心配置文件中修改 #### 6.2.2 后端实现 在 com.itheima.web 包下创建名为 SelectAllServlet 的 servlet,具体的逻辑如下: - 调用 service 的 selectAll() 方法进行查询所有的逻辑处理 - 将查询到的集合数据转换为 json 数据。我们将此过程称为 序列化;如果是将 json 数据转换为 Java 对象,我们称之为 反序列化 - 将 json 数据响应回给浏览器。这里一定要设置响应数据的类型及字符集 response.setContentType("text/json;charset=utf-8"); SelectAllServlet 代码如下:
    @WebServlet("/selectAllServlet")
    public class SelectAllServlet extends HttpServlet {
    private BrandService brandService = new BrandService(); @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //1. 调用Service查询
    List brands = brandService.selectAll(); //2. 将集合转换为JSON数据 序列化
    String jsonString = JSON.toJSONString(brands); //3. 响应数据 application/json text/json
    response.setContentType("text/json;charset=utf-8");
    response.getWriter().write(jsonString);
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    } #### 6.2.3 前端实现 1. **引入 js 文件** 在 brand.html 页面引入 axios 的 js 文件
    1. 绑定**页面加载完毕**事件

    在 brand.html 页面绑定加载完毕事件,该事件是在页面加载完毕后被触发,代码如下
    window.onload = function() {

    }

    1. 发送异步请求

    在页面加载完毕事件绑定的匿名函数中发送异步请求,代码如下:
    //2. 发送ajax请求
    axios({
    method:”get”,
    url:”http://localhost:8080/brand-demo/selectAllServlet
    }).then(function (resp) {

    });

    1. 处理响应数据

    在 then 中的回调函数中通过 resp.data 可以获取响应回来的数据,而数据格式如下
    JavaWeb - 图171
    现在我们需要拼接字符串,将下面表格中的所有的 tr 拼接到一个字符串中,然后使用 document.getElementById(“brandTable”).innerHTML = 拼接好的字符串 就可以动态的展示出用户想看到的数据
    JavaWeb - 图172
    而表头行是固定的,所以先定义初始值是表头行数据的字符串,如下
    //获取数据
    let brands = resp.data;
    let tableData = “ \n” +
    “ 序号\n” +
    “ 品牌名称\n” +
    “ 企业名称\n” +
    “ 排序\n” +
    “ 品牌介绍\n” +
    “ 状态\n” +
    “ 操作\n” +
    “ “;
    接下来遍历响应回来的数据 brands ,拿到每一条品牌数据
    for (let i = 0; i < brands.length ; i++) {
    let brand = brands[i];

    }
    紧接着就是从 brand 对象中获取数据并且拼接 数据行,累加到 tableData 字符串变量中
    tableData += “\n” +
    “ \n” +
    “ “+(i+1)+”\n” +
    “ “+brand.brandName+”\n” +
    “ “+brand.companyName+”\n” +
    “ “+brand.ordered+”\n” +
    “ “+brand.description+”\n” +
    “ “+brand.status+”\n” +
    “\n” +
    修改 删除\n” +
    “ “;
    最后再将拼接好的字符串写到表格中
    // 设置表格数据
    document.getElementById(“brandTable”).innerHTML = tableData;
    整体页面代码如下:
    <!DOCTYPE html>














    ### 6.3 添加品牌功能 ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660210370-da9a11a2-8063-4c6a-a18d-74ff42abdf14.png#from=url&id=gh0fc&margin=%5Bobject%20Object%5D&originHeight=197&originWidth=1370&originalType=binary&ratio=1&status=done&style=none)
    如上所示,当我们点击 新增 按钮,会跳转到 addBrand.html 页面。在 addBrand.html 页面输入数据后点击 提交 按钮,就会将数据提交到后端,而后端将数据保存到数据库中。
    具体的前后端交互的流程如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660214225-7efc46d6-70fd-4cad-a76b-42df2dedfad3.png#from=url&id=UkTvB&margin=%5Bobject%20Object%5D&originHeight=409&originWidth=1444&originalType=binary&ratio=1&status=done&style=none)
    说明:
    前端需要将用户输入的数据提交到后端,这部分数据需要以 json 格式进行提交,数据格式如下:
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660218761-16e49fee-507d-4ad0-95c6-f56f7a228eda.png#from=url&id=CIEt1&margin=%5Bobject%20Object%5D&originHeight=30&originWidth=831&originalType=binary&ratio=1&status=done&style=none) #### 6.3.1 后端实现 在 com.itheima.web 包下创建名为 AddServlet 的 servlet,具体的逻辑如下: - 获取请求参数由于前端提交的是 json 格式的数据,所以我们不能使用 request.getParameter() 方法获取请求参数 - 如果提交的数据格式是 username=zhangsan&age=23 ,后端就可以使用 request.getParameter() 方法获取 - 如果提交的数据格式是 json,后端就需要通过 request 对象获取输入流,再通过输入流读取数据 - 将获取到的请求参数(json格式的数据)转换为 Brand 对象 - 调用 service 的 add() 方法进行添加数据的逻辑处理 - 将 json 数据响应回给浏览器。 AddServlet 代码如下:
    @WebServlet("/addServlet")
    public class AddServlet extends HttpServlet { private BrandService brandService = new BrandService(); @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //1. 接收数据,request.getParameter 不能接收json的数据
    /* String brandName = request.getParameter("brandName");
    System.out.println(brandName);*/ // 获取请求体数据
    BufferedReader br = request.getReader();
    String params = br.readLine();
    // 将JSON字符串转为Java对象
    Brand brand = JSON.parseObject(params, Brand.class);
    //2. 调用service 添加
    brandService.add(brand);
    //3. 响应成功标识
    response.getWriter().write("success");
    } @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doGet(request, response);
    }
    } #### 6.3.2 前端实现 在 addBrand.html 页面给 提交 按钮绑定点击事件,并在绑定的匿名函数中发送异步请求,代码如下:
    //1. 给按钮绑定单击事件
    document.getElementById("btn").onclick = function () {
    //2. 发送ajax请求
    axios({
    method:"post",
    url:"http://localhost:8080/brand-demo/addServlet",
    data:???
    }).then(function (resp) {
    // 判断响应数据是否为 success
    if(resp.data == "success"){
    location.href = "http://localhost:8080/brand-demo/brand.html";
    }
    })
    }
    现在我们只需要考虑如何获取页面上用户输入的数据即可。
    首先我们先定义如下的一个 js 对象,该对象是用来封装页面上输入的数据,并将该对象作为上面发送异步请求时 data 属性的值。
    // 将表单数据转为json
    var formData = {
    brandName:"",
    companyName:"",
    ordered:"",
    description:"",
    status:"",
    };
    接下来获取输入框输入的数据,并将获取到的数据赋值给 formData 对象指定的属性。比如获取用户名的输入框数据,并把该数据赋值给 formData 对象的 brandName 属性
    // 获取表单数据
    let brandName = document.getElementById("brandName").value;
    // 设置数据
    formData.brandName = brandName;
    ==说明:其他的输入框都用同样的方式获取并赋值。==但是有一个比较特殊,就是状态数据,如下图是页面内容
    ![](https://cdn.nlark.com/yuque/0/2021/png/2755207/1639660227899-a3fe09e1-3721-40e9-9b50-8b3b628e3c5b.png#from=url&id=G9VcG&margin=%5Bobject%20Object%5D&originHeight=91&originWidth=712&originalType=binary&ratio=1&status=done&style=none)
    我们需要判断哪儿个被选中,再将选中的单选框数据赋值给 formData 对象的 status 属性,代码实现如下:
    let status = document.getElementsByName("status");
    for (let i = 0; i < status.length; i++) {
    if(status[i].checked){
    //
    formData.status = status[i].value ;
    }
    }
    **整体页面代码如下:**






    添加品牌



    品牌名称:

    企业名称:

    排序:

    描述信息:

    状态:
    禁用
    启用