配色: 字号:
SpringAOP与Redis搭建缓存
2016-09-13 | 阅:  转:  |  分享 
  
SpringAOP与Redis搭建缓存

近期项目查询数据库太慢,持久层也没有开启二级缓存,现希望采用Redis作为缓存。为了不改写原来代码,在此采用AOP+Redis实现。

目前由于项目需要,只需要做查询部分:

数据查询时每次都需要从数据库查询数据,数据库压力很大,查询速度慢,因此设置缓存层,查询数据时先从redis中查询,如果查询不到,则到数据库中查询,然后将数据库中查询的数据放到redis中一份,下次查询时就能直接从redis中查到,不需要查询数据库了。

redis作为缓存的优势:

1.内存级别缓存,查询速度毋庸置疑。

2.高性能的K-V存储系统,支持String,Hash,List,Set,SortedSet等数据类型,能够应用在很多场景中。

3.redis3.0版本以上支持集群部署。

4.redis支持数据的持久化,AOF,RDB方式。

?

实体类与表:

publicclassRiskNoteimplementsSerializable{



privatestaticfinallongserialVersionUID=4758331879028183605L;



privateIntegerApplId;

privateIntegerallqyorg3monNum;

privateDoubleloanF6endAmt;



privateStringisHighRisk1;

privateDatecreateDate;

privateStringrisk1Detail;



privateIntegerrisk2;

privateStringrisk3;

privateStringcreditpaymonth;



......

?



?

Redis与Spring集成参数:

redis.properties

#redissettings

redis.minIdle=5

redis.maxIdle=10

redis.maxTotal=50

redis.maxWaitMillis=1500

redis.testOnBorrow=true

redis.numTestsPerEvictionRun=1024

redis.timeBetweenEvictionRunsMillis=30000

redis.minEvictableIdleTimeMillis=1800000

redis.softMinEvictableIdleTimeMillis=10000

redis.testWhileIdle=true

redis.blockWhenExhausted=false



#redisConnectionFactorysettings

redis.host=192.168.200.128

redis.port=6379

?

?

集成配置文件:applicationContext_redis.xml

?











classpath:/redis.properties







































































































?

测试,所以各层级没有写接口。

DAO层查询数据,封装对象:

publicclassTestDao{



//查询

publicRiskNotegetByApplId(IntegerapplId)throwsException{



Class.forName("oracle.jdbc.driver.OracleDriver");

Connectionconnection=DriverManager.getConnection("jdbc:oracle:thin:@192.168.11.215:1521:MFTEST01","datacenter","datacenter");

PreparedStatementstatement=connection.prepareStatement("selectfromTEMP_RISK_NOTEwhereappl_id=?");



//执行

statement.setInt(1,applId);

ResultSetresultSet=statement.executeQuery(www.visa158.com);



RiskNoteriskNote=newRiskNote();

//解析

while(resultSet.next()){

riskNote.setApplId(resultSet.getInt("APPL_ID"));

riskNote.setAllqyorg3monNum(resultSet.getInt("ALLQYORG3MON_NUM"));

riskNote.setLoanF6endAmt(resultSet.getDouble("LOAN_F6END_AMT"));

riskNote.setIsHighRisk1(resultSet.getString("IS_HIGH_RISK_1"));

riskNote.setCreateDate(resultSet.getDate("CREATE_DATE"));

riskNote.setRisk1Detail(resultSet.getString("RISK1_DETAIL"));

riskNote.setRisk2(resultSet.getInt("RISK2"));

riskNote.setRisk3(resultSet.getString("RISK3"));

riskNote.setCreditpaymonth(resultSet.getString("CREDITPAYMONTH"));



}



returnriskNote;

}

}

?

Service层调用DAO:

@Service

publicclassTestService{



@Autowired

privateTestDaotestDao;



publicObjectget(IntegerapplId)throwsException{



RiskNoteriskNote=testDao.getByApplId(applId);



returnriskNote;



}

}

?

测试:

publicclassTestQueryRiskNote{





@Test

publicvoidtestQuery(www.hunanwang.net)throwsException{

ApplicationContextac=newFileSystemXmlApplicationContext("src/main/resources/spring/applicationContext_redis.xml");

TestServicetestService=(TestService)ac.getBean("testService");

RiskNoteriskNote=(RiskNote)testService.get(91193);

System.out.println(riskNote);

}

}

此时测试代码输出的是查询到的RiskNote对象,可以重写toString方法查看

结果如下:最后输出的对象



?

在虚拟机Linux系统上搭建Redis,具体教程请自行百度

redis支持多种数据结构,查询的对象可以直接使用hash结构存入redis。

因为项目中各个方法查询的数据不一致,比如有简单对象,有List集合,有Map集合,List中套Map套对象等复杂结构,为了实现统一性和通用性,redis中也刚好提供了set(byte[],byte[])方法,所以可以将对象序列化后存入redis,取出后反序列化为对象。

序列化与反序列化工具类:



/



@Description:序列化反序列化工具

/

publicclassSerializeUtil{

/



序列化

/

publicstaticbyte[]serialize(Objectobj){



ObjectOutputStreamoos=null;

ByteArrayOutputStreambaos=null;



try{

//序列化

baos=newByteArrayOutputStream();

oos=newObjectOutputStream(baos);



oos.writeObject(obj);

byte[]byteArray=baos.toByteArray();

returnbyteArray;



}catch(IOExceptione){

e.printStackTrace();

}

returnnull;

}



/



反序列化

@parambytes

@return

/

publicstaticObjectunSerialize(byte[]bytes){



ByteArrayInputStreambais=null;



try{

//反序列化为对象

bais=newByteArrayInputStream(bytes);

ObjectInputStreamois=newObjectInputStream(bais);

returnois.readObject();



}catch(Exceptione){

e.printStackTrace();

}

returnnull;

}

}



切面分析:

切面:查询前先查询redis,如果查询不到穿透到数据库,从数据库查询到数据后,保存到redis,然后下次查询可直接命中缓存

目标方法是查询数据库,查询之前需要查询redis,这是前置

假设从redis中没有查到,则查询数据库,执行完目标方法后,需要将查询的数据放到redis以便下次查询时不需要再到数据库中查,这是后置

所以,可以将切面中的通知定为环绕通知

切面类编写如下:

/

@Description:切面:查询前先查询redis,如果查询不到穿透到数据库,从数据库查询到数据后,保存到redis,然后下次查询可直接命中缓存

/

@Component

@Aspect

publicclassRedisAspect{



@Autowired

@Qualifier("redisCache")

privateRedisCacheredisCache;



//设置切点:使用xml,在xml中配置

@Pointcut("execution(com.club.common.redis.service.TestService.get(java.lang.Integer))andargs(applId)")//测试用,这里还额外指定了方法名称,方法参数类型,方法形参等,比较完整的切点表达式

publicvoidmyPointCut(){



}



@Around("myPointCut()")

publicObjectaround(ProceedingJoinPointjoinPoint){

//前置:到redis中查询缓存

System.out.println("调用从redis中查询的方法...");



//先获取目标方法参数

StringapplId=null;

Object[]args=joinPoint.getArgs();

if(args!=null&&args.length>0){

applId=String.valueOf(args[0]);

}



//redis中key格式:applId

StringredisKey=applId;



//获取从redis中查询到的对象

ObjectobjectFromRedis=redisCache.getDataFromRedis(redisKey);



//如果查询到了

if(null!=objectFromRedis){

System.out.println("从redis中查询到了数据...不需要查询数据库");

returnobjectFromRedis;

}



System.out.println("没有从redis中查到数据...");



//没有查到,那么查询数据库

Objectobject=null;

try{

object=joinPoint.proceed();

}catch(Throwablee){



e.printStackTrace();

}



System.out.println("从数据库中查询的数据...");



//后置:将数据库中查询的数据放到redis中

System.out.println("调用把数据库查询的数据存储到redis中的方法...");



redisCache.setDataToRedis(redisKey,object);



//将查询到的数据返回

returnobject;



}

}



?

从redis中查询数据,以及将数据库查询的数据保存到redis的方法:



/



/

publicclassRedisCache{



@Resource

privateJedisPooljedisPool;

publicJedisPoolgetJedisPool(){

returnjedisPool;

}

publicvoidsetJedisPool(JedisPooljedisPool){

this.jedisPool=jedisPool;

}



//从redis缓存中查询,反序列化

publicObjectgetDataFromRedis(StringredisKey){

//查询

Jedisjedis=jedisPool.getResource();

byte[]result=jedis.get(redisKey.getBytes());



//如果查询没有为空

if(null==result){

returnnull;

}



//查询到了,反序列化

returnSerializeUtil.unSerialize(result);

}



//将数据库中查询到的数据放入redis

publicvoidsetDataToRedis(StringredisKey,Objectobj){



//序列化

byte[]bytes=SerializeUtil.serialize(obj);



//存入redis

Jedisjedis=jedisPool.getResource();

Stringsuccess=jedis.set(redisKey.getBytes(),bytes);



if("OK".equals(success)){

System.out.println("数据成功保存到redis...");

}

}

}



?

测试1:此时redis中没有查询对象的数据

结果是:先到redis中查询,没有查到数据,然后代理执行从数据库中查询,然后把数据存入到redis中一份,那么下次查询就可以直接从redis中查询了



?

测试2:此时redis中已经有上一次从数据库中查询的数据了



在项目中测试后:效果还是非常明显的,有一个超级复杂的查询,格式化之后的sql是688行,每次刷新页面都需要重新查询,耗时10秒左右。

在第一次查询放到redis之后,从redis中查询能够在2秒内得到结果,速度非常快。

?

上面的是在项目改造前写的一个Demo,实际项目复杂的多,切点表达式是有两三个一起组成的,也着重研究了一下切点表达式的写法

如:

@Pointcut("(execution(com.club.risk.center.service.impl..(java.lang.String)))||(execution(com.club.risk.py.service.impl.PyServcieImpl.queryPyReportByApplId(java.lang.String)))||(execution(com.club.risk.zengxintong.service.Impl.ZXTServiceImpl.queryZxtReportByApplId(..)))")

这是多个切点组合形成使用||连接。

我在实际项目中使用的key也比applId复杂,因为可能只使用applId的话导致key冲突,

所以项目中使用的key是applId:方法全限定名,,这样的话key能够保证是一定不一致的。

如下:



StringapplId=null;

Object[]args=joinPoint.getArgs();

if(args!=null&&args.length>0){

applId=String.valueOf(args[0]);

}



//获取目标方法所在类

Stringtarget=joinPoint.getTarget().toString();

StringclassName=target.split("@")[0];



//获取目标方法的方法名称

StringmethodName=joinPoint.getSignature().getName();



//redis中key格式:applId:方法名称

StringredisKey=applId+":"+className+"."+methodName;



?

所以上面的是一种通用的处理,具体到项目中还要看具体情况。

以前没有自己写过AOP代码,这次使用突然发现AOP确实强大,在整个过程中除了配置文件我没有改任何以前的源代码,功能全部是切入进去的。

这个Demo也基本上实现了需求,只需要设置切点,能够将缓存应用到各种查询方法中,或设置切点为service.impl包,直接作用于所有service方法。



献花(0)
+1
(本文系白狐一梦首藏)