项目地址:
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); } }