SpringAOP与Redis搭建缓存
《SpringAOP与Redis搭建缓存》要点: 近期项目查询数据库太慢,持久层也没有开启二级缓存,现希望采用Redis作为缓存.为了不改写原来代码,在此采用AOP+Redis实现. 目前由于项目需要,只需要做查询部分: 数据查询时每次都需要从数据库查询数据,数据库压力很大,查询速度慢,因此设置缓存层,查询数据时先从redis中查询,如果查询不到,则到数据库中查询,然后将数据库中查询的数据放到redis中一份,下次查询时就能直接从redis中查到,不需要查询数据库了. redis作为缓存的优势: 1.内存级别缓存,查询速度毋庸置疑. 2.高性能的K-V存储系统,支持String,Hash,List,Set,Sorted Set等数据类型,能够应用在很多场景中. 3.redis3.0版本以上支持集群部署. 4.redis支持数据的持久化,AOF,RDB方式. 实体类与表: 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集成参数: 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 <!-- 加载配置数据 --> <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"> <!-- 最小空闲连接数 --> <property name="minIdle" value="${redis.minIdle}"/> <!-- 最大空闲连接数 --> <property name="maxIdle" value="${redis.maxIdle}"/> <!-- 最大连接数 --> <property name="maxTotal" value="${redis.maxTotal}"/> <!-- 获取连接时的最大等待毫秒数,小于零:阻塞不确定的时间,默认-1 --> <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/> <!-- 在获取连接的时候检查有效性,默认false --> <property name="testOnBorrow" value="${redis.testOnBorrow}"/> <!-- 每次释放连接的最大数目 --> <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/> <!-- 释放连接的扫描间隔(毫秒) --> <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/> <!-- 连接最小空闲时间 --> <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/> <!-- 连接空闲多久后释放,当空闲时间>该值 且 空闲连接>最大空闲连接数 时直接释放 --> <property name="softMinEvictableIdleTimeMillis" value="${redis.softMinEvictableIdleTimeMillis}"/> <!-- 在空闲时检查有效性,默认false --> <property name="testWhileIdle" value="${redis.testWhileIdle}"/> <!-- 连接耗尽时是否阻塞,false报异常,ture阻塞直到超时,默认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> <!-- 开启Aspect切面支持 --> <aop:aspectj-autoproxy/> </beans> 测试,所以各层级没有写接口. DAO层查询数据,封装对象: public class TestDao { //查询 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=?"); //执行 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层调用DAO: @Servicepublic class TestService { @Autowired private TestDao testDao; public Object get(Integer applId) throws Exception{ RiskNote riskNote = testDao.getByApplId(applId); return riskNote; } } 测试: 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); } } 此时测试代码输出的是查询到的RiskNote对象,可以重写toString办法查看 结果如下:最后输出的对象 在虚拟机Linux系统上搭建Redis,具体教程请自行百度 redis支持多种数据结构,查询的对象可以直接使用hash结构存入redis. 因为项目中各个办法查询的数据不一致,比如有简单对象,有List集合,有Map集合,List中套Map套对象等复杂结构,为了实现统一性和通用性,redis中也刚好提供了set(byte[],byte[])办法,所以可以将对象序列化后存入redis,取出后反序列化为对象. 序列化与反序列化工具类: /** * * @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 { //反序列化为对象 bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return null; } } 切面分析: 切面:查询前先查询redis,如果查询不到穿透到数据库,从数据库查询到数据后,保存到redis,然后下次查询可直接命中缓存 目标办法是查询数据库,查询之前需要查询redis,这是前置 假设从redis中没有查到,则查询数据库,执行完目标办法后,需要将查询的数据放到redis以便下次查询时不需要再到数据库中查,这是后置 所以,可以将切面中的通知定为环绕通知 切面类编写如下: /** * @Description: 切面:查询前先查询redis,然后下次查询可直接命中缓存 */@Component@Aspectpublic class RedisAspect { @Autowired @Qualifier("redisCache") private RedisCache redisCache; //设置切点:使用xml,在xml中配置 @Pointcut("execution(* com.club.common.redis.service.TestService.get(java.lang.Integer)) and args(applId)") //测试用,这里还额外指定了办法名称,办法参数类型,办法形参等,比较完整的切点表达式 public void myPointCut(){ } @Around("myPointCut()") public Object around(ProceedingJoinPoint joinPoint){ //前置:到redis中查询缓存 System.out.println("调用从redis中查询的办法..."); //先获取目标办法参数 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中查询到的对象 Object objectFromRedis = redisCache.getDataFromRedis(redisKey); //如果查询到了 if(null != objectFromRedis){ System.out.println("从redis中查询到了数据...不需要查询数据库"); return objectFromRedis; } System.out.println("没有从redis中查到数据..."); //没有查到,那么查询数据库 Object object = null; try { object = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("从数据库中查询的数据..."); //后置:将数据库中查询的数据放到redis中 System.out.println("调用把数据库查询的数据存储到redis中的办法..."); redisCache.setDataToRedis(redisKey,object); //将查询到的数据返回 return object; }} 从redis中查询数据,以及将数据库查询的数据保存到redis的办法: /** * * @Description:Redis缓存 */public class RedisCache { @Resource private JedisPool jedisPool; public JedisPool getJedisPool() { return jedisPool; } public void setJedisPool(JedisPool jedisPool) { this.jedisPool = jedisPool; } //从redis缓存中查询,反序列化 public Object getDataFromRedis(String redisKey){ //查询 Jedis jedis = jedisPool.getResource(); byte[] result = jedis.get(redisKey.getBytes()); //如果查询没有为空 if(null == result){ return null; } //查询到了,反序列化 return SerializeUtil.unSerialize(result); } //将数据库中查询到的数据放入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("数据成功保存到redis..."); } } } 测试1:此时redis中没有查询对象的数据 结果是:先到redis中查询,没有查到数据,然后代理执行从数据库中查询,然后把数据存入到redis中一份,那么下次查询就可以直接从redis中查询了 测试2:此时redis中已经有上一次从数据库中查询的数据了 这是在项目改造前写的一个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能够保证是一定不一致的. 如下: //先获取目标办法参数 String applId = null; Object[] args = joinPoint.getArgs(); if (args != null && args.length > 0) { applId = String.valueOf(args[0]); } //获取目标办法所在类 String target = joinPoint.getTarget().toString(); String className = target.split("@")[0]; //获取目标办法的办法名称 String methodName = joinPoint.getSignature().getName(); //redis中key格式: applId:办法名称 String redisKey = applId + ":" + className + "." + methodName; 所以上面的是一种通用的处理,具体到项目中还要看具体情况. 以前没有自己写过AOP代码,这次使用突然发现AOP确实强大,在整个过程中除了配置文件我没有改任何以前的源代码,功能全部是切入进去的. 这个Demo也基本上实现了需求,只需要设置切点,能够将缓存应用到各种查询办法中,或设置切点为service.impl包,直接作用于所有service办法. 编程之家PHP培训学院每天发布《SpringAOP与Redis搭建缓存》等实战技能,PHP、MYSQL、LINUX、APP、JS,CSS全面培养人才。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- scala – 如何将新列添加到DataFrame,并在缺少时给出它们的
- .NET WebService 调试,允许通过IE输入参数的设置,允许Per
- scala:tracing暗示选择和其他代码魔法
- angularjs – Karma – Jasmine:测试失败时显示行号
- 上一篇,在Websphere上使用Axis做的客户端调用Webservice要进
- angularjs – 使用量角器测试websockets服务器
- scala – IntelliJ无法解析build.sbt中的符号
- Memcached source code analysis -- Analysis of change of
- unix – 在Solaris上的某个端口上侦听什么进程?
- Axis2 WebService之会话管理(Session)