《SpringAOP與Redis搭建緩存》要點(diǎn):
本文介紹了SpringAOP與Redis搭建緩存,希望對(duì)您有用。如果有疑問(wèn),可以聯(lián)系我們。
近期項(xiàng)目查詢(xún)數(shù)據(jù)庫(kù)太慢,持久層也沒(méi)有開(kāi)啟二級(jí)緩存,現(xiàn)希望采用Redis作為緩存.為了不改寫(xiě)原來(lái)代碼,在此采用AOP+Redis實(shí)現(xiàn).
目前由于項(xiàng)目需要,只需要做查詢(xún)部分:
數(shù)據(jù)查詢(xún)時(shí)每次都需要從數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù),數(shù)據(jù)庫(kù)壓力很大,查詢(xún)速度慢,因此設(shè)置緩存層,查詢(xún)數(shù)據(jù)時(shí)先從redis中查詢(xún),如果查詢(xún)不到,則到數(shù)據(jù)庫(kù)中查詢(xún),然后將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)放到redis中一份,下次查詢(xún)時(shí)就能直接從redis中查到,不需要查詢(xún)數(shù)據(jù)庫(kù)了.
redis作為緩存的優(yōu)勢(shì):
1.內(nèi)存級(jí)別緩存,查詢(xún)速度毋庸置疑.
2.高性能的K-V存儲(chǔ)系統(tǒng),支持String,Hash,List,Set,Sorted Set等數(shù)據(jù)類(lèi)型,能夠應(yīng)用在很多場(chǎng)景中.
3.redis3.0版本以上支持集群部署.
4.redis支持?jǐn)?shù)據(jù)的持久化,AOF,RDB方式.
實(shí)體類(lèi)與表:
public class RiskNote implements Serializable { private static final long serialVersionUID = 4758331879028183605L;
private Integer ApplId; private Integer allqyorg3monNum; private Double loanF6endAmt;
private String isHighRisk1; private Date createDate; private String risk1Detail;
private Integer risk2; private String risk3; private String creditpaymonth;
......
Redis與Spring集成參數(shù):
redis.properties
#redis settings
redis.minIdle=5redis.maxIdle=10redis.maxTotal=50redis.maxWaitMillis=1500redis.testOnBorrow=trueredis.numTestsPerEvictionRun=1024redis.timeBetweenEvictionRunsMillis=30000redis.minEvictableIdleTimeMillis=1800000redis.softMinEvictableIdleTimeMillis=10000redis.testWhileIdle=trueredis.blockWhenExhausted=false#redisConnectionFactory settings
redis.host=192.168.200.128redis.port=6379
集成配置文件:applicationContext_redis.xml
<!-- 加載配置數(shù)據(jù) -->
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath*:/redis.properties</value>
</list>
</property>
</bean>
<!-- 注解掃描 -->
<context:component-scan base-package="com.club.common.redis"/>
<!-- jedis連接池配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最小空閑連接數(shù) -->
<property name="minIdle" value="${redis.minIdle}"/>
<!-- 最大空閑連接數(shù) -->
<property name="maxIdle" value="${redis.maxIdle}"/>
<!-- 最大連接數(shù) -->
<property name="maxTotal" value="${redis.maxTotal}"/>
<!-- 獲取連接時(shí)的最大等待毫秒數(shù),小于零:阻塞不確定的時(shí)間,默認(rèn)-1 -->
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<!-- 在獲取連接的時(shí)候檢查有效性, 默認(rèn)false -->
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
<!-- 每次釋放連接的最大數(shù)目 -->
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
<!-- 釋放連接的掃描間隔(毫秒) -->
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<!-- 連接最小空閑時(shí)間 -->
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
<!-- 連接空閑多久后釋放, 當(dāng)空閑時(shí)間>該值 且 空閑連接>最大空閑連接數(shù) 時(shí)直接釋放 -->
<property name="softMinEvictableIdleTimeMillis" value="${redis.softMinEvictableIdleTimeMillis}"/>
<!-- 在空閑時(shí)檢查有效性, 默認(rèn)false -->
<property name="testWhileIdle" value="${redis.testWhileIdle}"/>
<!-- 連接耗盡時(shí)是否阻塞, false報(bào)異常,ture阻塞直到超時(shí), 默認(rèn)true -->
<property name="blockWhenExhausted" value="${redis.blockWhenExhausted}"/>
</bean>
<!-- redis連接池 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool" destroy-method="close">
<constructor-arg name="poolConfig" ref="poolConfig"/>
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.port}"/>
</bean>
<bean id="redisCache" class="com.club.common.redis.RedisCache">
<property name="jedisPool" ref="jedisPool"></property>
</bean>
<bean id="testDao" class="com.club.common.redis.TestDao"></bean>
<bean id="testService" class="com.club.common.redis.service.TestService"></bean>
<!-- 開(kāi)啟Aspect切面支持 -->
<aop:aspectj-autoproxy/>
</beans>
測(cè)試,所以各層級(jí)沒(méi)有寫(xiě)接口.
DAO層查詢(xún)數(shù)據(jù),封裝對(duì)象:
public class TestDao {
//查詢(xún)
public RiskNote getByApplId(Integer applId) throws Exception{
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = DriverManager.getConnection("jdbc:oracle:thin:@192.168.11.215:1521:MFTEST01", "datacenter", "datacenter");
PreparedStatement statement = connection.prepareStatement("select * from TEMP_RISK_NOTE where appl_id=?");
//執(zhí)行
statement.setInt(1, applId);
ResultSet resultSet = statement.executeQuery();
RiskNote riskNote = new RiskNote(); //解析
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"));
}
return riskNote;
}
}
Service層調(diào)用DAO:
@Servicepublic class TestService {
@Autowired private TestDao testDao;
public Object get(Integer applId) throws Exception{
RiskNote riskNote = testDao.getByApplId(applId);
return riskNote;
}
}
測(cè)試:
public class TestQueryRiskNote {
@Test public void testQuery() throws Exception{
ApplicationContext ac = new FileSystemXmlApplicationContext("src/main/resources/spring/applicationContext_redis.xml");
TestService testService = (TestService) ac.getBean("testService");
RiskNote riskNote = (RiskNote)testService.get(91193);
System.out.println(riskNote);
}
}
此時(shí)測(cè)試代碼輸出的是查詢(xún)到的RiskNote對(duì)象,可以重寫(xiě)toString辦法查看
結(jié)果如下:最后輸出的對(duì)象
在虛擬機(jī)Linux系統(tǒng)上搭建Redis,具體教程請(qǐng)自行百度
redis支持多種數(shù)據(jù)結(jié)構(gòu),查詢(xún)的對(duì)象可以直接使用hash結(jié)構(gòu)存入redis.
因?yàn)轫?xiàng)目中各個(gè)辦法查詢(xún)的數(shù)據(jù)不一致,比如有簡(jiǎn)單對(duì)象,有List集合,有Map集合,List中套Map套對(duì)象等復(fù)雜結(jié)構(gòu),為了實(shí)現(xiàn)統(tǒng)一性和通用性,redis中也剛好提供了set(byte[],byte[])辦法,所以可以將對(duì)象序列化后存入redis,取出后反序列化為對(duì)象.
序列化與反序列化工具類(lèi):
/**
*
* @Description: 序列化反序列化工具 */public class SerializeUtil { /**
*
* 序列化 */
public static byte[] serialize(Object obj){
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try { //序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(obj); byte[] byteArray = baos.toByteArray(); return byteArray;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
*
* 反序列化
* @param bytes
* @return
*/
public static Object unSerialize(byte[] bytes){
ByteArrayInputStream bais = null;
try { //反序列化為對(duì)象
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
} return null;
}
}
切面分析:
切面:查詢(xún)前先查詢(xún)r(jià)edis,如果查詢(xún)不到穿透到數(shù)據(jù)庫(kù),從數(shù)據(jù)庫(kù)查詢(xún)到數(shù)據(jù)后,保存到redis,然后下次查詢(xún)可直接命中緩存
目標(biāo)辦法是查詢(xún)數(shù)據(jù)庫(kù),查詢(xún)之前需要查詢(xún)r(jià)edis,這是前置
假設(shè)從redis中沒(méi)有查到,則查詢(xún)數(shù)據(jù)庫(kù),執(zhí)行完目標(biāo)辦法后,需要將查詢(xún)的數(shù)據(jù)放到redis以便下次查詢(xún)時(shí)不需要再到數(shù)據(jù)庫(kù)中查,這是后置
所以,可以將切面中的通知定為環(huán)繞通知
切面類(lèi)編寫(xiě)如下:
/** * @Description: 切面:查詢(xún)前先查詢(xún)r(jià)edis,如果查詢(xún)不到穿透到數(shù)據(jù)庫(kù),從數(shù)據(jù)庫(kù)查詢(xún)到數(shù)據(jù)后,保存到redis,然后下次查詢(xún)可直接命中緩存 */@Component@Aspectpublic class RedisAspect { @Autowired @Qualifier("redisCache") private RedisCache redisCache; //設(shè)置切點(diǎn):使用xml,在xml中配置 @Pointcut("execution(* com.club.common.redis.service.TestService.get(java.lang.Integer)) and args(applId)") //測(cè)試用,這里還額外指定了辦法名稱(chēng),辦法參數(shù)類(lèi)型,辦法形參等,比較完整的切點(diǎn)表達(dá)式
public void myPointCut(){ } @Around("myPointCut()") public Object around(ProceedingJoinPoint joinPoint){ //前置:到redis中查詢(xún)緩存 System.out.println("調(diào)用從redis中查詢(xún)的辦法..."); //先獲取目標(biāo)辦法參數(shù) String applId = null; Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) { applId = String.valueOf(args[0]); } //redis中key格式: applId String redisKey = applId; //獲取從redis中查詢(xún)到的對(duì)象 Object objectFromRedis = redisCache.getDataFromRedis(redisKey); //如果查詢(xún)到了 if(null != objectFromRedis){ System.out.println("從redis中查詢(xún)到了數(shù)據(jù)...不需要查詢(xún)數(shù)據(jù)庫(kù)"); return objectFromRedis; } System.out.println("沒(méi)有從redis中查到數(shù)據(jù)..."); //沒(méi)有查到,那么查詢(xún)數(shù)據(jù)庫(kù) Object object = null; try { object = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("從數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)..."); //后置:將數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)放到redis中 System.out.println("調(diào)用把數(shù)據(jù)庫(kù)查詢(xún)的數(shù)據(jù)存儲(chǔ)到redis中的辦法..."); redisCache.setDataToRedis(redisKey, object); //將查詢(xún)到的數(shù)據(jù)返回 return object; }}
從redis中查詢(xún)數(shù)據(jù),以及將數(shù)據(jù)庫(kù)查詢(xún)的數(shù)據(jù)保存到redis的辦法:
/**
*
* @Description:Redis緩存 */public class RedisCache {
@Resource private JedisPool jedisPool; public JedisPool getJedisPool() { return jedisPool;
} public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool;
} //從redis緩存中查詢(xún),反序列化
public Object getDataFromRedis(String redisKey){ //查詢(xún)
Jedis jedis = jedisPool.getResource(); byte[] result = jedis.get(redisKey.getBytes());
//如果查詢(xún)沒(méi)有為空
if(null == result){ return null;
}
//查詢(xún)到了,反序列化
return SerializeUtil.unSerialize(result);
}
//將數(shù)據(jù)庫(kù)中查詢(xún)到的數(shù)據(jù)放入redis
public void setDataToRedis(String redisKey, Object obj){
//序列化
byte[] bytes = SerializeUtil.serialize(obj);
//存入redis
Jedis jedis = jedisPool.getResource();
String success = jedis.set(redisKey.getBytes(), bytes);
if("OK".equals(success)){
System.out.println("數(shù)據(jù)成功保存到redis...");
}
}
}
測(cè)試1:此時(shí)redis中沒(méi)有查詢(xún)對(duì)象的數(shù)據(jù)
結(jié)果是:先到redis中查詢(xún),沒(méi)有查到數(shù)據(jù),然后代理執(zhí)行從數(shù)據(jù)庫(kù)中查詢(xún),然后把數(shù)據(jù)存入到redis中一份,那么下次查詢(xún)就可以直接從redis中查詢(xún)了
測(cè)試2:此時(shí)redis中已經(jīng)有上一次從數(shù)據(jù)庫(kù)中查詢(xún)的數(shù)據(jù)了
這是在項(xiàng)目改造前寫(xiě)的一個(gè)Demo,實(shí)際項(xiàng)目復(fù)雜的多,切點(diǎn)表達(dá)式是有兩三個(gè)一起組成的,也著重研究了一下切點(diǎn)表達(dá)式的寫(xiě)法
如:
@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(..)))")
這是多個(gè)切點(diǎn)組合形成使用||連接.
我在實(shí)際項(xiàng)目中使用的key也比applId復(fù)雜,因?yàn)榭赡苤皇褂胊pplId的話導(dǎo)致key沖突,
所以項(xiàng)目中使用的key是applId:辦法全限定名,,這樣的話key能夠保證是一定不一致的.
如下:
//先獲取目標(biāo)辦法參數(shù)
String applId = null;
Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) {
applId = String.valueOf(args[0]);
}
//獲取目標(biāo)辦法所在類(lèi)
String target = joinPoint.getTarget().toString();
String className = target.split("@")[0];
//獲取目標(biāo)辦法的辦法名稱(chēng)
String methodName = joinPoint.getSignature().getName();
//redis中key格式: applId:辦法名稱(chēng)
String redisKey = applId + ":" + className + "." + methodName;
所以上面的是一種通用的處理,具體到項(xiàng)目中還要看具體情況.
以前沒(méi)有自己寫(xiě)過(guò)AOP代碼,這次使用突然發(fā)現(xiàn)AOP確實(shí)強(qiáng)大,在整個(gè)過(guò)程中除了配置文件我沒(méi)有改任何以前的源代碼,功能全部是切入進(jìn)去的.
這個(gè)Demo也基本上實(shí)現(xiàn)了需求,只需要設(shè)置切點(diǎn),能夠?qū)⒕彺鎽?yīng)用到各種查詢(xún)辦法中,或設(shè)置切點(diǎn)為service.impl包,直接作用于所有service辦法.
維易PHP培訓(xùn)學(xué)院每天發(fā)布《SpringAOP與Redis搭建緩存》等實(shí)戰(zhàn)技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培養(yǎng)人才。
轉(zhuǎn)載請(qǐng)注明本頁(yè)網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/10674.html