Spring AOP + Redis Cache database Query

Source: Internet
Author: User
Tags aop throwable

Application Scenarios

We want to be able to cache database query results into Redis so that the results can be taken directly from Redis when the same query is made the second time, thus reducing the number of database reads and writes.

Issues that need to be addressed
    1. Where is the code where the operation cache is written? Must be completely detached from the business logic code.
    2. How to avoid dirty reading? The data that is read from the cache must be consistent with the data in the database.
    3. How do I generate a unique identity for a database query result? That is, the identity (in Redis Key ), can uniquely determine a query result, the same query results, must be able to map to the same key . Only in this way can we guarantee the correctness of the cached content
    4. How do I serialize the results of a query? The result of the query may be a single Entity object, or it may be one List .
Solution to avoid dirty reads

We cache the results of the query, so once the data in the database changes, the cached results are not available. To achieve this guarantee, you can update delete have the relevant cache expire before you perform an update query (,) query on the related table insert . The next time you query, the program will re-read the new data cache from the database into Redis. So the question is, insert how do I know which cache should expire before executing one? For Redis, we can use Hash Set data structures to have one table corresponding to one Hash Set , and all queries on this table are saved under that set. This allows the set to expire directly when the table data changes. We can customize an annotation to indicate which tables are related to this action on the database query method through the attributes of the annotation, so that when you perform an expiration operation, you will be able to tell directly from the annotations which set should expire.

Generate a unique identity for a query

For MyBatis , we can use SQL strings directly as key . However, you must write an interceptor based on it so that MyBatis your cache code is MyBatis tightly coupled. If you change the frame of the persistence layer one day, your cache code will be written in white, so this scheme is not perfect.
Think about it, in fact, if the class name, method name, and parameter value of the two query call are the same, we can make sure that the results of these two queries must be the same (if the data is not changed). Therefore, we can combine these three elements into a single string as key, which solves the identity problem.

Serialization of query Results

The most convenient way to serialize is to use the and of the JDK itself ObjectOutputStream ObjectInputStream . The advantage is that almost any object, as long as the interface is implemented Serializable , can be serialized and deserialized using the same set of code . But the disadvantage is also fatal, that is, serialization results in large capacity, in Redis will consume a lot of memory (the corresponding JSON format of about 3 times times). So we only have the JSON option left.
The advantage of JSON is that it is compact and readable, but the drawback is that when deserializing an object, you must provide a specific type parameter ( Class object), and if it is an List object, you must also provide two types of List information, the element type in the list, to be correctly deserialized. This increases the complexity of the code. However, these difficulties can be overcome, so we still choose JSON as a serialized storage method.

Where is the code written?

No doubt it AOP 's time to play. In our case, the persistence framework uses MyBatis that, so our task is to intercept Mapper the invocation of the interface method by writing the Around(环绕通知) following logic:

    1. method is called before it is generated according to the class name, method name, and parameter value.Key
    2. by Key Redis initiating a query to
    3. If the cache is hit, the cached result is deserialized as the return value of the method call, and the call to the proxy method is blocked.
    4. If the cache misses, execute the proxy method, get the query results, serialize, and put the Key serialized results into Redis with the current.
Code implementation

Because we want to intercept the Mapper interface method, we must command spring to use the dynamic proxy of the JDK instead cglib of the proxy. To do this, we need to make the following configuration:

<!-- 当proxy-target-class为false时使用JDK动态代理 --><!-- 为true时使用cglib --><!-- cglib无法拦截接口方法 --><aop:aspectj-autoproxy proxy-target-class="false" />

Then define two annotations on the interface method to pass the type parameters:

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

Annotations are used in the following ways:

// 表示该方法需要执行 (缓存是否命中 ? 返回缓存并阻止方法调用 : 执行方法并缓存结果)的缓存逻辑@RedisCache(type = JobPostModel.class)JobPostModel selectByPrimaryKey(Integer id);
// 表示该方法需要执行清除缓存逻辑@RedisEvict(type = JobPostModel.class)int deleteByPrimaryKey(Integer id);

The code for AOP is as follows:

@Aspect@Component Public  class rediscacheaspect {     Public Static FinalLogger InfoLog = Logutils.getinfologger ();@Qualifier("Redistemplateforstring")@AutowiredStringredistemplate RT;/** * Before the method call, query the cache first.     If there is a cache, the cached data is returned and the method call is blocked; * If there is no cache, call the business method and 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* (..)) ") PublicObjectCache(Proceedingjoinpoint JP)throwsThrowable {//Get the class name, method name, and parametersString clazzname = Jp.gettarget (). GetClass (). GetName ();        String methodName = Jp.getsignature (). GetName (); object[] args = Jp.getargs ();generate key based on class name, method name, and parameterString key = Genkey (Clazzname, methodName, args);if(Infolog.isdebugenabled ()) {Infolog.debug ("Generate key:{}", key); }//Get the Proxy methodMethod me = ((methodsignature) jp.getsignature ()). GetMethod ();//Get an annotation on the method being proxiedClass Modeltype = me.getannotation (Rediscache.class). Type ();//Check if there is a cache in RedisString value = (string) rt.opsforhash (). Get (Modeltype.getname (), key);//result is the result of the method's final returnObject result =NULL;if(NULL= = value) {//Cache misses            if(Infolog.isdebugenabled ()) {Infolog.debug ("Cache Misses"); }//Call database Query methodresult = Jp.proceed (args);//Serialization of query resultsString JSON = serialize (result);//Serialize results into cacheRt.opsforhash (). Put (Modeltype.getname (), key, JSON); }Else{//Cache hit            if(Infolog.isdebugenabled ()) {Infolog.debug ("Cache hit, value = {}", value); }//Get the return value type of the proxy methodClass returntype = ((methodsignature) jp.getsignature ()). Getreturntype ();//Deserialize JSON obtained from the cacheresult = Deserialize (value, ReturnType, Modeltype);if(Infolog.isdebugenabled ()) {Infolog.debug ("deserialization result = {}", result); }        }returnResult }/** * Clears the cache before the method call and then calls the business method * @param JP * @return * @throws throwable */< /c0>    @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* (..)) ") PublicObjectEvictcache(Proceedingjoinpoint JP)throwsThrowable {//Get the Proxy methodMethod me = ((methodsignature) jp.getsignature ()). GetMethod ();//Get an annotation on the method being proxiedClass Modeltype = me.getannotation (Redisevict.class). Type ();if(Infolog.isdebugenabled ()) {Infolog.debug ("Empty cache: {}", Modeltype.getname ()); }//clear the corresponding cacheRt.delete (Modeltype.getname ());returnJp.proceed (Jp.getargs ()); }/** * Generates key based on class name, method name, and parameter * @param clazzname * @param methodName * @param args Method parameter * @return * /    protectedStringGenkey(String clazzname, String methodName, object[] args) {StringBuilder SB =NewStringBuilder (Clazzname);        Sb.append (Constants.delimiter);        Sb.append (MethodName); Sb.append (Constants.delimiter); for(Object Obj:args)            {Sb.append (obj.tostring ());        Sb.append (Constants.delimiter); }returnSb.tostring (); }protectedStringSerialize(Object target) {returnJson.tojsonstring (target); }protectedObjectDeserialize(String jsonstring, Class Clazz, class Modeltype) {//Serialization result should be a list object        if(Clazz.isassignablefrom (List.class)) {returnJson.parsearray (jsonstring, Modeltype); }//Serialization result is a normal object        returnJson.parseobject (jsonstring, clazz); }}

This completes the implementation of the database query cache.

Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Spring AOP + Redis Cache database Query

Related Article

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.