您好,登錄后才能下訂單哦!
這篇文章主要介紹了基于Ehcache如何實現Spring緩存,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
緩存,通過將數據保存在緩沖區中,可以為以后的相同請求提供更快速的查詢,同時可以避免方法的多次執行,從而提高應用的性能。
在企業級應用中,為了提升性能,Spring提供了一種可以在方法級別上進行緩存的緩存抽象。通過使用AOP原則,Spring對使用緩存的方法自動生成相應代理類,如果已經為提供的參數執行過該方法,那么就不必重新執行實際方法而是直接返回被緩存的結果。在基于Spring的Web應用中,為了啟用緩存功能,需要使用緩存注解對待使用緩存的方法進行標記。
Spring緩存僅僅提供了一種抽象,一般在企業級的Java應用中我們通常選擇使用第三方的緩存框架與Spring進行集成,比如:Ehcache、Guava和Hazelcast等。接下來我將使用Ehcache框架來詳細說明在SSM開發模式下的緩存配置和使用
(1)新建Java Web項目,并導入相應jar包:
最后的項目結構圖如下所示:
(2)在web.xml中配置SpringMVC需要處理的請求:
<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”,建表語句如下所示:
/* 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語句:
<select id="selectUserNameById" resultType="java.lang.String" parameterType="java.lang.Integer" > select name from user where id = #{id,jdbcType=INTEGER} </select>
(6)對應的在UserMapper.java中添加上這個功能的接口:
/** * 通過用戶Id查用戶名 * * @param id * 用戶id * @return 用戶名 */ String selectUserNameById(Integer id);
(7)UserManager.java和UserManagerImpl.java:
實際上這兩個類處于業務邏輯層,跟我們通常見到的xxService這種類是一樣的功能
i)UserManager.java:
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:
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框架的開發環境是否搭建成功:
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
如果出現在控制臺中打印出了以下內容,則說明我們的測試環境已經搭建成功了
用戶名: admin 郵箱: admin@zifangsky.cn
(1)導入相關jar包
這里就不多說了,下載Ehcache的jar包并導入進去即可,也可以參考上面的項目結構圖
(2)新建ehcache.xml:
<?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中添加緩存相關的配置:
<!-- 緩存配置 --> <!-- 啟用緩存注解功能 --> <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注解,表示啟用緩存:
// 測試,自定義了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中添加一個測試方法:
@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:
<%@ 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頁面,同時控制臺還輸出了:
用戶Id:4, 對應的用戶名是: 444
表明第一次請求時是執行了selectUserNameById這個方法的
接下來我們清除控制臺輸出,再次訪問:http://localhost:9080/EhcacheDemo/ehcache.html?userId=4
可以發現,jsp頁面中的內容不變,但是控制臺中并沒有輸出任何內容,也就是說在這次請求中并沒有執行selectUserNameById這個方法,而是從緩存中直接取得的緩存值。因此,到這里緩存就配置成功了
(1)@CacheEvict注解的使用:
@CacheEvict注解定義了相關方法負責從給定的緩存存儲器中移除某些緩存值。雖然大多數的緩存框架都提供了緩存數據的有效時間,但是使用這個注解可以立即顯式地從緩存存儲器中刪除過時的數據。這個注解通常用于在執行刪除操作時使用
i)在UserManagerImpl.java的deleteByPrimaryKey方法上添加@CacheEvict注解:
//@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中添加一個刪除方法:
@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:
<%@ 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
執行完之后,顯示視圖如下:
緊接著再訪問:http://localhost:9080/EhcacheDemo/delete.html?userId=5
執行完之后,顯示視圖如下:
最后,我們再次訪問:http://localhost:9080/EhcacheDemo/ehcache.html?userId=5
執行完之后,顯示視圖如下:
可以發現,這個操作重新執行了selectUserNameById方法,并且沒有從數據庫中查詢到id=5的用戶名,說明不僅在數據庫中刪掉了該條記錄同時userId=5的緩存也已經被刪掉了
(2)@CachePut注解的使用:
對于標注了@CachePut注解的方法,它首先執行該方法,然后將返回值放到緩存中。通俗來講,@CachePut有兩個功能:
如果緩存存儲器中已經有某個key的緩存了,那么在執行完標注了@CachePut注解的方法后,將會更新緩存存儲器中這個key的緩存
如果緩存存儲器中沒有某個key的緩存,那么在執行完標注了@CachePut注解的方法后,將會在緩存存儲器中使用這個方法的返回值來添加上這個key的緩存
i)在UserManagerImpl.java的selectByPrimaryKey方法和updateByPrimaryKeySelective方法上添加上對應的注解:
/** * 由于返回值是一個對象,因此啟動緩存需要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
public class User implements Serializable{ private static final long serialVersionUID = 4780025517769228888L; //其他的內容不變,這里就不粘貼這部分代碼了 }
ii)在UserController.java中添加上對應的查詢和更新方法:
@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
執行完之后,顯示視圖如下:
接著在瀏覽器中訪問:http://localhost:9080/EhcacheDemo/update.html?userId=2&name=user2
執行完之后,再次訪問:http://localhost:9080/EhcacheDemo/select.html?userId=2
執行完之后,顯示視圖如下:
可以發現,userId=2的緩存已經更新了,并且這次沒有執行selectByPrimaryKey這個方法。這就可以說明@CachePut注解可以用于更新已經存在的緩存
iv)測試@CachePut注解更新并添加不存在的緩存:
在上面測試的基礎上,在瀏覽器中直接訪問:http://localhost:9080/EhcacheDemo/update.html?userId=3&name=user3
執行完之后,顯示視圖如下:
然后再次訪問:http://localhost:9080/EhcacheDemo/select.html?userId=3
執行完之后,顯示視圖如下:
可以發現,這次操作并沒有執行selectByPrimaryKey這個方法但是userId=3的緩存已經添加到緩存存儲器中去了,說明在添加了@CachePut注解的updateByPrimaryKeySelective方法在第一次執行完之后不僅更新了數據庫,同時也使用返回值在緩存存儲器中添加上了userId=3的User緩存
感謝你能夠認真閱讀完這篇文章,希望小編分享的“基于Ehcache如何實現Spring緩存”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。