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=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

    <!-- 加载配置数据 -->
    <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:

@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方法。

分类: 缓存技术

时间: 2024-10-12 08:21:29

SpringAOP与Redis搭建缓存的相关文章

Window平台搭建Redis分布式缓存集群 (一)服务器搭建及性能测试

百度定义:Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型),为了保证效率,数据都是缓存在内存中. 区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步. 以前公司一直在使用ehcache缓存,但是随着业务增多和用户访问量的增多

ehcache memcache redis 三大缓存男高音_转

ehcache memcache redis 三大缓存男高音 2013-01-16 15:43 10500人阅读 评论(2) 收藏 举报 最近项目组有用到这三个缓存,去各自的官方看了下,觉得还真的各有千秋!今天特意归纳下各个缓存的优缺点,仅供参考!  Ehcache 在java项目广泛的使用.它是一个开源的.设计于提高在数据从RDBMS中取出来的高花费.高延迟采取的一种缓存方案.正因为Ehcache具有健壮性(基于java开发).被认证(具有apache 2.0  license).充满特色(稍

redis搭建及配置

redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hash(哈希类型).这些数据类型都支持push/pop.add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的.在此基础上,redis支持各种不同方式的排序.与memcached一样,为了保证效率,数据都是缓存在内存中.区别的是redis会周期性的把更新的数

024 引入Redis作为缓存

一 .概述 在本节之中,我们引入Redis作为缓存的实现. 二 .环境的搭建 首先我们引入redis的场景启动器. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 然后我们查看自动配置类: @Configuration @Condi

知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路

本文来自知乎官方技术团队的"知乎技术专栏",感谢原作者陈鹏的无私分享. 1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动化运维服务体系,提供很多强大的功能.本文作者陈鹏是该系统的负责人,本次文章深入介绍了该系统的方方面面,值得互联网后端程序员仔细研究. (本文同步发布于:http://www.52im.net/thread-1968-1-1.html) 2.关于作者 陈鹏:现任知乎存储平台组 Redis 平

springboot2.0+redis实现消息队列+redis做缓存+mysql

本博客仅供参考,本人实现没有问题. 1.环境 先安装redis.mysql 2.springboot2.0的项目搭建(请自行完成),本人是maven项目,因此只需配置,获取相应的jar包,配置贴出. <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifact

学习猿地 python教程 django教程9 Django使用redis实现缓存

# Django使用redis实现缓存 ### 环境搭建安装 * 1,安装redis服务 ``` sudo apt-get install redis ``` * 2,安装django组件 ``` sudo pip3 install django-redis ``` ### Django项目配置 ```py settings.py CACHES = { "default": { # 引擎 "BACKEND": "django_redis.cache.Red

Redis作为缓存:实战自我总结(转载)

转载:[http://www.tuicool.com/articles/zayY7v] redis缓存服务器笔记 redis是一个高性能的key-value存储系统,能够作为缓存框架和队列.但是由于他是一个内存系统,这些数据还是要存储到数据库中的. 作为缓存框架: create/updae/delete---同时存到redis和数据库 query--先从redis查,没有记录才从数据库查,并把从数据库查的结果也放一份到redis 作为缓存队列: 2.把对象Object存储到redis中,怎么存?

redis spring缓存配置

使用redis做缓存的思路是在spring的项目中配置拦截器,在service层做切面,在findXXX或者getXXX等方法上进行拦截判断是否缓存即可. 1.环境:spring 3.1.2 + spring data redis 1.0.0+ jedis 2.1.0 2.spring配置文件配置: <!-- jedis 配置 --> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConf