这里要分享的是:给数据库的部分表加缓存的实现
我们在javaee项目开发的时,当业务逻辑已实现,会着手去优化性能。减少数据库的访问压力,是优化的一个方面。在这个方面,就可能会用到缓存。
什么时候用缓存?当某些数据不是经常变化,以查询为主,增删改很少的数据,就可以考虑加缓存。
缓存的种类有很多,我所了解到的简单易用的有:ehcache、google提供的guavachace。
切面的好处:不侵入原来的代码,松藕合,在切面类中实现对缓存的管理,易维护,当有一天需要换另一个缓存技术方案的时候,只需要维护这个切面即可。(比如换成redis)
实现思路:
1. 分析出哪些需要加缓存的。
这边以一个积分模块为例,积分等级数据,积分规则数据,是很少变化的,可以加缓存。用户积分记录、用户积分获取和使用记录,都是经常变化的,所以不适合加缓存。
而对于后台管理的多条件分页查询,也是不适合加缓存的。
2. 写相应的切入点表达式,切入在相应的dao层方法,做相应的处理。
切入到查询方法,第一次查询,把数据以一定规则的key放入缓存。
当数据有更新的时候,需要同步更新缓存。而更新缓存最简单的,就是把它们全部清掉。清掉后,查询的时候发现缓存中找不到,又会把最新查出来的数据,放进去。这是缓存的数据就是最新的。
3. key的设计。
通过主健查询的时候,key为: “实体类名:{id:"id的值"}”例:TPointSetting:{"id":44}
当组合条件查询的时候,因为这次项目是用的是mybaits逆向工程生成的Mapper和实体类,非主键查询是这样的
TPointLevelExample example=new TPointLevelExample(); Criteria c = example.createCriteria(); c.andNameEqualTo(tPointLevel.getName()); List<TPointLevel> selectByExample = tPointLevelMapper.selectByExample(example); |
那么就需要将example对象转成字符串,作为key的一部分。
key是这样的(有点长):TPointSetting:{"distinct":"false","conditions":[{"betweenValue":false,"condition":"tag =","listValue":false,"noValue":false,"secondValue":null,"singleValue":true,"typeHandler":"","value":1}]}
我这里写了一个工具类:
package com.ice.util.f3.util;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import net.sf.json.JSONObject;
/**
* 查询参数 转成json字符串
* xuanhu @date 2018年7月7日 上午11:06:37
*
*/
public class ParamUtil {
public static String toString(Object param) {
try {
HashMap<String, Object> map = new HashMap<String,Object>();
Class<? extends Object> class1 = param.getClass();
String orderByClause=null;
String distinct=null;
List<JSONObject> conditions = new ArrayList<JSONObject>();
if(class1.getSimpleName().endsWith("Example")) {
Object example=param;
Field orderByClauseField = class1.getDeclaredField("orderByClause");
orderByClauseField.setAccessible(true);
if(orderByClauseField.get(example)!=null)orderByClause=orderByClauseField.get(example).toString();
Field distinctField = class1.getDeclaredField("distinct");
distinctField.setAccessible(true);
if(distinctField.get(example)!=null)distinct=distinctField.get(example).toString();
Field oredCriteriatField = class1.getDeclaredField("oredCriteria");
oredCriteriatField.setAccessible(true);
Object object = oredCriteriatField.get(example);
List<?> oredCriteria =null;
if(oredCriteriatField.get(example)!=null)oredCriteria=(List)oredCriteriatField.get(example);
Class<?> criteriaClass=null;
Class<?> criterionClass=null;
Class<?>[] declaredClasses = class1.getDeclaredClasses();
if(declaredClasses.length>0)for (Class<?> class2 : declaredClasses) {
if(class2.getSimpleName().endsWith("Criteria")) {
criteriaClass= class2;
}
if(class2.getSimpleName().endsWith("Criterion")) {
criterionClass= class2;
}
}
if(criteriaClass!=null && criterionClass!=null) {
for (Object criteria : oredCriteria) {
Field field = criteriaClass.getDeclaredField("criteria");
field.setAccessible(true);
if(field.get(criteria)!=null) {
List<?> criterions = (List)field.get(criteria);
for (Object criterion : criterions) {
JSONObject json = JSONObject.fromObject(criterion);
conditions.add(json);
}
}
}
}
//得到最终字符串
if(orderByClause!=null)map.put("orderByClause", orderByClause);
if(distinct!=null)map.put("distinct", distinct);
if(conditions.size()>0)map.put("conditions",conditions);
JSONObject json2 = JSONObject.fromObject(map);
return json2.toString();
}else{
return "{\"id\":\""+param.toString()+"\"}";
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "";
}
}
下面是切面类的代码: package com.ice;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.github.pagehelper.Page;
import com.github.pagehelper.SqlUtil;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.ice.util.f3.pojo.ReturnModel;
import com.ice.util.f3.util.ParamUtil;
@Aspect
@Component
@Order(1)
public class CacheAspect {
public static Logger log = Logger.getLogger(CacheAspect.class);
//EhCacheUtil cacheUtil = EhCacheUtil.getInstance();
private static final ThreadLocal<ProceedingJoinPoint> tl = new ThreadLocal<ProceedingJoinPoint>();
private static LoadingCache<String, Object> cache = CacheBuilder.newBuilder().refreshAfterWrite(24, TimeUnit.HOURS)// 给定时间内没有被读/写访问,则回收。
.expireAfterAccess(3, TimeUnit.HOURS)// 缓存过期时间
.maximumSize(1000).// 设置缓存个数
build(new CacheLoader<String, Object>() {
@Override
/** 当本地缓存命没有中时,调用load方法获取结果并将结果缓存 **/
public Object load(String appKey) throws Exception {
return getObject(appKey);
}
/** 数据库进行查询 **/
private Object getObject(String appKey) throws Exception {
log.debug("从数据库查询数据.....");
Object proceed=null;
try {
proceed = tl.get().proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return proceed;
}
});
@Pointcut("execution(* com.ice.dao.f3.TPointLevelMapper.insert*(..))"
+ " || execution(* com.ice.dao.f3.TPointLevelMapper.update*(..))"
+ " || execution(* com.ice.dao.f3.TPointLevelMapper.delete*(..))"
+ " || execution(* com.ice.dao.f3.TPointSettingMapper.insert*(..))"
+ " || execution(* com.ice.dao.f3.TPointSettingMapper.update*(..))"
+ " || execution(* com.ice.dao.f3.TPointSettingMapper.delete*(..))"
+ " || execution(* com.ice.dao.f3.TSpecialPointSettingMapper.insert*(..))"
+ " || execution(* com.ice.dao.f3.TSpecialPointSettingMapper.update*(..))"
+ " || execution(* com.ice.dao.f3.TSpecialPointSettingMapper.delete*(..))")
private void pointCutMethod() {
}
@Pointcut("execution(* com.ice.dao.f3.TPointLevelMapper.select*(..))"
+ " || execution(* com.ice.dao.f3.TPointSettingMapper.select*(..))"
+ " || execution(* com.ice.dao.f3.TSpecialPointSettingMapper.select*(..))")
private void pointCutForMapper() {
}
@Around("pointCutMethod()")//guava cache清缓存
public Object afterMethod4(ProceedingJoinPoint pjp) throws Throwable {
// 业务方法执行
Object result = pjp.proceed();
String classname = pjp.getSignature().getDeclaringTypeName();
String entityName= classname.substring(classname.lastIndexOf(".")+1).replace("Mapper", "");
String methodname = pjp.getSignature().getName();
if(methodname.contains("insert")||methodname.contains("update")||methodname.contains("delete")) {
Set<Entry<String, Object>> entrySet = cache.asMap().entrySet();
for (Entry<String, Object> entry : entrySet) {
if(entry.getKey()!=null && entry.getKey().indexOf(entityName) != -1) {
cache.invalidate(entry.getKey());
}
}
log.debug("清缓存:"+entityName);
}
return result;
}
@Around("pointCutForMapper()")
public Object afterMethod3(ProceedingJoinPoint pjp) throws Throwable {
Object result;
try {
tl.remove();
tl.set(pjp);
result = null;
boolean is_page=false;
String classname = pjp.getSignature().getDeclaringTypeName();
String entityName= classname.substring(classname.lastIndexOf(".")+1).replace("Mapper", "");
Page<Object> page = SqlUtil.getLocalPage();
if(page!=null ) is_page=true;
System.out.println("是否分页了:"+is_page);
String key = ParamUtil.toString(pjp.getArgs()[0]);
key=entityName+":"+key;
System.out.println(key);
// 业务方法执行
if(!is_page) {//没有分页的,从缓存中取
result = cache.get(key);
}else {
result = pjp.proceed();//分页的不放缓存
}
} catch (Exception e) {
throw e;
}finally {
tl.remove();
}
return result;
}
}
over,不足之处,请指正,谢谢