Spring AOP + Redis cache database query, aopredis

Source: Internet
Author: User
Tags key string

Spring AOP + Redis cache database query, aopredis
Application scenarios

We hope to cache the database query results to Redis so that the results can be directly obtained from redis for the second query, thus reducing the number of database reads and writes.

Solutions to problems to be solved avoid dirty reading

The query results are cached. Once the data in the database changes, the cached results are unavailable. To ensure this, You can execute the update query of the relevant table (update,delete,insert) Before the query, make the related cache expire. In this way, the program will re-read the new data from the database and cache it to redis during the next query. The problem arises.insertHow do I know which caches should expire before? For Redis, we can useHash SetData structure, so that a table corresponds toHash SetAll queries on this table are saved to this Set. In this way, when the table data changes, the Set can expire directly. We can customize an annotation to indicate the tables associated with the operation through the annotation attribute in the database query method, so that when performing the expiration operation, you can directly find out which sets should expire from the annotations.

Generate a unique identifier for the query

ForMyBatis, We can directly useSQLStringkey. However, you must writeMyBatisSo that your cache code andMyBatisTightly coupled. If you change the persistence layer framework one day, your cache code will be written in white, so this solution is not perfect.
If the class name, method name, and parameter value of the two queries are the same, we can determine that the results of these two queries must be the same (without any data changes ). Therefore, we can combine these three elements into a string as the key to solve the identification problem.

Serialized query results

The most convenient serialization method is to use the built-in JDKObjectOutputStreamAndObjectInputStream. The advantage is that almost any object, as long asSerializableInterface, allSame set of codeCan be serialized or deserialized. But the disadvantage is also fatal, that is, the serialization result capacity is too large, which will consume a large amount of memory in redis (about three times the corresponding JSON format ). Then we only have the JSON option.
The advantage of JSON is its compact structure and high readability, but the disadvantage is that the specific type parameters must be provided for deserialization objects (ClassObject ).ListObject, you must also provideListAnd List element types in order to be correctly deserialized. This increases the complexity of the Code. However, these difficulties can be overcome, so we still choose JSON as the serialization storage method.

Code written?

There is no doubt thatAOPPlaying now. In our example, the persistence framework usesMyBatisTherefore, our task is to interceptMapperInterface method call, throughAround (surround notification)Write the following logic:

Code Implementation

Because what we want to intercept isMapperTherefore, you must use the dynamic proxy insteadcglib. To do this, we need to make the following Configuration:

<! -- Use JDK dynamic proxy when proxy-target-class is false --> <! -- Use cglib --> <! -- Cglib cannot intercept interface methods --> <aop: aspectj-autoproxy proxy-target-class = "false"/>

Define two annotations on the Interface Method to pass type parameters:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)@Documentedpublic @interface RedisCache {    Class type();}
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RedisEvict {    Class type();}

The usage of annotations is as follows:

// Indicates that this method needs to be executed (is the cache hit? Return the cache and block method calls: Execute the method and cache the result.) The cache logic @ RedisCache (type = JobPostModel. class) JobPostModel selectByPrimaryKey (Integer id );
// Indicates that the method needs to clear the cache logic @ RedisEvict (type = JobPostModel. class) int deleteByPrimaryKey (Integer id );

The AOP code is as follows:

@ Aspect @ Componentpublic class RedisCacheAspect {public static final Logger infoLog = LogUtils. getInfoLogger (); @ Qualifier ("redisTemplateForString") @ Autowired StringRedisTemplate rt;/*** query the cache before calling the method. If a cache exists, the cached data is returned to prevent method calls. * if no cache exists, the business method is called, then put the result in the cache * @ param jp * @ return * @ throws Throwable */@ Around ("execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. select *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. get *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. find *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. search *(..)) ") public Object cache (ProceedingJoinPoint jp) throws Throwable {// obtain the class name, method name, and parameter String clazzName = jp. getTarget (). getClass (). getName (); String methodName = jp. getSignature (). getName (); Object [] args = jp. getArgs (); // generate key String key = genKey (clazzName, methodName, args) based on the class name, method name, and parameter; if (infoLog. isDebugEnabled () {infoLog. debug ("generate key :{}", key) ;}// obtain the Method me = (MethodSignature) jp. getSignature ()). getMethod (); // obtain the Class modelType = me. getAnnotation (RedisCache. class ). type (); // check whether redis has cached String value = (String) rt. opsForHash (). get (modelType. getName (), key); // result is the final result returned by the method. Object result = null; if (null = value) {// if (infoLog. isDebugEnabled () {infoLog. debug ("cache miss");} // call the database query method result = jp. proceed (args); // serialize the query result String json = serialize (result); // put the serialization result into the cache rt. opsForHash (). put (modelType. getName (), key, json);} else {// cache hit if (infoLog. isDebugEnabled () {infoLog. debug ("cache hit, value = {}", value) ;}// Class returnType = (MethodSignature) jp. getSignature ()). getReturnType (); // deserialization the json result obtained from the cache = deserialize (value, returnType, modelType); if (infoLog. isDebugEnabled () {infoLog. debug ("deserialization result = {}", result) ;}} return result;}/*** clear the cache before calling the method, then call the Business Method * @ param jp * @ return * @ throws Throwable */@ Around ("execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. insert *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. update *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. delete *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. increase *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. decrease *(..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. complaint (..)) "+" | execution (* com. fh. taolijie. dao. mapper. jobPostModelMapper. set *(..)) ") public Object evictCache (ProceedingJoinPoint jp) throws Throwable {// obtain the proxy Method me = (MethodSignature) jp. getSignature ()). getMethod (); // obtain the Class modelType = me. getAnnotation (RedisEvict. class ). type (); if (infoLog. isDebugEnabled () {infoLog. debug ("Clear cache: {}", modelType. getName ();} // clear the corresponding cache rt. delete (modelType. getName (); return jp. proceed (jp. getArgs ());} /*** generate key * @ param clazzName * @ param methodName * @ param args method parameter * @ return */protected String genKey (String clazzName, string methodName, Object [] args) {StringBuilder sb = new StringBuilder (clazzName); sb. append (Constants. DELIMITER); sb. append (methodName); sb. append (Constants. DELIMITER); for (Object obj: args) {sb. append (obj. toString (); sb. append (Constants. DELIMITER);} return sb. toString ();} protected String serialize (Object target) {return JSON. toJSONString (target);} protected Object deserialize (String jsonString, Class clazz, Class modelType) {// the serialization result should be List Object if (clazz. isAssignableFrom (List. class) {return JSON. parseArray (jsonString, modelType);} // The serialized result is a normal object return JSON. parseObject (jsonString, clazz );}}

This completes the implementation of database query cache.

Copyright Disclaimer: This article is an original article by the blogger and cannot be reproduced without the permission of the blogger.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.