基于Ehcache的Spring缓存详解

简介:

一 简介

缓存,通过将数据保存在缓冲区中,可以为以后的相同请求提供更快速的查询,同时可以避免方法的多次执行,从而提高应用的性能。

在企业级应用中,为了提升性能,Spring提供了一种可以在方法级别上进行缓存的缓存抽象。通过使用AOP原则,Spring对使用缓存的方法自动生成相应代理类,如果已经为提供的参数执行过该方法,那么就不必重新执行实际方法而是直接返回被缓存的结果。在基于Spring的Web应用中,为了启用缓存功能,需要使用缓存注解对待使用缓存的方法进行标记。

Spring缓存仅仅提供了一种抽象,一般在企业级的Java应用中我们通常选择使用第三方的缓存框架与Spring进行集成,比如:Ehcache、Guava和Hazelcast等。接下来我将使用Ehcache框架来详细说明在SSM开发模式下的缓存配置和使用

注:文末将有整个测试项目的完成源代码,以供大家参考

二 SSM开发环境的搭建

(1)新建Java Web项目,并导入相应jar包:

最后的项目结构图如下所示:

wKiom1e5F-nhIBL3AAB2C5Dd8J0071.png  wKioL1e5F_ig2QxpAABe4T8H9fw083.png


wKioL1e5GBzAp4KwAAA2OI-qWkM232.png

(2)在web.xml中配置SpringMVC需要处理的请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
< servlet >
     < servlet-name >springmvc</ servlet-name >
     < servlet-class >org.springframework.web.servlet.DispatcherServlet</ servlet-class >
     < init-param >
         < param-name >contextConfigLocation</ param-name >
         < param-value >classpath:context/jsp-dispatcher.xml</ param-value >
     </ init-param >
     < load-on-startup >1</ load-on-startup >
</ servlet >
 
< servlet-mapping >
     < servlet-name >springmvc</ servlet-name >
     < url-pattern >*.html</ url-pattern >
</ servlet-mapping >

(3)准备项目后面需要用到的数据库环境:

我这里使用了MySQL5.x,数据库名是“ehcache_db”,建表语句如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
Navicat MySQL Data Transfer
 
Source Server         : sel
Source Server Version : 50519
Source Host           : localhost:3306
Source  Database        : ehcache_db
 
Target Server Type    : MYSQL
Target Server Version : 50519
File Encoding         : 65001
 
Date : 2016-05-05 22:41:46
*/
 
SET  FOREIGN_KEY_CHECKS=0;
 
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP  TABLE  IF EXISTS ` user `;
CREATE  TABLE  ` user ` (
   `id`  int (11)  NOT  NULL  AUTO_INCREMENT,
   ` name varchar (32)  DEFAULT  NULL ,
   ` password varchar (64)  DEFAULT  NULL ,
   `email`  varchar (64)  DEFAULT  NULL ,
   `birthday`  date  DEFAULT  NULL ,
   PRIMARY  KEY  (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6  DEFAULT  CHARSET=utf8;
 
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT  INTO  ` user VALUES  ( '1' 'admin' '123456' 'admin@zifangsky.cn' '2009-06-30' );
INSERT  INTO  ` user VALUES  ( '2' 'test' '123456' 'test@zifangsky.cn' '1990-12-12' );
INSERT  INTO  ` user VALUES  ( '3' '333' '333' 'eee@zifangsky.cn' '2016-05-01' );
INSERT  INTO  ` user VALUES  ( '4' '444' '444' '444@zifangsky.cn' '2016-06-14' );
INSERT  INTO  ` user VALUES  ( '5' '555' '555' '555@zifangsky.cn' '2016-05-12' );

(4)配置数据源以及Mybatis相关配置:

在数据源配置这里我采用了C3P0连接池,当然也可以使用其他的连接池,这里就不多说了,不熟悉的话可以把本测试项目的源码下载下来参考一下即可。同时,由于在本测试项目中使用了Mybatis框架,因此可以使用mybatis-generator插件来自动生成一些基本的xxModel、xxMapper.java和xxMapper.xml等文件,如果关于这个插件不熟悉的话可以参考下我以前写过的这篇文章:http://www.zifangsky.cn/431.html

(5)在UserMapper.xml中添加一段根据用户ID查用户名的SQL语句:

1
2
3
4
5
   < select  id = "selectUserNameById"  resultType = "java.lang.String"  parameterType = "java.lang.Integer"  >
     select name
     from user
     where id = #{id,jdbcType=INTEGER}
   </ select >

(6)对应的在UserMapper.java中添加上这个功能的接口:

1
2
3
4
5
6
7
8
     /**
      * 通过用户Id查用户名
     
      * @param id
      *            用户id
      * @return 用户名
      */
     String selectUserNameById(Integer id);

(7)UserManager.java和UserManagerImpl.java:

实际上这两个类处于业务逻辑层,跟我们通常见到的xxService这种类是一样的功能

i)UserManager.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package  cn.zifangsky.manager;
 
import  cn.zifangsky.model.User;
 
public  interface  UserManager {
     int  deleteByPrimaryKey(Integer id);
 
     int  insert(User record);
 
     int  insertSelective(User record);
 
     User selectByPrimaryKey(Integer id);
 
     User updateByPrimaryKeySelective(User record);
 
     int  updateByPrimaryKey(User record);
     
     /**
      * 通过用户Id查用户名
     
      * @param id
      *            用户id
      * @return 用户名
      */
     String selectUserNameById(Integer id);
}

几个基本的增删改查接口,跟UserMapper.java这个类是差不多的

ii)UserManagerImpl.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package  cn.zifangsky.manager.impl;
 
import  javax.annotation.Resource;
 
import  org.springframework.stereotype.Service;
 
import  cn.zifangsky.manager.UserManager;
import  cn.zifangsky.mapper.UserMapper;
import  cn.zifangsky.model.User;
 
@Service (value =  "userManagerImpl" )
public  class  UserManagerImpl  implements  UserManager {
     @Resource (name =  "userMapper" )
     private  UserMapper userMapper;
 
     public  int  deleteByPrimaryKey(Integer id) {
         System.out.println( "deleteByPrimaryKey方法开始执行:" );
         
         return  userMapper.deleteByPrimaryKey(id);
     }
 
     public  int  insert(User record) {
         return  userMapper.insert(record);
     }
 
     public  int  insertSelective(User record) {
         return  userMapper.insertSelective(record);
     }
 
     public  User selectByPrimaryKey(Integer id) {
         System.out.println( "selectByPrimaryKey方法开始执行:" );
         
         return  userMapper.selectByPrimaryKey(id);
     }
 
     public  User updateByPrimaryKeySelective(User user) {
         System.out.println( "updateByPrimaryKeySelective方法开始执行:" );
                 
         userMapper.updateByPrimaryKeySelective(user);
 
         return  userMapper.selectByPrimaryKey(user.getId());  
     }
 
     public  int  updateByPrimaryKey(User record) {
         return  userMapper.updateByPrimaryKey(record);
     }
 
     public  String selectUserNameById(Integer id) {
         System.out.println( "selectUserNameById方法开始执行:" );
         
         String resultName = userMapper.selectUserNameById(id);
 
         System.out.println( "用户Id:"  + id +  ", 对应的用户名是: "  + resultName);
         return  resultName;
     }
 
}

在这个实现类中针对每个方法通过调用userMapper中的对应方法来完成了一些基本的增删改查功能。当然如果对这里出现的@Service@Resource这两个基本的SpringMVC注解不熟悉的话可以参考下我以前写过的这篇文章:http://www.zifangsky.cn/459.html

实际上,我们后面在启用缓存功能时就是在这个类上的一些方法上添加注解,当然现在可以不用管,待会再说。

(8)在UserController.java中添加一个测试方法,用于测试基于SSM框架的开发环境是否搭建成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package  cn.zifangsky.controller;
 
import  javax.annotation.Resource;
 
import  org.springframework.stereotype.Controller;
import  org.springframework.web.bind.annotation.RequestMapping;
import  org.springframework.web.bind.annotation.RequestParam;
import  org.springframework.web.servlet.ModelAndView;
 
import  cn.zifangsky.manager.UserManager;
import  cn.zifangsky.model.User;
 
@Controller
public  class  UserController {
     @Resource (name= "userManagerImpl" )
     private  UserManager userManager;
 
     @RequestMapping (value= "/test.html" )
     public  String user( @RequestParam (name= "userId" ,required= false ) Integer userId){
         User user = userManager.selectByPrimaryKey(userId);
     
         System.out.println( "用户名: "  + user.getName());
         System.out.println( "邮箱: "  + user.getEmail());
         
         return  "success" ;
     }
     
}

(9)测试基于SSM框架的开发环境:

启动项目后在浏览器中访问:http://localhost:9080/EhcacheDemo/test.html?userId=1

如果出现在控制台中打印出了以下内容,则说明我们的测试环境已经搭建成功了

1
2
用户名: admin
邮箱: admin@zifangsky.cn

三 Ehcache框架与Spring的集成

(1)导入相关jar包

这里就不多说了,下载Ehcache的jar包并导入进去即可,也可以参考上面的项目结构图

(2)新建ehcache.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<? xml  version = "1.0"  encoding = "UTF-8" ?>
< ehcache  xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation = "http://ehcache.org/ehcache.xsd" >
 
     < diskStore  path = "java.io.tmpdir"  />
     < defaultCache  maxElementsInMemory = "1000"  eternal = "false"
         timeToIdleSeconds = "120"  timeToLiveSeconds = "120"  overflowToDisk = "false"  />
 
     < cache  name = "myCache"  maxElementsOnDisk = "20000"  
         maxElementsInMemory = "10000"  
         timeToIdleSeconds = "0"           
         timeToLiveSeconds = "600"
         eternal = "true"  
         overflowToDisk = "true"  
         diskPersistent = "true"
         memoryStoreEvictionPolicy = "LFU"  />
</ ehcache >
 
<!--
     <diskStore>==========当内存缓存中对象数量超过maxElementsInMemory时,将缓存对象写到磁盘缓存中(需对象实现序列化接口) 
     
     <diskStore path="">==用来配置磁盘缓存使用的物理路径,Ehcache磁盘缓存使用的文件后缀名是*.data和*.index name=================缓存名称,cache的唯一标识(ehcache会把这个cache放到HashMap里) 
     
     maxElementsOnDisk====磁盘缓存中最多可以存放的元素数量,0表示无穷大 maxElementsInMemory==内存缓存中最多可以存放的元素数量,若放入Cache中的元素超过这个数值,则有以下两种情况 :
         1)若overflowToDisk=true,则会将Cache中多出的元素放入磁盘文件中 
         2)若overflowToDisk=false,则根据memoryStoreEvictionPolicy策略替换Cache中原有的元素 
         
     eternal==============缓存中对象是否永久有效,即是否永驻内存,true时将忽略timeToIdleSeconds和timeToLiveSeconds 
     
     timeToIdleSeconds====缓存数据在失效前的允许闲置时间(单位:秒),仅当eternal=false时使用,默认值是0表示可闲置时间无穷大,此为可选属性 
         即访问这个cache中元素的最大间隔时间,若超过这个时间没有访问此Cache中的某个元素,那么此元素将被从Cache中清除 
     
     timeToLiveSeconds====缓存数据在失效前的允许存活时间(单位:秒),仅当eternal=false时使用,默认值是0表示可存活时间无穷大 
         即Cache中的某元素从创建到清楚的生存时间,也就是说从创建开始计时,当超过这个时间时,此元素将从Cache中清除 
         
     overflowToDisk=======内存不足时,是否启用磁盘缓存(即内存中对象数量达到maxElementsInMemory时,Ehcache会将对象写到磁盘中) 
         会根据标签中path值查找对应的属性值,写入磁盘的文件会放在path文件夹下,文件的名称是cache的名称,后缀名是data 
         
     diskPersistent=======是否持久化磁盘缓存,当这个属性的值为true时,系统在初始化时会在磁盘中查找文件名为cache名称,后缀名为index的文件  
                      这个文件中存放了已经持久化在磁盘中的cache的index,找到后会把cache加载到内存  
                      要想把cache真正持久化到磁盘,写程序时注意执行net.sf.ehcache.Cache.put(Element element)后要调用flush()方法  
 
     diskExpiryThreadIntervalSeconds==磁盘缓存的清理线程运行间隔,默认是120秒  
     
     diskSpoolBufferSizeMB============设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB  
     
     memoryStoreEvictionPolicy========内存存储与释放策略,即达到maxElementsInMemory限制时,Ehcache会根据指定策略清理内存  
                      共有三种策略,分别为LRU(最近最少使用)、LFU(最常用的)、FIFO(先进先出)  
-->

这个文件是缓存的一些具体配置,有不懂的地方可以参考下注释中的内容

(3)在context.xml中添加缓存相关的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 缓存配置 -->
<!-- 启用缓存注解功能  -->
< cache:annotation-driven  cache-manager = "cacheManager"  />
<!-- Spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供的) -->
<!-- <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> 
     <property name="caches"> <set> <bean name="myCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"/> 
     </set> </property> </bean> -->
<!-- 若只想使用Spring自身提供的缓存器,则注释掉下面的两个关于Ehcache配置的bean,并启用上面的SimpleCacheManager即可 -->
<!-- Spring提供的基于的Ehcache实现的缓存管理器 -->
 
< bean  id = "cacheManagerFactory"
     class = "org.springframework.cache.ehcache.EhCacheManagerFactoryBean" >
     < property  name = "configLocation"  value = "classpath:ehcache.xml"  />
</ bean >
< bean  id = "cacheManager"  class = "org.springframework.cache.ehcache.EhCacheCacheManager" >
     < property  name = "cacheManager"  ref = "cacheManagerFactory"  />
</ bean >

(4)测试缓存效果:

i)在UserManagerImpl.java的selectUserNameById方法上添加@Cacheable注解,表示启用缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 测试,自定义了key:'selectUserNameById_' + #id
// 同时设置了id=2才启动缓存,当然实际开发并不会设置这种条件
// @Cacheable(value="myCache",key="'selectUserNameById_' + #id"
// ,condition="#id == 2")
// @Cacheable(value="myCache",key="'selectUserNameById_' + #id" ,unless="#id
// != 2")
@Cacheable (value =  "myCache" , key =  "'selectUserNameById_' + #id" )
public  String selectUserNameById(Integer id) {
     System.out.println( "selectUserNameById方法开始执行:" );
     
     String resultName = userMapper.selectUserNameById(id);
 
     System.out.println( "用户Id:"  + id +  ", 对应的用户名是: "  + resultName);
     return  resultName;
}

从上面的代码可以看出,为了使用缓存,我们在这个方法上添加了一个@Cacheable注解。这个注解的含义是:有这个注解的方法将启用缓存功能,Spring将会自动生成代理类,根据这里的key值来判断此次查询是否已经被缓存过,如果被缓存过则直接从缓存中取得该方法的执行结果,如果没被缓存过那么将会实际执行这个方法并将结果进行缓存。这里的value对应的是我们在ehcache.xml文件中配置的一项具体的缓存参数配置。还要说明的是,这里的key值使用了Spring表达式语言(即:SpEL),为的就是通过一个唯一的id对每条user表中的数据建立不同的缓存,以此避免多个查询结果混淆。

同时,从我上面的注释可以看出,condition和unless参数可以用于生成有条件的缓存,可以根据在满足一定条件的情况下才对执行结果进行缓存。这里就不多说了,自己尝试一下就懂了。

当然,Spring的缓存除了@Cacheable这个注解外,还有@CacheEvict和@CachePut这两个常用注解,分别表示删除指定缓存和更新指定缓存。这两个注解的使用方法我们下面再说

ii)在UserController.java中添加一个测试方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RequestMapping (value= "/ehcache.html" )
public  ModelAndView testEhcache( @RequestParam (name= "userId" ) Integer userId){
     ModelAndView modelAndView =  new  ModelAndView( "TestCache" );
     String userName = userManager.selectUserNameById(userId);
     
     modelAndView.addObject( "userId" , userId);
     if (userName !=  null ){
         modelAndView.addObject( "userName" , userName);
     }
     else {
         modelAndView.addObject( "userName" "null_ehcache" );
     }
     
     return  modelAndView;
}

iii)对应的WebContent/WEB-INF/jsp/TestCache.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
     pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>    
< html >
< head >
< meta  http-equiv = "Content-Type"  content = "text/html; charset=UTF-8" >
< base  href="<%=basePath%>">
< title >测试SpringMVC的缓存——ehcache</ title >
</ head >
< body >
     < div  align = "center" >
         用户Id是${userId },对应的用户名是 ${userName }
     </ div >
</ body >
</ html >

iv)运行项目,并测试:

在浏览器中访问:http://localhost:9080/EhcacheDemo/ehcache.html?userId=4

可以发现,不仅显示了对应的jsp页面,同时控制台还输出了:

1
  用户Id:4, 对应的用户名是: 444

表明第一次请求时是执行了selectUserNameById这个方法的

wKiom1e5GR_jdU8MAACqZy3iuOg337.png

接下来我们清除控制台输出,再次访问:http://localhost:9080/EhcacheDemo/ehcache.html?userId=4

可以发现,jsp页面中的内容不变,但是控制台中并没有输出任何内容,也就是说在这次请求中并没有执行selectUserNameById这个方法,而是从缓存中直接取得的缓存值。因此,到这里缓存就配置成功了

wKiom1e5GUHjkC3FAABDWlP73wY970.png

四 @CacheEvict和@CachePut缓存注解

(1)@CacheEvict注解的使用:

@CacheEvict注解定义了相关方法负责从给定的缓存存储器中移除某些缓存值。虽然大多数的缓存框架都提供了缓存数据的有效时间,但是使用这个注解可以立即显式地从缓存存储器中删除过时的数据。这个注解通常用于在执行删除操作时使用

i)在UserManagerImpl.java的deleteByPrimaryKey方法上添加@CacheEvict注解:

1
2
3
4
5
6
7
8
//@CacheEvict(value = "myCache", allEntries = true)  //清空所有缓存内容
// 在删除一条数据时同时删除该数据的缓存
@CacheEvict (value= "myCache" ,key= "'selectUserNameById_' + #id" )   
public  int  deleteByPrimaryKey(Integer id) {
     System.out.println( "deleteByPrimaryKey方法开始执行:" );
     
     return  userMapper.deleteByPrimaryKey(id);
}

与@Cacheable注解一样,@CacheEvict也提供了value、condition、key等属性,我们可以使用SpEL表达式自定义键和条件。此外,@CacheEvict注解还提供了两个特殊的属性,分别是:allEntries和beforeInvocation。分别表示是否清空指定的缓存存储器中的所有的缓存内容;是否在方法执行之前或者之后完成删除缓存操作。当然默认情况下,@Cacheable是在方法调用之后执行删除缓存操作

ii)UserController.java中添加一个删除方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@RequestMapping (value= "/delete.html" )
public  ModelAndView deleteEhcache( @RequestParam (name= "userId" ) Integer userId){
     ModelAndView modelAndView =  new  ModelAndView( "DeleteCache" );
     
     int  status = userManager.deleteByPrimaryKey(userId);
     modelAndView.addObject( "userId" , userId);
     if (status ==  1 )
         modelAndView.addObject( "status" "成功" );
     else
         modelAndView.addObject( "status" "失败" );
     
     return  modelAndView;
}

iii)这个操作对应的视图文件WebContent/WEB-INF/jsp/DeleteCache.jsp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<%@ page language="java" contentType="text/html; charset=UTF-8"
     pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>    
< html >
< head >
< meta  http-equiv = "Content-Type"  content = "text/html; charset=UTF-8" >
< base  href="<%=basePath%>">
< title >测试SpringMVC的缓存——ehcache</ title >
</ head >
< body >
     < div  align = "center" >
         删除用户Id是${userId }的数据,执行状态: ${status }
     </ div >
</ body >
</ html >

iv)运行项目并测试:

项目启动后在浏览器中访问:http://localhost:9080/EhcacheDemo/ehcache.html?userId=5

执行完之后,显示视图如下:

wKioL1e5GZziWOodAAATwXc4OHE535.png

紧接着再访问:http://localhost:9080/EhcacheDemo/delete.html?userId=5

执行完之后,显示视图如下:

wKiom1e5GbeRK1zXAAAVMh91oow048.png

最后,我们再次访问:http://localhost:9080/EhcacheDemo/ehcache.html?userId=5

执行完之后,显示视图如下:

wKioL1e5Gd-BYPnvAACPuxFXcSM924.png

可以发现,这个操作重新执行了selectUserNameById方法,并且没有从数据库中查询到id=5的用户名,说明不仅在数据库中删掉了该条记录同时userId=5的缓存也已经被删掉了

(2)@CachePut注解的使用:

对于标注了@CachePut注解的方法,它首先执行该方法,然后将返回值放到缓存中。通俗来讲,@CachePut有两个功能:

  • 如果缓存存储器中已经有某个key的缓存了,那么在执行完标注了@CachePut注解的方法后,将会更新缓存存储器中这个key的缓存

  • 如果缓存存储器中没有某个key的缓存,那么在执行完标注了@CachePut注解的方法后,将会在缓存存储器中使用这个方法的返回值来添加上这个key的缓存

i)在UserManagerImpl.java的selectByPrimaryKey方法和updateByPrimaryKeySelective方法上添加上对应的注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
     /**
      * 由于返回值是一个对象,因此启动缓存需要User这个类能够序列化, 也就是implements Serializable
      */
     @Cacheable (value =  "myCache" , key =  "'select_' + #id" )
     public  User selectByPrimaryKey(Integer id) {
         System.out.println( "selectByPrimaryKey方法开始执行:" );
         
         return  userMapper.selectByPrimaryKey(id);
     }
 
     // 每次都会执行方法,并将返回值存入对应的缓存中(如果该key的缓存存在则更新,如果不存在则添加该key的缓存)
//  @CachePut(value = "myCache", key = "'select_' + #user.getId()")
     @CachePut (value =  "myCache" , key =  "'select_' + #user.id" )
     public  User updateByPrimaryKeySelective(User user) {
         System.out.println( "updateByPrimaryKeySelective方法开始执行:" );
                 
         userMapper.updateByPrimaryKeySelective(user);
         /**
          * 用返回值更新缓存
         
          * 不能直接返回一个int类型的状态,不然因为跟selectByPrimaryKey(Integer id)这个方法定义的缓存
          * 的返回值不一样,因此项目运行报错
          * */
         return  userMapper.selectByPrimaryKey(user.getId());  
     }

为了避免混淆,因此在生成缓存时没有使用selectUserNameById这个方法,而是使用了selectByPrimaryKey这个方法,同时key值的前缀也定义了一个不一样的“select_”

同时因为返回值是一个User对象,因此要想启动缓存就需要User这个类能够序列化, 也就是implements Serializable

1
2
3
4
public  class  User  implements  Serializable{
     private  static  final  long  serialVersionUID = 4780025517769228888L;
     //其他的内容不变,这里就不粘贴这部分代码了
}

ii)在UserController.java中添加上对应的查询和更新方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RequestMapping (value= "/update.html" )
public  void  updateEhcache( @RequestParam (name= "userId" ) Integer userId, @RequestParam (name= "name" ) String name){       
     User u =  new  User();
     u.setId(userId);
     u.setName(name);
 
     userManager.updateByPrimaryKeySelective(u);
}
 
@RequestMapping (value= "/select.html" )
public  ModelAndView selectEhcache( @RequestParam (name= "userId" ) Integer userId){
     ModelAndView modelAndView =  new  ModelAndView( "TestCache" );
     User u = userManager.selectByPrimaryKey(userId);
 
     modelAndView.addObject( "userId" , userId);
     if (u !=  null ){
         modelAndView.addObject( "userName" , u.getName());
     } else {
         modelAndView.addObject( "userName" "null_select" );
     }
     
     return  modelAndView;
}

iii)测试@CachePut注解更新已存在的缓存:

重新启动项目后,在浏览器中访问:http://localhost:9080/EhcacheDemo/select.html?userId=2

执行完之后,显示视图如下:

wKiom1e5GjCg06bdAABpjJdPri0919.png

接着在浏览器中访问:http://localhost:9080/EhcacheDemo/update.html?userId=2&name=user2

执行完之后,再次访问:http://localhost:9080/EhcacheDemo/select.html?userId=2

执行完之后,显示视图如下:

wKiom1e5Gk7R6ANGAABAEbqxnmo020.png

可以发现,userId=2的缓存已经更新了,并且这次没有执行selectByPrimaryKey这个方法。这就可以说明@CachePut注解可以用于更新已经存在的缓存

iv)测试@CachePut注解更新并添加不存在的缓存:

在上面测试的基础上,在浏览器中直接访问:http://localhost:9080/EhcacheDemo/update.html?userId=3&name=user3

执行完之后,显示视图如下:

wKioL1e5GnfyoLPiAACCXfnuETU385.png

然后再次访问:http://localhost:9080/EhcacheDemo/select.html?userId=3

执行完之后,显示视图如下:

wKiom1e5GpSBsARyAABBZbPak-I591.png

可以发现,这次操作并没有执行selectByPrimaryKey这个方法但是userId=3的缓存已经添加到缓存存储器中去了,说明在添加了@CachePut注解的updateByPrimaryKeySelective方法在第一次执行完之后不仅更新了数据库,同时也使用返回值在缓存存储器中添加上了userId=3的User缓存




本文转自 pangfc 51CTO博客,原文链接:http://blog.51cto.com/983836259/1840771,如需转载请自行联系原作者

相关文章
|
3月前
|
缓存 NoSQL Java
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
【Azure Redis 缓存】示例使用 redisson-spring-boot-starter 连接/使用 Azure Redis 服务
|
10天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
45 2
|
2月前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
174 24
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
2月前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
37 1
|
3月前
|
缓存 NoSQL Java
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
Spring Cache 是 Spring 提供的简易缓存方案,支持本地与 Redis 缓存。通过添加 `spring-boot-starter-data-redis` 和 `spring-boot-starter-cache` 依赖,并使用 `@EnableCaching` 开启缓存功能。JetCache 由阿里开源,功能更丰富,支持多级缓存和异步 API,通过引入 `jetcache-starter-redis` 依赖并配置 YAML 文件启用。Layering Cache 则提供分层缓存机制,需引入 `layering-cache-starter` 依赖并使用特定注解实现缓存逻辑。
875 1
SpringBoot的三种缓存技术(Spring Cache、Layering Cache 框架、Alibaba JetCache 框架)
|
3月前
|
缓存 Java Spring
Spring缓存实践指南:从入门到精通的全方位攻略!
【8月更文挑战第31天】在现代Web应用开发中,性能优化至关重要。Spring框架提供的缓存机制可以帮助开发者轻松实现数据缓存,提升应用响应速度并减少服务器负载。通过简单的配置和注解,如`@Cacheable`、`@CachePut`和`@CacheEvict`,可以将缓存功能无缝集成到Spring应用中。例如,在配置文件中启用缓存支持并通过`@Cacheable`注解标记方法即可实现缓存。此外,合理设计缓存策略也很重要,需考虑数据变动频率及缓存大小等因素。总之,Spring缓存机制为提升应用性能提供了一种简便快捷的方式。
48 0
|
3月前
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
60 0
|
3月前
|
缓存 NoSQL Java
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
【Azure Redis 缓存】定位Java Spring Boot 使用 Jedis 或 Lettuce 无法连接到 Redis的网络连通性步骤
|
3月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
Java本地高性能缓存实践问题之在Spring Boot中启用缓存支持的问题如何解决
|
3月前
|
缓存 Java Spring
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
Java本地高性能缓存实践问题之的Spring Boot中启用缓存支持问题如何解决
下一篇
无影云桌面