本站点停止更新,请访问:blog.coocap.com

近期项目查询数据库太慢,持久层也没有开启二级缓存,现希望采用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方法。

本站点停止更新,请访问:blog.coocap.com

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

  1. Window平台搭建Redis分布式缓存集群 (一)server搭建及性能測试

    百度定义:Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对很多其它.包含string(字符串).list(链表).set(集合).zset(sort ...

  2. ASP.NET Core与Redis搭建一个简易分布式缓存

    ​本文主要介绍了缓存的概念,以及如何在服务器内存中存储内容.今天的目标是利用IDistributedCache来做一些分布式缓存,这样我们就可以横向扩展我们的web应用程序. 在本教程中,我将使用Re ...

  3. 从零搭建Spring Boot脚手架(6):整合Redis作为缓存

    1. 前言 上一文我们整合了Mybatis Plus,今天我们会把缓存也集成进来.缓存是一个系统应用必备的一种功能,除了在减轻数据库的压力之外.还在存储一些短时效的数据场景中发挥着重大作用,比如存储用 ...

  4. 基于redis分布式缓存实现

    Redis的复制功能是完全建立在之前我们讨论过的基 于内存快照的持久化策略基础上的,也就是说无论你的持久化策略选择的是什么,只要用到了Redis的复制功能,就一定会有内存快照发生,那么首先要注意你 的 ...

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

    本文来自知乎官方技术团队的“知乎技术专栏”,感谢原作者陈鹏的无私分享. 1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动 ...

  6. Spring Boot + Mybatis + Redis二级缓存开发指南

    Spring Boot + Mybatis + Redis二级缓存开发指南 背景 Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一.Mybat ...

  7. 从单机到2000万 QPS 并发的 Redis 高性能缓存实践之路

    1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动化运维服务体系,提供很多强大的功能.本文作者陈鹏是该系统的负责人,本次文 ...

  8. Redis搭建Windows平台

    安装程序下载 从官网下载安装程序. https://redis.io/download https://github.com/MicrosoftArchive/redis/releases 新地址:h ...

  9. redis——搭建

    https://blog.csdn.net/sinat_29699167/article/details/79699200 Django使用Redis进行缓存详细最全流程 https://blog.c ...

随机推荐

  1. 简单酷炫的canvas动画

    作为一个新人怀着激动而紧张的心情写了第一篇帖子还请大家多多支持,小弟在次拜谢. 驯鹿拉圣诞老人动画效果图如下 html如下: <div style="width:400px;heigh ...

  2. iOS微信第三方登录实现

    iOS微信第三方登录实现   一.接入微信第三方登录准备工作.移动应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统.在进行微信OAuth2.0授权登录接入之前,在微信开 ...

  3. Topshelf 支持Mono 扩展Topshelf.Linux

    使用Topshelf 5步创建Windows 服务 这篇文章大家可以了解到使用Topshelf可以很好的支持Windows服务的开发,但是它和Mono不兼容,Github上有一个扩展https://g ...

  4. Debian下安装mono

    从mono的官网上查,debian的步骤写得太乱了.其实总结起来,就是这么几步: apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --rec ...

  5. C#与C++的发展历程第四 - C#6的新时代

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  6. 轻量级的日期插件--datebox

    jquery的日期插件有好几款,H5中的input也可以自带日期选择.但为什么要再写一个,有两个理由,一个是引用的文件太大,而有时候只需要很简单的功能,二个是想加一些自定义的效果不好改. 我写的这个功 ...

  7. Android开发学习之路-机器学习库(图像识别)、百度翻译

    对于机器学习也不是了解的很深入,今天无意中在GitHub看到一个star的比较多的库,就用着试一试,效果也还行.比是可能比不上TensorFlow的,但是在Android上用起来比较简单,毕竟Tens ...

  8. 【CSS进阶】CSS 颜色体系详解

    说到 CSS 颜色,相比大家都不会陌生,本文是我个人对 CSS 颜色体系的一个系统总结与学习,分享给大家. 先用一张图直观的感受一下与 CSS 颜色相关大概覆盖了哪些内容. 接下来的行文内容大概会按照 ...

  9. ITTC数据挖掘平台介绍(五) 数据导入导出向导和报告生成

    一. 前言 经过了一个多月的努力,软件系统又添加了不少新功能.这些功能包括非常实用的数据导入导出,对触摸进行优化的画布和画笔工具,以及对一些智能分析的报告生成模块等.进一步加强了平台系统级的功能. 马 ...

  10. c# 检测操作系统版本

    我们通过System.Environment.OSVersion.Version获得操作系统的版本号,然后再根据版本号进行判断操作系统是什么版本 Version 类的属性 Operating syst ...