Linux安装软件

安装方式

  • 1.二进制发布包
      软件已经针对具体平台编译打包发布,只要解压,修改配置即可
  • 2.RPM包(依赖)
      软件已经按照redhat的包管理工具规范RPM进行打包发布,需要获取到相应的软件RPM发布包,然后用RPM命令进行安装
  • 3.Yum在线安装(必须有网络)
      软件已经以RPM规范打包,但发布在了网络上的一些服务器上,可用yum在线安装服务器上的rpm软件,并且会自动解决软件安装过程中的库依赖问题
  • 4.源码编译安装
      软件以源码工程的形式发布,需要获取到源码工程后用相应开发工具进行编译打包部署。

上传下载文件软件

FileZilla软件

1.下载安装之后直接使用

lrzsz软件

1.CRT软件下输入 yum install lrzsz

2.CRT软件 –> 选项 –> 会话设置 –> 设置上传下载目录

3.上传(rz): CRT软件下输入 rz –> 从uploads上传到linux当前目录下

4.下载(sz 文件名): CRT软件下输入 sz 文件名 –> 下载到downloads目录下

sftp协议(Alt+p)

1. 上传(put):通过Alt+p进入SFTP界面 –> 输入 put F://song.txt –> 上传到linux当前目录下

2.下载(get):通过Alt+p进入SFTP界面 –> 输入 get song.txt –> 下载到电脑的文档目录下


Linux安装JDK

先确定linux系统的版本 –> 选择JDK版本

1. 通过sftp进行上传

2. mv指令剪切到usr的local下面的jdk里面

3. 将原来的java的jdk卸载

1
2
3
4
5
java –version //查看java版本
rpm -qa | grep java //查看和java有关的内容

rpm -e --nodeps java-1.6.0-openjdk-1.6.0.35-1.13.7.1.el6_6.i686 //rpm -e --nodeps 然后后面跟自己电脑显示出来的java有关的内容
rpm -e --nodeps java-1.7.0-openjdk-1.7.0.79-2.5.5.4.el6.i686

4. 使用tar -zxvf jdk名字的形式 解压

5. 配置环境变量

1. 通过vim编辑器进入编译:vim /etc/profile
2. 在后面添加
    set java environment
    JAVA_HOME=/usr/local/jdk/jdk1.7.0_71
    CLASSPATH=.:$JAVA_HOME/lib.tools.jar
    PATH=$JAVA_HOME/bin:$PATH
    export JAVA_HOME CLASSPATH PATH
3. ESC键之后:wq退出之后重新保存
    source /etc/profile 使更改的配置立即生效

流程:


Linux安装Mysql

1. 通过lrzsz的rz方式将uploads的文件上传

2. 卸载当前已经有的mysql

3. 通tar -xvf(是jar而不是gz压缩包)指令解压

4. rmp -ivh指令 安装服务端 【然后获得随机密码】

5. rmp -ivh指令 安装服务端

6. 打开mysql服务

7. 重新登陆数据库mysql

8. 重新设置密码打开数据库

9. 远程访问(Navicat访问linux里面安装的mysql)

在navicat里面尝试新建连接(失败)

需要使用语句打开远程访问并且刷新

双击之后即可使用


Linux安装Tomcat

1. 通过put上传并且tar -zxvf指令解压文件

2. 解压之后进入tomcat的bin目录执行start.sh文件

3. 打开浏览器输入:linux的地址:8080 (如果能看到tomcat显示正常就ok)


Linux安装redis

1. 先在local目录下下载gcc编译文件

2. 去网络远程下载redis

3. 解压(tar -zxvf redis-3.0.4.tar.gz )

4. 输入make进行编译

*5. 安装(make /usr/local/redis install) *

6. 将redis-3.0.4里面的redis.conf 复制到redis/bin目录下

7. 直接执行./redis-server服务器端

8. 可以再执行redis-client服务器端然后查询操作


redis

基础篇Redis

1.Redis简单介绍

Redis是一种键值型的NoSql数据库,这里有两个关键字:

  • 键值型
  • NoSql

其中键值型,是指Redis中存储的数据都是以key.value对的形式存储,而value的形式多种多样,可以是字符串.数值.甚至json:

image-20240102180736486

而NoSql则是相对于传统关系型数据库而言,有很大差异的一种数据库。

对于存储的数据,没有类似Mysql那么严格的约束,比如唯一性,是否可以为null等等,所以我们把这种松散结构的数据库,称之为NoSQL数据库。

3.初始Redis

3.1.认识NoSQL

NoSql可以翻译做Not Only Sql(不仅仅是SQL),或者是No Sql(非Sql的)数据库。是相对于传统关系型数据库而言,有很大差异的一种特殊的数据库,因此也称之为非关系型数据库

3.1.1.结构化与非结构化

传统关系型数据库是结构化数据,每一张表都有严格的约束信息:字段名.字段数据类型.字段约束等等信息,插入的数据必须遵守这些约束:

而NoSql则对数据库格式没有严格约束,往往形式松散,自由。

可以是键值型:

也可以是文档型:

甚至可以是图格式:

3.1.2.关联和非关联

传统数据库的表与表之间往往存在关联,例如外键:

而非关系型数据库不存在关联关系,要维护关系要么靠代码中的业务逻辑,要么靠数据之间的耦合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
id: 1,
name: "张三",
orders: [
{
id: 1,
item: {
id: 10, title: "荣耀6", price: 4999
}
},
{
id: 2,
item: {
id: 20, title: "小米11", price: 3999
}
}
]
}

此处要维护“张三”的订单与商品“荣耀”和“小米11”的关系,不得不冗余的将这两个商品保存在张三的订单文档中,不够优雅。还是建议用业务来维护关联关系。

3.1.3.查询方式

传统关系型数据库会基于Sql语句做查询,语法有统一标准;

而不同的非关系数据库查询语法差异极大,五花八门各种各样。

3.1.4.事务

传统关系型数据库能满足事务ACID的原则。

而非关系型数据库往往不支持事务,或者不能严格保证ACID的特性,只能实现基本的一致性。

3.1.5.总结

除了上述四点以外,在存储方式.扩展性.查询性能上关系型与非关系型也都有着显著差异,总结如下:

  • 存储方式
    • 关系型数据库基于磁盘进行存储,会有大量的磁盘IO,对性能有一定影响
    • 非关系型数据库,他们的操作更多的是依赖于内存来操作,内存的读写速度会非常快,性能自然会好一些
  • 扩展性
    • 关系型数据库集群模式一般是主从,主从数据一致,起到数据备份的作用,称为垂直扩展。
    • 非关系型数据库可以将数据拆分,存储在不同机器上,可以保存海量数据,解决内存大小有限的问题。称为水平扩展。
    • 关系型数据库因为表之间存在关联关系,如果做水平扩展会给数据查询带来很多麻烦

3.2.认识Redis

Redis诞生于2009年全称是Remote Dictionary Server 远程词典服务器,是一个基于内存的键值型NoSQL数据库。

特征

  • 键值(key-value)型,value支持多种不同数据结构,功能丰富
  • 单线程,每个命令具备原子性
  • 低延迟,速度快(基于内存.IO多路复用.良好的编码)。
  • 支持数据持久化
  • 支持主从集群.分片集群
  • 支持多语言客户端

作者:Antirez

Redis的官方网站地址:https://redis.io/

3.3.安装Redis

大多数企业都是基于Linux服务器来部署项目,而且Redis官方也没有提供Windows版本的安装包。因此课程中我们会基于Linux系统来安装Redis.

此处选择的Linux版本为CentOS 7.

3.3.1.依赖库

Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖:

1
yum install -y gcc tcl

3.3.2.上传安装包并解压

然后将课前资料提供的Redis安装包上传到虚拟机的任意目录:

例如,我放到了/usr/local/src 目录:

解压缩:

1
tar -xzf redis-6.2.6.tar.gz

解压后:

image-20211211080339076

进入redis目录:

1
cd redis-6.2.6

运行编译命令:

1
make && make install

如果没有出错,应该就安装成功了。

默认的安装路径是在 /usr/local/bin目录下:

该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:

  • redis-cli:是redis提供的命令行客户端
  • redis-server:是redis的服务端启动脚本
  • redis-sentinel:是redis的哨兵启动脚本

3.3.3.启动

redis的启动方式有很多种,例如:

  • 默认启动
  • 指定配置启动
  • 开机自启

3.3.4.默认启动

安装完成后,在任意目录输入redis-server命令即可启动Redis:

1
redis-server

如图:

这种启动属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C则Redis停止。不推荐使用。

3.3.5.指定配置启动

如果要让Redis以后台方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下(/usr/local/src/redis-6.2.6),名字叫redis.conf:

image-20240102190447020

我们先将这个配置文件备份一份:

1
cp redis.conf redis.conf.bck

然后修改redis.conf文件中的一些配置:

1
2
3
4
5
6
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 守护进程,修改为yes后即可后台运行
daemonize yes
# 密码,设置后访问Redis必须输入密码
requirepass 123321

Redis的其它常见配置:

1
2
3
4
5
6
7
8
9
10
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志.持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"

启动Redis:

1
2
3
4
# 进入redis安装目录 
cd /usr/local/src/redis-6.2.6
# 启动
redis-server redis.conf

停止服务:

1
2
3
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务,
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u 123321 shutdown

3.3.6.开机自启

我们也可以通过配置来实现开机自启。

首先,新建一个系统服务文件:

1
vi /etc/systemd/system/redis.service

内容如下:

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target

然后重载系统服务:

1
systemctl daemon-reload

现在,我们可以用下面这组命令来操作redis了:

1
2
3
4
5
6
7
8
# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis

执行下面的命令,可以让redis开机自启:

1
systemctl enable redis

3.4.Redis桌面客户端

安装完成Redis,我们就可以操作Redis,实现数据的CRUD了。这需要用到Redis客户端,包括:

  • 命令行客户端
  • 图形化桌面客户端
  • 编程客户端

3.4.1.Redis命令行客户端

Redis安装完成后就自带了命令行客户端:redis-cli,使用方式如下:

1
redis-cli [options] [commonds]

其中常见的options有:

  • -h 127.0.0.1:指定要连接的redis节点的IP地址,默认是127.0.0.1
  • -p 6379:指定要连接的redis节点的端口,默认是6379
  • -a 123321:指定redis的访问密码

其中的commonds就是Redis的操作命令,例如:

  • ping:与redis服务端做心跳测试,服务端正常会返回pong

不指定commond时,会进入redis-cli的交互控制台:

3.4.2.图形化桌面客户端

GitHub上的大神编写了Redis的图形化桌面客户端,地址:https://github.com/uglide/RedisDesktopManager

不过该仓库提供的是RedisDesktopManager的源码,并未提供windows安装包。

在下面这个仓库可以找到安装包:https://github.com/lework/RedisDesktopManager-Windows/releases

3.4.3.安装

在课前资料中可以找到Redis的图形化桌面客户端:

解压缩后,运行安装程序即可安装:

安装完成后,在安装目录下找到rdm.exe文件:

双击即可运行:

3.4.4.建立连接

点击左上角的连接到Redis服务器按钮:

在弹出的窗口中填写Redis服务信息:

点击确定后,在左侧菜单会出现这个链接:

点击即可建立连接了。

Redis默认有16个仓库,编号从0至15. 通过配置文件可以设置仓库数量,但是不超过16,并且不能自定义仓库名称。

如果是基于redis-cli连接Redis服务,可以通过select命令来选择数据库:

1
2
# 选择 0号库
select 0

4.Redis常见命令

4.1 Redis数据结构介绍

Redis是一个key-value的数据库,

==key(一般是String类型),value(多种多样)==:

image-20240102190555758

可以通过Help命令来帮助我们去查看命令

image-20240102190625971

4.2 Redis 通用命令

通用指令是部分数据类型的,都可以使用的指令,常见的有:

  • ==keys== pattern:查看符合模板的所有key
  • ==del== key [key…]:删除指定的key(可以多个)
  • ==exists== key [key…]:判断key是否存在
  • ==expire== key seconds :给一个key设置有效期,有效期到期时该key会被自动删除
  • ==ttl== key:查看一个KEY的剩余有效期

通过help [command] 可以查看一个命令的具体用法,例如:

image-20240102190707881

课堂代码如下

  • KEYS
1
2
3
4
5
6
7
8
9
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379>

# 查询以a开头的key
127.0.0.1:6379> keys a*
1) "age"
127.0.0.1:6379>

贴心小提示:在生产环境下,不推荐使用keys 命令,因为这个命令在key过多的情况下,效率不高(模糊匹配)

  • DEL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
127.0.0.1:6379> help del

DEL key [key ...]
summary: Delete a key
since: 1.0.0
group: generic

127.0.0.1:6379> del name #删除单个
(integer) 1 #成功删除1个

127.0.0.1:6379> keys *
1) "age"

127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 #批量添加数据
OK

127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
4) "age"

127.0.0.1:6379> del k1 k2 k3 k4
(integer) 3 #此处返回的是成功删除的key,由于redis中只有k1,k2,k3 所以只成功删除3个,最终返回
127.0.0.1:6379>

127.0.0.1:6379> keys * #再查询全部的key
1) "age" #只剩下一个了
127.0.0.1:6379>
  • EXISTS
1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> help EXISTS

EXISTS key [key ...]
summary: Determine if a key exists
since: 1.0.0
group: generic

127.0.0.1:6379> exists age
(integer) 1

127.0.0.1:6379> exists name
(integer) 0
  • EXPIRE

贴心小提示:内存非常宝贵,对于一些数据,我们应当给他一些过期时间,当过期时间到了之后,他就会自动被删除~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> expire age 10
(integer) 1

127.0.0.1:6379> ttl age
(integer) 8

127.0.0.1:6379> ttl age
(integer) -2 #当这个key过期了,那么此时查询出来就是-2

127.0.0.1:6379> keys *
(empty list or set)

127.0.0.1:6379> set age 10 #如果没有设置过期时间
OK

127.0.0.1:6379> ttl age
(integer) -1 # ttl的返回值就是-1

4.3 Redis命令-String命令(三种格式)

String类型,也就是字符串类型,是Redis中最简单的存储类型。

其value是字符串,不过根据字符串的格式不同,又可以分为3类:

  • string:普通字符串
  • int:整数类型,可以做自增.自减操作
  • float:浮点类型,可以做自增.自减操作
image-20240102190734391

String的常见命令有:

添加/获取值

  • SET(==set==) key value:添加或者修改已经存在的一个String类型的键值对【可以新增/也可以修改】

  • GET(==get==) key:根据key获取String类型的value

  • MSET(==mset)== key value [key value]:批量添加多个String类型的键值对

  • MGET(==mget)== key[key]:根据多个key获取多个String类型的value

    自增/自减 [整数型可以incr/incrby,而浮点型只能incrbyfloat]

  • INCR(==incr)== key:让一个整型的key自增1,并返回自增完的结果

  • INCRBY(==incrby)== key increment:让一个整型的key自增(步长),返回自增完的结果,例如:incrby num 2 让num值自增2

  • INCRBYFLOAT(==incrbyfloat)== key increment:让一个浮点类型的数字自增(步长),并返回自增完的结果

    添加键值对

  • SETNX(==setnx)== key value:(只有key不存在)添加一个String类型的键值对,否则不执行【只有新增效果】

  • SETEX(==setex)== key seconds value:添加一个String类型的键值对 + 指定有效期 【set +expire】

贴心小提示:以上命令除了INCRBYFLOAT 都是常用命令

  • SET 和GET: 如果key不存在则是新增,如果存在则是修改
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> set name Rose  //原来不存在
OK

127.0.0.1:6379> get name
"Rose"

127.0.0.1:6379> set name Jack //原来存在,就是修改
OK

127.0.0.1:6379> get name
"Jack"
  • MSET和MGET
1
2
3
4
5
6
7
8
9
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3
OK

127.0.0.1:6379> MGET name age k1 k2 k3
1) "Jack" //之前存在的name
2) "10" //之前存在的age
3) "v1"
4) "v2"
5) "v3"
  • INCR和INCRBY和DECY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
127.0.0.1:6379> get age 
"10"

127.0.0.1:6379> incr age //增加1
(integer) 11

127.0.0.1:6379> get age //获得age
"11"

127.0.0.1:6379> incrby age 2 //一次增加2
(integer) 13 //返回目前的age的值

127.0.0.1:6379> incrby age 2
(integer) 15

127.0.0.1:6379> incrby age -1 //也可以增加负数,相当于减
(integer) 14

127.0.0.1:6379> incrby age -2 //一次减少2个
(integer) 12

127.0.0.1:6379> DECR age //相当于 incr 负数,减少正常用法
(integer) 11

127.0.0.1:6379> get age
"11"
  • SETNX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> help setnx

SETNX key value
summary: Set the value of a key, only if the key does not exist
since: 1.0.0
group: string

127.0.0.1:6379> set name Jack //设置名称
OK
127.0.0.1:6379> setnx name lisi //如果key不存在,则添加成功
(integer) 0
127.0.0.1:6379> get name //由于name已经存在,所以lisi的操作失败
"Jack"
127.0.0.1:6379> setnx name2 lisi //name2 不存在,所以操作成功
(integer) 1
127.0.0.1:6379> get name2
"lisi"
  • SETEX
1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6379> setex name 10 jack   【setex key seconds value】
OK

127.0.0.1:6379> ttl name
(integer) 8

127.0.0.1:6379> ttl name
(integer) 7

127.0.0.1:6379> ttl name
(integer) 5

4.4 Redis命令-Key的层级结构(区分相同名称)

我们可以通过给key==添加前缀==加以区分,不过这个前缀不是随便加的,有一定的规范:

Redis的key允许有多个单词形成层级结构,多个单词之间用’==:==’隔开,格式如下:

image-20240102190810025

这个格式并非固定,也可以根据自己的需求来删除或添加词条。

例如我们的项目名称叫 heima,有user和product两种不同类型的数据,我们可以这样定义key:

  • user相关的key:heima:user:1

  • product相关的key:heima:product:1

如果Value是一个Java对象 –> 序列化为JSON字符串后存储:

KEY VALUE
heima:user:1 {“id”:1, “name”: “Jack”, “age”: 21}
heima:product:1 {“id”:1, “name”: “小米11”, “price”: 4999}

一旦我们向redis采用这样的方式存储,那么在可视化界面中,redis会以层级结构来进行存储,形成类似于这样的结构,更加方便Redis获取数据

image-20240102190848438

4.5 Redis命令-Hash命令(类似HashMap)

Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。

String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便:

image-20240102190911476

Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD(更加灵活):

image-20240102190937450

Hash类型的常见命令(==h/hm+ String操作==) –[field-value就像集合的key-value键值对]

  • hset(==hset)== key field value:添加/修改一个key的键值对field-value
  • HGET(==hget)== key field:获取一个key的键值对中field值
  • HMSET(==hmset==) key field value [field value…]:批量添加key的多个键值对field-value[添加name/age/id]
  • HMGET(==hmget==) key field [field …]:批量获取key的多个键值对field-value[获取name/age/id]

关于value里面的相关值操作:

  • HGETALL(==hgetall==) key:获取一个key中的所有的field和value 【获取所有key-value键值对】
  • HKEYS(==hkeys==) key :获取一个key中的所有的field 【获取所有key】
  • HVALS(==hvals==) key:获取一个key中的所有的field的value 【获取所有value】
  • HINCRBY(==hincrby==) key field increment:让一个key的field值自增(步长)
  • HSETNX(==hsetnx==) key field value:(只有field不存在)添加一个key的键值对field-value,否则不执行

贴心小提示:哈希结构也是我们以后实际开发中常用的命令哟

  • HSET和HGET
1
2
3
4
5
6
7
8
9
10
127.0.0.1:6379> HSET heima:user:3 name Lucy//大key是 heima:user:3 小key是name,小value是Lucy
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 21// 如果操作不存在的数据,则是新增
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 17 //如果操作存在的数据,则是修改
(integer) 0
127.0.0.1:6379> HGET heima:user:3 name
"Lucy"
127.0.0.1:6379> HGET heima:user:3 age
"17"
  • HMSET和HMGET
1
2
3
4
5
6
7
8
127.0.0.1:6379> HMSET heima:user:4 name HanMeiMei
OK
127.0.0.1:6379> HMSET heima:user:4 name LiLei age 20 sex man
OK
127.0.0.1:6379> HMGET heima:user:4 name age sex
1) "LiLei"
2) "20"
3) "man"
  • HGETALL
1
2
3
4
5
6
7
127.0.0.1:6379> HGETALL heima:user:4
1) "name"
2) "LiLei"
3) "age"
4) "20"
5) "sex"
6) "man"
  • HKEYS和HVALS
1
2
3
4
5
6
7
8
127.0.0.1:6379> HKEYS heima:user:4
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> HVALS heima:user:4
1) "LiLei"
2) "20"
3) "man"
  • HINCRBY
1
2
3
4
5
6
7
8
127.0.0.1:6379> HINCRBY  heima:user:4 age 2
(integer) 22
127.0.0.1:6379> HVALS heima:user:4
1) "LiLei"
2) "22"
3) "man"
127.0.0.1:6379> HINCRBY heima:user:4 age -2
(integer) 20
  • HSETNX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> HSETNX heima:user4 sex woman
(integer) 1
127.0.0.1:6379> HGETALL heima:user:3
1) "name"
2) "Lucy"
3) "age"
4) "17"
127.0.0.1:6379> HSETNX heima:user:3 sex woman
(integer) 1
127.0.0.1:6379> HGETALL heima:user:3
1) "name"
2) "Lucy"
3) "age"
4) "17"
5) "sex"
6) "woman"

4.6 Redis命令-List命令(类似LinkedList)

Redis中的List类型与Java中的LinkedList类似,可以看做是一个==双向链表结构==(支持正向检索和反向检索)

特征也与LinkedList类似:

  • 有序
  • 元素可以重复
  • 插入和删除快
  • 查询速度一般

常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。

List的常见命令有: ** **【left左侧队头 right右侧队尾】

  • LPUSH(==lpush==) key element … :向列表左侧插入一个或多个元素
  • LPOP(==lpop==) key:移除并返回列表左侧的第一个元素,没有则返回nil
  • RPUSH(==rpush==) key element … :向列表右侧插入一个或多个元素
  • RPOP(==rpop==) key:移除并返回列表右侧的第一个元素
  • LRANGE(==lrange==) key star end:返回一段角标范围内的所有元素 [角标从0开始]
  • BLPOP(==blpop==)和BRPOP(==brpop==):与LPOP和RPOP类似 (等待指定时间) 而不是直接返回nil 【阻塞
image-20240102191014676
  • LPUSH和RPUSH
1
2
3
4
127.0.0.1:6379> LPUSH users 1 2 3
(integer) 3
127.0.0.1:6379> RPUSH users 4 5 6
(integer) 6
  • LPOP和RPOP
1
2
3
4
127.0.0.1:6379> LPOP users
"3"
127.0.0.1:6379> RPOP users
"6"
  • LRANGE
1
2
3
127.0.0.1:6379> LRANGE users 1 2
1) "1"
2) "4"

4.7 Redis命令-Set命令(类似HashSet)

Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:

  • 无序
  • 元素不可重复
  • 查找快
  • 支持交集.并集.差集等功能

Set类型的常见命令

  • SADD(==sadd==) key member1 … :向set中添加一个或多个元素 【s + add】

  • SREM(==srem==) key member … : 移除set中的指定元素 【s + rem】

  • SCARD(==scard==) key: 返回set中元素的个数 【s + card】

    判断set集合是否有某一个元素

  • SISMEMBER(==sismember==) key member:判断一个元素是否存在于set中 【s + ismember】

  • SMEMBERS(==smembers==):获取set中的所有元素 【 s + members】

    两个set集合的交集/并集/差集

  • SINTER(==sinter==) key1 key2 … :求key1与key2的交集 【s + inter】

  • SDIFF(==sdiff==) key1 key2 … :求key1与key2的差集 【s + diff】

  • SUNION(==sunion==) key1 key2 ..:求key1和key2的并集 【s + union 】

例如两个集合:s1和s2:

求交集:SINTER s1 s2

求s1与s2的不同:SDIFF s1 s2

具体命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6379> sadd s1 a b c
(integer) 3
127.0.0.1:6379> smembers s1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> srem s1 a
(integer) 1

127.0.0.1:6379> SISMEMBER s1 a
(integer) 0

127.0.0.1:6379> SISMEMBER s1 b
(integer) 1

127.0.0.1:6379> SCARD s1
(integer) 2

案例

  • 将下列数据用Redis的Set集合来存储:
  • 张三的好友有:李四.王五.赵六 sadd zhangsan lisi wangwu zhaoliu
  • 李四的好友有:王五.麻子.二狗 sadd lisi wangwu mazi ergou
  • 利用Set的命令实现下列功能:
  • 计算张三的好友有几人 scard zhangsan
  • 计算张三和李四有哪些共同好友 sinter zhangsan lisi
  • 查询哪些人是张三的好友却不是李四的好友 sdiff zhangsan lisi
  • 查询张三和李四的好友总共有哪些人 sunion zhangsan lisi
  • 判断李四是否是张三的好友 sismember zhangsan lisi
  • 判断张三是否是李四的好友 sismember lisi zhangsan
  • 将李四从张三的好友列表中移除 srem zhangsan lisi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
127.0.0.1:6379> SADD zs lisi wangwu zhaoliu
(integer) 3

127.0.0.1:6379> SADD ls wangwu mazi ergou
(integer) 3

127.0.0.1:6379> SCARD zs
(integer) 3

127.0.0.1:6379> SINTER zs ls
1) "wangwu"

127.0.0.1:6379> SDIFF zs ls
1) "zhaoliu"
2) "lisi"

127.0.0.1:6379> SUNION zs ls
1) "wangwu"
2) "zhaoliu"
3) "lisi"
4) "mazi"
5) "ergou"

127.0.0.1:6379> SISMEMBER zs lisi
(integer) 1

127.0.0.1:6379> SISMEMBER ls zhangsan
(integer) 0

127.0.0.1:6379> SREM zs lisi
(integer) 1

127.0.0.1:6379> SMEMBERS zs
1) "zhaoliu"
2) "wangwu"

4.8 Redis命令-SortedSet类型(类似于TreeSet)

Redis的SortedSet是一个==可排序的set集合==,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。

SortedSet具备下列特性:

  • 可排序
  • 元素不重复
  • 查询速度快

因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。

SortedSet的常见命令有: z+操作

  • ZADD(==zadd==) key score member:添加一个或多个元素到sorted set ,如果已经存在则更新其score值 【z + add 】

  • ZREM(==zrem==) key member:删除sorted set中的一个指定元素 【z +rem】

    获取单个member的属性值

  • ZSCORE(==zscore==) key member : 获取key中的指定元素member的score值 【z +score】

  • ZRANK(==zrank==) key member:获取key中的指定元素member的排名 【z+ rank】

    获取整个key的所有值

  • ZCARD(==zcard==) key:获取key中的元素个数 【z +card】

  • ZCOUNT(==zcount==) key min max:统计score值在[min,max]的所有元素的个数 【z +count】

  • ZINCRBY(==zincrby==) key increment member:让sorted set中的指定元素自增(步长) 【z +incrby】

    按照score排序:

  • ZRANGE(==zrange==) key min max:score排序后,获取排名(min,max)的元素 【z +range】

  • ZRANGEBYSCORE(==zrangebyscore==) key min max:score排序后,获取score(min,max)范围内元素 【z + rangebyscore 】

    两个set集合之间的并/交/差集:

  • ZDIFF(==zdiff==) .ZINTER(==zinter==) .ZUNION(==zunion==) :求差集.交集.并集 【z + diff/inter/union】

注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可,例如:

  • 升序获取sorted set 中的指定元素的排名:ZRANK(zrank) key member
  • 降序获取sorted set 中的指定元素的排名:ZREVRANK(zrevrank) key memeber

案例:

image-20231228113828355

案例代码:

image-20231228144511392

5.Java客户端-Jedis

在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/

其中Java客户端也包含很多:

image-20240102191100196

标记为❤的就是推荐使用的java客户端,包括:

  • Jedis和Lettuce:这两个主要是提供了Redis命令对应的API,方便我们操作Redis,而SpringDataRedis又对这两种做了抽象和封装,因此我们后期会直接以SpringDataRedis来学习。
  • Redisson:是在Redis基础上实现了分布式的可伸缩的java数据结构,例如Map.Queue等,而且支持跨进程的同步机制:Lock.Semaphore等待,比较适合用来实现特殊的功能需求。

5.1 Jedis连接(不推荐)

入门案例详细步骤

案例分析:

0)创建工程:

image-20240102191137914

1)引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--jedis(github上的才有用)-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>

2)建立连接

新建一个单元测试类,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
private Jedis jedis;

@BeforeEach
void setUp() {
// 1.建立连接
// jedis = new Jedis("192.168.150.101", 6379);
jedis = JedisConnectionFactory.getJedis();
// 2.设置密码
jedis.auth("123321");
// 3.选择库
jedis.select(0);
}

3)测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
void testString() {
// 存入数据
String result = jedis.set("name", "虎哥");
System.out.println("result = " + result);
// 获取数据
String name = jedis.get("name");
System.out.println("name = " + name);
}

@Test
void testHash() {
// 插入hash数据
jedis.hset("user:1", "name", "Jack");
jedis.hset("user:1", "age", "21");

// 获取
Map<String, String> map = jedis.hgetAll("user:1");
System.out.println(map);
}

4)释放资源

1
2
3
4
5
6
@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}

5.2 Jedis连接池(推荐)

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用Jedis连接池代替Jedis的直连方式

有关池化思想,并不仅仅是这里会使用,很多地方都有,比如说我们的数据库连接池,比如我们tomcat中的线程池,这些都是池化思想的体现。

5.2.1.创建Jedis的连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JedisConnectionFacotry {

private static final JedisPool jedisPool;
//静态代码块赋值
static {
//配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大连接
poolConfig.setMaxTotal(8);
//最大空闲连接
poolConfig.setMaxIdle(8);
//最小空闲连接
poolConfig.setMinIdle(0);
//设置最长等待时间(ms)
poolConfig.setMaxWaitMillis(1000);
//创建连接池对象
jedisPool = new JedisPool(poolConfig,
"192.168.150.101",6379,1000,"123321");
}
//获取jedis对象
public static Jedis getJedis(){
return jedisPool.getResource();
}
}

代码说明:

  • 1) JedisConnectionFacotry:工厂设计模式是实际开发中非常常用的一种设计模式,我们可以使用工厂,去降低代的耦合,比如Spring中的Bean的创建,就用到了工厂设计模式

  • 2)静态代码块:随着类的加载而加载,确保只能执行一次,我们在加载当前工厂类的时候,就可以执行static的操作完成对 连接池的初始化

  • 3)最后提供返回连接池中连接的方法.

5.2.2.改造原始代码

代码说明:

1.在我们完成了使用工厂设计模式来完成代码的编写之后,我们在获得连接时,就可以通过工厂来获得。

,而不用直接去new对象,降低耦合,并且使用的还是连接池对象。

2.当我们使用了连接池后,当我们关闭连接其实并不是关闭,而是将Jedis还回连接池的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 @BeforeEach
void setUp(){
//建立连接
/*jedis = new Jedis("127.0.0.1",6379);*/
jedis = JedisConnectionFacotry.getJedis();
//选择库
jedis.select(0);
}

@AfterEach
void tearDown() {
if (jedis != null) {
jedis.close();
}
}

6.Java客户端-SpringDataRedis(Spring整合)

SpringData是Spring中数据操作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)
  • 提供了RedisTemplate统一API来操作Redis
  • 支持Redis的发布订阅模型
  • 支持Redis哨兵和Redis集群
  • 支持基于Lettuce的响应式编程
  • 支持基于JDK.JSON.字符串.Spring对象的数据序列化及反序列化
  • 支持基于Redis的JDKCollection实现

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

image-20240102191209384

6.1.快速入门

SpringBoot已经提供了对SpringDataRedis的支持,使用非常简单:

6.1.1.导入pom坐标

1
2
3
4
5
6
7
8
9
10
<!--redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--common-pool-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
image-20231228163928851

6.1.2 .配置文件

1
2
3
4
5
6
7
8
9
10
11
spring:
redis:
host: 192.168.150.101
port: 6379
password: 123321
lettuce: //默认是这个 如果要用Jedis就要自己配置(线程不安全)
pool:
max-active: 8 #最大连接
max-idle: 8 #最大空闲连接
min-idle: 0 #最小空闲连接
max-wait: 100ms #连接等待时间

6.1.3.测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
class RedisDemoApplicationTests {
@Autowired
private RedisTemplate<String, Object> redisTemplate; //工具类

@Test
void testString() {
//插入一条string类型数据
redisTemplate.opsForValue().set("namesyx","syx");
//读取一条string类型数据
Object namesyx = redisTemplate.opsForValue().get("namesyx");
System.out.println(namesyx);
}
}

贴心小提示:SpringDataJpa使用起来非常简单,记住如下几个步骤即可

SpringDataRedis的使用步骤:

  • 引入spring-boot-starter-data-redis依赖
  • 在application.yml配置Redis信息
  • 注入RedisTemplate

6.2 .数据序列化器

RedisTemplate可以接收任意Object作为值写入Redis:

写入前会把Object序列化为字节形式,默认是采用JDK序列化,得到的结果是这样的:

缺点:

  • 可读性差
  • 内存占用较大

6.2.1 自定义序列化(会存在不必要的结果)

我们可以自定义RedisTemplate的序列化方式,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
// 创建RedisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置连接工厂
template.setConnectionFactory(connectionFactory);
// 创建JSON序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer =
new GenericJackson2JsonRedisSerializer();
// 设置Key的序列化
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 设置Value的序列化
template.setValueSerializer(jsonRedisSerializer);
template.setHashValueSerializer(jsonRedisSerializer);
// 返回
return template;
}
}

这里采用了==JSON序列化==来代替默认的JDK序列化方式。最终结果如图:

image-20231228165408294

整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。

6.2.2 StringRedisTemplate(手动序列化)

为了减少内存的消耗,我们可以采用==手动序列化==的方式,换句话说,就是不借助默认的序列化器,而是我们自己来控制序列化的动作,同时,我们只采用String的序列化器,这样,在存储value时,我们就不需要在内存中就不用多存储数据,从而节约我们的内存空间

image-20240102191247093

因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。

而是直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@SpringBootTest
class RedisStringTests {

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
void testString() {
// 写入一条String数据
stringRedisTemplate.opsForValue().set("verify:phone:13600527634", "124143");
// 获取string数据
Object name = stringRedisTemplate.opsForValue().get("name");
System.out.println("name = " + name);
}

private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
// 创建对象
User user = new User("虎哥", 21);
// 手动序列化
String json = mapper.writeValueAsString(user);
// 写入数据
stringRedisTemplate.opsForValue().set("user:200", json);

// 获取数据
String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
// 手动反序列化
User user1 = mapper.readValue(jsonUser, User.class);
System.out.println("user1 = " + user1);
}

}

此时我们再来看一看存储的数据,小伙伴们就会发现那个class数据已经不在了,节约了我们的空间~

image-20240102191313565

最后小总结:

RedisTemplate的两种序列化实践方案:

  • 方案一:
    • 自定义RedisTemplate
    • 修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer
  • 方案二:
    • 使用StringRedisTemplate
    • 写入Redis时,手动把对象序列化为JSON
    • 读取Redis时,手动把读取到的JSON反序列化为对象

两种方法的对比:

image-20231228170912182

6.2.3 StringRedisTeplate举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootTest
class RedisStringTests { //测试类

@Autowired
private StringRedisTemplate stringRedisTemplate;

@Test
void testHash() {
stringRedisTemplate.opsForHash().put("user:400", "name", "虎哥");
stringRedisTemplate.opsForHash().put("user:400", "age", "21");

Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
System.out.println("entries = " + entries);
}
}
image-20231228171636022

7.Redis底层

7.1 跳表

基本原理和查询步骤:

image-20240905195515341

  • 跳跃表基于单链表加索引的方式实现
  • 跳跃表以空间换时间的方式提升了查找速度
  • Redis有序集合在节点元素较大或者元素数量较多时使用跳跃表实现
  • Redis的跳跃表实现由 zskiplist和 zskiplistnode两个结构组成,其中 zskiplist用于保存跳跃表信息(比如表头节点、表尾节点、长度),而zskiplistnode则用于表示跳跃表节点
  • Redis每个跳跃表节点的层高都是1至32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但每个节点的成员对象必须是唯一的跳跃表中的节点按照分值大小进行排序,当分值相同时,节点按照成员对象的大小进行排序

谷歌Car引入装饰者模式

谷歌增强Car问题

  • java设计了汽车开发约定ICar类
1
2
3
4
5
public interface ICar {
public void start(); //启动
public void run(); //运行
public void stop(); //停止
}
  • 希望在将谷歌Car接入到生态圈平台时,增强汽车启动功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public  final class GoogleCar implements ICar {   //我已经final了 不让任何类继承我去重写方法

@Override
public void start() {
System.out.println("GoogleCar判断天气是否良好");
System.out.println("GoogleCar判断情况是否良好");
System.out.println("GoogleCar控制谷歌汽车启动"); //调用谷歌汽车提供的c语言函数
}

@Override
public void run() {
System.out.println("控制谷歌汽车运动");
}

@Override
public void stop() {
System.out.println("控制谷歌汽车停止");
}

}
  • 现在在谷歌Car的基础上去查天气情况
**1.手动在谷歌Car上自己添加(除非开发的时候把源码给你)
**2.new一个类继承谷歌Car,重写方法(除非谷歌Car不是final 可以被继承)
**3.new一个类实现Icar,增强方法(装饰者模式)**

装饰者模式(实现接口)

  • 使用环境:适用于二次开发的时候,无法(×)获取到源码(不能在源码更改),无法(×)使用继承(不能重写)前提下,要对已存在对象上的功能增强.

  • 前提: 可以获取到被装饰的对象GoogleCar实现的所有接口 ICar

  • 实现思路: new一个类实现ICar接口,然后test测试的时候new的对象是GoogleCar对象

  • 弊端:如果被实现的接口中的方法过多,装饰类中的方法过多冗余(原来的方法不能太多)

MyCar(新创建的类 实现原来的接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class MyCar implements ICar{
ICar car;

//构造方法
public MyCar(ICar car) {
this.car=car; //通过TestCar里面new的对象传过来
}

@Override
public void start() {
System.out.println("MyCar判断天气是否良好"); //增强的部分
System.out.println("MyCar判断路况是否拥堵"); //增强的部分
car.start();
}

@Override
public void run() {
car.run();
}

@Override
public void stop() {
car.stop();
}
}

测试类TestCar

1
2
3
4
5
6
7
8
public class TestCar {
public static void main(String[] args) {
ICar car=new MyCar(new GoogleCar()); //面向接口编程 右边new的是GoogleCar对象
car.start();
car.run();
car.stop();
}
}

分析:
其实就是test里面new的对象 –> 新写的增强MyCar的构造方法接收 –> 然后调用方法(里面既有新写的方法内容 + 谷歌原来的方法内容)


动态代理(字节码)

解决装饰者模式中实现方法太多接口的冗余问题

  • 原理:通过虚拟机在内存中创建类似MyCar.class文件

    要创建MyCar.class文件告诉虚拟机:

    1_被创建的字节码文件上应该有多少方法
    2_被创建的字节码上的方法如何来实现

在class类里面本来就有.class的文件用于虚拟机识别的字节码:

只是在前面ICar和GoogleCar的基础上直接写test测试用字节码方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class TestCar {
public static void main(String[] args) {
//使用字节码

//1param: 固定值: 告诉虚拟机用哪个字节码加载器加载内存中创建出的字节码文件
//2param: 告诉虚拟机内存中正在被创建的字节码文件中应该有哪些方法
//3param: 告诉虚拟机正在被创建的字节码上的各个方法如何处理

ICar car=(ICar)Proxy.newProxyInstance(TestCar.class.getClassLoader(), GoogleCar.class.getInterfaces(),new InvocationHandler() { //第三个参数使用匿名内部类

//method:代表正在执行的方法
//args:代表正在执行的方法中的参数
//Object:代表方法执行完毕之后的返回值
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if(method.getName().equalsIgnoreCase("start")){ //让你本来有的方法和要改变的方法对比
System.out.println("检查天气是否良好");
method.invoke(new GoogleCar(), args); //这是固定的调用GoogleCar的方法
System.out.println("检查路况是否拥堵"); //增强的方法(位置可以变化)
}
else
{
method.invoke(new GoogleCar(), args); //这是固定的调用GoogleCar的方法
}
return null;
}

});

car.start(); //调用方法输出结果
car.run();
car.stop();

}
}

字节码加载器(3种 – 系统引导加载器): jdk有一些程序 – 专门将各种字节码文件 – > 到内存(IO流技术)


动态代理解决全站乱码

1.项目中新建一个页面Index.html

1
2
3
4
5
6
7
8
9
10
11
<h1>post方式提交中文</h1>
<form action="/day18_v3/ServletDemo" method="post">
User:<input type="" name="username"/><br/>
<input type="submit"/>
</form>

<h1>get方式提交中文</h1>
<form action="/day18_v3/ServletDemo" method="get">
User:<input type="" name="username"/><br/>
<input type="submit"/>
</form>

2.servlet代码

1
2
3
无论是在post/get方法,执行以下语句不存在中文乱码问题(可以在前面加一句防止乱码的)
String um=request.getParameter("username"); //获取username
System.out.println(um);

3.过滤器Filter中,为request上的getParameter()功能进行增强

判断当前的请求是get/post  
  request.getMethod();
如果是post, 
      设置一句话: request.setCharacterEncoding(“utf-8”); 
                      放行
如果是get,
      调用原先的String v=request.getParameter(name);
                  将v进行转码,
                      放行

注解

注解(数据类型)

注解作用

1. 编译检查(@Test的junit单元测试)
2. 配置(框架 / 例如封装数据库的用户名和密码)
3. 生成帮助文档(写好作者、调用的api等)

注解特点

1. 可以在变量、方法、类上加载

1
2
3
4
5
6
7
	@Test(timeout=1000)  //方法
public void test03(){}

--------------------------------------

@SuppressWarnings("unused") //变量
int i;

2. 可以有属性也可以没有属性

1
@Override     @Test(timeout=1000)

3. 注解有范围(源码,编译期间,运行期间)

3.1 源码:

1
2
3
4
5
String类之上

@Author: 编写代码的作者
@Since :从哪里调用
@See :调用什么api

3.2 编译期:

1
2
3
@Override    声明当前的方法是重写父类的方法
@Deprecated 抑制编译器发生警告信息
@Suppresswarning 抑制编译器发生警告信息(如果有变量未使用,未遵循泛型格式错误不报警告)

3.3 运行期:

1
@Test  单元测试Junit4(一般)

注解举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//以下注解的含义是:声明当前的方法是重写父类的方法
@Override
public String toString() {
return super.toString();
}

public void test01(){
//以下注解的含义是:抑制编译器发生警告信息
@SuppressWarnings("unused")
int i;

//以下注解的含义是:抑制编译器发生警告信息(如果有变量未使用,未遵循泛型格式错误不报警告)
@SuppressWarnings({ "unused", "rawtypes" })
List a=new ArrayList();
}

//以下注解的含义是:声明以下的方法是过时的方法,不建议大家使用
@Deprecated
public void test02(){

}

//以下注解的含义是:如果当前方法的执行时间超过1秒,会报错
@Test(timeout=1000) //单元测试
public void test03(){
try {
Thread.sleep(1100);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("DDDDDDDDDDDDD");
}

自定义注解

格式

1
2
3
4
public @interface 注解名称{
public 属性类型 属性名称1();
public 属性类型 属性名称2() default 默认值;
}

属性支持的类型

1
2
3
4
5
6
1.基本数据类型(4类8种)
2.String
3.Class
4.Annotation(注解类型)
5.枚举类型
6.以上类型的一维数组类型

写一个名为MyAnno01的注解

1
2
3
4
5
6
7
8
public @interface MyAnno01 {

//1 不加默认值
public long timeout();

//2 加默认值
public long timeout1() default 1000;
}

自动登录(过滤器)

自动登录分析


环境搭建

建立数据库

建立页面

1
2
3
4
5
6
7
8
<body>
<form method="post" action="">
账号:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="checkbox" name="auto_login">自动登录<br>
<input type="submit" value="登录">
</form>
</body>

拷贝jar和JDBCUtils类,C3P0配置文件


MVC模型各层实现

dao层代码(接口写方法)

1
2
3
public interface UserDao {	
UserBean login(UserBean user) throws SQLException; //将传入的数据封装成一个对象 返回所有信息
}

UserBean层(数据封装)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class UserBean {

private int id; //数据库的所有属性
private String username;
private String password;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

}

UserDaoImpl层(具体实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class UserDaoImpl implements UserDao {
@Override
public UserBean login(UserBean user) throws SQLException {

//1.调用DBUtils的类
QueryRunner runner=new QueryRunner(JDBCUtil02.getDataSource());

//2.写sql语句
String sql="select * from t_user where username=? and password=?";

//3.调用query方法
return runner.query(sql,new BeanHandler<UserBean>(UserBean.class),user.getUsername(),user.getPassword());
}
}

Servlet层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//1.获取数据
String username=request.getParameter("username");
String password=request.getParameter("password");
String autologin=request.getParameter("auto_login");
//测试控制台输出一下输入情况: System.out.println(username+"="+password+"="+autologin);

UserBean user=new UserBean(); //将登陆界面的账号密码封装一下
user.setUsername(username);
user.setPassword(password);

//2.dao层实现
UserDao dao=new UserDaoImpl();
UserBean userBean=dao.login(user); //UserDao存的是

//3.判断跳转
if(userBean!=null) //成功进入首页
{
request.getSession().setAttribute("userBean", userBean);
response.sendRedirect("index.jsp"); //使用重定位
}

else //输入不正确就还是这个界面
{
request.getRequestDispatcher("login.jsp").forward(request, response); //使用转发

BeanUtils介绍

使用框架:

主页的jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
<body>
<form method="post" action="RegisterServlet">
账号:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
邮箱:<input type="text" name="email"><br>
电话:<input type="text" name="phone"><br>
地址:<input type="text" name="address"><br>
生日:<input type="text" name="birthday"><br>

<input type="submit" value="注册">
</form>
</body>

Servlet代码

1
2
3
4
5
6
7
8
9
//注册自己的日期转换器
ConvertUtils.register(new MyDateConverter(), Date.class);

//转化数据
Map map = request.getParameterMap();
UserBean bean = new UserBean();
BeanUtils.populate(bean, map);

System.out.println("22222bean="+bean.toString());

MyDateConverter类(具体用来String转Date)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyDateConverter implements Converter {

@Override
// 将value 转换 c 对应类型
// 存在Class参数目的编写通用转换器,如果转换目标类型是确定的,可以不使用c 参数
public Object convert(Class c, Object value) {
String strVal = (String) value;
// 将String转换为Date --- 需要使用日期格式化
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
Date date = dateFormat.parse(strVal);
return date;
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}


过滤器

思路:

过滤器的核心 帮用户完成登录的功能。

  • 实现思路
  1. 先判断session是否有效, 如果有效,就不用取cookie了,直接放行。

  2. 如果session失效了,那么就取 cookie。

    1. 没有cookie 第一次来 放行

    2. 有cookie

      1. 取出来cookie的值,然后完成登录
      2. 把这个用户的值存储到session中
      3. 放行。

具体实现:

1. new一个新的Filter的文件,然后需要配置web里面的路径

2. 编写取出cookie比对的工作类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CookieUtil {

public static Cookie findCookie(Cookie[] cookies,String name) { //传入已经有的cookie和传进来的name比对

if(cookies!=null)
{
for(Cookie cookie:cookies) //循环
{
if(name.equals(cookie.getName()))
{
return cookie; //匹配传过来的和存的有相同的
}
}
}
return null;
}
}

3.编写思路实现(只实现doFilter()方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class AutoLoginFilter implements Filter {
public AutoLoginFilter() {}

public void destroy() {}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

try {

HttpServletRequest r=(HttpServletRequest)request; //强转一下

//先判断,session中还有没有那个userBean
UserBean userBean=(UserBean) r.getSession().getAttribute("userBean");
if(userBean !=null) //还有效
{
chain.doFilter(request, response); //放行
}
else //会话失效
{

//1.从请求中取出cookie 但是cookie有很多key-value
Cookie[] cookies=r.getCookies();

//2.从一堆的cookie里面找到之前的那个cookie
Cookie cookie=CookieUtil.findCookie(cookies,"auto_login"); //servlet里面new的时候给的是 auto_login

if(cookie==null) //第一次登陆
{
chain.doFilter(request, response); //放行
}
else //不是第一次
{

//来到这里 表明什么情况
String value = cookie.getValue();
String username = value.split("#itheima#")[0];
String password = value.split("#itheima#")[1];

//完成登录
UserBean user = new UserBean();
user.setUsername(username);
user.setPassword(password);

UserDao dao = new UserDaoImpl();
userBean = dao.login(user);

//使用session存这个值到域中,方便下一次未过期前还可以用。
r.getSession().setAttribute("userBean", userBean);

chain.doFilter(request, response); //放行
}
}
}
catch (SQLException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
chain.doFilter(request, response); //异常也要放行
}
}

public void init(FilterConfig fConfig) throws ServletException {}

}

总结

展示代码层次

尝试使用:


Filter

Filter(过滤器)

其实就是对客户端发出来的请求进行过滤。 浏览器发出,然后服务器派servlet处理。 在中间就可以过滤,其实过滤器起到的是拦截的作用。

  • 作用
1
2
3
1. 对一些敏感词汇进行过滤(敏感词汇)
2. 统一设置编码(不用繁琐的设置utf-8)
3. 自动登录

Filter使用

定义一个类实现Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  //构造方法
public FilterDemo() {

}

public void destroy() {

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("到过滤器了");
chain.doFilter(request, response); //放行通过的代码
}

public void init(FilterConfig fConfig) throws ServletException {
// FilterConfig可以用于获取filter在注册的名字 以及初始化参数
}

new注册过滤器(url-pattern可以更改)

web.xml里面注册,注册的手法与servlet基本一样。

1
2
3
4
5
6
7
8
9
<filter>
<display-name>FilterDemo</display-name>
<filter-name>FilterDemo</filter-name>
<filter-class>guolvqi.FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>FilterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

url-pattern 写法格式

1. 全路径匹配  以 /  开始 

    /LoginServlet

2. 以目录匹配 以 / 开始  以 * 结束

    /demo01/*  

3. 以后缀名匹配  以 * 开始 以后缀名结束
    *.jsp  *.html *.do 

dispatcher 设置

REQUEST : 只要是请求过来,都拦截,默认就是REQUEST 
FORWARD : 只要是转发都拦截。 
ERROR : 页面出错发生跳转 
INCLUDE : 包含页面的时候就拦截。

Filter生命周期

  • 创建

在服务器启动的时候就创建。

  • 销毁

服务器停止的时候。


Filter执行顺序

  1. 客户端发出请求,先经过过滤器,如果过滤器放行,那么才能到servlet。

  2. 如果有多个过滤器, 那么他们会按照注册的映射顺序 来 排队。只要有一个过滤器,不放行,那么后面排队的过滤器以及咱们的servlet都不会收到请求。


JQuery省市联动

一、搭建环境(在仿百度的环境下)

1.在之前仿百度的数据库中新建city表

2.准备jsp页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>

省份: <select name="province" id ="province">
<option value="" >-请选择 -
<option value="1" >广东
<option value="2" >湖南
<option value="3" >湖北
<option value="4" >四川
</select>

城市: <select name="city" id="city">
<option value="" >-请选择 -
</select>

</body>

页面结果:

最终框架:


二、dao和impl层实现(需要添加CityBean和list集合)

dao接口写方法:

1
2
3
public interface CityDao {
List<CityBean> findCity(int pid) throws SQLException;
}

daoImpl具体实现接口方法:

1
2
3
4
5
6
7
8
9
10
public class CityDaoImpl implements CityDao{
@Override
public List<CityBean> findCity(int pid) throws SQLException {
//1.使用DBUtils的对象
QueryRunner runner=new QueryRunner(JDBCUtil02.getDataSource());
//2.调用方法
String sql="select * from city where pid=?";
return runner.query(sql, new BeanListHandler<CityBean>(CityBean.class),pid);
}
}

三、Servlet实现(使用XStream框架)

Servlet三步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//1.获取省份id  (pid)
int pid=Integer.parseInt(request.getParameter("pid"));

//2.dao实现 找到所有符合的城市
CityDao dao=new CityDaoImpl();
List<CityBean> list = dao.findCity(pid);

//3.返回数据 手动拼 ---> XStream转bean对象成xml
XStream xStream=new XStream();

//想把id做成属性
// xStream.useAttributeFor(CityBean.class,"id");

//设置别名
xStream.alias("city",CityBean.class);

//一个对象 ---> xml字符串
String xml=xStream.toXML(list);

response.setContentType("text/xml;charset=utf-8"); //返回的是一份有中文的xml文件(小心坑)!!!!!!!
response.getWriter().write(xml);

第三步需要用Xstream–将bean对象为xml(导入jar包)

尝试手动添加pid,然后看看转换出来的样子:


四、 完善jsp页面和功能#

city.jsp页面引入jq的包和功能实现文件city.js(单独写)

city.jsp页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>

<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script> <!-- 调用jq包 -->
<script type="text/javascript" src="js/city.js"></script> <!-- 具体的jq代码去city.js找 -->
</head>
<body>

省份: <select name="province" id ="province">
<option value="" >-请选择 -
<option value="1" >广东
<option value="2" >湖南
<option value="3" >湖北
<option value="4" >四川
</select>

城市: <select name="city" id="city">
<option value="" >-请选择 -
</select>
</body>

完成功能的city.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$(function(){
//1.找到省份元素
$("#province").change(function(){

//2.一旦里面的值改变---去找城市数据
//获取值
var pid=$(this).val();

$.post("CityServlet",{pid:pid},function(data,status){ //pid:pid(省份id值)
//先清空以前的值
$("#city").html("<option value=''> -请选择-");

//遍历:
$(data).find("city").each(function(){ // 从data里面使用find找到xml文件所有city标签---each迭代
//取city的孩子chidren
var id=$(this).children("id").text(); //val是取值 text是转成文本
var cname=$(this).children("cname").text(); //val是取值 text是转成文本

$("#city").append("<option value='"+id+"'>"+cname);
});
});
});
});

city.js文件步骤分析:

1
2
3
4
5
6
7
1. 先要通过province的id值change获取省份元素
2. 获取pid的值
2.1 使用post来响应
2.1.1 通过html()来赋值为0(清空)
2.1.2 循环取出xml文件内的cname值
通过find()来找到所有的cname标---然后each迭代--通过children找到所属的cname值
找到之后通过append()添加

五、代码框架

代码框架:


六、Json

服务器和客户端数据传输方式:

  • xml
1
2
3
4
5
6
7
8
9
10
11
12
<list>
<city>
<id>1<id>
<pid>1</pid>
<cname>深圳</cname>
</city>
<city >
<id>2<id>
<pid>1</pid>
<cname>东莞</cname>
</city>
</list>
  • json
1
2
3
4
5
6
7
8
9
10
11
12
13
 [
{
"cname": "深圳",
"id": 1,
"pid": 1
},
{
"cname": "东莞",
"id": 2,
"pid": 1
}
...
]

使用json格式数据显示省市联动效果

1. 导入jar包

2. 编写servlet代码(第三步不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

//1. 获取参数
int pid = Integer.parseInt(request.getParameter("pid"));

//2 dao实现--找出所有的城市
CityDao dao = new CityDaoImpl();
List<CityBean> list = dao.findCity(pid);

//3. 把list ---> json数据
//JSONArray ---> 变成数组 , 集合 []
//JSONObject ---> 变成简单的数据 { name : zhangsan , age:18}

JSONArray jsonArray = JSONArray.fromObject(list); //转成对象
String json = jsonArray.toString(); //弄成字符串

response.setContentType("text/html;charset=utf-8");
response.getWriter().write(json);

3. 编写页面

还是原来的主页面只不过跳转现在的json.js代码:

1
2
3
4
5
6
7
8
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>

<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script> <!-- 调用jq包 -->
<!-- <script type="text/javascript" src="js/city.js"></script> 具体的jq代码去city.js找 -->
<script type="text/javascript" src="js/json.js"></script> <!-- 具体的jq代码去json.js找 -->
</head>

json.js代码(只是each不同)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$(function() {
//1.找到省份的元素
$("#province").change(function() {
//2. 一旦里面的值发生了改变,那么就去请求该省份的城市数据
//$("#province").varl();
var pid = $(this).val();

$.post( "CityServlet02",{pid:pid} ,function(data,status){

//先清空
$("#city").html("<option value='' >-请选择-");
//再遍历,追加
$(data).each(function(index , c) {
$("#city").append("<option value='"+c.id+"' >"+c.cname)
});
},"json" );

});
});

两者展示的js文件区别(each迭代方式不同)


Listener

一、Listener(监听器)

监听某一个事件发生、状态改变

监听器内部机制接口回调

接口回调

  • 需求

A在执行循环,当循环到5的时候,通知B。

事先先把一个对象传递给A—当A执行到5的时候,通过这个对象,来调用B中的方法。 但是注意,不是直接传递B的实例,而是传递一个接口的实例过去。(所以叫接口回调)

就相当于我现在有个A类,但是具体循环之后怎么走的类我现在没有,保不准以后会有,所以我需要写一个接口传给a类一个对象,然后有B类的时候就可以实现接口,然后传递接口实例对象

test类调用a,a去找b(继承接口):


二、8个Web监听器(JSP四个作用域)#

其中三个作用域(JSP共四个)

  request     ---httpServletRequest
  session     ---httpSession
aapplication  ---ServletContext

监听器三种类型

1
2
3
1. 监听三个作用域创建和销毁(3个)
2. 监听三个作用域属性状态变更(3个)
3. 监听httpSession里面存值的状态变更(2个)

使用步骤

  • 1. 定义类,实现对应接口(自动给两个方法)

  • 2. 注册 | 配置监听器


三、 监听三个作用域创建和销毁

1. ServletContextListener

servletcontext创建:

    1. 启动服务器的时候

servletContext销毁:

    2. 关闭服务器. 从服务器移除项目
  • 1. 定义一个类,实现ServletContextListener接口(自动给两个方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyServletContextListener implements ServletContextListener {

//初始化调用
@Override
public void contextInitialized(ServletContextEvent sce) {
// TODO 自动生成的方法存根
System.out.println("初始化了");
}

//销毁调用
@Override
public void contextDestroyed(ServletContextEvent sce) {
// TODO 自动生成的方法存根
System.out.println("销毁了");
}

}
  • 2. Web.xml文件中注册 | 配置监听器
1
2
3
<listener>
<listener-class>Listener.MyServletContextListener</listener-class>
</listener>

点击控制栏Servers中红框

控制台输出结果:

2. ServletRequestListener

request创建:

      访问服务器上的任意资源都会有请求出现。

      访问 html: 会
      访问 jsp:    会
      访问 servlet : 会 

request销毁:

      服务器已经对这次请求作出了响应。(打开页面并且刷新一次)
  • 1. 定义类,实现对应接口(自动给两个方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyRequestListener implements ServletRequestListener {

@Override
public void requestDestroyed(ServletRequestEvent sre) {
// TODO 自动生成的方法存根
System.out.println("初始化了");
}

@Override
public void requestInitialized(ServletRequestEvent sre) {
// TODO 自动生成的方法存根
System.out.println("销毁了");
}

}
  • 2. web.xml文件内注册 | 配置监听器
1
2
3
<listener>
<listener-class>Listener.MyRequestListener</listener-class>
</listener>
  • 3.分别建立jsp、h5、servlet文件打开浏览器刷新
1
之后就会在控制台输出初始化和销毁的提示!

3. HttpSessionListener

session的创建:        
   只要调用getSession
     html:        不会
     jsp:        会      getSession();
     servlet:     会

session的销毁:
     1.超时  30分钟(默认时间)        
     2.非正常关闭 销毁
     3.正常关闭服务器(序列化)
  • 1. 定义类,实现对应接口(自动给两个方法)
1
2
3
4
5
6
7
8
9
10
11
12
public class MySessionListener implements HttpSessionListener {

@Override
public void sessionCreated(HttpSessionEvent se) {
System.out.println("创建session了");
}

@Override
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("销毁session了");
}
}
  • 2. web.xml文件内注册 | 配置监听器
1
2
3
<listener>
<listener-class>Listener.MySessionListener</listener-class>
</listener>

##4. 三个监听器作用 ##

1.ServletContextListener

    利用它来,在servletcontext创建的时候, 

    1. 完成自己想要的初始化工作

    2. 执行自定义任务调度。 执行某一个任务。 Timer  

2.HttpSessionListener

    统计在线人数(一个回合就是session)

四、 监听三个作用域属性状态变更

可以监听在作用域中值—添加|替换|移除动作。

1. ServletContextAttributeListener

2. ServletRequestAttributeListener

3. HttpSessionAttributeListener

4. 以HttpSessionAttributeListener为例#

  • 1. 建立类实现对应接口(自动三个方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyHttpSession implements HttpSessionAttributeListener {
@Override
public void attributeAdded(HttpSessionBindingEvent se) {
System.out.println("属性被添加了");
}
@Override
public void attributeRemoved(HttpSessionBindingEvent se) {
System.out.println("属性被移除了");
}
@Override
public void attributeReplaced(HttpSessionBindingEvent se) {
System.out.println("属性被替换了");
}
}
  • 2. web.xml文件配置
1
2
3
 <listener>
<listener-class>Listener02.MyHttpSession</listener-class>
</listener>
  • 3. 新建jsp页面
1
2
3
4
5
<%
session.setAttribute("name","abobama");
session.setAttribute("name","zhangsan");
session.removeAttribute("name");
%>

运行jsp页面 然后刷新:


五、 监听httpSession里面存值的状态变更

这一类监听器不用注册。(直接继承即可)

1. HttpSessionBindingListener

  • 1.让javaBean 实现该接口即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Bean01 implements HttpSessionBindingListener{

//自定义属性--可以被修改
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}

@Override
public void valueBound(HttpSessionBindingEvent event) {
// TODO 自动生成的方法存根
System.out.println("值被绑定进来了");
}

@Override
public void valueUnbound(HttpSessionBindingEvent event) {
// TODO 自动生成的方法存根
System.out.println("解绑了");
}

}

2. HttpSessionActivationListener(激活)

用于监听session的值

  • 1.钝化 (序列化)

把内存中的数据 存储 到硬盘

  • 2.活化 (反序列化)

把硬盘中的数据 读取 到内存

  • 1.让javaBean 实现该接口即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Bean01 implements HttpSessionActivationListener{
//钝化
@Override
public void sessionWillPassivate(HttpSessionEvent se) {
// TODO 自动生成的方法存根
System.out.println("session被钝化了...");
}

//活化
@Override
public void sessionDidActivate(HttpSessionEvent se) {
// TODO 自动生成的方法存根
System.out.println("session被活化了...");
}
}

3. 钝化活化作用

session中的值可能会很多,并且我们有很长一段时间不使用这个内存中的值,那么可以考虑把session的值可以存储到硬盘上【钝化】,等下一次在使用的时候,在从硬盘上提取出来。 【活化】

4. 设置钝化时间(配置)

  • 1.在tomcat里面 conf/context.xml 里面配置

    对所有的运行在这个服务器的项目生效  
  • 2.在conf/Catalina/localhost/context.xml 配置

    对 localhost生效。  localhost:8080
  • 3.在自己的web工程项目中的 META-INF/context.xml

    只对当前的工程生效。(一般在这设置,因为项目的目录不同)

配置代码:

1
2
3
4
5
<Context>
<Manager className="org.apache.catalina.session.PersistentManager" maxIdleSwap="1">
<Store className="org.apache.catalina.session.FileStore" directory="itheima"/>
</Manager>
</Context>
   **maxIdleSwap** : 1分钟不用就钝化
   **directory**:  钝化后的那个文件存放的目录位置。 
例如:F:\tomcat\apache-tomcat-7.0.52\work\Catalina\localhost\项目名\文件名

JQuery仿百度搜索

一、建数据库和jsp页面

新建的jsp页面:写一个输入框一个按钮一个下拉div表

1
2
3
4
5
<body>
<input type="text" name="word" id="word" style="width:700px ; height:50px">
<input type="button" value="查询" style="width:100px ; height:50px">
<div id="div01" style="width:700px ; height:200px ; border:1px solid blue ; display:none"></div>
</body>

新建的数据库


二、 前期准备#

1.导入jar文件

2.导入数据池的xml配置文件并且更改数据库名字

3.导入所需要的的JDBCUtil02类

4.导入之前ajax写验证用户名时候的dao和impl类进行修改


三、 书写Servlet的前两步操作#

1
2
3
4
5
6
7
8
//1.获取参数
String word=request.getParameter("word");

//2.让dao实现
WordsDao dao=new WordsDaoImpl();
List<WordBean> list = dao.findWords(word);

//3.返回数据

四、 写jsp页面代码

导入jq包和heima.js文件(所有操作放里面写)

1
2
<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="js/heima.js"></script>

五、写heima.js文件

要使用固定格式和JSTL格式(导入jar包)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//捕捉弹起事件  对一个元素进行onkeyup事件监听
// 格式:$(document).reday(function(){})

$(function(){
$("#word").keyup(function(){
//2. 获取输入框的值
//var word=$("#word").val();
var word=$(this).val();

if(word == "") //查询的为0
{
$("#div01").hide();
}
else //查询东西要匹配
{
//3. 请求数据。
$.post("/day16_02/FindWordsServlet",{word:word},function(data,status){
//alert(data);
$("#div01").show();
$("#div01").html(data);
});
}
})
});

六、书写servlet第三步

让servlet跳到list.jsp页面:

1
2
3
4
5
6
//数据存到作用域(因为list.jsp用了JSTL)
request.setAttribute("list",list);

//3.返回数据
response.setContentType("text/html;charset=utf-8");
request.getRequestDispatcher("list.jsp").forward(request, response); //跳转到list.jsp页面

list.jsp页面(用JSTL去遍历出来):

1
2
3
4
5
6
7
8
9
 <%@ taglib uri="http://java.sun.com/jsp/jstl/core"  prefix="c"%>

<table style="width: 100%">
<c:forEach items="${list}" var="wordBean"> <!--一定要去servlet里面将数据存到作用域-->
<tr>
<td>${wordBean.words}</td>
</tr>
</c:forEach>
</table>

七、代码分析

代码框架:

分析代码流程:


八、具体代码

dao类代码:

1
2
3
4
public interface WordsDao {
//假如传入一个a ---> 返回 aa abc等 ---> 所以要用list集合
List<WordBean> findWords(String word) throws SQLException;
}

daoImpl类代码:

1
2
3
4
5
6
7
8
9
10
public class WordsDaoImpl implements WordsDao {
@Override
public List<WordBean> findWords(String word) throws SQLException {
//1.使用DBUtils的对象
QueryRunner runner=new QueryRunner(JDBCUtil02.getDataSource());

//2.调用简便方法
String sql="select * from words where words like ? limit ?"; //模糊查询 限制只能查出5个
return runner.query(sql, new BeanListHandler<WordBean>(WordBean.class),word+"%",5);
}

Servlet类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class FindWordsServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//有中文
request.setCharacterEncoding("utf-8");
try {
//1.获取参数
String word=request.getParameter("word");
System.out.println("word="+word); //控制台输出 看我输入的是不是被抓过来了

//2.让dao实现
WordsDao dao=new WordsDaoImpl();
List<WordBean> list = dao.findWords(word);

//循环输出满足条件的结果
for (WordBean wordBean : list) {
System.out.println("==="+wordBean.toString());
}

//数据存到作用域
request.setAttribute("list",list);

//3.返回数据
response.setContentType("text/html;charset=utf-8");
request.getRequestDispatcher("list.jsp").forward(request, response); //跳转到list.jsp页面

} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}

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

}

domain获取数据库属性类代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class WordBean {

private int id;
private String words;

public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getWords() {
return words;
}
public void setWords(String words) {
this.words = words;
}

@Override
public String toString() {
return "WordBean [id=" + id + ", words=" + words + "]";
}

}

heima.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//捕捉弹起事件  对一个元素进行onkeyup事件监听
// 格式:$(document).reday(function(){})

$(function(){
$("#word").keyup(function(){
//2. 获取输入框的值
//var word=$("#word").val();
var word=$(this).val();


if(word == "") //查询的为0
{
$("#div01").hide();
}
else //查询东西要匹配
{
//3. 请求数据。
$.post("/day16_02/FindWordsServlet",{word:word},function(data,status){
//alert(data);
$("#div01").show();
$("#div01").html(data);
});
}
})
});

主页demo08.jsp页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>

<script type="text/javascript" src="js/jquery-1.11.3.min.js"></script>
<script type="text/javascript" src="js/heima.js"></script>
</head>

<body>
<center> <!-- 居中-->
<h2>黑马</h2>
<input type="text" name="word" id="word" style="width: 600px ; height: 50px ;font-size: 20px;">
<input type="button" value="黑马一下" style="height: 55px ; width: 100px ;">

<div id="div01" style="position:relative; left:-54px; width:600px; height:200px ; border:1px solid blue; display: none"></div>
</center>
</body>
</html>

list.jsp展示页面:

1
2
3
4
5
6
7
8
9
10
11
12
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>


<table style="width: 100%">
<c:forEach items="${list}" var="wordBean"> <!--一定要去servlet里面将数据存到作用域-->
<tr>
<td>${wordBean.words}</td>
</tr>
</c:forEach>
</table>

JDBCUtil02类(获取数据池对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class JDBCUtil02 {

static ComboPooledDataSource dataSource = null; //使用c3p0开源数据连接池的类
static{
dataSource = new ComboPooledDataSource();
}

public static DataSource getDataSource(){
return dataSource; //返回一个dataSource 方便获取
}

/*
* 获取连接对象
*/
public static Connection getConn() throws SQLException{
return dataSource.getConnection(); //返回一个连接
}

/*
* 释放资源
*/
public static void release(Connection conn , Statement st , ResultSet rs){
closeRs(rs);
closeSt(st);
closeConn(conn);
}
public static void release(Connection conn , Statement st){
closeSt(st);
closeConn(conn);
}


private static void closeRs(ResultSet rs){
try {
if(rs != null){
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
rs = null;
}
}

private static void closeSt(Statement st){
try {
if(st != null){
st.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
st = null;
}
}

private static void closeConn(Connection conn){
try {
if(conn != null){
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
conn = null;
}
}
}

最终结果展示:

,