spring @Cacheable @CachePut... 使用redis缓存详细步骤

2024-08-26 09:18

本文主要是介绍spring @Cacheable @CachePut... 使用redis缓存详细步骤,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

spring @Cacheable @CachePut... 使用redis缓存详细步骤

本文以一个spring的maven工程,整理记录使用注解缓存的问题,基本不需要自己写过多的封装的代码,很多人都实现Cache接口重新定义自己的缓存操作。其实不用也可以,Spring已经做了很多了。

预期目标

  • 查询:如果缓存中存在,直接从缓存中取,不查数据库。如果缓存中没有,从数据库查询并存入缓存,并设置超时时间。

  • 修改:删除缓存中的内容(如果存在)。

  • 删除:同步删除缓存中的内容。

实现步骤

  • pom,只列举关键部分,详细部分可以通过https://download.csdn.net/download/u013041642/10423952下载源码

    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>1.6.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.8.1</version>
    </dependency>
  • web.xml

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
    
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml, classpath:spring-cache.xml</param-value>
      </context-param>
    
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>/WEB-INF/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
    
      <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
  • spring-mvc.xml

    <context:component-scan base-package="com.susq.work.*" >
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
    </context:component-scan>
    
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <constructor-arg value="UTF-8"/>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
  • config.properties

    ## mysql
    database.url=jdbc:mysql://127.0.0.1/credit?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&rewriteBatchedStatements=true
    database.username=root
    database.password=
    database.driverClass=com.mysql.jdbc.Driver
    
    ## redis
    redis.host=127.0.0.1
    redis.port=6379
    redis.password=foobared
    redis.timeout=60000
    redis.database=0
    redis.pool.maxActive=300
    redis.pool.maxIdle=100
    redis.pool.maxWait=2000

  • spring-mybatis.xml

    <context:property-placeholder location="classpath:config.properties"/>
    
    <context:component-scan base-package="com.susq.work.*">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource_user"/>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager" />
    
    <bean id="dataSource_user" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${database.driverClass}"/>
        <property name="url" value="${database.url}"/>
        <property name="username" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
    </bean>
    
    <bean id="userSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource_user"/>
        <property name="configLocation" value="classpath:sqlMapConfig.xml"/>
        <property name="mapperLocations" value="classpath*:com/susq/work/dao/mapper/UserMapper.xml"/>
    </bean>
    
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="userSqlSessionFactory"/>
        <property name="basePackage" value="com.susq.work.dao"/>
    </bean>
  • spring-cache.xml

    <context:property-placeholder location="classpath:config.properties" />
    
    <cache:annotation-driven cache-manager="cacheManager"/>
    
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="${redis.pool.maxActive}" />
        <property name="maxIdle" value="${redis.pool.maxIdle}" />
        <property name="maxWaitMillis" value="${redis.pool.maxWait}" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
    </bean>
    
    <bean id="connectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="hostName" value="${redis.host}"/>
        <property name="port" value="${redis.port}"/>
        <property name="password" value="${redis.password}" />
        <property name="timeout" value="${redis.timeout}"/>
        <property name="database" value="${redis.database}"/>
        <property name="poolConfig" ref="jedisPoolConfig"/>
    </bean>
    
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
        <property name="connectionFactory"   ref="connectionFactory" />
        <property name="keySerializer" ref="keySerializer"/>  <!-- key使用String序列化方式 -->
        <property name="hashKeySerializer" ref="keySerializer"/> <!-- value使用String序列化 -->
        <property name="valueSerializer" ref="jackson2JsonRedisSerializer"/> <!-- value使用json序列化 -->
        <!--<property name="hashValueSerializer" ref="jackson2JsonRedisSerializer"/>-->
        <property name="hashValueSerializer" ref="jdkSerializer"/>
    </bean>
    
    <bean id="keySerializer"
          class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    <bean id="jackson2JsonRedisSerializer"
          class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    <bean id="jdkSerializer"
          class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
    <!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <!-- 这里可以配置多个cache; 不同名称的缓存可以设置前缀用来区分缓存的类型,设置不同的超时时间或是redisTemplate,StringRedisTemplate -->
                <bean class="org.springframework.data.redis.cache.RedisCache">
                    <constructor-arg name="redisOperations" ref="redisTemplate"/>
                    <constructor-arg name="name" value="common"/>
                    <constructor-arg name="prefix" value=""/>
                    <constructor-arg name="expiration" value="10" />
                </bean>
                <bean class="org.springframework.data.redis.cache.RedisCache">
                    <constructor-arg name="redisOperations" ref="redisTemplate"/>
                    <constructor-arg name="name" value="default"/>
                    <constructor-arg name="prefix" value="DEF:"/> <!--这是通用的前缀-->
                    <constructor-arg name="expiration" value="60"/> <!--这里的单位是秒-->
                </bean>
            </set>
        </property>
    </bean>
  • service实现类, 在对应的方法上加上注解

    @Cacheable 调用方法时, 返回结果作为value放入缓存

    @CachePut 调用方法时 方法入参作为value 放入缓存

    @CacheEvict 调用方法是 从缓存中删除key 指定的数据

    condition 方法调用前判断,满足时,存入缓存, condition 默认为“”

    unless 方法调用后判断,满足时,不存 unless 默认为 “”

    这里的spel 表达式中,#result 代表返回值, 比如第一个selectByPrimaryKey接口上的#result就代表返回的user实例。

    package com.susq.work.service.impl;
    
    import com.susq.work.dao.UserMapper;
    import com.susq.work.model.User;
    import com.susq.work.service.UserService;
    import org.springframework.cache.annotation.CacheEvict;
    import org.springframework.cache.annotation.CachePut;
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    
    /**
     * @author susq
     * @since 2018-01-09-16:56
     */
    @Service("userService")
    public class UserServiceImpl implements UserService {
    
        @Resource
        private UserMapper userMapper;
    
        @Override
        @Cacheable(cacheNames = "common", key = "#id", unless = "#result != null")
        public User selectByPrimaryKey(String id) {
            return userMapper.selectByPrimaryKey(id);
        }
    
        @CachePut(value = "common", key = "#user.getId()")
        public User save(User user) {
            userMapper.saveUser(user);
            return user;
        }
    
        @CachePut(value = "common", key = "#user.getId()")
        public User update(User user) {
            userMapper.updateBySelective(user);
            return user;
        }
    
        @Override
        @CacheEvict(value = "common", key = "#id")
        public void delete(String id) {
            userMapper.delete(id);
        }
    }

结果展示

  • 写了个Controller 测一测,单元测试也可以

    package com.susq.work.controller;
    
    import com.susq.work.model.User;
    import com.susq.work.service.UserService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author susq
     * @since 2018-05-03-19:04
     */
    @RestController
    @Slf4j
    @RequestMapping(value = "/mybatis")
    public class MybatisController {
    
        @Resource
        private UserService userService;
    
        @RequestMapping(value = "/get")
        public User getUser(@RequestParam String id) {
            User user = userService.selectByPrimaryKey(id);
            log.info("my log " + user);
            return user;
        }
    
        @RequestMapping(value = "/save")
        public Map<String, String> saveUser(@RequestBody User user) {
            userService.save(user);
            Map<String, String> result = new HashMap<>();
            result.put("success", "00000000");
            return result;
        }
    
        @RequestMapping(value = "/update")
        public void updateUser(@RequestBody User user) {
            userService.update(user);
        }
    
        @RequestMapping(value = "/delete")
        public Map<String, String> deleteUser(@RequestParam String id) {
            userService.delete(id);
            Map<String, String> result = new HashMap<>();
            result.put("success", "00000000");
            return result;
        }
    }
  • 调两次get接口,可以看到日志如下, 第一次查询的时候,缓存中还没有,直接从数据库中查的,后面再请求就是从redis拿了。但是common缓存我配置的超时时间是10秒,10秒过后,再请求又会查一次数据库放入缓存了。

    2018-05-18 21:52:35.405 DEBUG [http-nio-8090-exec-1]com.susq.work.dao.UserMapper.selectByPrimaryKey.debug:132 -==>  Preparing: select id, name, address from user where id = ? 
    2018-05-18 21:52:35.468 DEBUG [http-nio-8090-exec-1]com.susq.work.dao.UserMapper.selectByPrimaryKey.debug:132 -==> Parameters: 1(String)
    2018-05-18 21:52:35.493 DEBUG [http-nio-8090-exec-1]com.susq.work.dao.UserMapper.selectByPrimaryKey.debug:132 -<==      Total: 1
    2018-05-18 21:52:35.794 INFO  [http-nio-8090-exec-1]com.susq.work.controller.MybatisController.getUser:30 -my log User(id=1, name=tingsuby, address=中国)
    2018-05-18 21:52:38.797 INFO  [http-nio-8090-exec-3]com.susq.work.controller.MybatisController.getUser:30 -my log User(id=1, name=tingsuby, address=中国)
  • 也可以运行redis终端,请求一次get, 会看到我们的缓存已经加进去了,并且带着我们配置的前缀COMMON:


    上面 配置的 valueSerializer 是GenericJackson2JsonRedisSerializer , 所以get 以后基本我们能看到我们存进去的原始信息,虽然汉字不能正常显示,起码比JdkSerializationRedisSerializer 辨识度好多了。

一些问题

  • @Cacheable注解不起作用

    人们都说缓存的配置文件,要在spring的主配置文件中才生效,放在<context-param> 里面不起作用,我就直接在 spring-mvc.xml 中 import resouce 引入了spring-cache的配置文件,发现根本无卵用。除非在springMVC容器扫描的时候,扫描除Controller外所有的Bean 才起作用。但是SpringMVC 的容器我们只是放Controller 的Bean的,其余的Bean应该加载在Spring 主容器中,所以这样做并不好。 于是我试了一下放在web.xml 的 <context-param> 节点中时可以的。而且如果有多个文件,加载的顺序并不重要,例如

     <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mybatis.xml, classpath:spring-cache.xml</param-value>
      </context-param>

    或者

     <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-cache.xml, classpath:spring-mybatis.xml</param-value>
      </context-param>

    都是可以的。

这篇关于spring @Cacheable @CachePut... 使用redis缓存详细步骤的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1108162

相关文章

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java