主要幾個問題點
1. 什麼作爲緩存中的key?
以類名+方法名+業務id 做爲緩存中key。
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName() + "-id:" + id;
2. 細節-如何保證添加到一級和二級緩存的數據有效時間相同
例如代碼流程:
1. 先添加二級redis緩存
2. 再添加一級EhCache緩存
解決方法1:一級緩存的有效期時間減去二級緩存代碼的執行時間 = 一級緩存時間有效期。 不靠譜
解決方法2:如有效期爲2小時,則提前10分鐘去刷新重置緩存。 靠譜
流程圖
代碼
EhCacheUtils緩存工具類
代碼很簡單,根據流程圖理解差不多就能完成,所以有些代碼沒copy出來。
/**
* 功能說明:
* 功能作者:
* 創建日期:
* 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com
*/
package com.itmayiedu.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.stereotype.Component;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
/**
* 功能說明: <br>
* 創建作者:每特教育-餘勝軍<br>
* 創建時間:2018年8月4日 下午3:31:42<br>
* 教育機構:每特教育|螞蟻課堂<br>
* 版權說明:上海每特教育科技有限公司版權所有<br>
* 官方網站:www.itmayiedu.com|www.meitedu.com<br>
* 聯繫方式:qq644064779<br>
* 注意:本內容有每特教育學員共同研發,請尊重原創版權
*/
@Component
public class EhCacheUtils {
// @Autowired
// private CacheManager cacheManager;
@Autowired
private EhCacheCacheManager ehCacheCacheManager;
// 添加本地緩存 (相同的key 會直接覆蓋)
public void put(String cacheName, String key, Object value) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
Element element = new Element(key, value);
cache.put(element);
}
// 獲取本地緩存
public Object get(String cacheName, String key) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
Element element = cache.get(key);
return element == null ? null : element.getObjectValue();
}
public void remove(String cacheName, String key) {
Cache cache = ehCacheCacheManager.getCacheManager().getCache(cacheName);
cache.remove(key);
}
}
RedisService工具類
/**
* 功能說明:
* 功能作者:
* 創建日期:
* 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com
*/
package com.itmayiedu.service;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
/**
* 功能說明: <br>
* 創建作者:每特教育-餘勝軍<br>
* 創建時間:2018年8月1日 下午4:07:32<br>
* 教育機構:每特教育|螞蟻課堂<br>
* 版權說明:上海每特教育科技有限公司版權所有<br>
* 官方網站:www.itmayiedu.com|www.meitedu.com<br>
* 聯繫方式:qq644064779<br>
* 注意:本內容有每特教育學員共同研發,請尊重原創版權
*/
@Component
public class RedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// public void set(String key, Object object, Long time) {
// stringRedisTemplate.opsForValue();
// // 存放String 類型
// if (object instanceof String) {
// setString(key, object);
// }
// // 存放 set類型
// if (object instanceof Set) {
// setSet(key, object);
// }
// // 設置有效期 以秒爲單位
// stringRedisTemplate.expire(key, time, TimeUnit.SECONDS);
// }
//
public void setString(String key, Object object) {
// 開啓事務權限
stringRedisTemplate.setEnableTransactionSupport(true);
try {
// 開啓事務 begin
stringRedisTemplate.multi();
String value = (String) object;
stringRedisTemplate.opsForValue().set(key, value);
System.out.println("存入完畢,馬上開始提交redis事務");
// 提交事務
stringRedisTemplate.exec();
} catch (Exception e) {
// 需要回滾事務
stringRedisTemplate.discard();
}
}
public void setSet(String key, Object object) {
Set<String> value = (Set<String>) object;
for (String oj : value) {
stringRedisTemplate.opsForSet().add(key, oj);
}
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
}
業務邏輯層
以下代碼可寫成註解形式aop,重複使用。
/**
* 功能說明:
* 功能作者:
* 創建日期:
* 版權歸屬:每特教育|螞蟻課堂所有 www.itmayiedu.com
*/
package com.itmayiedu.service;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.alibaba.fastjson.JSONObject;
import com.itmayiedu.entity.Users;
import com.itmayiedu.mapper.UserMapper;
/**
* 功能說明: <br>
* 創建作者:每特教育-餘勝軍<br>
* 創建時間:2018年8月4日 下午4:14:35<br>
* 教育機構:每特教育|螞蟻課堂<br>
* 版權說明:上海每特教育科技有限公司版權所有<br>
* 官方網站:www.itmayiedu.com|www.meitedu.com<br>
* 聯繫方式:qq644064779<br>
* 注意:本內容有每特教育學員共同研發,請尊重原創版權
*/
@Service
public class UserService {
@Autowired
private EhCacheUtils ehCacheUtils;
private static final String CACHENAME_USERCACHE = "userCache";
@Autowired
private RedisService redisService;
@Autowired
private UserMapper userMapper;
public Users getUser(Long id) {
// 根據規則獲取key
String key = this.getClass().getName() + "-" + Thread.currentThread().getStackTrace()[1].getMethodName()
+ "-id:" + id;
// 1.先查找一級緩存(本地緩存),如果本地緩存有數據直接返回
Users ehUser = (Users) ehCacheUtils.get(CACHENAME_USERCACHE, key);
if (ehUser != null) {
System.out.println("使用key:" + key + ",查詢一級緩存 ehCache 獲取到ehUser:" + JSONObject.toJSONString(ehUser));
return ehUser;
}
// 2. 如果本地緩存沒有該數據,直接查詢二級緩存(redis)
String redisUserJson = redisService.getString(key);
if (!StringUtils.isEmpty(redisUserJson)) {
// 將json 轉換爲對象(如果二級緩存redis中有數據直接返回二級緩存)
JSONObject jsonObject = new JSONObject();
Users user = jsonObject.parseObject(redisUserJson, Users.class);
// 更新一級緩存
ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
System.out.println("使用key:" + key + ",查詢二級緩存 redis 獲取到ehUser:" + JSONObject.toJSONString(user));
return user;
}
// 3. 如果二級緩存redis中也沒有數據,查詢數據庫
Users user = userMapper.getUser(id);
if (user == null) {
return null;
}
// 問題:如何保證二級緩存和一級緩存的失效失效一致? 一級緩存的有效時間減去二級緩存代碼執行時間。
// 更新二級緩存
String userJson = JSONObject.toJSONString(user);
redisService.setString(key, userJson);
// 更新和一級緩存
ehCacheUtils.put(CACHENAME_USERCACHE, key, user);
System.out.println("使用key:" + key + ",一級緩存和二級都沒有數據,直接查詢db" + userJson);
return user;
}
}