Redis——Jedis

Redis——Jedis

九月 11, 2019

1.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

2.jedis的相关jar包

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

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

3.Jedis对象的使用

整个过程类似之前使用Connection对象一样,一个Jedis对象就是一个数据库连接对象,这不过这个数据库是Redis(内存数据库)
1)创建Jedis对象
    Jedis jedis = new Jedis();
    或者:
    Jedis jedis = new Jedis("127.0.0.1",6379);
    或者:
    Jedis jedis = new Jedis(new URI("redis://:passwd@127.0.0.1:6379/0"));
    注意:这里最后一个0代表的时候index为0的数据库,同时这种方式必须要求有密码

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

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


所以在使用junit进行基本的jedis操作测试的时候,可以编写如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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注解的方法

4.Jedis对Redis中key的操作

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
@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();
}

5.Jedis对Redis中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
35
36
37
38
39
@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"));
}

6.Jedis对Redis中hash的操作

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
@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));
}

7.Jedis对Redis中其他类型的操作

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

8.Jedis处理Redis中的事务

Transaction tx = jedis.multi();

//操作数据

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

例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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());
}

9.JedisPool的使用(连接池)

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

**1)创建JedisPool**
1
2
3
4
5
6
7
8
9
10
11
12
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对象**
1
2
JedisPool pool = new JedisPool();
Jedis jedis = pool.getResource();
**3)从JedisPool获得的Jedis对象再使用后调用close方法即可**
1
2
3
4
5
6
7
8
9
10
11
通过查看源码可知,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();
}
}

10.使用Jedis存储java对象

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

1)创建工具类SerializingUtils
负责把对象和byte[]之间进行转换
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
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

封装的工具类:
注意:这里只时封装了几个方法,可以根据自己的实现需求而进一步的封装扩展
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
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();
}
}
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
	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();
}
//返回的是long,通过intValue转成int值
return jedis.dbSize().intValue();
}

public static Jedis getResource(){
return jedisPool.getResource();
}

}

11.Redis和mybatis结合

注意:Jedis是用来操作Redis的

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

一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
二级缓存,又叫自定义缓存,实现了Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。
所以我们可以自定义缓存类,实现Mybatis提供的缓存接口Cache,其中的方法使用Jedis来实现,然后把这个缓存实现类配置为Mybatis的二级缓存提供者即可
1)建表语句及实体类:
1
2
3
4
5
6
create table t_user(
id number primary key,
name varchar2(50) not null,
age number,
dob date
);
1
2
3
4
5
6
7
8
9
10
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
}
2)Mybatis配置文件及config.properties文件
src下面的mybatis-config.xml文件:
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
<?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"/>
</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文件
1
2
3
4
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:XE
username=test
password=test
3)Mybatis映射接口和映射文件
映射接口:
1
2
3
4
public interface UserMapper {
public void insertUser(User user);
public List<User> findAllUsers();
}
映射文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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>
4)Jedis相关配置文件和工具类
src下面的config.properties

封装Jedis的工具类
JedisUtils.java
SerializingUtils.java
封装MyBatis的工具类
MyBatisSqlSessionFactory.java
5)自定义cache接口实现类MybatisRedisCache.java
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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>
6)配置标签
在需要缓冲的映射文件中加入<cache/>标签,并且指定提供缓存功能的cache实现类的全限定名
<cache type="com.briup.cache.MybatisRedisCache"></cache>
7)测试方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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中取出
8)注意事项
注意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>

12.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需要我们做的事情:
        配置Spring对Cache的支持
        配置jedis工厂、redis模板、cache管理器
        声明哪些方法要使用缓存
1)配置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才行
2)配置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>
3)声明某些方法要使用缓存
    @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) {..}



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


    3)@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注解是否起作用即可。

13.SSM结合Redis

其实就是在上面的spring和redis结合的情况下,再加入SpringMVC即可    
参照项目
    redis_ssm_1
        其中redis和MyBatis结合
    redis_ssm_2
        其中redis和Spring4.3.3结合
        spring-data-redis-1.7.5.RELEASE.jar
        jedis-2.9.0.jar
    redis_ssm_3
        其中redis和Spring3.2.4结合
        spring-data-redis-1.6.2.RELEASE.jar
        jedis-2.6.2.jar