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方法。
SpringAOP与Redis搭建缓存的更多相关文章
- Window平台搭建Redis分布式缓存集群 (一)server搭建及性能測试
百度定义:Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对很多其它.包含string(字符串).list(链表).set(集合).zset(sort ...
- ASP.NET Core与Redis搭建一个简易分布式缓存
本文主要介绍了缓存的概念,以及如何在服务器内存中存储内容.今天的目标是利用IDistributedCache来做一些分布式缓存,这样我们就可以横向扩展我们的web应用程序. 在本教程中,我将使用Re ...
- 从零搭建Spring Boot脚手架(6):整合Redis作为缓存
1. 前言 上一文我们整合了Mybatis Plus,今天我们会把缓存也集成进来.缓存是一个系统应用必备的一种功能,除了在减轻数据库的压力之外.还在存储一些短时效的数据场景中发挥着重大作用,比如存储用 ...
- 基于redis分布式缓存实现
Redis的复制功能是完全建立在之前我们讨论过的基 于内存快照的持久化策略基础上的,也就是说无论你的持久化策略选择的是什么,只要用到了Redis的复制功能,就一定会有内存快照发生,那么首先要注意你 的 ...
- 知乎技术分享:从单机到2000万QPS并发的Redis高性能缓存实践之路
本文来自知乎官方技术团队的“知乎技术专栏”,感谢原作者陈鹏的无私分享. 1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动 ...
- Spring Boot + Mybatis + Redis二级缓存开发指南
Spring Boot + Mybatis + Redis二级缓存开发指南 背景 Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一.Mybat ...
- 从单机到2000万 QPS 并发的 Redis 高性能缓存实践之路
1.引言 知乎存储平台团队基于开源Redis 组件打造的知乎 Redis 平台,经过不断的研发迭代,目前已经形成了一整套完整自动化运维服务体系,提供很多强大的功能.本文作者陈鹏是该系统的负责人,本次文 ...
- Redis搭建Windows平台
安装程序下载 从官网下载安装程序. https://redis.io/download https://github.com/MicrosoftArchive/redis/releases 新地址:h ...
- redis——搭建
https://blog.csdn.net/sinat_29699167/article/details/79699200 Django使用Redis进行缓存详细最全流程 https://blog.c ...
随机推荐
- JDBC MySQL 多表关联查询查询
public static void main(String[] args) throws Exception{ Class.forName("com.mysql.jdbc.Driver&q ...
- js数组去重几种思路
在一些后台语言中都内置了一些方法来处理数组或集合中重复的数据.但是js中并没有类似的方法,网上已经有一些方法,但是不够详细.部分代码来源于网络.个人总计如下:大致有4种思路 1)使用两次循环比较原始的 ...
- Linux LVM逻辑卷配置过程详解
许多Linux使用者安装操作系统时都会遇到这样的困境:如何精确评估和分配各个硬盘分区的容量,如果当初评估不准确,一旦系统分区不够用时可能不得不备份.删除相关数据,甚至被迫重新规划分区并重装操作系统,以 ...
- 理解Storm并发
作者:Jack47 PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 注:本文主要内容翻译自understanding-the-parall ...
- 探索c#之Async、Await剖析
阅读目录: 基本介绍 基本原理剖析 内部实现剖析 重点注意的地方 总结 基本介绍 Async.Await是net4.x新增的异步编程方式,其目的是为了简化异步程序编写,和之前APM方式简单对比如下. ...
- Go语言实战 - revel框架教程之CSRF(跨站请求伪造)保护
CSRF是什么?请看这篇博文“浅谈CSRF攻击方式”,说的非常清楚. 现在做网站敢不防CSRF的我猜只有两种情况,一是没什么人访问,二是局域网应用.山坡网之前属于第一种情况,哈哈,所以至今没什么问题. ...
- python实现一个控制台下的进度条
今天写练习爬虫感觉很需要个进度条,就随手用函数实现了一个,到了晚上突然感觉到这个东西应该单独写出来以后肯定用用得着. 代码也很简单,我就不细讲了,直接上代码了. 测试代码: instance.py i ...
- Java基础之IO流
很长时间都没有更新了,最近在补充JavaSE的一些细节部分 关于IO流的一些总结 首先要介绍的是File类,File类用于对文件和目录的一些操作 1.创建文件CreateNewFile() 2.对文件 ...
- Docker私有仓库搭建
# 环境 系统 Linux 3.10.0-123.9.3.el7.x86_64 CentOS 7.0.1406 (Core) Docker 1.12.0, build 8eab29e 1.获取镜像 私 ...
- 前端开发面试题收集(html部分)
1.问:<keygen>是正确的HTML5标签吗? 答:是. <keygen> 标签规定用于表单的密钥对生成器字段.当提交表单时,私钥存储在本地,公钥发送到服务器. 2.问:& ...