project address:
https://github.com/CarrowZhu/springredis
Project Description:
Implement redis caching operations based on spring-data-redis annotations
requirement
JDK6
Spring4
Principle & Implementation
1) AOP
2) Implementation refers to Spring's Cache annotation
differences:
1) Support TTL
2) Support Hash
Configuration instructions
XML configuration file
xsi:schemaLocation=" http://www.siyuan.com/schema/springredis
http://www.siyuan.com/schema/springredis/springredis.xsd "
<springRedis:annotation-driven />
property description
redisTemplate: Advice The redisTemplate to be used, the default is "redisTemplate"
order: Advice execution order, the default priority is the highest (Ordered.HIGHEST_PRECEDENCE)
exceptionHandler: beanId, operation exception handler, must implement the interface com.siyuan.springredis.interceptor.SpringRedisExceptionHandler, the default is com.siyuan.springredis.interceptor.LoggerExceptionHandler
annotation
1) @SpringRedisConfig: Class level configuration
property description
value: equivalent to redisTemplate
redisTemplate: (String) redisTemplate to be used in Advice
2) @SpringRedisValueCache: method level, the data type of the operation is String
Corresponding operation flow: read cache, hit return, miss -> get data -> cache
attribute description
value: equivalent to key
redisTemplate: (String) redisTemplate to be used in Advice
condition: (String) Support SpringEL, cache operation condition
timeout: (long) TTL, <= 0 means never expire, default is 0
timeUnit: (TimeUnit) TTL unit, default is TimeUnit.MILLISECONDS
key: (String) Support SpringEL, The key value corresponding to the cache must be provided
refreshTTL: (boolean) Whether to refresh the TTL when the cache hits, the default is false
3) @SpringRedisValueEvict: method level, the data type of the operation is String
Corresponding process: clear cache
property description
value: equivalent to key
redisTemplate: (String) redisTemplate to be used in Advice
condition: (String) Support SpringEL, cache operation condition
key : (String) Support SpringEL, cache the corresponding key value, must provide
4) @SpringRedisHashCache: method level, the data type of the operation is Hash
Corresponding operation process: similar to @SpringRedisValueCache
property description
value: equivalent to key
redisTemplate: (String) redisTemplate to be used in Advice
condition: (String) Support SpringEL, cache operation Condition
timeout: (long) TTL, <= 0 means never expires, the default is 0
timeUnit: (TimeUnit) TTL unit, the default is TimeUnit.MILLISECONDS
key: (String) SpringEL is supported, the corresponding key value is cached, and
refreshTTL must be provided: (boolean) Whether to refresh the TTL when the cache hits, the default is false
hashKey: (String) Support SpringEL, cache the corresponding hashKey value, must provide
5) @SpringRedisHashEvict: method level, the data type of operation is Hash
corresponding process: similar to @SpringRedisValueEvict
attribute description
value: equivalent to key
redisTemplate: (String) redisTemplate to be used in Advice
condition: (String) Support SpringEL, cache operation Condition
key: (String) Support SpringEL, cache the corresponding key value, must provide
hashKey: (String) Support SpringEL, cache the corresponding hashKey value, must provide
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
Example
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")); // don't 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")); // don't cache stu = service.getById(100L); assertArrayEquals(new Object[] { new Student(100L, "name:100") }, new Object[] { stu }); // don't 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")); // don't 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")); // don't cache stu = service.getById2(100L); assertArrayEquals(new Object[] { new Student(100L, "name:100") }, new Object[] { stu }); // don't 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); } }