spring boot cache first experience
1. Project setup
Use MySQL as the database, Spring boot integrates mybatis to operate the database, so when using the spring boot cache component, you need to build a simple ssm environment.
First is the project dependency
org.springframework.boot spring-boot-starter- cache org.springframework.boot spring-boot-starter-web org. mybatis.spring.boot mybatis-spring-boot-starter 1.3.2 org.springframework.boot groupId> spring-boot-starter-data-redis mysql mysql-connector-java 5.1. 48 runtime org.springframework.boot spring-boot-starter-test test
Data for database testing
CREATE TABLE `student` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `gender` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int(11) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of student-- ----------------------------INSERT INTO `student` VALUES (1,'eric','male', 22 );INSERT INTO `student` VALUES (2,'alice','female', 23);INSERT INTO `student` VALUES (3,'bob','male', 21);
Correspondence The entity class code is as follows:
public class Student {private Integer id; private String name; private String gender; private Integer age; //Omit constructor, getter, setter, toString}
Corresponding mapper:
public interface StudentMapper {@Select("select * from student where id = #{id}") Student getStudentById(Integer id);}
Corresponding service:
@Servicepublic class StudentService {@Autowired private StudentMapper studentMapper; public Student getStudentById(Integer id) {return studentMapper.getStudentById(id); }}
Corresponding test class:
@RunWith(SpringRunner .class)@SpringBootTestpublic class DemoCacheApplicationTests {@Autowired private StudentService studentService; /** * Test whether mybatis is correctly configured*/ @Test public void contextLoads() {System.out.println(studentService.getStudentById(1)); })< /pre>Run the above test method and print successfully
student{id=1, name='eric', gender='male', age=22} pre>The shelf of the project is basically set up successfully, the next step is to use the cache annotation provided by springboot to test it.
Before that, let’s get some background knowledge.
First is the JSR107 caching specification. Java Caching defines 5 core interfaces, namely CachingProvider, CacheManager, Cache, Entry
and Expiry.
- CachingProvider
Defines the creation, configuration, acquisition, management and control of multiple CacheManagers. An application can access multiple CachingProviders during runtime. - CacheManager
defines the creation, configuration, acquisition, management and control of multiple uniquely named Caches, which exist in the context of CacheManager. A CacheManager is owned by only one CachingProvider. - Cache
is a data structure similar to Map and temporarily stores values indexed by Key. A Cache is owned by only one CacheManager. - Entry
is a key-value pair stored in the Cache. - Expiry
Each item stored in the Cache has a defined validity period. Once this time has passed, the entry is in an expired state. Once expired, the entry will not be accessible, updated, or deleted. The cache validity period can be set through ExpiryPolicy.
< /p>
Spring has defined org.springframework.cache.Cache and org.springframework.cache.CacheManager interfaces from 3.1 to unify different caching technologies, and supports the use of JCache [JSR-107] annotations to simplify our development.
Let’s take a look at the basic structure of the Cache interface first:
Cache interface standardizes the basic operations of various caches for cached components, spring provides various commonly used xxxCache Implementation, such as: RedisCache, EhCacheCache, ConcurrentMapCache, etc.
Under the currently added dependencies, you can find these Cache implementations
Each time a method that requires a cache function is called, Spring will check whether the target method of the specified parameter has been called, and if so, Get it directly from the cache, if not, call the method and cache the result before returning it to the user. The data after that is obtained directly from the cache.
So the following aspects should be considered when using caching:
- Determine whether the method needs to be cached
- Determine the method caching strategy (such as key setting , Whether the cached data uses json format or Java serialization)
- How to ensure the consistency of cache and database data
- Read the previously cached data from the cache every time
First of all, not all methods need to be cached. Generally speaking, data that is frequently accessed and not frequently modified needs to be cached.
The key generation strategy can be directly specified using the key attribute, or you can specify the keyGenerator
By default, the cached data uses the Java serial number method, we can store it It is in json format, depending on the needs of the project.
The consistency of the cache is more complicated. This article does not involve the discussion of the consistency of the cache and the database in the case of high concurrency. It just ensures that the data in the cache is updated in time when the data is modified or deleted. In other words, after the data is cached, if the modified method is called later and the data is modified, the CachePut annotation is required to modify the data in the cache in time, or the delete method is called, and the CacheEvict annotation is required. To delete the corresponding cached data.
As for reading the cached data from the cache every time, let Spring handle it automatically.
Cache | Cache interface, encapsulate the basic operation of cache | tr>
---|---|
CacheManager | Cache manager, manages various cache components, an application can have Multiple cache managers |
@Cacheable | Mainly for method configuration, which can be based on method request Parameter to cache its results |
@CacheEvict | Clear the cache |
@CachePut | Ensure that the method is called and the result is cached. It is generally used to modify data. |
@EnableCaching | Enable annotation-based caching |
keyGenerator | Key generation strategy when caching data |
< strong>serialize | Value serialization strategy when caching data |
@CachePut
and @Cacheable
What is the difference between the two annotations?
@CachePut: This annotation can ensure that the method is executed, and the return value of the method is also recorded in the cache.
@Cacheable: When the method is called repeatedly with the same parameters, the method itself will not be called and executed, that is, the method itself is skipped, and instead the result of the method is directly found from the cache and returned .
? For @CachePut this annotation, what is its function? Every time the method is executed, what is the meaning of caching? The answer is simple. The cached data of the same key of the same cache instance can be updated with @CachePut, and @Cacheable is the updated value of @CachePut when it takes a value. But at the same time, note must be the same cache instance object, and the key must be consistent! ! !
@Cacheable,@CachePut,@CacheEvict annotation commonly used attributes are as follows:
attributes | Function | Example |
---|---|---|
value | The name of the cache , Defined in the spring configuration file, must specify at least one | For example: @Cacheable(value=”mycache”) or @Cacheable(value={”cache1”,”cache2”} | < /tr>
key | The cached key can be empty. If you specify to write according to SpEL expression, if you don’t specify it, it defaults to all of the method Parameter combination | For example: @Cacheable(value=”testcache”,key=”#userName”) |
condition td> | The condition of the cache can be empty, written in SpEL, and return true or false. Only when it is true can cache/clear the cache. It can be judged before and after the method is called. | For example: @ Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries (@CacheEvict ) | Whether to clear all cache contents, the default is false, if specified as true, all caches will be cleared immediately after the method is called | For example: @CachEvict(value=”testcache ”,AllEntries=true) |
beforeInvocation (@Cac heEvict) | Whether to clear before the method is executed, the default is false, if specified as true, the cache will be cleared before the method is executed, by default, if the method Execution throws an exception, the cache will not be cleared | For example: @CachEvict(value=”testcache”,beforeInvocation=true) |
unless (@CachePut) (@Cacheable) | Used to veto caching, unlike condition, this expression is only judged after the method is executed At this time, you can get the return value result for judgment. If the condition is true, it will not be cached, and fasle will only be cached. | For example: @Cacheable(value=”testcache”,unless=”#result == null”) |
Cache SpEL available metadata
Name | Location | Description | Example |
---|---|---|---|
methodName | root object | The name of the currently called method | #root.methodName | < /tr>
method | root object | The currently called method | #root.method.name< /td> |
target | root object | The target object currently called | #root .target |
targetClass | root object | The target object class currently being called | < td>#root.targetClass|
args | root object | The parameter list of the currently called method | #root.args[0] |
cac hes | root object | The cache list used by the current method call (such as @Cacheable(value={"cache1", "cache2"})), there are two caches | td>#root.caches[0].name |
argument name | evaluation context | The name of the method parameter. You can directly #parameter name, or you can use the form of #p0 or #a0, 0 represents the index of the parameter; | #iban, #a0, # p0 |
result | evaluation context | The return value after the method is executed (only when the method is executed Judgment is valid, such as'unless','cache put' expression'cache evict' expression beforeInvocation=false) | #result |
This is good, there is no need to memorize, the default configuration is sufficient.
2. Cache usage process analysis
First, we need to introduce spring-boot-starter-cache dependency
Then use @EnableCaching Turn on the cache function
Then you can use cache annotations to support it.
Let’s take a look at what the official API says:
@Target(value=TYPE)@Retention(value=RUNTIME)@Documented@Import( value=CachingConfigurationSelector.class)public @interface EnableCaching
Enables Spring's annotation-driven cache management capability, To be used together with @Configuration
classes as follows:
@Configuration@EnableCachingpublic class AppConfig {@Bean public MyService myService() {// configure and return a class having @Cacheable methods return new MyService();} @Bean public CacheManager cacheManager() {/ / configure and return an implementation of Spring's CacheManager SPI SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default"))); return cacheManager; }}
@EnableCaching is responsible for registering the necessary Spring components that power annotation-driven cache management, such as the CacheInterceptor and the proxy- or AspectJ-based advice that weaves the interceptor into the call stack when @Cacheable methods are invoked.
The description of the official document is concise and clear, we only need to enable the cache, and then customize the CacheManager. Can.
If the JSR-107 API and Spring's JCache implementation are present, the necessary components to manage standard cache annotations are also registered. This creates the proxy- or AspectJ-based advice that weaves the interceptor into the call stack when methods annotated with CacheResult
, CachePut
, CacheRemove
or CacheRemoveAll
are invoked .
The powerful spring also supports JSR107 cache annotations! ! ! Of course, this article mainly focuses on explaining Spring's caching annotations.
For those that wish to establish a more direct relationship between @EnableCaching
and the exact cache manager bean to be used , the CachingConfigurer
callback interface may be implemented. Notice the @Override
-annotated methods below:
If you want to customize your CacheManager explicitly, you can Use like below
@Configuration @EnableCaching public class AppConfig extends CachingConfigurerSupport {@Bean public MyService myService() {// configure and return a class having @Cacheable methods return new MyService() ;} @Bean @Override public CacheManager cacheManager() {// configure and return an implementation of Spring's CacheManager SPI SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default"))); return cacheManager;} @Bean @Override public KeyGenerator keyGene rator() {// configure and return an implementation of Spring's KeyGenerator SPI return new MyKeyGenerator();} }
This approach may be desirable simply because it is more explicit, or it may be necessary in order to distinguish between two CacheManager
Because there can be multiple CacheManagers in an application environment, the declaration of CacheManagers can be more intuitive.
Notice also the keyGenerator
method in the example above. This allows for customizing the strategy for cache key generation, per Spring's KeyGenerator
SPI. Normally , @EnableCaching
will configure Spring's SimpleKeyGenerator
for this purpose, but when implementing CachingConfigurer
, a key generator < strong>must be provided explicitly. Return null
or new SimpleKeyGenerator()
from this method if no customization is necessary. p>
If you implement the CachingConfigurer
interface, you need to clearly define keyGenerator
CachingConfigurer
offers additional customization options: it is recommended to extend from < code>CachingConfigurerSupport that provides a default implementation for all methods which can be useful if you do not need to customize everything. See CachingConfigurer
Javadoc for further details.
By inheriting CachingConfigurerSup port to achieve other customized functions. The structure of the CachingConfigurerSupport class is as follows. You can rewrite only the functions you need to customize, and all others will return null by default. If null is returned, the automatic configuration of spring boot will take effect.
/** * An implementation of {@link CachingConfigurer} with empty methods allowing * sub-classes to override only the methods they're interested in. * * @author Stephane Nicoll * @since 4.1 * @see CachingConfigurer */public class CachingConfigurerSupport implements CachingConfigurer {@Override public CacheManager cacheManager() {return null;} @Override public KeyGenerator keyGenerator() {return null;} @Override public CacheResolver cacheResolver() {return null; } @Override public CacheErrorHandler errorHandler() {return null; }}
The mode()
attribute controls how advice is applied: If the mode is AdviceMode.PROXY< /code> (the default), then the other attributes control the behavior of the proxying. Please note that proxy mode allows for interception of calls through the proxy only; local calls within the same class cannot get intercepte d that way.
Note that if the mode() is set to AdviceMode.ASPECTJ
, then the value of the proxyTargetClass()
attribute will be ignored. Note also that in this case the spring-aspects
module JAR must be present on the classpath, with compile-time weaving or load-time weaving applying the aspect to the affected classes. There is no proxy involved in such a scenario; local calls will be intercepted as well.
It’s really enjoyable.
3.actual hand
@CacheConfig
Annotation can define all the cache annotations used in the current class (@Cacheable
,@CachePut
,@CacheEvict
), the following sample code actually only configures the cache name of the current class
@Service@CacheConfig(cacheNames = "student")public class StudentService {@Autowired private StudentMapper studentMapper; @Cacheable public Student getStudentById(Integer id) {System.out.println("Query students from the database:" + id); return studentMapper.getStudentById(id);} @CachePut public Student updateStudent(Student student) {System.out.println("Update student data in the database:" + student); studentMapper.updateStudent(student); return student ;} @CacheEvict public void deleteStudent(Integer id) {System.out.println("Delete students in the database:"+id); studentMapper.delStudent(id); }}
The above is just simple Use these three annotations for more detailed attribute usage, please see the following content. Let's first test the effect of the cache.
The code of the test class is as follows:
@RunWith(SpringRunner.class)@SpringBootTestpublic class DemoCacheApplicationTests {@Autowired private StudentService studentService; @Test public void contextLoads() {System.out.println(studentService.getStudentById(1));} @Test public void testUpdate() {studentService.updateStudent(new Student(1,"gotohell","female",23));} @Test public void testDelete() {studentService.deleteStudent(1); }}
First test the @Cacheable annotation, the first time this method is called, the printed log is as follows:
Query students from the database: 1student{id=1, name='mmm', gender='male', age=21}
The second time this method is called, the printed log is as follows:
student{id=1, name='mmm', gender='male', age=21}
Indicates that the cache has taken effect, and no students are obtained from the database data. Let’s take a look at the content in the cache,
This is the result of using jdk serialization storage by default. We can choose to store data in json format. In addition, the key generation strategy is the cache name prefix plus method parameters by default. I think this is enough by default and no additional customization is required.
Let’s test the modification again,
Print the log as follows:
Update the student data in the database: student{id=1, name= 'gotohell', gender='female', age=23}
Check the data in the database. It has been modified successfully. Since the redis data is serialized, we won’t take a screenshot here. Let’s call it directly. Check it once to see if it has been updated.
The print result is as follows:
student{id=1, name='mmm', gender='male', age=21}
< p>It means that the data in the cache is not updated. Is it because the @CachePut annotation does not work?
Check out redis
It was discovered that the cache key used by default for the second modified data is the object. Why? Because by default, the key generation strategy is the cache name student+method Parameter, and the parameter of the update method is the student object, so the test cannot get the updated data because the two keys are inconsistent.
So as long as the key of the update method is specified as 1, is it OK?
@CachePut(key = "#result.id")public Student updateStudent( Student student) {System.out.println("Update student data in the database:" + student); studentMapper.updateStudent(student); return student;}
The re-specified key is like this, it Support spring expressions, the specific usage rules have been listed in the previous table. After retesting, print the log as follows:
student{id=1, name='gotohell', gender='female', age=23}
Get The updated data indicates that the key is working.
Let’s test the deletion again, and the print log is as follows:
Delete students in the database: 1
The data in the database has been successfully deleted , The data in the cache has also been emptied.
< p>Call the query again at this time, and the printed log is as follows:
Query the student from the database: 1null
From the printed log, it is a query The database, because the cache is no longer there, but the data in the database is also deleted, so null is returned
4. Use JSON to serialize objects< /h2>
This requires us to customize the CacheManager and add a new configuration class
@Configurationpublic class MyRedisConfig {@Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {// Initialize a RedisCacheWriter RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); //Set the value serialization method of CacheManager to json serialization RedisSerializationContext.SerializationPair
Re-test the query method and find that the cached value is serialized in JSON format.
< p>Source code address: https://github.com/lingEric/springboot-integration-hello