项目地址:
https://github.com/CarrowZhu/springredis
项目简介:
基于spring-data-redis的注解实现redis缓存操作
requirement
JDK6
Spring4
原理&实现
1)AOP
2)实现参考自Spring的Cache注解
区别:
1)支持TTL
2)支持Hash
配置说明
XML配置文件
xsi:schemaLocation="http://www.siyuan.com/schema/springredis
http://www.siyuan.com/schema/springredis/springredis.xsd"
<springRedis:annotation-driven />
属性说明
redisTemplate:Advice中将使用的redisTemplate,默认为"redisTemplate"
order:Advice的执行顺序,默认优先级最高(Ordered.HIGHEST_PRECEDENCE)
exceptionHandler:beanId,操作异常处理器,必须实现接口com.siyuan.springredis.interceptor.SpringRedisExceptionHandler,默认为com.siyuan.springredis.interceptor.LoggerExceptionHandler
注解
1)@SpringRedisConfig:Class级别配置
属性说明
value:等同于redisTemplate
redisTemplate:(String)Advice中将使用的redisTemplate
2)@SpringRedisValueCache:方法级别,操作的数据类型为String
对应操作流程:读cache,hit返回,miss -> 获取数据 -> cache
属性说明
value:等同于key
redisTemplate:(String)Advice中将使用的redisTemplate
condition:(String)支持SpringEL,缓存操作条件
timeout:(long)TTL,<=0表示永不过期,默认为0
timeUnit:(TimeUnit)TTL单位,默认为TimeUnit.MILLISECONDS
key:(String)支持SpringEL,缓存对应的key值,必须提供
refreshTTL:(boolean)缓存命中时是否刷新TTL,默认为false
3)@SpringRedisValueEvict :方法级别,操作的数据类型为String
对应的流程:清除缓存
属性说明
value:等同于key
redisTemplate:(String)Advice中将使用的redisTemplate
condition:(String)支持SpringEL,缓存操作条件
key:(String)支持SpringEL,缓存对应的key值,必须提供
4)@SpringRedisHashCache:方法级别,操作的数据类型为Hash
对应操作流程:与 @SpringRedisValueCache 类似
属性说明
value:等同于key
redisTemplate:(String)Advice中将使用的redisTemplate
condition:(String)支持SpringEL,缓存操作条件
timeout:(long)TTL,<=0表示永不过期,默认为0
timeUnit:(TimeUnit)TTL单位,默认为TimeUnit.MILLISECONDS
key:(String)支持SpringEL,缓存对应的key值,必须提供
refreshTTL:(boolean)缓存命中时是否刷新TTL,默认为false
hashKey:(String)支持SpringEL,缓存对应的hashKey值,必须提供
5)@SpringRedisHashEvict :方法级别,操作的数据类型为Hash
对应的流程:与 @SpringRedisValueEvict 类似
属性说明
value:等同于key
redisTemplate:(String)Advice中将使用的redisTemplate
condition:(String)支持SpringEL,缓存操作条件
key:(String)支持SpringEL,缓存对应的key值,必须提供
hashKey:(String)支持SpringEL,缓存对应的hashKey值,必须提供
SpringEL
Name > Location > Example
methodName > root object > #root.methodName
method > root object > #root.method.name
target > root object > #root.target
targetClass > root object > #root.targetClass
args > root object > #root.args[0]
argument name > evaluation context > #name (编译时必须保留方法名信息)
result > evaluation context > #result
示例
1)ApplicationContext-SpringRedis.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:springRedis="http://www.siyuan.com/schema/springredis" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.siyuan.com/schema/springredis http://www.siyuan.com/schema/springredis/springredis.xsd"> <springRedis:annotation-driven /> <context:component-scan base-package="com.siyuan.springredis" /> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" /> <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory" p:key-serializer-ref="stringRedisSerializer"> <property name="defaultSerializer"> <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" /> </property> </bean> <bean id="studentRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="jedisConnectionFactory" p:key-serializer-ref="stringRedisSerializer" p:hash-key-serializer-ref="stringRedisSerializer"> <property name="defaultSerializer"> <bean class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer"> <constructor-arg index="0" value="#{T(com.siyuan.springredis.Student)}" /> </bean> </property> </bean> </beans>
2)Student.java
package com.siyuan.springredis;
public class Student {
private Long id;
private String name;
public Student() {
}
public Student(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Student [id=" + id + ", name=" + name + "]";
}
}
3)StudentDAO.java
package com.siyuan.springredis;
public interface StudentDAO {
Student getById(long id);
void updateStudent(Student student);
}
4)StudentService.java
package com.siyuan.springredis;
import java.util.concurrent.TimeUnit;
import org.springframework.stereotype.Service;
import com.siyuan.springredis.annotation.SpringRedisConfig;
import com.siyuan.springredis.annotation.SpringRedisHashCache;
import com.siyuan.springredis.annotation.SpringRedisHashEvict;
import com.siyuan.springredis.annotation.SpringRedisValueCache;
import com.siyuan.springredis.annotation.SpringRedisValueEvict;
@Service("studentService")
@SpringRedisConfig("studentRedisTemplate")
public class StudentService {
private StudentDAO studentDAO;
@SpringRedisValueCache(key = "'student:' + #id", condition = "#id > 100",
timeout = 60, timeUnit = TimeUnit.MINUTES, refreshTTL = true)
public Student getById(long id) {
return studentDAO.getById(id);
}
@SpringRedisValueEvict(key = "'student:' + #student.id", condition = "#student.id > 100")
public void updateStudent(Student student) {
studentDAO.updateStudent(student);
}
@SpringRedisHashCache(key = "'students'", hashKey = "#id.toString()", condition = "#id > 100",
timeout = 60, timeUnit = TimeUnit.MINUTES, refreshTTL = true)
public Student getById2(long id) {
return studentDAO.getById(id);
}
@SpringRedisHashEvict(key = "'students'", hashKey = "#student.id.toString()", condition = "#student.id > 100")
public void updateStudent2(Student student) {
studentDAO.updateStudent(student);
}
public StudentDAO getStudentDAO() {
return studentDAO;
}
public void setStudentDAO(StudentDAO studentDAO) {
this.studentDAO = studentDAO;
}
}
5)StudentServiceTest.java
package com.siyuan.springredis.test;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.Mockito.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.siyuan.springredis.Student;
import com.siyuan.springredis.StudentDAO;
import com.siyuan.springredis.StudentService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/ApplicationContext-SpringRedis.xml")
public class StudentServiceTest {
@Autowired
@Qualifier("studentRedisTemplate")
private RedisTemplate<String, Student> redisTemplate;
@Autowired
private StudentService service;
private StudentDAO studentDAO;
private StudentDAO mock;
@Before
public void setUp() {
redisTemplate.delete("student:123");
redisTemplate.delete("student:100");
redisTemplate.delete("students");
studentDAO = service.getStudentDAO();
mock = mock(StudentDAO.class);
service.setStudentDAO(mock);
}
@Test
public void testGetById() {
when(mock.getById(123L)).thenReturn(new Student(123L, "name:123"));
// no cache
Student stu = service.getById(123L);
assertArrayEquals(new Object[] { new Student(123L, "name:123") }, new Object[] { stu });
// cache
stu = service.getById(123L);
assertArrayEquals(new Object[] { new Student(123L, "name:123") }, new Object[] { stu });
verify(mock, times(1)).getById(123L);
when(mock.getById(100L)).thenReturn(new Student(100L, "name:100"));
// no cache
stu = service.getById(100L);
assertArrayEquals(new Object[] { new Student(100L, "name:100") }, new Object[] { stu });
// no cache
stu = service.getById(100L);
assertArrayEquals(new Object[] { new Student(100L, "name:100") }, new Object[] { stu });
verify(mock, times(2)).getById(100L);
}
@Test
public void testUpdateStudent() {
// evict
Student stu = new Student(123L, "name:123");
redisTemplate.opsForValue().set("student:123", stu);
service.updateStudent(stu);
assertNull(redisTemplate.opsForValue().get("student:123"));
// do not evict
stu = new Student(100L, "name:100");
redisTemplate.opsForValue().set("student:100", stu);
service.updateStudent(stu);
assertNotNull(redisTemplate.opsForValue().get("student:100"));
}
@Test
public void testGetById2() {
when(mock.getById(123L)).thenReturn(new Student(123L, "name:123"));
// no cache
Student stu = service.getById2(123L);
assertArrayEquals(new Object[] { new Student(123L, "name:123") }, new Object[] { stu });
// cache
stu = service.getById2(123L);
assertArrayEquals(new Object[] { new Student(123L, "name:123") }, new Object[] { stu });
verify(mock, times(1)).getById(123L);
when(mock.getById(100L)).thenReturn(new Student(100L, "name:100"));
// no cache
stu = service.getById2(100L);
assertArrayEquals(new Object[] { new Student(100L, "name:100") }, new Object[] { stu });
// no cache
stu = service.getById2(100L);
assertArrayEquals(new Object[] { new Student(100L, "name:100") }, new Object[] { stu });
verify(mock, times(2)).getById(100L);
}
@Test
public void testUpdateStudent2() {
// evict
Student stu = new Student(123L, "name:123");
redisTemplate.opsForHash().put("students", "123", stu);
service.updateStudent2(stu);
assertNull(redisTemplate.opsForHash().get("students", "123"));
// do not evict
stu = new Student(100L, "name:100");
redisTemplate.opsForHash().put("students", "100", stu);
service.updateStudent2(stu);
assertNotNull(redisTemplate.opsForHash().get("students", "100"));
}
@After
public void clear() {
service.setStudentDAO(studentDAO);
}
}