Java Web

zhenxi
2022-07-25 / 0 评论 / 4 阅读 / 正在检测是否收录...

学习记录 1


1 Mysql

1.1 mysql的安装与配置

在本科阶段已经完成了关于mysql的安装以及环境变量的配置等等。

image-20220805105121879

1.2 Sql语言

  • DDL(Data Definition Language) : 数据定义语言,用来定义数据库对象:数据库,表,列等。用来操作数据库,表等。
  • DML(Data Manipulation Language) 数据操作语言,用来对数据库中表的数据进行增删改。用来对表中数据进行增删改。
  • DQL(Data Query Language) 数据查询语言,用来查询数据库中表的记录(数据)。用来对数据进行查询操作。
  • DCL(Data Control Language) 数据控制语言,用来定义数据库的访问权限和安全级别,及创建用户。对数据库进行权限控制。

1.3 DDL

  • 查询所有的数据库
SHOW DATABASES;

image-20220806090000187

  • 创建数据库:
CREATE DATABASE 数据库名称;

image-20220806090205179

  • 创建数据库(判断,如果不存在则创建)
CREATE DATABASE IF NOT EXISTS 数据库名称;

image-20220806090505388

  • 删除数据库
DROP DATABASE 数据库名称;

image-20220806090813549

  • 删除数据库(判断,如果存在则删除)
DROP DATABASE IF EXISTS 数据库名称;

image-20220806090912522

  • 使用数据库
USE 数据库名称;
  • 查看当前使用的数据库
SELECT DATABASE();

image-20220806090958602

  • 查询当前数据库下所有表名称
SHOW TABLES;

image-20220806091227925

  • 查询表结构
DESC 表名称;

image-20220806091404605

  • 创建表
CREATE TABLE 表名 (
    字段名1  数据类型1,
    字段名2  数据类型2,
    …
    字段名n  数据类型n
);

image-20220806091708005

案例:

需求:设计一张学生表,请注重数据类型、长度的合理性
    1. 编号
    2. 姓名,姓名最长不超过10个汉字
    3. 性别,因为取值只有两种可能,因此最多一个汉字
    4. 生日,取值为年月日
    5. 入学成绩,小数点后保留两位
    6. 邮件地址,最大长度不超过 64
    7. 家庭联系电话,不一定是手机号码,可能会出现 - 等字符
    8. 学生状态(用数字表示,正常、休学、毕业...)

image-20220806092844800

  • 删除表
DROP TABLE 表名;
  • 删除表时判断表是否存在
DROP TABLE IF EXISTS 表名;

image-20220806093206819

  • 修改表名
ALTER TABLE 表名 RENAME TO 新的表名;

-- 将表名student修改为stu
alter table student rename to stu;

image-20220806093403158

  • 添加一列
ALTER TABLE 表名 ADD 列名 数据类型;

-- 给stu表添加一列address,该字段类型是varchar(50)
alter table stu add address varchar(50);

image-20220806093446334

  • 修改数据类型
ALTER TABLE 表名 MODIFY 列名 新数据类型;

-- 将stu表中的address字段的类型改为 char(50)
alter table stu modify address char(50);

image-20220806093542951

  • 修改列名和数据类型
ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;

-- 将stu表中的address字段名改为 addr,类型改为varchar(50)
alter table stu change address addr varchar(50);

image-20220806093613968

  • 删除列
ALTER TABLE 表名 DROP 列名;

-- 将stu表中的addr字段 删除
alter table stu drop addr;

image-20220806093637722

1.4 IDEA连接mysql

现在版本的IDEA非常强大,可以直接用IDEA连接数据库进行增删改查操作。

image-20220806101647559

填写相关信息后,进行连接测试。如图即连接成功。注意:IDEA默认连接8.版本的MySQL,若是5.版本的MySQL就需要更换JDBC的jar包。

1.5 DML

  • 给指定列添加数据
INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…);

image-20220806102428318

  • 给全部列添加数据
INSERT INTO 表名 VALUES(值1,值2,…);

image-20220806102915777

  • 批量添加数据
INSERT INTO 表名(列名1,列名2,…) VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;
INSERT INTO 表名 VALUES(值1,值2,…),(值1,值2,…),(值1,值2,…)…;

image-20220806103255587

image-20220806103522305

  • 修改表数据
UPDATE 表名 SET 列名1=值1,列名2=值2,… [WHERE 条件] ;

练习

  • 将云龙的性别改为女

    update stu set gender = '女' where name = '云龙';
  • 将云龙的生日改为 1999-12-12 分数改为99.99

    update stu set birthday = '1999-12-12',score = 99.99 where name = '云龙';
  • 注意:如果update语句没有加where条件,则会将表中所有数据全部修改!

    update stu set email = '773395726@qq.com';

image-20220806104720595

没有加where条件时会IDEA会拒绝执行:

image-20220806104846917

  • 删除数据
DELETE FROM 表名 [WHERE 条件] ;
  • 练习
-- 删除云龙记录
delete from stu where name = '云龙';

-- 删除stu表中所有的数据
delete from stu;

image-20220806105146914

为了数据库的安全,IDEA拒绝执行删除stu表中的所有数据。

image-20220806105228648

1.6 DQL

数据准备

-- 删除stu表
drop table if exists stu;


-- 创建stu表
CREATE TABLE stu (
 id int, -- 编号
 name varchar(20), -- 姓名
 age int, -- 年龄
 sex varchar(5), -- 性别
 address varchar(100), -- 地址
 math double(5,2), -- 数学成绩
 english double(5,2), -- 英语成绩
 hire_date date -- 入学时间
);

-- 添加数据
INSERT INTO stu(id,NAME,age,sex,address,math,english,hire_date) 
VALUES 
(1,'马运',55,'男','杭州',66,78,'1995-09-01'),
(2,'马花疼',45,'女','深圳',98,87,'1998-09-01'),
(3,'马斯克',55,'男','香港',56,77,'1999-09-02'),
(4,'柳白',20,'女','湖南',76,65,'1997-09-05'),
(5,'柳青',20,'男','湖南',86,NULL,'1998-09-01'),
(6,'刘德花',57,'男','香港',99,99,'1998-09-01'),
(7,'张学右',22,'女','香港',99,99,'1998-09-01'),
(8,'德玛西亚',18,'男','南京',56,65,'1994-09-02');
  • 查询多个字段
SELECT 字段列表 FROM 表名;
SELECT * FROM 表名; -- 查询所有数据
  • 去除重复记录
SELECT DISTINCT 字段列表 FROM 表名;
  • 起别名
AS: AS 也可以省略
  • 练习
  • 查询name、age两列

    select name,age from stu;

image-20220806110049924

  • 查询所有列的数据,列名的列表可以使用*替代

    select * from stu;

image-20220806110237595

  • 查询地址信息

    select address from stu;

image-20220806110544349

  • 去除重复记录

    select distinct address from stu;

image-20220806110623792

  • 查询姓名、数学成绩、英语成绩。并通过as给math和english起别名(as关键字可以省略)

    select name,math as 数学成绩,english as 英文成绩 from stu;
    select name,math 数学成绩,english 英文成绩 from stu;

image-20220806110728319

条件查询:SELECT 字段列表 FROM 表名 WHERE 条件列表;

  • 查询年龄大于20岁的学员信息

    select * from stu where age > 20;

image-20220806151039689

  • 查询年龄大于等于20岁的学员信息

    select * from stu where age >= 20;

image-20220806151216215

  • 查询年龄大于等于20岁 并且 年龄 小于等于 30岁 的学员信息

    select * from stu where age >= 20 &&  age <= 30;
    select * from stu where age >= 20 and  age <= 30;

    上面语句中 && 和 and 都表示并且的意思。建议使用 and 。

    也可以使用 between ... and 来实现上面需求

    select * from stu where age BETWEEN 20 and 30;

image-20220806151327550

  • 查询入学日期在'1998-09-01' 到 '1999-09-01' 之间的学员信息

    select * from stu where hire_date BETWEEN '1998-09-01' and '1999-09-01';

image-20220806151421526

  • 查询年龄等于18岁的学员信息

    select * from stu where age = 18;

image-20220806151532835

  • 查询年龄不等于18岁的学员信息

    select * from stu where age != 18;
    select * from stu where age <> 18;

image-20220806151802571

  • 查询年龄等于18岁 或者 年龄等于20岁 或者 年龄等于22岁的学员信息

    select * from stu where age = 18 or age = 20 or age = 22;
    select * from stu where age in (18,20 ,22);

image-20220806151917366

  • 查询英语成绩为 null的学员信息

    null值的比较不能使用 = 或者 != 。需要使用 is 或者 is not

    select * from stu where english is null;
    select * from stu where english is not null;

image-20220806152012154

模糊查询:

使用like关键字,可以使用通配符进行占位:

(1)_ : 代表单个任意字符。

(2)% : 代表任意个数字符。

  • 查询姓'马'的学员信息

    select * from stu where name like '马%';

image-20220806154046867

  • 查询第二个字是'花'的学员信息

    select * from stu where name like '_花%';

image-20220806154143501

  • 查询名字中包含 '德' 的学员信息

    select * from stu where name like '%德%';

image-20220806154215848

排序查询:

SELECT 字段列表 FROM 表名 ORDER BY 排序字段名1 [排序方式1],排序字段名2 [排序方式2] …;

上述语句中的排序方式有两种,分别是:

  • ASC : 升序排列 (默认值)
  • DESC : 降序排列
  • 查询学生信息,按照年龄升序排列

    select * from stu order by age ;

image-20220808164349176

  • 查询学生信息,按照数学成绩降序排列

    select * from stu order by math desc ;

image-20220808164840852

  • 查询学生信息,按照数学成绩降序排列,如果数学成绩一样,再按照英语成绩升序排列

    select * from stu order by math desc , english asc ;

image-20220808164752759

聚合函数:

函数名功能
count(列名)统计数量(一般选用不为null的列)
max(列名)最大值
min(列名)最小值
sum(列名)求和
avg(列名)平均值

语法

SELECT 聚合函数名(列名) FROM 表;
  • 统计班级一共有多少个学生

    select count(id) from stu;
    select count(english) from stu;

    上面语句根据某个字段进行统计,如果该字段某一行的值为null的话,将不会被统计。所以可以在count(*) 来实现。* 表示所有字段数据,一行中也不可能所有的数据都为null,所以建议使用 count(*)

    select count(*) from stu;

image-20220808164910874

  • 查询数学成绩的最高分

    select max(math) from stu;

image-20220808164932121

  • 查询数学成绩的最低分

    select min(math) from stu;

image-20220808164949930

  • 查询数学成绩的总分

    select sum(math) from stu;

image-20220808165008797

  • 查询数学成绩的平均分

    select avg(math) from stu;

image-20220808165028545

  • 查询英语成绩的最低分

    select min(english) from stu;

image-20220808165057049

分组查询:

语法

SELECT 字段列表 FROM 表名 [WHERE 分组前条件限定] GROUP BY 分组字段名 [HAVING 分组后条件过滤];
  • 查询男同学和女同学各自的数学平均分

    select sex, avg(math) from stu group by sex;

    注意:分组之后,查询的字段为聚合函数和分组字段,查询其他字段无任何意义

    select name, sex, avg(math) from stu group by sex;  -- 这里查询name字段就没有任何意义

image-20220808165138456

  • 查询男同学和女同学各自的数学平均分,以及各自人数

    select sex, avg(math),count(*) from stu group by sex;

image-20220808165202730

  • 查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组

    select sex, avg(math),count(*) from stu where math > 70 group by sex;

image-20220808165234800

  • 查询男同学和女同学各自的数学平均分,以及各自人数,要求:分数低于70分的不参与分组,分组之后人数大于2个的

    select sex, avg(math),count(*) from stu where math > 70 group by sex having count(*)  > 2;

image-20220808165325113

where 和 having 区别:

  • 执行时机不一样:where 是分组之前进行限定,不满足where条件,则不参与分组,而having是分组之后对结果进行过滤。
  • 可判断的条件不一样:where 不能对聚合函数进行判断,having 可以。

分页查询:

语法

SELECT 字段列表 FROM 表名 LIMIT  起始索引 , 查询条目数;

注意: 上述语句中的起始索引是从0开始。

  • 从0开始查询,查询3条数据

    select * from stu limit 0 , 3;

image-20220808170106447

  • 每页显示3条数据,查询第1页数据

    select * from stu limit 1 , 3;

image-20220808170135616

  • 每页显示3条数据,查询第2页数据

    select * from stu limit 3 , 3;

image-20220808170153524

  • 每页显示3条数据,查询第3页数据

    select * from stu limit 6 , 3;

image-20220808170211585

娱乐学习:搭建Hexo+Gitee个人博客

在本科阶段,通过哔哩哔哩学习了用Hexo框架、以及使用Gitee部署、Git上传,成功搭建了一个个人博客,用于记录自己的学习历程。但是由于大三下学期考研,开始停止使用个人博客。直到现在想起,准备重新启用个人博客,但是由于大四下学期进行毕业设计,由于电脑配置太低运行较卡,于是进行重装系统,从而导致了本地的个人博客文件、与Gitee密钥的连接、Git Node.js软件等等都自动删除了。于是准备重新搭建部署一下个人博客。

1 博客现状

之前搭建的个人博客:

GIF 2022-8-5 11-06-07

可以发现,有些图片不显示了。在本科阶段写的博客,里面的图片我都进行上传到了自己搭建的Gitee图床之中。但是Gitee搭建的图床仓库无法进行公开,所以图片无法进行正常显示。这里需要进行对图片的整改,把Gitee图床的图片都转移到阿里云的OSS对象存储空间里。

image-20220805111122353

2 重新搭建

2.1 安装相关软件:

通过运行版本命令可以看到node.js和Git都已经成功安装。

image-20220805111755945

安装hexo

在cmd命令行中输入以下命令

npm install hexo-cli -g

image-20220805112129832

2.2 初始化文件夹

image-20220805112748297

hexo init blog  # 初始化创建,会再桌面创建blog文件夹
cd blog    # 进入blog目录
npm install   # 进一步安装hexo所需文件

image-20220805113029861

初始化之后的目录:

image-20220805113123877

hexo clean   # 清除所有记录 /hexo c
hexo generate  # 生成静态网页 /hexo g
hexo server    # 启动服务 /hexo s

image-20220805113416306

在浏览器进入http://localhost:4000/

image-20220805113717089

2.3 生成密钥

首先在Gitee创建一个仓库,这里我依旧使用我原来的仓库。

image-20220805113946823

生成密钥:

配置ssh账户和邮箱,邮箱不必与 GitHub、Gitee 账号相同,个人邮箱即可
查看账户邮箱

git config --global user.name
git config --global user.email

配置账户邮箱

git config --global user.email abc@qq.com      # 设置邮箱
git config --global user.name 'abc'            # 设置用户名

本地生成ssh公钥

命令行执行如下命令,邮箱是刚才配置的邮箱abc@qq.com

ssh-keygen -t rsa -C "xxxxx@xxxxx.com"

按照提示完成三次回车,即可生成 ssh key

默认生成目录是 C:\Users\PMB.ssh (pmb各自电脑的用户名)

image-20220805114254523

生成的密钥文件:

image-20220805114509969

gitee创建公钥

首先用户登录之后,打开设置页面,安全设置里面ssh公钥打开,下面就是公钥的模板,标题随便起,知道表达意思即可,公钥就是id_rsa.pub文件内容。

image-20220806154846946

测试连接是否成功:

ssh -T git@gitee.com

image-20220805115244936

2.4 部署

安装自动部署发布工具:

npm install hexo-deployer-git --save

使用下面命令进行上传部署:

hexo clean
hexo g 
hexo d

image-20220805121553238

在hexo d的时候会让你填写用户名和密码,最好不要填写错误,需要更改注册表。

image-20220805121621595

打开gitee可以看到已经推送到了gitee的仓库中:

image-20220805121522384

进入仓库,选择服务中的Gitee Pages进行部署:

image-20220805121804615

image-20220805121835196

部署成功可以看到已经开启Gitee Pages服务:

image-20220805122649952

登入网站,发现报错。查一下到底是为什么报错?

image-20220805124342964

成功找到解决办法:原来是由于我把过去的配置文件直接粘贴到了现在生成的文件,由于过去的配置文件和现在的配置文件有一些差异,于是安装一下相关插件即可。

ix

安装成功后,再进行部署:

image-20220805124050904

OK,部署成功了,接下来就是修改本地的一些文件,把页面的图片等等都进行更改即可。

3 博客现状

博客地址:https://studentliuchang.gitee.io/

image-20220805212348946

以后就可以把自己的学习记录上传到博客上了,不仅可以防止学习记录丢失,而且可以方面查看复习自己学过的东西。

学习记录 3

1 JDBC概述

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

2 JDBC入门实现

通过Maven引入MySQL驱动jar包:

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
</dependencies>

JDBC案例一:使用JDBC连接Mysql,把stu表中的7号性别改为男性。

image-20220807084356570

package com.liu.JDBC;

import java.sql.Connection;
import java.sql.DriverAction;
import java.sql.DriverManager;
import java.sql.Statement;

public class JDBCDemo01 {
    public static void main(String[] args) throws Exception {
        //注册驱动:通过反射把实现类加载到内存
        Class.forName("com.mysql.cj.jdbc.Driver");
        //获取连接
        String url = "jdbc:mysql://127.0.0.1:3306/test";
        String name= "root";
        String password = "131411";
        Connection connection = DriverManager.getConnection(url, name, password);
        //定义执行SQL
        String sql = "update stu set sex='男' where id = 7";
        //通过连接对象获取执行sql的对象 Statement
        Statement statement = connection.createStatement();
        //通过执行sql对象执行sql语句
        int i = statement.executeUpdate(sql);
        System.out.println(i);
        //关闭连接对象
        statement.close();
        connection.close();
    }
}

image-20220807084735567

注意:视频中的数据库是5.版本的,但由于我的数据库是8.版本的,在项目中引入的jar包也是8.版本的。而且,在通过反射获取实现类时,要多一个cj包!刚开始学习数据库时,遇到这个问题时,我一度以为我的MySQL安装有问题,最后也是花了一些时间才知道该如何解决版本不一样的问题。版本不一致问题很容易遇到,要警惕,代码无错,版本有差异就会报错。

3 JDBC相关API

3.1 DriverManager类

变量和类型方法描述
static ConnectiongetConnection(String url)尝试建立与给定数据库URL的连接。
static ConnectiongetConnection(String url, String user, String password)尝试建立与给定数据库URL的连接。
static voidregisterDriver(Driver driver)使用 DriverManager注册给定的驱动程序。
static voidregisterDriver(Driver driver, DriverAction da)使用 DriverManager注册给定的驱动程序。

registerDriver()方法

registerDriver()方法是用于注册驱动的,但是入门案例却是通过反射加载Driver类进内存。

image-20220807104438642

在该类中的静态代码块中已经执行了 DriverManager 对象的 registerDriver() 方法进行驱动的注册了,那么我们只需要加载 Driver 类,该静态代码块就会执行。而 Class.forName("com.mysql.cj.jdbc.Driver"); 就可以加载 Driver 类。

注意:

  • MySQL 5之后的驱动包,可以省略注册驱动的步骤
  • 自动加载jar包中META-INF/services/java.sql.Driver文件中的驱动类

image-20220807104947698

getConnection(String url, String user, String password)方法

参数说明:

  • url : 连接路径

    语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2…

    示例:jdbc:mysql://127.0.0.1:3306/stu

    • 如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称?参数键值对
    • 配置 useSSL=false 参数,禁用安全连接方式,解决警告提示
  • user :用户名
  • poassword :密码

简化后的JDBC案例:

package com.liu.JDBC;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCDemo02 {
    public static void main(String[] args) throws Exception {
        //获取连接
        String url = "jdbc:mysql:///test";
        String name= "root";
        String password = "131411";
        Connection connection = DriverManager.getConnection(url, name, password);
        //定义执行SQL
        String sql = "update stu set sex='女' where id = 7";
        //通过连接对象获取执行sql的对象 Statement
        Statement statement = connection.createStatement();
        //通过执行sql对象执行sql语句
        int i = statement.executeUpdate(sql);
        System.out.println(i);
        //关闭连接对象
        statement.close();
        connection.close();
    }
}

3.2 Connection类

变量和类型方法描述
StatementcreateStatement()创建一个 Statement对象,用于将SQL语句发送到数据库。
PreparedStatementprepareStatement(String sql)创建一个 PreparedStatement对象,用于将参数化SQL语句发送到数据库。
voidsetAutoCommit(boolean autoCommit)将此连接的自动提交模式设置为给定状态。
voidcommit()使自上次提交/回滚以来所做的所有更改成为永久更改,并释放此 Connection对象当前持有的所有数据库锁。
voidrollback()撤消当前事务中所做的所有更改,并释放此 Connection对象当前持有的所有数据库锁。

案例:使用Connection类中的方法开启事务、回滚事务、提交事务。

package com.liu.JDBC;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class JDBCDemo03 {
    public static void main(String[] args) throws Exception {
        //获取连接
        String url = "jdbc:mysql:///test";
        String name= "root";
        String password = "131411";
        Connection connection = DriverManager.getConnection(url, name, password);

        //定义执行SQL
        String sql1 = "update stu set age=23 where id = 7";
        String sql2 = "update stu set age=20 where id = 8";
        //通过连接对象获取执行sql的对象 Statement
        Statement statement = connection.createStatement();

        //开启事务
        connection.setAutoCommit(false);

        try {
            //通过执行sql对象执行sql语句
            int i = statement.executeUpdate(sql1);
            System.out.println(i);
            int i1 = statement.executeUpdate(sql2);
            System.out.println(i1);
            //提交事务
            connection.commit();
        } catch (Exception e) {
            //回滚事务
            connection.rollback();
            e.printStackTrace();
        }
        //关闭连接对象
        statement.close();
        connection.close();
    }
}

3.3 Statement类

变量和类型方法描述
intexecuteUpdate(String sql)执行给定的SQL语句,这可能是 INSERTUPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。 执行完DML语句返回受影响的行数。
ResultSetexecuteQuery(String sql)执行给定的SQL语句,该语句返回单个 ResultSet对象。

executeUpdate(String sql)方法执行DML语句:

执行DML语句时,该方法应该返回受影像的行数。

我们采用单元测试的方式进行测试,首先在Maven依赖中添加juint地址。

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.1</version>
    <scope>test</scope>
</dependency>
@Test
public void Test1() throws Exception {
    //获取连接
    String url = "jdbc:mysql:///test";
    String name= "root";
    String password = "131411";
    Connection connection = DriverManager.getConnection(url, name, password);
    //定义执行SQL:DML语句
    String sql = "update stu set sex='女' where id = 7";
    String sql2 = "insert into stu values (9,'刘畅',23,'男','河南',90,70,'1999-06-14')," +
        "(10,'成龙',22,'男','河南',100,80,'2000-06-14')";
    //通过连接对象获取执行sql的对象 Statement
    Statement statement = connection.createStatement();
    //通过执行sql对象执行sql语句
    int i = statement.executeUpdate(sql);
    System.out.println(i);//只更改了一条数据,返回值应该是1
    if (i>0){
        System.out.println("修改成功");
    }else {
        System.out.println("修改失败");
    }
    int i1 = statement.executeUpdate(sql2);
    System.out.println(i1); //修改两条数据,返回值应该是2
    if (i1>0){
        System.out.println("修改成功");
    }else {
        System.out.println("修改失败");
    }
    //关闭连接对象
    statement.close();
    connection.close();
}
/**
运行结果:
1
修改成功
2
修改成功
*/

执行DDL语句时,执行成功也可能返回0:

@Test
public void Test2() throws Exception {
    //获取连接
    String url = "jdbc:mysql:///test";
    String name= "root";
    String password = "131411";
    Connection connection = DriverManager.getConnection(url, name, password);
    //定义执行SQL:DML语句
    String sql = "create database if not exists stu1";
    String sql2 = "drop database stu1";
    //通过连接对象获取执行sql的对象 Statement
    Statement statement = connection.createStatement();
    //通过执行sql对象执行sql语句
    int i = statement.executeUpdate(sql);
    System.out.println(i);//返回值为1
    int i1 = statement.executeUpdate(sql2);
    System.out.println(i1);//执行成功,但是返回值为0
    //关闭连接对象
    statement.close();
    connection.close();
}

executeQuery(String sql)方法:执行DQL

ResultSet类:

变量和类型方法描述
booleannext()将光标从当前位置向前移动一行。 判断当前行是否为有效行。
xxxgetXxx(参数)xxx : 数据类型;如: int getInt(参数) ;String getString(参数) ;int类型的参数:列的编号,从1开始 String类型的参数: 列的名称

案例:查询stu表中的所有内容,并且封装成Student对象,并存入一个list集合之中。

package com.liu.pojo;

import java.util.Date;

public class Student {
    private int id;
    private String name;
    private int age;
    private String sex;
    private String address;
    private Double math;
    private Double english;
    private Date hire_date;

    public Student() {
    }

    public Student(int id, String name, int age, String sex, String address, Double math, Double english, Date hire_date) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.address = address;
        this.math = math;
        this.english = english;
        this.hire_date = hire_date;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Double getMath() {
        return math;
    }

    public void setMath(Double math) {
        this.math = math;
    }

    public Double getEnglish() {
        return english;
    }

    public void setEnglish(Double english) {
        this.english = english;
    }

    public Date getHire_date() {
        return hire_date;
    }

    public void setHire_date(Date hire_date) {
        this.hire_date = hire_date;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                ", math=" + math +
                ", english=" + english +
                ", hire_date=" + hire_date +
                '}';
    }
}
@Test
public void Test3() throws Exception{
    //获取连接
    String url = "jdbc:mysql:///test";
    String name= "root";
    String password = "131411";
    Connection connection = DriverManager.getConnection(url, name, password);
    //定义执行SQL:DQL语句
    String sql = "select * from stu";
    //通过连接对象获取执行sql的对象 Statement
    Statement statement = connection.createStatement();
    //通过执行sql对象执行sql语句
    ResultSet resultSet = statement.executeQuery(sql);
    List<Student> list = new ArrayList<>();
    while (resultSet.next()){
        Student student = new Student();
        student.setId(resultSet.getInt(1));
        student.setName(resultSet.getString(2));
        student.setAge(resultSet.getInt(3));
        student.setSex(resultSet.getString("sex"));
        student.setAddress(resultSet.getString("address"));
        student.setMath(resultSet.getDouble("math"));
        student.setEnglish(resultSet.getDouble(7));
        student.setHire_date(resultSet.getDate("hire_date"));
        list.add(student);
    }
    System.out.println(list);
    //关闭连接对象
    resultSet.close();
    statement.close();
    connection.close();
}

3.4 PreparedStatement类

PreparedStatement作用:

  • 预编译SQL语句并执行:预防SQL注入问题

案例一:模拟SQL注入问题

 @Test
public void Test4() throws Exception {
    //获取连接
    String url = "jdbc:mysql:///test";
    String name= "root";
    String password = "131411";
    Connection connection = DriverManager.getConnection(url, name, password);
    //定义执行SQL:DML语句
    String name1 = "马运";
    String age = "' or '1' = '1";
    String sql = "select *from stu where name ='"+name1+"' and age = '"+age+"'";
    //通过连接对象获取执行sql的对象 Statement
    Statement statement = connection.createStatement();
    //通过执行sql对象执行sql语句
    ResultSet resultSet = statement.executeQuery(sql);
    if (resultSet.next()){
        System.out.println("登录成功");
    }else {
        System.out.println("登录失败");
    }
    //关闭连接对象
    statement.close();
    connection.close();
}
/**
运行结果:
select *from stu where name ='马运' and age = '' or '1' = '1'
登录成功
*/

从上面语句可以看出条件 username = '马运' and password = '' 不管是否满足,而 or 后面的 '1' = '1' 是始终满足的,最终条件是成立的,就可以正常的进行登陆了。

案例二:解决SQL注入问题

使用Connection类方法创建PreparedStatement

变量和类型方法描述
PreparedStatementprepareStatement(String sql)创建一个 PreparedStatement对象,用于将参数化SQL语句发送到数据库。

PreparedStatement使用时SQL语句中的参数值,使用占位符替代。

变量和类型方法描述
voidsetXxx(参数1,参数2)Xxx:数据类型 ; 如 setInt (参数1,参数2)<br/> 参数: 参数1: ?的位置编号,从1 开始 参数2: ?的值
@Test
public void Test5() throws Exception{
    //获取连接
    String url = "jdbc:mysql:///test";
    String name= "root";
    String password = "131411";
    Connection connection = DriverManager.getConnection(url, name, password);
    //定义执行SQL:DML语句
    String name1 = "马运";
    int age = 55;
    String sql = "select * from stu where name = ? and age = ?";
    //通过连接对象获取执行sql的对象 Statement
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // 设置?的值
    preparedStatement.setString(1,name1);
    preparedStatement.setInt(2,age);
    ResultSet resultSet = preparedStatement.executeQuery();
    if (resultSet.next()){
        System.out.println("登录成功");
    }else {
        System.out.println("登录失败");
    }
    //关闭连接对象
    resultSet.close();
    preparedStatement.close();
    connection.close();
}

4 数据库连接池

4.1 数据库连接池概述

  • 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;
  • 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏
  • 好处

    • 资源重用
    • 提升系统响应速度
    • 避免数据库连接遗漏

4.2 Driud连接池

  • Druid连接池是阿里巴巴开源的数据库连接池项目
  • 功能强大,性能优秀,是Java语言最好的数据库连接池之一

使用过程:

  • 导入jar包 druid-1.1.12.jar
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.24</version>
</dependency>
  • 定义配置文件
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///test?useSSL=false&useServerPrepStmts=true
username=root
password=131411
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
  • 加载配置文件
  • 获取数据库连接池对象
  • 获取连接
@Test
public void Test1() throws Exception {
    //加载配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/main/resources/properties.properties"));
    //获取数据库连接池对象
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    //获取连接
    Connection connection = dataSource.getConnection();
    System.out.println(connection);
}

4.3 需求案例

完成商品品牌数据的增删改查操作

  • 查询:查询所有数据
  • 添加:添加品牌
  • 修改:根据id修改
  • 删除:根据id删除

环境准备

  • 数据库表 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);

image-20220808111435721

  • 在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;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBrandName() {
        return brandName;
    }

    public void setBrandName(String brandName) {
        this.brandName = brandName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Brand{" +
                "id=" + id +
                ", brandName='" + brandName + '\'' +
                ", companyName='" + companyName + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}

查询所有

 @Test
public void Test2() throws Exception{
    //加载配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/main/resources/properties.properties"));
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    //编写SQL
    String sql = "select *from tb_brand";
    Statement statement = connection.createStatement();
    ResultSet resultSet = statement.executeQuery(sql);
    ArrayList<Brand> arrayList = new ArrayList<>();
    while (resultSet.next()){
        Brand brand = new Brand();
        brand.setId(resultSet.getInt("id"));
        brand.setBrandName(resultSet.getString("brand_name"));
        brand.setCompanyName(resultSet.getString("company_name"));
        brand.setOrdered(resultSet.getInt("ordered"));
        brand.setDescription(resultSet.getString("description"));
        brand.setStatus(resultSet.getInt("status"));
        arrayList.add(brand);
    }
    System.out.println(arrayList);
    resultSet.close();
    statement.close();
    connection.close();
}

添加数据

@Test
public void Test3() throws Exception{
    //加载配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/main/resources/properties.properties"));
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    String sql = "insert into tb_brand (brand_name, company_name, ordered, description, status) values(?,?,?,?,?)";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    String brandName = "香飘飘";
    String companyName = "香飘飘";
    int ordered = 1;
    String description = "绕地球一圈";
    int status = 1;
    preparedStatement.setString(1,brandName);
    preparedStatement.setString(2,companyName);
    preparedStatement.setInt(3,ordered);
    preparedStatement.setString(4,description);
    preparedStatement.setInt(5,status);
    int i = preparedStatement.executeUpdate();
    if (i>0){
        System.out.println("插入成功");
    }else {
        System.out.println("插入失败");
    }

    preparedStatement.close();
    connection.close();
}

修改数据

@Test
public void Test4() throws Exception{
    //加载配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/main/resources/properties.properties"));
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    String sql = "update tb_brand set description = ? where id = ?";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    String description = "绕地球三圈";
    int id = 4;
    preparedStatement.setString(1,description);
    preparedStatement.setInt(2,id);
    int i = preparedStatement.executeUpdate();
    if (i>0){
        System.out.println("更新成功");
    }else {
        System.out.println("更新失败");
    }
    preparedStatement.close();
    connection.close();
}

删除数据

@Test
public void Test5() throws Exception{
    //加载配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src/main/resources/properties.properties"));
    DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
    Connection connection = dataSource.getConnection();
    String sql = "delete from tb_brand where id = ?";
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
    int id = 4;
    preparedStatement.setInt(1,id);
    int i = preparedStatement.executeUpdate();
    if (i>0){
        System.out.println("删除成功");
    }else {
        System.out.println("删除失败");
    }
    preparedStatement.close();
    connection.close();
}

学习记录 3

1 MyBatis入门

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

  • 创建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', '男', '西安');
  • 创建模块,导入坐标

在创建好的模块中的 pom.xml 配置文件中添加依赖的坐标

<dependencies>
    <!--mybatis 依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <!--mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <!-- 添加slf4j日志api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</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>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.24</version>
    </dependency>
</dependencies>

注意:需要在项目的 resources 目录下创建logback的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--
        CONSOLE :表示当前的日志信息是可以输出到控制台的。
    -->
    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%level] %blue(%d{HH:mm:ss.SSS}) %cyan([%thread]) %boldGreen(%logger{15}) - %msg %n</pattern>
        </encoder>
    </appender>

    <logger name="com.itheima" level="DEBUG" additivity="false">
        <appender-ref ref="Console"/>
    </logger>

    <!--

      level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF
     , 默认debug
      <root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
      -->
    <root level="DEBUG">
        <appender-ref ref="Console"/>
    </root>
</configuration>
  • 编写 MyBatis 核心配置文件 -- > 替换连接信息 解决硬编码问题

在模块下的 resources 目录下创建mybatis的配置文件 mybatis-config.xml,内容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///test?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="131411"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--加载SQL的映射文件-->
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>
  • 编写 SQL 映射文件 --> 统一管理sql语句,解决硬编码问题

在模块的 resources 目录下创建映射配置文件 UserMapper.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="test">
    <select id="selectAll" resultType="com.itheima.pojo.User">
        select * from tb_user;
    </select>
</mapper>
  • com.liu.pojo 包下创建 User类
public class User {
    private int id;
    private String username;
    private String password;
    private String gender;
    private String addr;
    
    //省略了 setter 和 getter
}
  • com.liu.mybatisDemo 包下编写 MybatisDemo 测试类
package com.liu.mybatisDemo;

import com.liu.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisDemo {
    public static void main(String[] args) throws IOException {
        //获取sqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //执行sql语句
        List<User> list = sqlSession.selectList("test.selectAll");
        System.out.println(list);
    }
}

运行结果

[DEBUG] 18:19:38.125 [main] o.a.i.l.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. 
[DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. 
[DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. 
[DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. 
[DEBUG] 18:19:38.145 [main] o.a.i.d.p.PooledDataSource - PooledDataSource forcefully closed/removed all connections. 
[DEBUG] 18:19:38.255 [main] o.a.i.t.j.JdbcTransaction - Opening JDBC Connection 
[DEBUG] 18:19:38.545 [main] o.a.i.d.p.PooledDataSource - Created connection 1118078504. 
[DEBUG] 18:19:38.545 [main] o.a.i.t.j.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@42a48628] 
[DEBUG] 18:19:38.555 [main] test.selectAll - ==>  Preparing: select * from tb_user; 
[DEBUG] 18:19:38.595 [main] test.selectAll - ==> Parameters:  
[DEBUG] 18:19:38.635 [main] test.selectAll - <==      Total: 3 
[User{id=1, username='zhangsan', password='123', gender='男', addr='北京'}, User{id=2, username='李四', password='234', gender='女', addr='天津'}, User{id=3, username='王五', password='11', gender='男', addr='西安'}]

2 Mapper代理开发

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

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

image-20220808192548831

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

image-20220808192725438

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

image-20220808192826051

  • com.liu.Mapper 包下创建 UserMapper接口,代码如下:
package com.liu.Mapper;

import com.liu.pojo.User;

import java.util.List;

public interface UserMapper {
    List<User> selectAll();
}
  • resources 下创建 com/liu/Mapper 目录,并在该目录下创建 UserMapper.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.liu.Mapper.UserMapper">
    <select id="selectAll" resultType="com.liu.pojo.User">
        select * from tb_user;
    </select>
</mapper>
  • com.liu.mybatisDemo 包下创建 MybatisDemo2 测试类,代码如下:
package com.liu.mybatisDemo;

import com.liu.Mapper.UserMapper;
import com.liu.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisDemo01 {
    public static void main(String[] args) throws IOException {
        //获取sqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //获取userMapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.selectAll();
        System.out.println(users);
        sqlSession.close();
    }
}

注意:

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

<mappers>
    <!--加载SQL的映射文件-->
    <!--<mapper resource="com/liu/Mapper/UserMapper.xml"/>-->
    <!--Mapper处理方式,扫描文件-->
    <package name="com.liu.Mapper"/>
</mappers>

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

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

<!--取别名-->
<typeAliases>
    <package name="com.liu.pojo"/>
</typeAliases>

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

<mapper namespace="com.liu.Mapper.UserMapper">
    <select id="selectAll" resultType="User">
        select * from tb_user;
    </select>
</mapper>

3 MyBatis增删改查

3.1 查询操作

编写接口方法

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

Mybatis针对多参数有多种实现:

  • 使用 @Param("参数名称") 标记每一个参数,在映射配置文件中就需要使用 #{参数名称} 进行占位
  • 将多个参数封装成一个 实体对象 ,将该实体对象作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和实体类属性名保持一致。
  • 将多个参数封装到map集合中,将map集合作为接口的方法参数。该方式要求在映射配置文件的SQL中使用 #{内容} 时,里面的内容必须和map集合中键的名称一致。
//查询操作
List<Brand> selectAll();
Brand selectById(int id);
//散装参数
List<Brand> selectByBrand(@Param("status") int status,@Param("companyName") String companyName, @Param("brandName") String brandName);
//实体参数
List<Brand> selectByStatusAndName(Brand brand);
//map参数
List<Brand> selectByBrandByMap(Map map);
//查询单个
List<Brand> selectSingle(Brand brand);

编写SQL语句

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

mybatis提供了两种参数占位符:

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

  • ${} :拼接SQL。底层使用的是 Statement,会存在SQL注入问题。

Mybatis对动态SQL有很强大的支撑:

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

    if 标签:

  • if 标签:条件判断

    • test 属性:逻辑表达式

where 标签:

  • 作用:

    • 替换where关键字
    • 会动态的去掉第一个条件前的 and
    • 如果所有的参数没有值则不加where关键字

choose标签:

  • choose 标签类似于Java 中的switch语句。
  • when 标签类似于Java 中的case语句。
<!--数据库表中的字段与编写的实体类中的属性名字段不一致-->
<resultMap id="brandResultMap" type="brand">
    <!--id:唯一标识;type:映射的类型-->
    <!--id:主键字段映射-->
    <!--result:非主键字段映射-->
    <result column="brand_name" property="brandName"/>
    <result column="company_name" property="companyName"/>
</resultMap>
<select id="selectAll" resultMap="brandResultMap">
    select *
    from tb_brand;
</select>
<select id="selectById" resultMap="brandResultMap">
    select *
    from tb_brand
    where id = #{id};
</select>
<select id="selectByBrand" resultMap="brandResultMap">
    select *
    from tb_brand
    where status = #{status}
    and company_name like #{companyName}
    and brand_name like #{brandName};
</select>
<select id="selectByStatusAndName" resultMap="brandResultMap">
    select *
    from tb_brand
    where status = #{status}
    and company_name like #{companyName}
    and brand_name like #{brandName};
</select>
<select id="selectByBrandByMap" resultMap="brandResultMap">
    select *from tb_brand
    <where>
        <if test="status!=null">
            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>
<select id="selectSingle" resultMap="brandResultMap">
    select *from tb_brand
    <where>
        <choose>
            <when test="status!=null">
                status = #{status}
            </when>
            <when test="companyName!=null and companyName!=''">
                company_name like #{companyName}
            </when>
            <when test="brandName!=null and brandName!=''">
                brand_name like #{brandName}
            </when>
        </choose>
    </where>
</select>

编写测试方法

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

@Test
public void Test1(){
    List<Brand> brands = brandMapper.selectAll();
    for (Brand brand : brands) {
        System.out.println(brand);
    }
}

@Test
public void Test2(){
    Brand brand = brandMapper.selectById(1);
    System.out.println(brand);
}
@Test
public void Test3(){
    List<Brand> brands = brandMapper.selectByBrand(1, "%华为%", "%华为%");
    for (Brand brand : brands) {
        System.out.println(brand);
    }
}
@Test
public void Test4(){
    Brand brand = new Brand();
    brand.setStatus(1);
    brand.setCompanyName("%华为%");
    brand.setBrandName("%华为%");
    List<Brand> brands = brandMapper.selectByStatusAndName(brand);
    for (Brand brand1 : brands) {
        System.out.println(brand1);
    }
}
@Test
public void Test5(){
    Map map = new HashMap();
    map.put("status",1);
    map.put("companyName","%华为%");
    map.put("brandName","%华为%");
    List<Brand> brands = brandMapper.selectByBrandByMap(map);
    for (Brand brand : brands) {
        System.out.println(brand);
    }
}
@Test
public void Test6(){
    Brand brand = new Brand();
    //brand.setStatus(1);
    //brand.setCompanyName("%华为%");
    brand.setBrandName("%华为%");
    List<Brand> brands = brandMapper.selectSingle(brand);
    for (Brand brand1 : brands) {
        System.out.println(brand1);
    }
}

3.2 添加操作

编写接口方法

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

//插入操作
void insert(Brand brand);

编写SQL语句

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

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

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

  • useGeneratedKeys:是够获取自动增长的主键值。true表示获取
  • keyProperty :指定将获取到的主键值封装到哪儿个属性里
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into tb_brand(brand_name, company_name, ordered, description, status)
    VALUES (#{brandName}, #{companyName}, #{ordered}, #{description}, #{status});
</insert>

编写测试方法

test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法:

@Test
public void Test7(){
    Brand brand = new Brand("锤子手机", "锤子的专卖店", 100, "手机中的战斗机", 1);
    brandMapper.insert(brand);
    //主键返回
    Integer id = brand.getId();
    System.out.println(id);
    //提交事务,如果不提交事务则会自动回滚
    sqlSession.commit();
}

image-20220809093207571

3.3 修改操作

编写接口方法

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

//修改功能
int update(Brand brand);

编写SQL语句

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

  • set 标签可以用于动态包含需要更新的列,忽略其它不更新的列。
<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>

编写测试方法

test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法:

@Test
public void Test8(){
    Brand brand = new Brand(6,"锤子手机", "锤子的专卖店", 200, "性价比之王,手机中的战斗机", 1);
    int update = brandMapper.update(brand);
    System.out.println(update);
    sqlSession.commit();
}

3.4 删除操作

编写接口方法

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

//删除操作
void deleteById(int id);
//删除多个
void deleteByIdIs(int[] ids);

编写SQL语句

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

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

foreach 标签

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

  • collection 属性:

    • mybatis会将数组参数,封装为一个Map集合。

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

编写测试方法

test/java 下的 com.liu.Mapper 包下的 MybatisTest类中 定义测试方法:

@Test
public void Test9(){
    brandMapper.deleteById(6);
    List<Brand> brands = brandMapper.selectAll();
    for (Brand brand : brands) {
        System.out.println(brand);
    }
    sqlSession.commit();
}

@Test
public void Test10(){
    int[] ids = {1,5,6};
    brandMapper.deleteByIdIs(ids);
    List<Brand> brands = brandMapper.selectAll();
    for (Brand brand : brands) {
        System.out.println(brand);
    }
    sqlSession.commit();
}

4 MyBatis注解开发

注解一般开发较为简单的语句,对于复杂的SQL语句一定要用配置文件来开发。

  • 查询 :@Select
  • 添加 :@Insert
  • 修改 :@Update
  • 删除 :@Delete

对User类进行注解的增删改查开发

编写接口方法

public interface UserMapper {
    @Select("select * from tb_user")
    List<User> selectAll();
    @Insert("insert into tb_user(username, password, gender, addr) values (#{username},#{password},#{gender},#{addr})")
    void insert(User user);
    @Update("update tb_user set username = #{username} where id=#{id}")
    int update(@Param("username") String username,@Param("id") int id);
    @Delete("delete from tb_user where id=#{id}")
    void delete(int id);
}

编写测试方法

public class UserTest {
    //获取sqlSessionFactory
    String resource = "mybatis-config.xml";
    InputStream inputStream;
    {
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //获取sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    @Test
    public void Test1(){
        List<User> users = userMapper.selectAll();
        for (User user : users) {
            System.out.println(user);
        }
    }
    @Test
    public void Test2(){
        User user = new User();
        user.setUsername("刘畅");
        user.setPassword("1314");
        user.setGender("男");
        user.setAddr("河南");
        userMapper.insert(user);
        sqlSession.commit();
    }
    @Test
    public void Test3(){
        int update = userMapper.update("六珍惜", 1);
        sqlSession.commit();
    }
    @Test
    public void Test4(){
        userMapper.delete(5);
        sqlSession.commit();
    }
}

学习记录 4

1 Servlet

1.1 Servlet概述

  • Servlet是JavaWeb最为核心的内容,它是Java提供的一门==动态==web资源开发技术。
  • 使用Servlet就可以实现,根据不同的登录用户在页面上动态显示不同内容。
  • Servlet是JavaEE规范之一,其实就是一个接口,将来我们需要定义Servlet类实现Servlet接口,并由web服务器运行Servlet。

1.2 Servlet练习

需求分析: 编写一个Servlet类,并使用IDEA中Tomcat插件进行部署,最终通过浏览器访问所编写的Servlet程序。==

具体的实现步骤为:

  1. 创建Web项目web-demo,导入Servlet依赖坐标
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <!--
        此处为什么需要添加该标签?
        provided指的是在编译和测试过程中有效,最后生成的war包时不会加入
         因为Tomcat的lib目录中已经有servlet-api这个jar包,如果在生成war包的时候生效就会和Tomcat中的jar包冲突,导致报错
      -->
        <scope>provided</scope>
    </dependency>
</dependencies>
  1. 创建:定义一个类,实现Servlet接口,并重写接口中所有方法,并在service方法中输入一句话
public class ServletDemo1 implements Servlet {

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("servlet hello world~");
    }
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}
  1. 配置:在类上使用@WebServlet注解,配置该Servlet的访问路径
@WebServlet("/demo1")
  1. 访问:启动Tomcat,浏览器中输入URL地址访问该Servlet
http://localhost:8080/web-demo/demo1
  1. 器访问后,在控制台会打印servlet hello world~ 说明servlet程序已经成功运行。

1.3 执行流程

  • 浏览器发出http://localhost:8080/web-demo/demo1请求,从请求中可以解析出三部分内容,分别是localhost:8080web-demodemo1

    • 根据localhost:8080可以找到要访问的Tomcat Web服务器
    • 根据web-demo可以找到部署在Tomcat服务器上的web-demo项目
    • 根据demo1可以找到要访问的是项目中的哪个Servlet类,根据@WebServlet后面的值进行匹配
  • 找到ServletDemo1这个类后,Tomcat Web服务器就会为ServletDemo1这个类创建一个对象,然后调用对象中的service方法

    • ServletDemo1实现了Servlet接口,所以类中必然会重写service方法供Tomcat Web服务器进行调用
    • service方法中有ServletRequest和ServletResponse两个参数,ServletRequest封装的是请求数据,ServletResponse封装的是响应数据,后期我们可以通过这两个参数实现前后端的数据交互。

小结

  • Servlet由谁创建?Servlet方法由谁调用?

Servlet由web服务器创建,Servlet方法由web服务器调用

  • 服务器怎么知道Servlet中一定有service方法?

因为我们自定义的Servlet,必须实现Servlet接口并复写其方法,而Servlet接口中有service方法

1.4 servlet生命周期

Servlet运行在Servlet容器(web服务器)中,其生命周期由容器来管理,分为4个阶段:

  • 加载和实例化:默认情况下,当Servlet第一次被访问时,由容器创建Servlet对象。
  • 初始化:在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象,完成一些如加载配置文件、创建连接等初始化的工作。该方法只调用一次。
  • 请求处理:每次请求Servlet时,Servlet容器都会调用Servlet的service()方法对请求进行处理。
  • 服务终止:当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法完成资源的释放。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。

4个阶段的中所使用的方法:

  • 初始化方法,在Servlet被创建时执行,只执行一次
void init(ServletConfig config) 
  • 提供服务方法, 每次Servlet被访问,都会调用该方法
void service(ServletRequest req, ServletResponse res)
  • 销毁方法,当Servlet被销毁时,调用该方法。在内存释放或服务器关闭时销毁Servlet
void destroy() 

剩下的两个方法是:

  • 获取Servlet信息
String getServletInfo() 
//该方法用来返回Servlet的相关信息,没有什么太大的用处,一般我们返回一个空字符串即可
public String getServletInfo() {
    return "";
}
  • 获取ServletConfig对象
ServletConfig getServletConfig()

ServletConfig对象,在init方法的参数中有,而Tomcat Web服务器在创建Servlet对象的时候会调用init方法,必定会传入一个ServletConfig对象,我们只需要将服务器传过来的ServletConfig进行返回即可。

2 Request类

2.1 Request概述

request:获取请求数据

  • 浏览器会发送HTTP请求到后台服务器:Tomcat。
  • HTTP的请求中会包含很多请求数据:请求行+请求头+请求体。
  • 后台服务器Tomcat会对HTTP请求中的数据进行解析并把解析结果存入到一个对象中。
  • 所存入的对象即为request对象,所以我们可以从request对象中获取请求的相关参数。
  • 获取到数据后就可以继续后续的业务,比如获取用户名和密码就可以实现登录操作的相关业务。
  • Request的继承体系为ServletRequest-->HttpServletRequest-->RequestFacade

2.2 HttpServletRequest获取请求数据:请求行

返回值类型方法声明描述
StringgetMethod()该方法用于获取 HTTP 请求方式(如 GET、POST 等)。
StringgetRequestURI()该方法用于获取请求行中的资源名称部分,即位于 URL 的主机和端口之后,参数部分之前的部分。
StringgetQueryString()该方法用于获取请求行中的参数部分,也就是 URL 中“?”以后的所有内容。
StringgetContextPath()返回当前 Servlet 所在的应用的名字(上下文)。对于默认(ROOT)上下文中的 Servlet,此方法返回空字符串""。
StringgetServletPath()该方法用于获取 Servlet 所映射的路径。
StringgetRemoteAddr()该方法用于获取客户端的 IP 地址。
StringgetRemoteHost()该方法用于获取客户端的完整主机名,如果无法解析出客户机的完整主机名,则该方法将会返回客户端的 IP 地址。

测试使用:

@WebServlet("/ServletDemo01")
public class ServletDemo01 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //请求行
        //返回发出此请求的 HTTP 方法的名称,例如 GET、POST 或 PUT.
        String method = request.getMethod();
        System.out.println(method);
        //返回请求 URI 中指示请求上下文的部分。即项目访问路径
        String contextPath = request.getContextPath();
        System.out.println(contextPath);
        //返回的 URL 包含协议、服务器名称、端口号和服务器路径,但不包含查询字符串参数。
        StringBuffer requestURL = request.getRequestURL();
        System.out.println(requestURL);
        //返回此请求的 URL 从协议名称到 HTTP 请求第一行中的查询字符串的部分
        String requestURI = request.getRequestURI();
        System.out.println(requestURI);
        //返回路径后面的请求 URL 中包含的查询字符串
        String queryString = request.getQueryString();
        System.out.println(queryString);
    }

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

使用PostMan发起一个GET请求:

image-20220809195144455

获取的请求行数据:

image-20220809195317138

2.3 HttpServletRequest获取请求数据:请求头

返回值类型方法声明描述
StringgetHeader(String name)该方法用于获取一个指定头字段的值。 如果请求消息中包含多个指定名称的头字段,则该方法返回其中第一个头字段的值。
EnumerationgetHeaders(String name)该方法返回指定头字段的所有值的枚举集合, 在多数情况下,一个头字段名在请求消息中只出现一次,但有时可能会出现多次。
EnumerationgetHeaderNames()该方法返回请求头中所有头字段的枚举集合。
StringgetContentType()该方法用于获取 Content-Type 头字段的值。
intgetContentLength()该方法用于获取 Content-Length 头字段的值 。
StringgetCharacterEncoding()该方法用于返回请求消息的字符集编码 。

API测试:

@WebServlet("/ServletDemo02")
public class ServletDemo02 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String header = request.getHeader("User-Agent");
        System.out.println(header);
        //获得所有请求头字段的枚举集合
        Enumeration<String> headers = request.getHeaderNames();
        while (headers.hasMoreElements()) {
            //获得请求头字段的值
            String value = request.getHeader(headers.nextElement());
            System.out.println(value);
        }
        String contentType = request.getContentType();
        System.out.println(contentType);
        String characterEncoding = request.getCharacterEncoding();
        System.out.println(characterEncoding);
    }

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

使用PostMan发起一个GET请求:

image-20220810190415704

获取的请求头数据:

image-20220810190335146

2.4 HttpServletRequest获取请求数据:请求体

返回值类型方法声明功能描述
StringgetParameter(String name)返回指定参数名的参数值。
String [ ]getParameterValues (String name)以字符串数组的形式返回指定参数名的所有参数值(HTTP 请求中可以有多个相同参数名的参数)。
EnumerationgetParameterNames()以枚举集合的形式返回请求中所有参数名。
MapgetParameterMap()用于将请求中的所有参数名和参数值装入一个 Map 对象中返回。

API测试:

@WebServlet("/ServletDemo03")
public class ServletDemo03 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String user = request.getParameter("user");
        System.out.println(user);
        String[] users = request.getParameterValues("user");
        for (String s : users) {
            System.out.println(s);
        }
        Map<String, String[]> map = request.getParameterMap();
        map.forEach(((s, strings) -> System.out.println(s+"="+request.getParameter(s))));
    }
}

使用PostMan发起一个POST请求:

image-20220810191911674

获取的请求体数据:

image-20220810191855651

2.5 Request请求乱码问题

  • POST请求和GET请求的参数中如果有中文,后台接收数据就会出现中文乱码问题

    GET请求在Tomcat8.0以后的版本就不会出现了

image-20220810210605825

  • POST请求解决方案是:设置输入流的编码

    request.setCharacterEncoding("UTF-8");

image-20220810211923355

2.6 Request请求转发

RequestDispatcher 接口

javax.servlet 包中定义了一个 RequestDispatcher 接口,RequestDispatcher 对象由 Servlet 容器创建,用于封装由路径所标识的 Web 资源。利用 RequestDispatcher 对象可以把请求转发给其他的 Web 资源。

Servlet 可以通过 2 种方式获得 RequestDispatcher 对象:

  1. 调用 ServletContext 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,必须为绝对路径;
  2. 调用 ServletRequest 的 getRequestDispatcher(String path) 方法,参数 path 指定目标资源的路径,可以为绝对路径,也可以为相对路径。

RequestDispatcher 接口中提供了以下方法。

返回值类型方法功能描述
voidforward(ServletRequest request,ServletResponse response)用于将请求转发给另一个 Web 资源。该方法必须在响应提交给客户端之前被调用,否则将抛出 IllegalStateException 异常
voidinclude(ServletRequest request,ServletResponse response)用于将其他的资源作为当前响应内容包含进来

request 域对象

request 是 Servlet 的三大域对象之一,它需要与请求转发配合使用,才可以实现动态资源间的数据传递。

返回值类型方法描述
voidsetAttribute(String name, Object o)将 Java 对象与属性名绑定,并将它作为一个属性存放到 request 对象中。参数 name 为属性名,参数 object 为属性值。
ObjectgetAttribute(String name)根据属性名 name,返回 request 中对应的属性值。
voidremoveAttribute(String name)用于移除 request 对象中指定的属性。
EnumerationgetAttributeNames()用于返回 request 对象中的所有属性名的枚举集合。

测试:

@WebServlet("/ServletDemo04")
public class ServletDemo04 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        request.setAttribute("user","刘畅");
        request.getRequestDispatcher("/ServletDemo05").forward(request,response);

    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
@WebServlet("/ServletDemo05")
public class ServletDemo05 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        String user = (String) request.getAttribute("user");
        System.out.println(user);
    }

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

3 Response类

3.1 Response概述

response:设置响应数据

  • 业务处理完后,后台就需要给前端返回业务处理的结果即响应数据。
  • 把响应数据封装到response对象中。
  • 后台服务器[Tomcat]会解析response对象,按照[响应行+响应头+响应体]格式拼接结果。
  • 浏览器最终解析结果,把内容展示在浏览器给用户浏览。

3.2 HttpServletResponse的API

响应行API

返回值类型方法描述
voidsetStatus(int status)用于设置 HTTP 响应消息的状态码,并生成响应状态行。
voidsendError(int sc)用于发送表示错误信息的状态码。

响应头API

返回值类型方法描述
voidaddHeader(String name,String value)用于增加响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
voidsetHeader (String name,String value)用于设置响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值。
voidaddIntHeader(String name,int value)用于增加值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。
voidsetIntHeader(String name, int value)用于设置值为 int 类型的响应头字段,其中,参数 name 用于指定响应头字段的名称,参数 value 用于指定响应头字段的值,类型为 int。
voidsetContentType(String type)用于设置 Servlet 输出内容的 MIME 类型以及编码格式。
voidsetCharacterEncoding(String charset)用于设置输出内容使用的字符编码。

响应体API

返回值类型方法描述
ServletOutputStreamgetOutputStream()用于获取字节输出流对象。
PrintWritergetWriter()用于获取字符输出流对象。

3.3 Respones请求重定向

(1)浏览器发送请求给服务器,服务器中对应的资源A接收到请求。

(2)资源A现在无法处理该请求,就会给浏览器响应一个302的状态码+location的一个访问资源B的路径。

(3)浏览器接收到响应状态码为302就会重新发送请求到location对应的访问地址去访问资源B。

(4)资源B接收到请求后进行处理并最终给浏览器响应结果,这整个过程就叫重定向。

转发和重定向的区别

转发和重定向都能实现页面的跳转,但是两者也存在以下区别。

区别转发重定向
浏览器地址栏 URL 是否发生改变
是否支持跨域跳转
请求与响应的次数一次请求和一次响应两次请求和两次响应
是否共享 request 对象和 response 对象
是否能通过 request 域对象传递数据
速度相对要快相对要慢
行为类型服务器行为客户端行为

response.sendRedirect()

HttpServletResponse 接口中的 sendRedirect() 方法用于实现重定向。

返回值类型方法描述
voidsendRedirect(String location)向浏览器返回状态码为 302 的响应结果,让浏览器访问新的 URL。若指定的 URL 是相对路径,Servlet 容器会将相对路径转换为绝对路径。参数 location 表示重定向的URL。
@WebServlet("/ServletDemo06")
public class ServletDemo06 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String contextPath = request.getContextPath();
        System.out.println("ServletDemo06访问");
        response.sendRedirect(contextPath+"/ServletDemo07");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }
}
@WebServlet("/ServletDemo07")
public class ServletDemo07 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("ServletDemo07访问");
    }

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

image-20220810220301400

4 综合案例-登录案例

实现登录:

Maven导入相关依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>

    <!--mybatis 依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>
    <!--mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.29</version>
    </dependency>
    <!-- 添加slf4j日志api -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</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>

在resources下导入mybatis核心配置

<?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.liu.pojo"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///test?useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="131411"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.liu.dao"/>
    </mappers>
</configuration>

编写实体类User

package com.liu.pojo;

public class User {
    private int id;
    private String username;
    private String password;
    private String gender;
    private String addr;

    //省略了 setter 和 getter toString

编写Dao层

package com.liu.dao;

import com.liu.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserDao {
    @Select("select * from tb_user where username = #{username} and password = #{password}")
    User selectLogin(@Param("username") String username, @Param("password") String password);
}

编写Mybatis工具类,简化重复代码

package com.liu.utils;

import com.liu.dao.UserDao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MybatisUtils {
    public static UserDao getUserDao(){
        //获取sqlSessionFactory
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        {
            try {
                inputStream = Resources.getResourceAsStream(resource);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession.getMapper(UserDao.class);
    }

}

编写Service层

package com.liu.service;

import com.liu.dao.UserDao;
import com.liu.pojo.User;
import com.liu.utils.MybatisUtils;

public class UserService {

    public boolean login(String username,String password){
        UserDao userDao = MybatisUtils.getUserDao();
        User user = userDao.selectLogin(username, password);
        if (user!=null){
            return true;
        }else {
            return false;
        }
    }
}

进行测试Dao层

import com.liu.dao.UserDao;
import com.liu.pojo.User;
import com.liu.service.UserService;
import com.liu.utils.MybatisUtils;
import org.junit.Test;

public class Test01 {
    @Test
    public void Test1(){
        UserDao userDao = MybatisUtils.getUserDao();
        User user = userDao.selectLogin("六珍惜", "123");
        System.out.println(user);
    }
    @Test
    public void Test2(){
        UserService userService = new UserService();
        boolean b = userService.login("六珍惜", "123");
        System.out.println(b);
        boolean b1 = userService.login("liuchang", "123");
        System.out.println(b1);
    }
}

编写Web层

package com.liu.web;

import com.liu.service.UserService;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("utf-8");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        System.out.println(username);
        System.out.println(password);
        UserService userService = new UserService();
        boolean b = userService.login(username, password);
        String contextPath = request.getContextPath();
        if (b){
            request.setAttribute("username",username);
            request.setAttribute("password",password);
            request.getRequestDispatcher("/LoginSuccessfulServlet").forward(request,response);
            response.sendRedirect(contextPath+"/LoginSuccessfulServlet");
        }else {
            response.sendRedirect(contextPath+"/LoginFailedServlet");
        }
    }

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

登录成功跳转

package com.liu.web;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/LoginSuccessfulServlet")
public class LoginSuccessfulServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = response.getWriter();
        String username = (String) request.getAttribute("username");
        String password = (String) request.getAttribute("password");
        writer.write("<h1>登录成功了!</h1>"+"欢迎您:"+username);
    }

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

登录失败跳转

package com.liu.web;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;

@WebServlet("/LoginFailedServlet")
public class LoginFailedServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html; charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.write("<h1>登录失败了!</h1>");
    }

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

编写前端:

index.jsp(从网上复制的)

<%@ page contentType="text/html;charset=UTF-8" language="java"%>

<!DOCTYPE html>
<html>
<body>
<div class="main">
    <div class="title">
        <span>密码登录</span>
    </div>

    <div class="title-msg">
        <span>请输入登录账户和密码</span>
    </div>

    <form class="login-form" action="/LoginServlet" method="post" novalidate >
        <!--输入框-->
        <div class="input-content">
            <!--autoFocus-->
            <div>
                <input type="username" autocomplete="off"
                       placeholder="用户名" name="username" required/>
            </div>

            <div style="margin-top: 16px">
                <input type="password"
                       autocomplete="off" placeholder="登录密码" name="password" required maxlength="32"/>
            </div>
        </div>

        <!--登入按钮-->
        <div style="text-align: center">
            <button type="submit" class="enter-btn" >登录</button>
        </div>
        <div class="foor">
            <div class="left"><span>忘记密码</span></div>

            <div class="right"><span>注册账户</span></div>
        </div>
    </form>
</div>
</body>
</html>

<style>
    body{
        background: #353f42;
    }

    *{
        padding: 0;
        margin: 0;
    }
    .main {
        margin: 0 auto;
        padding-left: 25px;
        padding-right: 25px;
        padding-top: 15px;
        width: 350px;
        height: 350px;
        background: #FFFFFF;
        /*以下css用于让登录表单垂直居中在界面,可删除*/
        position: absolute;
        top: 50%;
        left: 50%;
        margin-top: -175px;
        margin-left: -175px;
    }

    .title {
        width: 100%;
        height: 40px;
        line-height: 40px;
    }

    .title span {
        font-size: 18px;
        color: #353f42;
    }

    .title-msg {
        width: 100%;
        height: 64px;
        line-height: 64px;
    }

    .title:hover{
        cursor: default    ;
    }

    .title-msg:hover{
        cursor: default    ;
    }

    .title-msg span {
        font-size: 12px;
        color: #707472;
    }

    .input-content {
        width: 100%;
        height: 120px;
    }

    .input-content input {
        width: 330px;
        height: 40px;
        border: 1px solid #dad9d6;
        background: #ffffff;
        padding-left: 10px;
        padding-right: 10px;
    }

    .enter-btn {
        width: 350px;
        height: 40px;
        color: #fff;
        background: #0bc5de;
        line-height: 40px;
        text-align: center;
        border: 0px;
    }

    .foor{
        width: 100%;
        height: auto;
        color: #9b9c98;
        font-size: 12px;
        margin-top: 20px;
    }

    .enter-btn:hover {
        cursor:pointer;
        background: #1db5c9;
    }

    .foor div:hover {
        cursor:pointer;
        color: #484847;
        font-weight: 600;
    }

    .left{
        float: left;
    }
    .right{
        float: right;
    }
</style>

运行测试:

学习记录 5

1 会话技术

从打开浏览器访问某个网站,到关闭浏览器的过程,称为一次会话。会话技术是指在会话中,帮助服务器记录用户状态和数据的技术。

常用的会话技术分为两种:

  1. Cookie :客户端会话技术
  2. Session :服务端会话技术

2 Cookie技术

Cookie 属于客户端会话技术,它是服务器发送给浏览器的小段文本信息,存储在客户端浏览器的内存中或硬盘上。当浏览器保存了 Cookie 后,每次访问服务器,都会在 HTTP 请求头中将这个 Cookie 回传给服务器。

2.1 Cookie的分类

Cookie分为两种:

  1. 会话级别 Cookie(默认):Cookie 保存到浏览器的内存中,浏览器关闭则 Cookie 失效。
  2. 持久的 Cookie:Cookie 以文本文件的形式保存到硬盘上。

2.2 Cookie的工作流程

Cookie 是基于 HTTP 协议实现的,工作流程如下。

  1. 客户端浏览器访问服务器时,服务器通过在 HTTP 响应中增加 Set-Cookie 字段,将数据信息发送给浏览器。
  2. 浏览器将 Cookie 保存在内存中或硬盘上。
  3. 再次请求该服务器时,浏览器通过在 HTTP 请求消息中增加 Cookie 请求头字段,将 Cookie 回传给 Web 服务器。服务器根据 Cookie 信息跟踪客户端的状态。

2.3 Cookie API

HttpServletResponse 接口和 HttpServletRequest 接口也都定义了与 Cookie 相关的方法,如下表所示。

方法描述所属接口
void addCookie(Cookie cookie)用于在响应头中增加一个相应的 Set-Cookie 头字段。javax.servlet.http.HttpServletResponse
Cookie[] getCookies()用于获取客户端提交的 Cookie。javax.servlet.http.HttpServletRequest

javax.servlet.http.Cookie 类中提供了一系列获取或者设置 Cookie 的方法,如下表。

返回值类型方法描述
intgetMaxAge()用于获取指定 Cookie 的最大有效时间,以秒为单位。 默认情况下取值为 -1,表示该 Cookie 保留到浏览器关闭为止。
StringgetName()用于获取 Cookie 的名称。
StringgetPath()用于获取 Cookie 的有效路径。
booleangetSecure()如果浏览器只通过安全协议发送 Cookie,则返回 true;如果浏览器可以使用任何协议发送 Cookie,则返回 false。
StringgetValue()用于获取 Cookie 的值。
intgetVersion()用于获取 Cookie 遵守的协议版本。
voidsetMaxAge(int expiry)用于设置 Cookie 的最大有效时间,以秒为单位。 取值为正值时,表示 Cookie 在经过指定时间后过期。取值为负值时,表示 Cookie 不会被持久存储,在 Web 浏览器退出时删除。取值为 0 时,表示删除该 Cookie。
voidsetPath(String uri)用于指定 Cookie 的路径。
voidsetSecure(boolean flag)用于设置浏览器是否只能使用安全协议(如 HTTPS 或 SSL)发送 Cookie。
voidsetValue(String newValue)用于设置 Cookie 的值。
@WebServlet("/CookieServlet")
public class CookieServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie cookie = new Cookie("username","liuchang");
        //设置其存在5000秒
        cookie.setMaxAge(5000);
        response.addCookie(cookie);
    }

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

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

image-20220811100917483

image-20220811100822973

2.4 Cookie的使用细节

使用 Cookie 开发时需要注意以下细节:

  • 一个 Cookie 只能标识一种信息,它至少包含一个名称(NAME)和一个值(VALUE)。
  • 如果创建了一个 Cookie,并发送到浏览器,默认情况下它是一个会话级别的 Cookie。用户退出浏览器就被删除。如果希望将 Cookie 存到磁盘上,则需要调用 setMaxAge(int maxAge) 方法设置最大有效时间,以秒为单位。
  • 使用 setMaxAge(0) 手动删除 Cookie时,需要使用 setPath 方法指定 Cookie 的路径,且该路径必须与创建 Cookie 时的路径保持一致。

3 Session技术

Session 是服务器端会话技术。当浏览器访问 Web 服务器的资源时,服务器可以为每个用户浏览器创建一个 Session 对象,每个浏览器独占一个 Session 对象。

由于每个浏览器独占一个 Session,所以用户在访问服务器的资源时,可以把数据保存在各自的 Session 中。当用户再次访问该服务器中的其它资源时,其它资源可以从 Session 中取出数据,为用户服务。

3.1 Session 的工作原理

  1. 当客户端第一次请求会话对象时,服务器会创建一个 Session 对象,并为该 Session 对象分配一个唯一的 SessionID(用来标识这个 Session 对象);
  2. 服务器将 SessionID 以 Cookie(Cookie 名称为:“JSESSIONID”,值为 SessionID 的值)的形式发送给客户端浏览器;
  3. 客户端浏览器再次
  4. 发送 HTTP 请求时,会将携带 SessionID 的 Cookie 随请求一起发送给服务器;
  5. 服务器从请求中读取 SessionID,然后根据 SessionID 找到对应的 Session 对象。

3.2 Session 与 Cookie 对比

Session 和 Cookie 都属于会话技术,都能帮助服务器保存和跟踪用户状态,但两者也存在差异,如下表。

不同点CookieSession
存储位置不同Cookie 将数据存放在客户端浏览器内存中或硬盘上。Session 将数据存储在服务器端。
大小和数量限制不同浏览器对 Cookie 的大小和数量有限制。Session 的大小和数量一般不受限制。
存放数据类型不同Cookie 中保存的是字符串。Session 中保存的是对象。
安全性不同Cookie 明文传递,安全性低,他人可以分析存放在本地的 Cookie 并进行 Cookie 欺骗。Session 存在服务器端,安全性较高。
对服务器造成的压力不同Cookie 保存在客户端,不占用服务器资源。Session 保存在服务端,每一个用户独占一个 Session。若并发访问的用户十分多,就会占用大量服务端资源。
跨域支持上不同Cookie 支持跨域名访问。Session 不支持跨域名访问。

3.3 Session API

Session 对象由服务器创建,通过 HttpServletRequest.getSession() 方法可以获得 HttpSession 对象。

HttpSession 接口定义了一系列对 Session 对象操作的方法,如下表。

返回值类型方法描述
longgetCreationTime()返回创建 Session 的时间。
StringgetId()返回获取 Seesion 的唯一的 ID。
longgetLastAccessedTime()返回客户端上一次发送与此 Session 关联的请求的时间。
intgetMaxInactiveInterval()返回在无任何操作的情况下,Session 失效的时间,以秒为单位。
ServletContextgetServletContext()返回 Session 所属的 ServletContext 对象。
voidinvalidate()使 Session 失效。
voidsetMaxInactiveInterval(int interval)指定在无任何操作的情况下,Session 失效的时间,以秒为单位。负数表示 Session 永远不会失效。

3.4 Session 域对象

Session 对象也是一种域对象,它可以对属性进行操作,进而实现会话中请求之间的数据通讯和数据共享。

在 javax.servlet.http.HttpSession 接口中定义了一系列操作属性的方法,如下表。

返回值类型方法描述
voidsetAttribute(String name, Object o)把一个 Java 对象与一个属性名绑定,并将它作为一个属性存放到 Session 对象中。 参数 name 为属性名,参数 object 为属性值。
ObjectgetAttribute(String name)根据指定的属性名 name,返回 Session 对象中对应的属性值。
voidremoveAttribute(String name)从 Session 对象中移除属性名为 name 的属性。
EnumerationgetAttributeNames()用于返回 Session 对象中的所有属性名的枚举集合。

Session 、request 以及 ServletContext 合称为 Servlet 的三大域对象,它们都能保存和传递数据,但是三者也存在许多差异,如下表。

不同requestSessionServletContext
类型javax.servlet.http.HttpServletRequestjavax.servlet.http.HttpSessionjavax.servlet.ServletContext
创建客户端向容器发送请求时创建。容器第一次调用 getSession() 方法时创建。Servlet 容器启动时创建。
销毁容器对这次请求做出响应后销毁。Session 销毁的时机: 关闭服务器或应用被卸载。Session 过期,默认为 30 分钟。手动调用 session.invalidate() 方法进行销毁。容器关闭或者 Web 应用被移除时销毁。
有效范围只对当前请求涉及的 Servlet 有效。Session 对本次会话期间的所有 Servlet 都有效。对整个 Web 应用内的所有 Servlet 有效。
数量Web 应用中的所有 Servlet 实例都可以有多个 request 对象。Web 应用中可以有多个 Session,多个 Servet 实例可以共享同一 Session 对象。在整个 Web 应用中只有一个 Context 对象。
数据共享每一次请求都是一个新的 request 对象。 通过和请求转发的配合使用可以实现一次请求中 Web 组件之间共享的数据。每一次会话都是一个新的 Session 对象。 通过 Session 域对象可以实现一次会话中的多个请求之间共享数据。在一个应用中有且只有一个 Context 对象,作用于整个 Web 应用,可以实现多次会话之间的数据共享。

学习笔记6

Filter、Listener、Ajax

1 Filter

1.1 Filter概述

Servlet Filter 又称 Servlet 过滤器,它是在 Servlet 2.3 规范中定义的,能够对 Servlet 容器传给 Web 资源的 request 对象和 response 对象进行检查和修改。

Filter 不是 Servlet,不能直接访问,它本身也不能生成 request 对象和 response 对象,它只能为 Web 资源提供以下过滤功能:

  • 在 Web 资源被访问前,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
  • 将请求传递到下一个过滤器或目标资源。
  • 在 Web 资源被访问后,检查 response 对象,修改响应头和响应正文。
注意:过滤器并不是必须要将请求传递到下一个过滤器或目标资源,它可以自行对请求进行处理,并发送响应给客户端,也可以将请求转发或重定向到其他的 Web 资源。

Filter 是 Servlet 规范中最实用的技术,通过它可以对服务器管理的所有 Web 资源(例如 JSP、Servlet、静态 HTML 文件、静态图片等)进行拦截,从而实现一些特殊的功能,例如用户的权限控制、过滤敏感词、设置统一编码格式等。

1.2 Filter 接口API

与开发 Servlet 需要实现 javax.servlet.Servlet 接口类似,开发过滤器要实现 javax.servlet.Filter 接口,并提供一个公开的不带参的构造方法。在 Filter 接口中,定义了 3 个方法,如下表所示。

返回值类型方法功能描述
voidinit (FilterConfig filterConfig)该方法用于初始化过滤器。
voiddoFilter(ServletRequest request,SeivletResponse response, FilterChain chain)该方法完成实际的过滤操作,当客户端请求的 URL 与过滤器映射的 URL 匹配时,容器会先调用该方法对请求进行拦截。 参数 request 和 response 表示请求和响应对象。 参数 chain 代表当前 Filter 链对象,在该方法内部,调用 chain.doFilter() 方法,才能把请求交付给 Filter 链中的下一个 Filter 或者 Web 资源。
voiddestroy()该方法在销毁 Filter 对象之前被调用,用于释放被 Filter 对象占用的资源。

1.3 Filter 的工作流程

  1. 客户端请求访问容器内的 Web 资源。
  2. Servlet 容器接收请求,并针对本次请求分别创建一个 request 对象和 response 对象。
  3. 请求到达 Web 资源之前,先调用 Filter 的 doFilter() 方法,检查 request 对象,修改请求头和请求正文,或对请求进行预处理操作。
  4. 在 Filter 的 doFilter() 方法内,调用 FilterChain.doFilter() 方法,将请求传递给下一个过滤器或目标资源。
  5. 目标资源生成响应信息返回客户端之前,处理控制权会再次回到 Filter 的 doFilter() 方法,执行 FilterChain.doFilter() 后的语句,检查 response 对象,修改响应头和响应正文。
  6. 响应信息返回客户端。

1.4 Filter的生命周期

Filter 的生命周期分为 3 个阶段:

  1. 初始化阶段
  2. 拦截和过滤阶段
  3. 销毁阶段

初始化阶段

Servlet 容器负责加载和实例化 Filter。容器启动时,读取 web.xml 或 @WebFilter 的配置信息对所有的过滤器进行加载和实例化。

加载和实例化完成后,Servlet 容器调用 init() 方法初始化 Filter 实例。在 Filter 的生命周期内, init() 方法只执行一次。

拦截和过滤阶段

该阶段是 Filter 生命周期中最重要的阶段。当客户端请求访问 Web 资源时,Servlet 容器会根据 web.xml 或 @WebFilter 的过滤规则进行检查。当客户端请求的 URL 与过滤器映射匹配时,容器将该请求的 request 对象、response 对象以及 FilterChain 对象以参数的形式传递给 Filter 的 doFilter() 方法,并调用该方法对请求/响应进行拦截和过滤。

销毁阶段

Filter 对象创建后会驻留在内存中,直到容器关闭或应用被移除时销毁。销毁 Filter 对象之前,容器会先调用 destory() 方法,释放过滤器占用的资源。在 Filter 的生命周期内,destory() 只执行一次。

1.5 Filter案例

实现对登录的验证:

创建Filter:在IDEA中可以直接使用模板进行创建

image-20220813085601062

package com.liu.web.filterDemo;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebServlet("/*")
public class FilterDemo01 implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("filter执行");
        //进行转换
        HttpServletRequest req = (HttpServletRequest) request;
        //判断访问资源路径是否和登录注册相关
        //1,在数组中存储登陆和注册相关的资源路径
        String[] urls = {"/","/LoginServlet"};
        //2,获取当前访问的资源路径
        String url = req.getRequestURL().toString();

        //3,遍历数组,获取到每一个需要放行的资源路径
        for (String u : urls) {
            //4,判断当前访问的资源路径字符串是否包含要放行的的资源路径字符串
            if(url.contains(u)){
                //找到了,放行
                chain.doFilter(request, response);
                //break;
                return;
            }
        }
        Object username = req.getSession().getAttribute("username");
        if (username != null){
            chain.doFilter(request, response);
        }else {
            System.out.println("请进行登录");
            req.getRequestDispatcher("/LoginServlet").forward(request, response);
        }

    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("filter执行");
    }

    @Override
    public void destroy() {

    }
}

2 Listener

2.1 Listener概述

监听器 Listener 是一个实现特定接口的 Java 程序,这个程序专门用于监听另一个 Java 对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即自动执行。

监听器的相关概念:

  • 事件:方法调用、属性改变、状态改变等。
  • 事件源:被监听的对象( 例如:request、session、servletContext)。
  • 监听器:用于监听事件源对象 ,事件源对象状态的变化都会触发监听器。
  • 注册监听器:将监听器与事件源进行绑定。

2.2 Listener的分类

Servlet 规范中定义了 8 个监听器接口,可以用于监听 ServletContext、HttpSession 和 ServletRequest 对象的生命周期和属性变化事件。开发 Servlet 监听器需要实现相应的监听器接口并重写接口中的方法。

监听器 Listener 按照监听的事件划分,可以分为 3 类:

  1. 监听对象创建和销毁的监听器
  2. 监听对象中属性变更的监听器
  3. 监听 HttpSession 中的对象状态改变的监听器

这里面只有 ServletContextListener 这个监听器后期我们会接触到,ServletContextListener 是用来监听 ServletContext 对象的创建和销毁。

ServletContextListener 接口中有以下两个方法

  • void contextInitialized(ServletContextEvent sce)ServletContext 对象被创建了会自动执行的方法
  • void contextDestroyed(ServletContextEvent sce)ServletContext 对象被销毁时会自动执行的方法

2.3 Listener的创建

创建Listener:在IDEA中可以直接使用模板进行创建

image-20220813092328351

package com.liu.web.filterDemo;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;

@WebListener
public class ListenerDemo01 implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {

    public ListenerDemo01() {
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        /* This method is called when the servlet context is initialized(when the Web application is deployed). */
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        /* This method is called when the servlet Context is undeployed or Application Server shuts down. */
    }

    @Override
    public void sessionCreated(HttpSessionEvent se) {
        /* Session is created. */
    }

    @Override
    public void sessionDestroyed(HttpSessionEvent se) {
        /* Session is destroyed. */
    }

    @Override
    public void attributeAdded(HttpSessionBindingEvent sbe) {
        /* This method is called when an attribute is added to a session. */
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent sbe) {
        /* This method is called when an attribute is removed from a session. */
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent sbe) {
        /* This method is called when an attribute is replaced in a session. */
    }
}

学习总结

在本周中,主要复习了关于MySql数据库SQL语言的基本语法、底层JDBC的实现、Mybatis框架的基本使用、Servlet、Request、Response、会话技术等等,本周学习进度正常,不过跳过了一些课程,例如:Maven的配置、HTML/CSS/JS前端语言。对于Maven的配置在本科阶段已经会简单的使用,等到看Maven的高级教程时再学习;而对于前端语言的基本语法和基本使用,我也有大致的了解,前端语言入门易学精难,于是就把这两个课程给跳过了。

在接下来的时间里,离开学还剩下三周时间,下周计划是将相关的JavaWeb底层中的类复习完毕,先学习Spring(Spring有四天的课程),接着学习Git版本控制工具(1天课程)。在下下周学习Mybatis框架的高级使用、SSM结合、以及SpringBoot框架。最后一周继续学习SpringBoot框架。继续努力~~~

0

评论

博主关闭了所有页面的评论