Redis&Jedis 学习笔记

Redis

Redis是什么

Redis 是完全开源免费的一个高性能的key-value数据库
	内存数据库

Redis 有以下三个特点:
	1)Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
	2)Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储(这些数据类型主要指的的是value的,key是正常的字符串)
	3)Redis支持数据的备份

Redis优势

性能极高
	Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型
	Redis支持二进制类型存储。 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子操作
	Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
丰富的特性
	Redis还支持 publish/subscribe(发布/订阅模式), 通知, key值过期等等特性。

Redis的下载

Redis官网地址:
	http://redis.io/

Redis最新stable版本下载地址(稳定版本)
	http://download.redis.io/

Redis的源码同时也托管在github上面
	https://github.com/antirez/redis 

windows版本下载地址(官网提供的是Linux版本)
	https://github.com/MSOpenTech/redis/releases
	或者
	https://github.com/dmajkic/redis/downloads

Redis的安装

根据自己的情况,新建一个目录,名字为redis(例如,D:\redis),注意路径中不要出现中文字符.
把下载好的Redis的压缩包解压到新建的指定文件夹中,可见一些可执行文件(版本不同可能此处会有区别)
redis-server.exe		redis 服务器
redis-cli.exe			redis 命令行客户端
redis-benchmar.exe		redis 性能测试工具
redis-check-aof.exe		AOF   文件修复工具

Redis简单的测试

启动Redis服务器
	开启一个cmd窗口
	使用cd命令切换目录到 D:\redis 
	运行 redis-server.exe redis.windows.conf
		启动成功后会看到启动的图案和启动信息(包括Redis版本、监听端口号等)
	注意:这个服务器的运行窗口不能关闭,如果关闭Redis服务器就停了
	注意:ctrl+c可以也关闭redis服务器

启动Redis客户端
	另开启一个cmd窗口,原来的不要关闭,不然就无法访问服务端了
	切换到redis目录下运行 
		redis-cli.exe -h 127.0.0.1 -p 6379
		或者
		redis-cli.exe
		不加任何参数默认就是127.0.0.1 和 6379

	设置键值对操作 set name briup
	取出键值对操作 get name

	这时候客户端使用shutdown命令可以关闭服务端

注意:
	Redis中命令本身不区分大小写(但是key值是区分
	大小写的),在redis自带的客户端执行相关命令是自带命令格式的提示的

客户端常用命令:

clear 清屏
Auth 命令用于检测给定的密码和配置文件中的密码是否相符
Ping 命令使用客户端向 Redis 服务器发送一个 PING ,如果服务器运作正常的话,会返回一个 PONG 。
	通常用于测试与服务器的连接是否仍然生效,或者用于测量延迟值。
Quit 命令用于关闭当前客户端与redis服务的连接。
	一旦所有等待中的回复(如果有的话)顺利写入到客户端,连接就会被关闭
Select 命令用于切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值。
dbsize 查看当前数据库中有多少个key
info   返回redis的相关信息
flushdb  删除当前选择数据库中的所有key
flushall 删除所有数据库中的数据

Redis配置参数介绍

redis.windows.conf文件中配置项如下:

1)*Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程(windows版本中不支持)

	daemonize no

2)*当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定(windows版本中不支持)

	pidfile /var/run/redis.pid

3)*指定Redis监听端口,默认端口为6379

	port 6379

4)*绑定的主机地址

	bind 127.0.0.1

5)*当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

	timeout 300

6)指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice(redis版本不同会有所区别)

	loglevel notice

7)日志记录方式,默认为标准输出

	logfile stdout

8)*设置数据库的数量,默认数据库的index为0,可以使用SELECT <dbid>命令在连接上指定数据库id

	databases 16

9)*指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

	save <seconds> <changes>

	Redis默认配置文件中提供了三个条件:

	save 900 1

	save 300 10

	save 60 10000

	分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
 
10)*指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大

	rdbcompression yes

11)*指定redis的本地数据库文件名,默认值为dump.rdb

	dbfilename dump.rdb

12)*指定redis本地数据库存放目录,默认是./ 可以根据自己的情况更改

	dir ./

13)设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

	slaveof <masterip> <masterport>

14)当master服务设置了密码保护时,slav服务连接master的密码

	masterauth <master-password>

15)*设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭

	requirepass briup

16)*设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息

	maxclients 128

17)指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区

	maxmemory <bytes>

18)指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no

	appendonly no

19)*指定更新日志文件名,默认为appendonly.aof

	appendfilename appendonly.aof

20)*指定更新日志条件,共有3个可选值
	no:表示等操作系统进行数据缓存同步到磁盘(快)     
	always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)     
	everysec:表示每秒同步一次(折衷,默认值)

	appendfsync everysec
 
21)指定是否启用虚拟内存机制,默认值为no
	VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中

	vm-enabled no

22)虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

	vm-swap-file /tmp/redis.swap

23)将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

	 vm-max-memory 0

24)Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的数据大小来设定的,建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不确定,就使用默认值

	 vm-page-size 32

25)设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,在磁盘上每8个pages将消耗1byte的内存。

	 vm-pages 134217728

26)设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

	 vm-max-threads 4

27)设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

	glueoutputbuf yes

28)指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

	hash-max-zipmap-entries 64

	hash-max-zipmap-value 512

29)指定是否激活重置哈希,默认为开启

	activerehashing yes

30)指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

	include /path/to/local.conf

Redis支持的数据类型(主要指的value,key一般就是字符串)

string(字符串) hash(哈希) list(列表) set(集合) zset(sorted set:有序集合)

string(字符串)

	string是redis最基本的类型,一个key对应一个value。
	string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
	string类型是Redis最基本的数据类型,一个键最大能存储512MB。
	例如:
	127.0.0.1:6379> SET name "briup"
	OK

	127.0.0.1:6379> GET name
	"briup"
	
	//删除
	127.0.0.1:6379> del name
	(integer) 1

	注意:value中的双引号要不要都可以

hash(哈希)

	Redis hash 是一个键值对集合。
	Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
	例如:
	127.0.0.1:6379> HMSET user username briup password 123
	OK

	127.0.0.1:6379> HGETALL user
	1) "username"
	2) "briup"
	3) "password"
	4) "123"

	127.0.0.1:6379> hget user username
	"briup"
	
	//删除
	127.0.0.1:6379> del user
	(integer) 1

	例子中使用了 hash 数据类型存储了包含用户信息的用户对象。 
	每个 hash 可以存储2的31次方个键值对。

	注意:命令不区分大小写

list(列表)

	Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

	127.0.0.1:6379> lpush mylist briup
	(integer) 1

	127.0.0.1:6379> lpush mylist hello
	(integer) 2

	127.0.0.1:6379> lpush mylist world
	(integer) 3

	127.0.0.1:6379> lrange mylist 0 0
	1) "world"

	127.0.0.1:6379> lrange mylist 0 1
	1) "world"
	2) "hello"

	127.0.0.1:6379> lrange mylist 0 2
	1) "world"
	2) "hello"
	3) "briup"

	//删除
	127.0.0.1:6379> del mylist
	(integer) 1

	列表最多可存储2的31次方个元素。

set(集合)

	Redis的Set是string类型的无序集合。
	集合是通过哈希表实现的。

	sadd 命令
	添加一个string元素到,key对应的set集合中,成功返回1,如果元素已经存在则返回0,key对应的set不存在返回错误。
	
	例如:
	127.0.0.1:6379> sadd myset hello
	(integer) 1
	127.0.0.1:6379> sadd myset world
	(integer) 1
	127.0.0.1:6379> sadd myset briup
	(integer) 1
	127.0.0.1:6379> sadd myset briup
	(integer) 0
	127.0.0.1:6379> smembers myset
	1) "hello"
	2) "world"
	3) "briup"

	//删除
	127.0.0.1:6379> del myset
	(integer) 1

	集合中最大的成员数为 2的31次方。

zset(有序集合)

	Redis中 zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
	不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。

	zset的成员是唯一的,但分数(score)却可以重复。

	zadd 命令
	添加元素到集合,元素在集合中存在则更新对应score
	例如:

	127.0.0.1:6379> zadd myzset 2 briup
	(integer) 1

	127.0.0.1:6379> zadd myzset 0 hello
	(integer) 1

	127.0.0.1:6379> zadd myzset 1 world
	(integer) 1
	
	127.0.0.1:6379> zadd myzset 1 gogo
	(integer) 1
	
	127.0.0.1:6379> zrangebyscore myzset 0 3
	1) "hello"
	2) "gogo"
	3) "world"
	4) "briup"
	
	//删除
	127.0.0.1:6379> del myzset
	(integer) 1

Redis对key的操作命令

DEL 命令用于删除已存在的键。不存在的 key 会被忽略。
EXISTS 命令用于检查给定 key 是否存在。
Expire 命令用于设置 key 的过期时间。key 过期后将不再可用。
Expireat 命令用于以时间戳(unix timestamp)格式设置 key 的过期时间。key 过期后将不再可用。
PEXPIREAT 命令用于设置 key 的过期时间,单位是毫秒。key 过期后将不再可用
PERSIST 命令用于移除给定 key 的过期时间,使得 key 永不过期。
Keys 命令用于查找所有符合给定模式 pattern 的 key,可以使用*通配符
MOVE 命令用于将当前数据库的 key 移动到给定的数据库 db 当中。
	默认Redis中有16个数据库,下标分别为0-15,默认使用的是0(select 0)
	通过select可以切换数据库 例如: select 3
TTL 命令以秒为单位返回 key 的剩余过期时间。
Pttl 命令以毫秒为单位返回 key 的剩余过期时间。
RANDOMKEY 命令从当前数据库中随机返回一个 key 。
Rename 命令用于修改 key 的名称 。
	rename msg my_msg
Renamenx 命令用于在新的 key 不存在时修改 key 的名称 。
	renamenx username name
	只要新的名字在redis中不存在的时候才会修改成功
Type 命令用于返回 key 所储存的值的类型。

Redis对字符串(String)的操作命令

SET 命令用于设置给定 key 的值。如果 key 已经存储其他值, SET 就覆写旧值,且无视类型。
Get 命令用于获取指定 key 的值。如果 key 不存在,返回 nil 。如果key 储存的值不是字符串类型,返回一个错误。

Getrange 命令用于获取存储在指定 key 中字符串的子字符串。字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内)。

Getset 命令用于设置指定 key 的值,并返回 key 旧的值。

Mset 命令用于同时设置一个或多个 key-value 对。
Mget 命令返回所有(一个或多个)给定 key 的值。 如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。

Setex 命令为指定的 key 设置值及其过期时间。如果 key 已经存在, SETEX 命令将会替换旧的值。
Psetex 命令以毫秒为单位设置 key 的生存时间。

Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
Msetnx 命令用于所有给定 key 都不存在时,同时设置一个或多个 key-value 对。

Setrange 命令用指定的字符串覆盖给定 key 所储存的字符串值,覆盖的位置从偏移量 offset 开始。
Strlen 命令用于获取指定 key 所储存的字符串值的长度。当 key 储存的不是字符串值时,返回一个错误。

Incr 命令将 key 中储存的数字值增一。
	如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
	如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
	本操作的值限制在 64 位(bit)有符号数字表示之内。
Incrby 命令将 key 中储存的数字加上指定的增量值。
	如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCRBY 命令。
	如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
	本操作的值限制在 64 位(bit)有符号数字表示之内。
Incrbyfloat 命令为 key 中所储存的值加上指定的浮点数增量值。
	如果 key 不存在,那么 INCRBYFLOAT 会先将 key 的值设为 0 ,再执行加法操作。
Decr 命令将 key 中储存的数字值减一。
	如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECR 操作。
	如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
	本操作的值限制在 64 位(bit)有符号数字表示之内。
Decrby 命令将 key 所储存的值减去指定的减量值。
	如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 DECRBY 操作。
	如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
	本操作的值限制在 64 位(bit)有符号数字表示之内。
Append 命令用于为指定的 key 追加值。	
	如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾。
	如果 key 不存在, APPEND 就简单地将给定 key 设为 value ,就像执行 SET key value 一样。

Redis对哈希(Hash)的操作命令

Hset 命令用于为哈希表中的字段赋值 。
	如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
	如果字段已经存在于哈希表中,旧值将被覆盖。
Hsetnx 命令用于为哈希表中不存在的的字段赋值 。
	如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
	如果字段已经存在于哈希表中,操作无效。
	如果 key 不存在,一个新哈希表被创建并执行 HSETNX 命令。
Hmset 命令用于同时将多个 field-value (字段-值)对设置到哈希表中。
	此命令会覆盖哈希表中已存在的字段。
	如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作
Hget 命令用于返回哈希表中指定字段的值。
Hmget 命令用于返回哈希表中,一个或多个给定字段的值。
	如果指定的字段不存在于哈希表,那么返回一个 nil 值。
Hgetall 命令用于返回哈希表中,所有的字段和值。
Hexists 命令用于查看哈希表的指定字段是否存在。
Hlen 命令用于获取哈希表中字段的数量。
Hdel 命令用于删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略。
Hkeys 命令用于获取哈希表中的所有字段名。
Hvals 命令返回哈希表所有字段的值。
Hincrby 命令用于为哈希表中的字段值加上指定增量值。
	增量也可以为负数,相当于对指定字段进行减法操作。
	如果哈希表的 key 不存在,一个新的哈希表被创建并执行 HINCRBY 命令。
	如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 。
	对一个储存字符串值的字段执行 HINCRBY 命令将造成一个错误。
	本操作的值被限制在 64 位(bit)有符号数字表示之内。
Hincrbyfloat 命令用于为哈希表中的字段值加上指定浮点数增量值。
	如果指定的字段不存在,那么在执行命令前,字段的值被初始化为 0 。

Redis对列表(List)的操作命令

Lpush 命令将一个或多个值插入到列表头部。 如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。 当 key 存在但不是列表类型时,返回一个错误。

Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

Lpushx 将一个或多个值插入到已存在的列表头部,列表不存在时操作无效。
Lpop 命令用于移除并返回列表的第一个元素。
Llen 命令用于返回列表的长度。 如果列表 key 不存在,则 key 被解释为一个空列表,返回 0 。 如果 key 不是列表类型,返回一个错误。

Lindex 命令用于通过索引获取列表中的元素。你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

Linsert 命令用于在列表的元素前或者后插入元素。 当指定元素不存在于列表中时,不执行任何操作。 当列表不存在时,被视为空列表,不执行任何操作。 如果 key 不是列表类型,返回一个错误。
	例如:LINSERT list before hello go
	在名字为list的列表中,往元素hello之前插入数据go

Lset 通过索引来设置元素的值。当索引参数超出范围,或对一个空列表进行 LSET 时,返回一个错误。
Ltrim 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素	,不在指定区间之内的元素都将被删除。下标 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
Lrem 根据参数 COUNT 的值,移除列表中与参数 VALUE 相等的元素。
	COUNT 的值可以是以下几种:
	count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
	count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
	count = 0 : 移除表中所有与 VALUE 相等的值。

Rpush 命令用于将一个或多个值插入到列表的尾部(最右边)。
Rpushx 命令用于将一个或多个值插入到已存在的列表尾部(最右边)。如果列表不存在,操作无效
Rpop 命令用于移除并返回列表的最后一个元素。
Rpoplpush 命令用于移除列表的最后一个元素,并将该元素添加到另一个列表并返回。

Blpop 命令移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
Brpop 命令移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
Brpoplpush 命令从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

Redis对集合(Set)的操作命令

Sadd 命令将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。
	假如集合 key 不存在,则创建一个只包含添加的元素作成员的集合。
	当集合 key 不是集合类型时,返回一个错误。
Scard 命令返回集合中元素的数量。
Smembers 命令返回集合中的所有的成员。 不存在的集合 key 被视为空集合。
Sdiff 命令返回给定集合之间的差集。不存在的集合 key 将视为空集。
Sdiffstore 命令将给定集合之间的差集存储在指定的集合中。如果指定的集合 key 已存在,则会被覆盖。
Sinter 命令返回给定所有给定集合的交集。 不存在的集合 key 被视为空集。 当给定集合当中有一个空集时,结果也为空集(根据集合运算定律)。
Sinterstore 命令将给定集合之间的交集存储在指定的集合中。如果指定的集合已经存在,则将其覆盖。
Sismember 命令判断成员元素是否是集合的成员。
Smove 命令将指定成员 member 元素从 source 集合移动到 destination 集合
	SMOVE 是原子性操作。
	如果 source 集合不存在或不包含指定的 member 元素,则 SMOVE 命令不执行任何操作,仅返回 0 。否则, member 元素从 source 集合中被移除,并添加到 destination 集合中去。
	当 destination 集合已经包含 member 元素时, SMOVE 命令只是简单地将 source 集合中的 member 元素删除。
	当 source 或 destination 不是集合类型时,返回一个错误。
Spop 命令用于移除并返回集合中的一个随机元素。
Srandmember 命令用于返回集合中的一个随机元素。
	从 Redis 2.6 版本开始, Srandmember 命令接受可选的 count 参数:
	如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合。
	如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。
	该操作和 SPOP 相似,但 SPOP 将随机元素从集合中移除并返回,而 Srandmember 则仅仅返回随机元素,而不对集合进行任何改动。
Srem 命令用于移除集合中的一个或多个成员元素,不存在的成员元素会被忽略。
	当 key 不是集合类型,返回一个错误。
	在 Redis 2.4 版本以前, SREM 只接受单个成员值。
Sunion 命令返回给定集合的并集。不存在的集合 key 被视为空集。
Sunionstore 命令将给定集合的并集存储在指定的集合 destination 中。
Sscan 命令用于迭代集合键中的元素。

Redis对有序集合(sorted set)的操作命令

Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。
	如果某个成员已经是有序集的成员,那么更新这个成员的分数值,并通过重新插入这个成员元素,来保证该成员在正确的位置上。
	分数值可以是整数值或双精度浮点数。
	如果有序集合 key 不存在,则创建一个空的有序集并执行 ZADD 操作。
	当 key 存在但不是有序集类型时,返回一个错误。
Zcard 命令用于计算集合中元素的数量。
Zcount 命令用于计算有序集合中指定分数区间的成员数量。
Zincrby 命令对有序集合中指定成员的分数加上增量 increment
	可以通过传递一个负数值 increment ,让分数减去相应的值,比如 ZINCRBY key -5 member ,就是让 member 的 score 值减去 5 。
	当 key 不存在,或分数不是 key 的成员时, ZINCRBY key increment member 等同于 ZADD key increment member 。
	当 key 不是有序集类型时,返回一个错误。
	分数值可以是整数值或双精度浮点数。
Zinterstore 命令计算给定的一个或多个有序集的交集,其中给定 key 的数量必须以 numkeys 参数指定,并将该交集(结果集)储存到 destination 。
	默认情况下,结果集中某个成员的分数值是所有给定集下该成员分数值之和。
Zlexcount 命令在计算有序集合中指定字典区间内成员数量。
Zrange 返回有序集中,指定区间内的成员。
	其中成员的位置按分数值递增(从小到大)来排序。
	具有相同分数值的成员按字典序(lexicographical order )来排列。
	如果你需要成员按值递减(从大到小)来排列,请使用 ZREVRANGE 命令。
	下标参数 start 和 stop 都以 0 为底,也就是说,以 0 表示有序集第一个成员,以 1 表示有序集第二个成员,以此类推。
	你也可以使用负数下标,以 -1 表示最后一个成员, -2 表示倒数第二个成员,以此类推
Zrangebylex 通过字典区间返回有序集合的成员。
Zrangebyscore 返回有序集合中指定分数区间的成员列表。有序集成员按分数值递增(从小到大)次序排列。
	具有相同分数值的成员按字典序来排列(该属性是有序集提供的,不需要额外的计算)。
	默认情况下,区间的取值使用闭区间 (小于等于或大于等于),你也可以通过给参数前增加 ( 符号来使用可选的开区间 (小于或大于)。
Zrank 返回有序集中指定成员的排名。其中有序集成员按分数值递增(从小到大)顺序排列。
Zrem 命令用于移除有序集中的一个或多个成员,不存在的成员将被忽略。
	当 key 存在但不是有序集类型时,返回一个错误。
Zremrangebylex 命令用于移除有序集合中给定的字典区间的所有成员。
Zremrangebyrank 命令用于移除有序集中,指定排名(rank)区间内的所有成员
Zremrangebyscore 命令用于移除有序集中,指定分数(score)区间内的所有成员。
Zrevrange 命令返回有序集中,指定区间内的成员。
	其中成员的位置按分数值递减(从大到小)来排列。
	具有相同分数值的成员按字典序的逆序(reverse lexicographical order)排列。
	除了成员按分数值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE 命令一样。
Zrevrangebyscore 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列。
	具有相同分数值的成员按字典序的逆序(reverse lexicographical order )排列。
	除了成员按分数值递减的次序排列这一点外, ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
Zrevrank 命令返回有序集中成员的排名。其中有序集成员按分数值递减(从大到小)排序。
	排名以 0 为底,也就是说, 分数值最大的成员排名为 0 。
	使用 ZRANK 命令可以获得成员按分数值递增(从小到大)排列的排名。
Zscore 命令返回有序集中,成员的分数值。 如果成员元素不是有序集 key 的成员,或 key 不存在,返回 nil 。
Zunionstore 命令计算给定的一个或多个有序集的并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination 。
	默认情况下,结果集中某个成员的分数值是所有给定集下该成员分数值之和 。
Zscan 命令用于迭代有序集合中的元素(包括元素成员和元素分值)

Redis中的事务

multi命令 和 exec命令
例如:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name tom
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> set dob 1999-11-11
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) OK

例如:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name tom
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> seta dob 1999-11-11
(error) ERR unknown command 'seta'
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> get dob
(nil)

例如:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name tom
QUEUED
127.0.0.1:6379> set age 20
QUEUED
127.0.0.1:6379> hget name age
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get name
"tom"
127.0.0.1:6379> get age
"20"

Redis的事务中的错误分俩种:
语法错误
	这种情况需要区分Redis的版本,Redis 2.6.5之前的版本会忽略错误的命令,执行其他正确的命令,2.6.5之后的版本会忽略这个事务中的所有命令,都不执行

运行错误 
	运行错误表示命令在执行过程中出现错误,比如用hget命令获取string的值
	这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令会被Redis接受并执行。如果事务里有一条命令执行错误,其他命令依旧会执行

Jedis

Jedis 是 Redis 官方首选的 Java 客户端开发包。

源码地址:https://github.com/xetorthio/jedis/releases
其相关jar可以从maven的中央仓库中下载:
http://central.maven.org/maven2/redis/clients/jedis/2.9.0/jedis-2.9.0.jar

jedis的相关jar包

在java中使用jedis中的api操作redis的时候所需jar包主要有俩个:
	jedis-2.x.jar
	commons-pool2-2.4.2.jar

另外需要一个junit的包进行单元测试:
	junit-4.x.jar

Jedis对象的使用

整个过程类似之前使用Connection对象一样,一个Jedis对象就是一个数据库连接对象,这不过这个数据库是Redis(内存数据库)
1)创建Jedis对象
	Jedis jedis = new Jedis();
	或者:
	Jedis jedis = new Jedis("127.0.0.1",6379);
	或者:
	Jedis jedis = new Jedis(new java.net.URI("redis://:[email protected]:6379/0"));
	注意:这里最后一个0代表的时候index为0的数据库
	
	如果没有密码的时候可以这样创建对象
	Jedis jedis = new Jedis(new java.net.URI("redis://127.0.0.1:6379/0"));

2)使用Jedis对象
	使用Jedis的API对redis进行操作,Jedis中的方法名和Redis中命令名基本上一模一样,所以只要了解Redis中命令的作用,那么Jedis中对应的方法的作用也是一样的.

3)关闭Jedis对象
	每个Jedis对象在内部都会建立一个Socket和Redis服务器连接,所以Jedis对象使用完毕后可以进行关闭。

所以在使用junit进行基本的jedis操作测试的时候,可以编写如下代码:
public class RedisTest {
	private Jedis jedis;
	@Before
	public void before(){
		jedis = new Jedis();
	}
	@After
	public void after(){
		jedis.close();
	}
	
	@Test
	public void test(){
		//使用jedis对进行操作
	}
	
}

意思是,在单元测试方法执行之前,junit先调用使用了@Before注解的方法,然后再调用使用了@Test注解的方法,最后再调用使用了@After注解的方法

Jedis对Redis中key的操作

@Test
public void test_key(){
	//选择数据库 默认是0
	System.out.println(jedis.select(0));
	//查看当前数据库
	System.out.println(jedis.getDB());
	//查看当前数据库中所有key值
	System.out.println(jedis.keys("*"));
	//判断当前数据库中是否存在名字为name的key值
	System.out.println(jedis.exists("name"));
	//判断key值的在Redis中的数据类型
	System.out.println(jedis.type("name"));
	//查看key的过期时间 单位是毫秒 -1表示永不过期 -2表示已经过去
	System.out.println(jedis.pttl("name"));
	//查看key的过期时间 单位是秒   -1表示永不过期 -2表示已经过去
	System.out.println(jedis.ttl("name"));
	//修改key的名字 不管新名字是否被使用 如果老的key不存在会报错
	System.out.println(jedis.rename("name", "username"));
	//修改key的名字 新名字没有被使用的时候才生效 如果老的key不存在会报错
	System.out.println(jedis.renamenx("username", "name"));
	//把指定的key移动到指定的数据库中(数据库索引表示)
	System.out.println(jedis.move("name",3));
	//在当前数据库中随机获取一个key
	System.out.println(jedis.randomKey());
	//设置指定key的过期时间 单位是秒
	System.out.println(jedis.expire("name", 10));
	//设置指定key的过期时间 单位是毫秒
	System.out.println(jedis.pexpire("name", 5000L));
	//设置指定key的过期时间 时间用时间戳表示
	System.out.println(jedis.expireAt("name", 1000000L));
	//在指定的key过期之前,移除key上的过期时间
	System.out.println(jedis.persist("name"));
	
	//当前数据库中key的数量 返回值Long类型
	jedis.dbSize();
	//清空当前db
	jedis.flushDB();
	//清空所有db
	jedis.flushAll();
}

Jedis对Redis中string的操作

@Test
public void test_String(){
	//设置k-v的值,如果k已经则会覆盖旧值,且无视数据类型
	System.out.println(jedis.set("name", "zhangsan"));
	//获得指定key的值
	System.out.println(jedis.get("name"));
	//通过下标截取指定key的值
	System.out.println(jedis.getrange("name", 0, 3));
	//替换key值,并且返回旧值
	System.out.println(jedis.getSet("name", "briup"));
	//可以连续设置多组K-V
	System.out.println(jedis.mset("name","briup","age","20"));
	//获得多个key分别对应的值
	System.out.println(jedis.mget("name","age"));
	//设置K-V的同时设置一下过期时间,单位是秒
	System.out.println(jedis.setex("password", 10, "123"));
	//设置K-V的同时设置一下过期时间,单位是毫秒
	System.out.println(jedis.psetex("myname", 10000L, "test"));
	//设置K-V,只有指定的key没有被使用的时候才生效
	System.out.println(jedis.setnx("name","tom"));
	//连续设置多组K-V,只有在所有指定的key都没有被使用的时候才生效
	System.out.println(jedis.msetnx("a","testA","b","testB"));
	//使用第三个参数的值,从下标为3的位置开始覆盖指定key的值
	System.out.println(jedis.setrange("name", 3, "aaaa"));
	//返回指定key的值的长度
	System.out.println(jedis.strlen("name"));
	//指定的值自增1 num的值必须是整数
	System.out.println(jedis.incr("num"));
	//指定的值自增5 num的值必须是整数
	System.out.println(jedis.incrBy("num",5));
	//指定的值自减1 num的值必须是整数
	System.out.println(jedis.decr("num"));
	//指定的值自减5 num的值必须是整数
	System.out.println(jedis.decrBy("num", 5));
	//指定的值自增10.5 
	System.out.println(jedis.incrByFloat("num", 10.5));
	//指定的key的值后面进行追加
	System.out.println(jedis.append("name", "gogogo"));
}

Jedis对Redis中hash的操作

@Test
public void test_Hash(){
	//为哈希表中的字段赋值
	//给key为user的hash表中的name字段赋值为briup
	System.out.println(jedis.hset("user", "name", "briup"));
	//为哈希表中的字段赋值 字段name不存在是才生效
	System.out.println(jedis.hsetnx("user", "name", "tom"));
	//连续为哈希表中的多个字段赋值
	Map<String,String> map = new HashMap<>();
	map.put("age", "20");
	map.put("dob", "2016-11-11");
	System.out.println(jedis.hmset("user",map));
	//获得名字为user的hash表中指定字段的值
	System.out.println(jedis.hget("user", "name"));
	//连续获得名字为user的hash表中多个字段的值
	System.out.println(jedis.hmget("user", "age","dob"));
	//获得名字为user的hash表中所有字段的值
	System.out.println(jedis.hgetAll("user"));
	//检查名字为user的hash表中指定字段是否存在
	System.out.println(jedis.hexists("user", "name"));
	//获得名字为user的hash表中字段的个数
	System.out.println(jedis.hlen("user"));
	//删除名字为user的hash表中的指定字段,同时忽略掉不存在的字段
	System.out.println(jedis.hdel("user", "dob","f1","f2"));
	//获得名字为user的hash表中所有的字段名
	System.out.println(jedis.hkeys("user"));
	//获得名字为user的hash表中所有的字段值
	System.out.println(jedis.hvals("user"));
	//名字为user的hash表中的age字段值自增1
	System.out.println(jedis.hincrBy("user", "age", 1L));
	//名字为user的hash表中的age字段值自增10.5
	System.out.println(jedis.hincrByFloat("user", "age", 10.5));
}

Jedis对Redis中其他类型的操作

Jedis对Redis中list的操作
Jedis对Redis中set的操作
Jedis对Redis中zset的操作
和Redis中的命令名字及用户一样,这里就不再一一列举

Jedis处理Redis中的事务

Transaction tx = jedis.multi();

//操作数据

//List集合中是每个操作返回的执行结果
List<Object> results = tx.exec();

例如:

@Test
public void test_List(){
	//开启事务之后 要是tx来操作,而不是jedis对象
	Transaction tx = jedis.multi();
	tx.set("age", "20");
	tx.set("test", "test");
	Response<String> response = tx.get("name");
	
	//返回值是List集合,里面是每个操作的执行结果
	tx.exec();
	
	//事务中查询到的数据 要到事务结束后再拿出结果
	System.out.println(response.get());
}

JedisPool的使用(连接池)

在不同的线程中使用相同的Jedis实例会发生并发访问问题。但是创建太多的Jedis实例也不好,因为这意味着会建立很多Socket连接。单一Jedis实例不是线程安全的。为了避免这些问题,可以使用JedisPool, JedisPool是一个线程安全的网络连接池。可以用JedisPool创建一些可靠Jedis实例,可以从池中拿到Jedis的实例。 这种方式可以解决那些问题并且会实现高效的性能 

1)创建JedisPool
	JedisPool pool = new JedisPool();
	或者
	JedisPool pool = new JedisPool("127.0.0.1",6379);
	或者
	JedisPoolConfig config = new JedisPoolConfig();  
	//设置最大连接数
	config.setMaxTotal(80);
	//设置最大空闲数
	config.setMaxIdle(20);
	//设置超时时间
	config.setMaxWaitMillis(3000);
	JedisPool jedisPool = new JedisPool(config, "127.0.0.1", 6379); 

2)使用JedisPool获得Jedis对象
	JedisPool pool = new JedisPool();
	Jedis jedis = pool.getResource();

3)从JedisPool获得的Jedis对象再使用后调用close方法即可
	通过查看源码可知,close方法内部会把这个jedis对象进回收
	public void close(){
		if (this.dataSource != null) {
			if (this.client.isBroken())
				this.dataSource.returnBrokenResource(this);
			else
				this.dataSource.returnResource(this);
		}else{
			this.client.close();
		}
	}

使用Jedis存储java对象

Jedis类继承了BinaryJedis类
Jedis中的方法的参数基本上全都是String类型,BinaryJedis类中的方法参数基本全都是byte[]类型的,所以Jedis对象也可以调用那些可以接受byte[]类型参数的方法,而且java中的任何可以被序列化的对象都可以转换为字节数组. 

1)创建工具类SerializingUtils
负责把对象和byte[]之间进行转换
public class SerializingUtils {

	public static byte[] serialize(Object obj) {
		ByteArrayOutputStream bos = null;
		ObjectOutputStream out = null;
		try {
			bos = new ByteArrayOutputStream();
			out = new ObjectOutputStream(bos);
			out.writeObject(obj);
			out.flush();
		}
		catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
				if(out!=null)out.close();
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
		return bos.toByteArray();
	}
	
	public static Object deserialize(byte[] data) {
		ObjectInputStream in = null;
		Object obj = null;
		try {
			ByteArrayInputStream bis = new ByteArrayInputStream(data);
			in = new ObjectInputStream(bis);
			obj = in.readObject();
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {
				if(in!=null)in.close();
			}catch (IOException e) {
				e.printStackTrace();
			}
		}
		return obj;
	}
	
}


2)创建工具类JedisUtils
把Jedis对Redis的操作进行封装,使其更加方便使用,例如可以直接存/取java对象,要求对象实现序列化接口,同时也可以使用之前的Jedis相关方法

src下面的资源文件redis.properties
redis.pool.maxTotal=100
redis.pool.maxIdle=20
redis.pool.maxWait=3000
redis.ip=127.0.0.1
redis.port=6379

封装的工具类:
注意:这里只时封装了几个方法,可以根据自己的实现需求而进一步的封装扩展
public class JedisUtils {
	public static JedisPool jedisPool;

	static {
		//ResourceBundle会查找classpath下的xxx.properties的文件,xxx是方法中指定的
		ResourceBundle resourceBundle = ResourceBundle.getBundle("redis");
		int maxTotal = Integer.parseInt(resourceBundle.getString("redis.pool.maxTotal"));
		int maxIdle = Integer.parseInt(resourceBundle.getString("redis.pool.maxIdle"));
		int maxWait = Integer.parseInt(resourceBundle.getString("redis.pool.maxWait"));
		String ip = resourceBundle.getString("redis.ip");
		int port = Integer.parseInt(resourceBundle.getString("redis.port"));

		JedisPoolConfig config = new JedisPoolConfig();
		// 设置最大连接数
		config.setMaxTotal(maxTotal);
		// 设置最大空闲数
		config.setMaxIdle(maxIdle);
		// 设置超时时间
		config.setMaxWaitMillis(maxWait);
		// 初始化连接池
		jedisPool = new JedisPool(config, ip, port);

	}
	
	public static void set(Object key, Object value) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.set(SerializingUtils.serialize(key), SerializingUtils.serialize(value));
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			jedis.close();
		}
	}
	
	public static Object get(Object key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			byte[] keyBytes = SerializingUtils.serialize(key);
			if(jedis.exists(keyBytes)){
				return SerializingUtils.deserialize(jedis.get(keyBytes));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			jedis.close();
		}
		return null;
	}
	
	public static void del(Object key) {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.del(SerializingUtils.serialize(key));
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			jedis.close();
		}
	}
	
	public static void clear() {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			jedis.flushDB();
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			jedis.close();
		}
	}
	public static int getSize() {
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			jedis.close();
		}
		return jedis.dbSize().intValue();
	}
	
	public static Jedis getResource(){
		return jedisPool.getResource();
	}

}

Redis和mybatis结合

注意:Jedis是用来操作Redis的

mybatis缓存分为一级缓存和二级缓存

一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。

二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。

所以我们可以自定义缓存类,实现Mybatis提供的缓存接口Cache,其中的方法使用Jedis来实现,然后把这个缓存实现类配置为Mybatis的二级缓存提供者即可

建表语句及实体类:

create table t_user(
	id number primary key,
	name varchar2(50) not null,
	age number,
	dob date
);

public class User implements Serializable{
	private static final long serialVersionUID = 1L;
	private long id;
	private String name;
	private int age;
	private Date dob;

	get/set
}

Mybatis配置文件及config.properties文件

src下面的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>
	<properties resource="config.properties"></properties>
	
	<settings>
		<!-- 开启二级缓存 -->
		<setting name="cacheEnabled" value="true"/>
		<!-- 禁止延迟加载 -->
		<setting name="lazyLoadingEnabled" value="false" />  
	</settings>
	
	<typeAliases>
		<package name="com.briup.bean"/>
	</typeAliases>
	
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC"></transactionManager>
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" /> 
				<property name="url" value="${url}" /> 
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	
	<mappers>
		<mapper resource="com/briup/dao/UserMapper.xml"/>
	</mappers>
	
</configuration>


src下面的config.properties文件

driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=briup
password=briup

Mybatis映射接口和映射文件

映射接口:
public interface UserMapper {
	public void insertUser(User user);
	public List<User> findAllUsers();
}

映射文件:
<?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.briup.dao.UserMapper">
	<cache type="com.briup.cache.MybatisRedisCache"></cache>
	
	<insert id="insertUser" parameterType="User">
		<selectKey resultType="long" keyProperty="id" order="BEFORE">
			select my_seq.nextval from dual
		</selectKey>
		insert into t_user(id,name,age,dob) 
		values(#{id},#{name},#{age},#{dob})
	</insert>
	
	<select id="findAllUsers" resultType="User">
		select id,name,age,dob
		from t_user
	</select>
	
</mapper>

Jedis相关配置文件和工具类

src下面的config.properties

封装Jedis的工具类
JedisUtils.java
SerializingUtils.java
封装MyBatis的工具类
MyBatisSqlSessionFactory.java

自定义cache接口实现类MybatisRedisCache.java

package com.briup.cache;
import java.util.concurrent.locks.ReadWriteLock;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheException;
import com.briup.util.JedisUtils;
import redis.clients.jedis.Jedis;
/**
 * Cache为缓存接口,给缓存供应商的SPI(Service Provider Interface)
 * Cache接口的实现类必须有一个具有String类型参数的构造方法,该参数作为实现类对象的id,对其进行唯一标识
 */
public class MybatisRedisCache implements Cache{

	private String id;
	
	public MybatisRedisCache(String id) {
		this.id = id;
	}
	
	/**
	 * 清空缓存
	 */
	@Override
	public void clear() {
		JedisUtils.clear();
	}
	
	/**
	 * 获取缓存对象的唯一标识
	 */
	@Override
	public String getId() {
		return this.id;
	}
	
	/**
	 * 从缓存对象中获取key对应的value
	 */
	@Override
	public Object getObject(Object key) {
		return JedisUtils.get(key);
	}
	
	/**
	 * 获取读写锁
	 * 可选的方法,从3.2.6起这个方法不再被框架核心调用
	 * 任何需要的锁,都必须由缓存供应商提供
	 */
	@Override
	public ReadWriteLock getReadWriteLock() {
		
		return null;
	}
	
	/**
	 * 获取缓存对象中存储的键/值对的数量
	 * 可选的方法,没有被框架核心调用
	 */
	@Override
	public int getSize() {
		return JedisUtils.getSize();
	}
	
	/**
	 * 保存key/value到缓存对象中
	 * key可以是任何对象
	 */
	@Override
	public void putObject(Object key, Object value) {
		JedisUtils.set(key, value);
	}
	
	/**
	 * 可选的方法,没有被核心框架调用,移除key对应的value
	 */
	@Override
	public Object removeObject(Object key) {
		
		return null;
	}
	
	/**
	 * 重新equals方法
	 */
	@Override
	public boolean equals(Object o) {
		if (getId() == null)
			throw new CacheException("Cache instances require an ID.");
		if (this == o)
			return true;
		if (!(o instanceof Cache))
			return false;

		Cache otherCache = (Cache) o;
		return getId().equals(otherCache.getId());
	}
	
	/**
	 * 重新hashCode方法
	 */
	@Override
	public int hashCode() {
		if (getId() == null)
			throw new CacheException("Cache instances require an ID.");
		return getId().hashCode();
	}

}

注意:在mybatis中开启二级缓存(虽然默认也是开启的)
<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>

配置标签

在需要缓冲的映射文件中加入<cache/>标签,并且指定提供缓存功能的cache实现类的全限定名
<cache type="com.briup.cache.MybatisRedisCache"></cache>

测试方法

@Test
public void findAllUsers(){
	SqlSession session = null;
	try {
		session = MyBatisSqlSessionFactory.openSession();
		
		UserMapper dao = session.getMapper(UserMapper.class);
		
		List<User> list = dao.findAllUsers();
		System.out.println(list);
		
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		if(session!=null)session.close();
	}
}

第一次运行,mybatis【发出】对应select进行查询数据库,然后在redis中查看key可知,已经把数据存进了redis中,
之后再运行,mybatis【不发出】sql语句,数据直接从redis中取出

注意事项

注意1
在MyBatis中标签中有flushCache、useCache这两个配置属性
如果属性是在select标签中设置: 
	flushCache默认为false,表示任何时候此标签中的sql语句被调用,都不会去清空本地缓存和二级缓存。 
	useCache默认为true,表示会将本条语句的查询结果进行二级缓存。 
如果属性是在insert、update、delete标签中设置
	flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。 
	useCache属性在该情况下不存在。

注意2
<settings>
	<!-- 开启二级缓存 -->
	<setting name="cacheEnabled" value="true"/>

	<!-- 禁用延迟加载 --> 
	<!-- 因为不应该把一个代理对象缓存到Redis里面 --> 
	<setting name="lazyLoadingEnabled" value="false" />  
</settings>

Redis和Spring结合(同时还要加上Mybatis)

主要依赖的核心jar为:
	spring-data-redis-1.x.x.RELEASE.jar
注意:
	spring3.2.4需要结合使用spring-data-redis-1.6.2.RELEASE.jar

	spring4.3.3可以结合使用spring-data-redis-1.6.2.RELEASE.jar或者更高的版本

从3.1开始,spring引入了对Cache的支持。当我们在调用一个方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。
	使用Spring Cache需要我们做的事情:
		1.配置Spring对Cache的支持
		2.配置jedis工厂、redis模板、cache管理器
		3.声明哪些方法要使用缓存

配置Spring对Cache的支持

	配置Spring对基于注解的Cache的支持,首先我们需要在Spring的配置文件中引入cache命名空间,其次通过<cache:annotation-driven />就可以启用Spring对基于注解的Cache的支持。

	<cache:annotation-driven/>有一个cache-manager属性用来指定当前所使用的CacheManager对应的bean的名称,默认值是cacheManager,所以当我们的CacheManager的id为cacheManager时我们可以不指定该参数,否则就需要我们指定了。

	<cache:annotation-driven/>还可以指定一个mode属性,可选值有proxy和aspectj。默认是使用proxy。当mode为proxy时,只有缓存方法在外部被调用的时候Spring Cache才会发生作用,这也就意味着如果一个缓存方法在其声明对象内部被调用时Spring Cache是不会发生作用的。而mode为aspectj时就不会有这种问题。另外使用proxy时,只有public方法上的@Cacheable等标注才会起作用,如果需要非public方法上的方法也可以使用Spring Cache时把mode设置为aspectj。

	<cache:annotation-driven/>还可以指定一个proxy-target-class属性,表示是否要代理class,默认为false。缓存注解@Cacheable、@cacheEvict等是可以标注在接口上,这对于基于接口的代理来说是没有什么问题的,但是需要注意的是当我们设置proxy-target-class为true或者mode为aspectj时,是直接基于class进行操作的,定义在接口上的@Cacheable等Cache注解不会被识别到,那对应的Spring Cache也不会起作用了。
	同时注意<aop:config proxy-target-class="false">这里设置为false才行

配置jedis工厂、redis模板、cache管理器

	<!-- 配置jedis工厂 -->
	<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
		<property name="hostName" value="127.0.0.1"></property>
		<property name="port" value="6379"></property>
		<property name="usePool" value="true"></property>
		<property name="database" value="0"></property>
	</bean>
	
	<!-- 配置redis模板 -->
	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory"></property>
	</bean>
	
	<!-- 配置cache管理器 -->
	<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
		<constructor-arg index="0" ref="redisTemplate"></constructor-arg>
	</bean>

声明某些方法要使用缓存

@Cacheable

		标记在一个方法上,表示该方法是支持缓存的.对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法
		@Cacheable中常用的有三个属性,value、key和condition。

		value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。注意是cache的名称,而不是cache中的key,key值会默认生成也可以是自己指定的。cache的名称是我们操作时用的,cache的key是redis中存储value时用的。

		key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。
			例如:使用user的id值作为缓存的key值
			@Cacheable(value="users", key="#user.id")
			public User find(User user) {..}

		condition属性指定缓存发生的条件,有的时候我们可能希望满足一定条件才进行缓存。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理
			例如:user的id值大于等于100的时候才进行缓存
			@Cacheable(value="users",condition="#user.id>=100")
			public User find(User user) {..}

@CachePut

		对于使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是,使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CacheEvict

		该注解是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。

		@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的意思与@Cacheable对应的属性类似。

		allEntries属性
		allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false。
		@CacheEvict(value="users", allEntries=true)
		public void delete(Integer id) {...}

		beforeInvocation属性
		清除操作默认是在对应方法成功执行之后触发的,但是方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素
		@CacheEvict(value="users", beforeInvocation=true)
		public void delete(Integer id) {...}

注意1:
测试的项目是把SpringMVC学习中的SSM项目里面的代码拿过来进行测试的,没有修改之前的配置,只是去掉了web层而已.


注意2:
为了测试方便,在项目中的测试使用了spring-test-3.2.4.RELEASE.jar和Junit4.12(必须是4.12版本或以上)
例如:可以自动帮我们读取spring配置文件并且自动完成依赖注入
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class Spring_Test {
	
	@Resource
	private User user;
	
	@Test
	public void test(){
		System.out.println(user);
	}
	
}

之后测试的时候这里可以读取service层配置文件、dao层配置文件、redis的配置文件,然后将service的实现类对象注入到此测试类中,直接使用并调用service方法并观察spring的cache注解是否起作用即可。

SSM结合Redis

其实就是在上面的spring和redis结合的情况下,再加入SpringMVC即可	
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章