热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringAOP与Redis搭建缓存

SpringAOP与Redis搭建缓存近期项目查询数据库太慢,持久层也没有开启二级缓存,现希望采用Redis作为缓存。为了不改写原来代码,在此采用AOP+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=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

#redisConnectionFactory settings
redis.host=192.168.200.128
redis.port=6379
技术分享

集成配置文件:applicationContext_redis.xml

技术分享
    
    
        
        
        
            
                classpath*:/redis.properties
            
        
    

    
    

    
    
          
         
         
        
            
        
            
        
           
        
        
        
        
        
        
        
        
        
        
        
        
           
        
    
    
    
        
        
        
    
    
    
        
    
     
    
    
    
    
    

  
技术分享

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

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:

技术分享
@Service
public 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,如果查询不到穿透到数据库,从数据库查询到数据后,保存到redis,然后下次查询可直接命中缓存
 */
@Component
@Aspect
public 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中已经有上一次从数据库中查询的数据了

技术分享

在项目中测试后:效果还是非常明显的,有一个超级复杂的查询,格式化之后的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能够保证是一定不一致的。

如下:

技术分享
    //先获取目标方法参数
        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方法。

 
分类: 缓存技术

SpringAOP与Redis搭建缓存


推荐阅读
  • 本文将从基础概念入手,详细探讨SpringMVC框架中DispatcherServlet如何通过HandlerMapping进行请求分发,以及其背后的源码实现细节。 ... [详细]
  • Spring Boot使用AJAX从数据库读取数据异步刷新前端表格
      近期项目需要是实现一个通过筛选选取所需数据刷新表格的功能,因为表格只占页面的一小部分,不希望整个也页面都随之刷新,所以首先想到了使用AJAX来实现。  以下介绍解决方法(请忽视 ... [详细]
  • 汇总了2023年7月7日最新的网络安全新闻和技术更新,包括最新的漏洞披露、工具发布及安全事件。 ... [详细]
  • 本文详细介绍如何在SSM(Spring + Spring MVC + MyBatis)框架中实现分页功能。包括分页的基本概念、数据准备、前端分页栏的设计与实现、后端分页逻辑的编写以及最终的测试步骤。 ... [详细]
  • 深入解析Unity3D游戏开发中的音频播放技术
    在游戏开发中,音频播放是提升玩家沉浸感的关键因素之一。本文将探讨如何在Unity3D中高效地管理和播放不同类型的游戏音频,包括背景音乐和效果音效,并介绍实现这些功能的具体步骤。 ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • 电商高并发解决方案详解
    本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ... [详细]
  • LeetCode 102 - 二叉树层次遍历详解
    本文详细解析了LeetCode第102题——二叉树的层次遍历问题,提供了C++语言的实现代码,并对算法的核心思想和具体步骤进行了深入讲解。 ... [详细]
  • JavaScript 中引号的多层嵌套使用技巧
    本文详细介绍了在 JavaScript 编程中如何处理引号的多级嵌套问题,包括双引号、单引号以及转义字符的正确使用方法。 ... [详细]
  • 本文回顾了作者在求职阿里和腾讯实习生过程中,从最初的迷茫到最后成功获得Offer的心路历程。文中不仅分享了个人的面试经历,还提供了宝贵的面试准备建议和技巧。 ... [详细]
  • 春季职场跃迁指南:如何高效利用金三银四跳槽季
    随着每年的‘金三银四’跳槽高峰期的到来,许多职场人士都开始考虑是否应该寻找新的职业机会。本文将探讨如何制定有效的职业规划、撰写吸引人的简历以及掌握面试技巧,助您在这关键时期成功实现职场跃迁。 ... [详细]
  • Python3爬虫入门:pyspider的基本使用[python爬虫入门]
    Python学习网有大量免费的Python入门教程,欢迎大家来学习。本文主要通过爬取去哪儿网的旅游攻略来给大家介绍pyspid ... [详细]
  • 从CodeIgniter中提取图像处理组件
    本指南旨在帮助开发者在未使用CodeIgniter框架的情况下,如何独立使用其强大的图像处理功能,包括图像尺寸调整、创建缩略图、裁剪、旋转及添加水印等。 ... [详细]
  • flea,frame,db,使用,之 ... [详细]
  • PHP面试题精选及答案解析
    本文精选了新浪PHP笔试题及最新的PHP面试题,并提供了详细的答案解析,帮助求职者更好地准备PHP相关的面试。 ... [详细]
author-avatar
居生扬_977
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有