場景
前端使用Vue+ElementUI,要顯示下拉框,下拉框的字典數據從數據庫中的字典表中獲取。
但是如果每次下拉框都要項后台發動請求數據庫的請求,性能可想而知。
所以可以在查詢下拉框的字典數據時先從Redis緩存中查找,如果有則直接返回,如果沒有再從數據庫中查詢並將其存進Redis緩存中。
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
關注公眾號
霸道的程序猿
獲取編程相關電子書、教程推送與免費下載。
實現
首先數據庫設計一個字典表。具體字段信息如下
主要要有以下幾個字段,dict_type相同的為一組,dict_value為下拉框實現獲取和數據庫實際存儲的值,dict_label為實現顯示的值。
示例數據
然后使用代碼生成工具生成此字典表的后台相關層的代碼。
然后構建前端頁面這里使用el-select
<el-select v-model="queryParams.ddlb" placeholder="請選擇調動類別" clearable size="small"> <el-option v-for="dict in ddlbOptions" :key="dict.dictValue" :label="dict.dictLabel" :value="dict.dictValue" /> </el-select>
給這個下拉框的el-option綁定數據為ddlbOptions,這是一個對象數組,並且其key和value為每個對象的dictValue屬性,顯示的值dictLabel為每個對象的dictLabel屬性。
需要提前聲明這個對象數組。
export default { name: "Ddjl", data() { return { ddlbOptions: [],
然后在此vue頁面的created方法中去調用后台接口查詢字典的數據。
這樣就能在頁面一加載完就能獲取到所有的下拉項。
created() { this.getDicts("kq_ddlx").then((response) => { this.ddlbOptions = response.data; }); },
在這里調用了一個js的方法並且將響應的data數據賦值給上面下拉框綁定的對象數組。並且在調用時傳遞了一個參數,
此參數用來作為查詢字典時的唯一標志。
getDicts這個根據字典類型獲取字典數據的方法再多個頁面都能用到,所以將其抽離為全局方法。
在main.js中掛載全局方法
Vue.prototype.getDicts = getDicts
右邊對全局方法進行賦值的getDicts來自外部第三方的引用
import { getDicts } from "@/api/system/dict/data";
這是在api/system/dict/data.js中的請求數據的接口方法,通過下面將其暴露
// 根據字典類型查詢字典數據信息 export function getDicts(dictType) { return request({ url: '/system/dict/data/type/' + dictType, method: 'get' }) }
其中request是封裝的axios的對象,用來發送請求,這里是get請求並且攜帶傳遞的字典類型參數。
來到對應的SpringBoot的后台
@GetMapping(value = "/type/{dictType}") public AjaxResult dictType(@PathVariable String dictType) { return AjaxResult.success(dictTypeService.selectDictDataByType(dictType)); }
Controller調用了service的方法,來到具體的實現方法中
@Override public List<SysDictData> selectDictDataByType(String dictType) { List<SysDictData> dictDatas = DictUtils.getDictCache(dictType); if (StringUtils.isNotNull(dictDatas)) { return dictDatas; } dictDatas = dictDataMapper.selectDictDataByType(dictType); if (StringUtils.isNotNull(dictDatas)) { DictUtils.setDictCache(dictType, dictDatas); return dictDatas; } return null; }
來看一下整體的邏輯,首先根據接收到的字典類型參數去Redis緩存中去查詢是否存在,
如果之前存在則將緩存中的直接返回,如果不存在則再去上面一開始建立的表中去進行查詢,並且將查詢結果放進Redis緩存中。
對數據庫查詢的具體的xml代碼
<select id="selectDictDataByType" parameterType="SysDictData" resultMap="SysDictDataResult"> select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark from sys_dict_data where status = '0' and dict_type = #{dictType} order by dict_sort asc </select>
至於對Redis緩存的存取值,需要做一些前期的准備工作。
首先pom文件中引入相關依賴。
<!-- redis 緩存操作 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
然后在application.yml配置Redis服務端的連接
# Spring配置 spring: # 資源信息 messages: # 國際化資源文件路徑 basename: i18n/messages profiles: active: druid # 文件上傳 servlet: multipart: # 單個文件大小 max-file-size: 10MB # 設置總上傳的文件大小 max-request-size: 20MB # 服務模塊 devtools: restart: # 熱部署開關 enabled: true # redis 配置 redis: # 地址 #本地測試用 host: 127.0.0.1 port: 6379 password: 123456 # 連接超時時間 timeout: 10s lettuce: pool: # 連接池中的最小空閑連接 min-idle: 0 # 連接池中的最大空閑連接 max-idle: 8 # 連接池的最大數據庫連接數 max-active: 8 # #連接池最大阻塞等待時間(使用負值表示沒有限制) max-wait: -1ms
這里配置的Redis服務端是我自己的本地,在Windows下安裝並配置Redis服務端參照
https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/107486313
這樣就能對Redis進行存儲數據的操作。
再看上面的實現查詢字典數據的方法中DictUtils.getDictCache(dictType);
是調用了字典工具類中的根據字典類型獲取字典的緩存的方法,方法代碼如下
public static List<SysDictData> getDictCache(String key) { Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); if (StringUtils.isNotNull(cacheObj)) { List<SysDictData> DictDatas = StringUtils.cast(cacheObj); return DictDatas; } return null; }
請注意上面的
SpringUtils.getBean(RedisCache.class)
這里在調用根據key獲取緩存的字典的value時需要先獲取RedisCache這個spring redis的工具類。
以為這是在字典工具類中,所以封裝了一個spring工具類,方便在非spring管理環境中獲取bean。
此工具類代碼
import org.springframework.aop.framework.AopContext; import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.stereotype.Component; /** * spring工具類 方便在非spring管理環境中獲取bean * */ @Component public final class SpringUtils implements BeanFactoryPostProcessor { /** Spring應用上下文環境 */ private static ConfigurableListableBeanFactory beanFactory; @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { SpringUtils.beanFactory = beanFactory; } /** * 獲取對象 * * @param name * @return Object 一個以所給名字注冊的bean的實例 * @throws org.springframework.beans.BeansException * */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) throws BeansException { return (T) beanFactory.getBean(name); } /** * 獲取類型為requiredType的對象 * * @param clz * @return * @throws org.springframework.beans.BeansException * */ public static <T> T getBean(Class<T> clz) throws BeansException { T result = (T) beanFactory.getBean(clz); return result; } /** * 如果BeanFactory包含一個與所給名稱匹配的bean定義,則返回true * * @param name * @return boolean */ public static boolean containsBean(String name) { return beanFactory.containsBean(name); } /** * 判斷以給定名字注冊的bean定義是一個singleton還是一個prototype。 如果與給定名字相應的bean定義沒有被找到,將會拋出一個異常(NoSuchBeanDefinitionException) * * @param name * @return boolean * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return beanFactory.isSingleton(name); } /** * @param name * @return Class 注冊對象的類型 * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return beanFactory.getType(name); } /** * 如果給定的bean名字在bean定義中有別名,則返回這些別名 * * @param name * @return * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException * */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return beanFactory.getAliases(name); } /** * 獲取aop代理對象 * * @param invoker * @return */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) AopContext.currentProxy(); } }
這樣就能通過
SpringUtils.getBean(RedisCache.class)
獲取RedisCache這個工具類,在此工具類中調用getCacheObject方法通過緩存的鍵值獲取對應的數據。
此方法的實現
/** * 獲得緩存的基本對象。 * * @param key 緩存鍵值 * @return 緩存鍵值對應的數據 */ public <T> T getCacheObject(String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); }
此工具類的實現
import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundSetOperations; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.ListOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Component; /** * spring redis 工具類 * **/ @SuppressWarnings(value = { "unchecked", "rawtypes" }) @Component public class RedisCache { @Autowired public RedisTemplate redisTemplate; /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 * @return 緩存的對象 */ public <T> ValueOperations<String, T> setCacheObject(String key, T value) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); operation.set(key, value); return operation; } /** * 緩存基本的對象,Integer、String、實體類等 * * @param key 緩存的鍵值 * @param value 緩存的值 * @param timeout 時間 * @param timeUnit 時間顆粒度 * @return 緩存的對象 */ public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); operation.set(key, value, timeout, timeUnit); return operation; } /** * 獲得緩存的基本對象。 * * @param key 緩存鍵值 * @return 緩存鍵值對應的數據 */ public <T> T getCacheObject(String key) { ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 刪除單個對象 * * @param key */ public void deleteObject(String key) { redisTemplate.delete(key); } /** * 刪除集合對象 * * @param collection */ public void deleteObject(Collection collection) { redisTemplate.delete(collection); } /** * 緩存List數據 * * @param key 緩存的鍵值 * @param dataList 待緩存的List數據 * @return 緩存的對象 */ public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList) { ListOperations listOperation = redisTemplate.opsForList(); if (null != dataList) { int size = dataList.size(); for (int i = 0; i < size; i++) { listOperation.leftPush(key, dataList.get(i)); } } return listOperation; } /** * 獲得緩存的list對象 * * @param key 緩存的鍵值 * @return 緩存鍵值對應的數據 */ public <T> List<T> getCacheList(String key) { List<T> dataList = new ArrayList<T>(); ListOperations<String, T> listOperation = redisTemplate.opsForList(); Long size = listOperation.size(key); for (int i = 0; i < size; i++) { dataList.add(listOperation.index(key, i)); } return dataList; } /** * 緩存Set * * @param key 緩存鍵值 * @param dataSet 緩存的數據 * @return 緩存數據的對象 */ public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet) { BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 獲得緩存的set * * @param key * @return */ public <T> Set<T> getCacheSet(String key) { Set<T> dataSet = new HashSet<T>(); BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key); dataSet = operation.members(); return dataSet; } /** * 緩存Map * * @param key * @param dataMap * @return */ public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap) { HashOperations hashOperations = redisTemplate.opsForHash(); if (null != dataMap) { for (Map.Entry<String, T> entry : dataMap.entrySet()) { hashOperations.put(key, entry.getKey(), entry.getValue()); } } return hashOperations; } /** * 獲得緩存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(String key) { Map<String, T> map = redisTemplate.opsForHash().entries(key); return map; } /** * 獲得緩存的基本對象列表 * * @param pattern 字符串前綴 * @return 對象列表 */ public Collection<String> keys(String pattern) { return redisTemplate.keys(pattern); } }
然后通過上面一系列的
Object cacheObj = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
就能根據key獲取到緩存中的value,然后將其轉換成字典對象的list
if (StringUtils.isNotNull(cacheObj)) { List<SysDictData> DictDatas = StringUtils.cast(cacheObj); return DictDatas; }
因為在上面字典接口的實現方法中緩存中存儲時
if (StringUtils.isNotNull(dictDatas)) { DictUtils.setDictCache(dictType, dictDatas); return dictDatas; }
也是存儲的 List<SysDictData>
所以在取值時可以通過
List<SysDictData> DictDatas = StringUtils.cast(cacheObj);
來獲取緩存中字典的值,這里又調用了字符串工具類的cast方法,方法定義
public static <T> T cast(Object obj) { return (T) obj; }
字符串工具類StringUtils
import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.ruoyi.common.core.text.StrFormatter; /** * 字符串工具類 * */ public class StringUtils extends org.apache.commons.lang3.StringUtils { /** 空字符串 */ private static final String NULLSTR = ""; /** 下划線 */ private static final char SEPARATOR = '_'; /** * 獲取參數不為空值 * * @param value defaultValue 要判斷的value * @return value 返回值 */ public static <T> T nvl(T value, T defaultValue) { return value != null ? value : defaultValue; } /** * * 判斷一個Collection是否為空, 包含List,Set,Queue * * @param coll 要判斷的Collection * @return true:為空 false:非空 */ public static boolean isEmpty(Collection<?> coll) { return isNull(coll) || coll.isEmpty(); } /** * * 判斷一個Collection是否非空,包含List,Set,Queue * * @param coll 要判斷的Collection * @return true:非空 false:空 */ public static boolean isNotEmpty(Collection<?> coll) { return !isEmpty(coll); } /** * * 判斷一個對象數組是否為空 * * @param objects 要判斷的對象數組 ** @return true:為空 false:非空 */ public static boolean isEmpty(Object[] objects) { return isNull(objects) || (objects.length == 0); } /** * * 判斷一個對象數組是否非空 * * @param objects 要判斷的對象數組 * @return true:非空 false:空 */ public static boolean isNotEmpty(Object[] objects) { return !isEmpty(objects); } /** * * 判斷一個Map是否為空 * * @param map 要判斷的Map * @return true:為空 false:非空 */ public static boolean isEmpty(Map<?, ?> map) { return isNull(map) || map.isEmpty(); } /** * * 判斷一個Map是否為空 * * @param map 要判斷的Map * @return true:非空 false:空 */ public static boolean isNotEmpty(Map<?, ?> map) { return !isEmpty(map); } /** * * 判斷一個字符串是否為空串 * * @param str String * @return true:為空 false:非空 */ public static boolean isEmpty(String str) { return isNull(str) || NULLSTR.equals(str.trim()); } /** * * 判斷一個字符串是否為非空串 * * @param str String * @return true:非空串 false:空串 */ public static boolean isNotEmpty(String str) { return !isEmpty(str); } /** * * 判斷一個對象是否為空 * * @param object Object * @return true:為空 false:非空 */ public static boolean isNull(Object object) { return object == null; } /** * * 判斷一個對象是否非空 * * @param object Object * @return true:非空 false:空 */ public static boolean isNotNull(Object object) { return !isNull(object); } /** * * 判斷一個對象是否是數組類型(Java基本型別的數組) * * @param object 對象 * @return true:是數組 false:不是數組 */ public static boolean isArray(Object object) { return isNotNull(object) && object.getClass().isArray(); } /** * 去空格 */ public static String trim(String str) { return (str == null ? "" : str.trim()); } /** * 截取字符串 * * @param str 字符串 * @param start 開始 * @return 結果 */ public static String substring(final String str, int start) { if (str == null) { return NULLSTR; } if (start < 0) { start = str.length() + start; } if (start < 0) { start = 0; } if (start > str.length()) { return NULLSTR; } return str.substring(start); } /** * 截取字符串 * * @param str 字符串 * @param start 開始 * @param end 結束 * @return 結果 */ public static String substring(final String str, int start, int end) { if (str == null) { return NULLSTR; } if (end < 0) { end = str.length() + end; } if (start < 0) { start = str.length() + start; } if (end > str.length()) { end = str.length(); } if (start > end) { return NULLSTR; } if (start < 0) { start = 0; } if (end < 0) { end = 0; } return str.substring(start, end); } /** * 格式化文本, {} 表示占位符<br> * 此方法只是簡單將占位符 {} 按照順序替換為參數<br> * 如果想輸出 {} 使用 \\轉義 { 即可,如果想輸出 {} 之前的 \ 使用雙轉義符 \\\\ 即可<br> * 例:<br> * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br> * 轉義{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br> * 轉義\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br> * * @param template 文本模板,被替換的部分用 {} 表示 * @param params 參數值 * @return 格式化后的文本 */ public static String format(String template, Object... params) { if (isEmpty(params) || isEmpty(template)) { return template; } return StrFormatter.format(template, params); } /** * 字符串轉set * * @param str 字符串 * @param sep 分隔符 * @return set集合 */ public static final Set<String> str2Set(String str, String sep) { return new HashSet<String>(str2List(str, sep, true, false)); } /** * 字符串轉list * * @param str 字符串 * @param sep 分隔符 * @param filterBlank 過濾純空白 * @param trim 去掉首尾空白 * @return list集合 */ public static final List<String> str2List(String str, String sep, boolean filterBlank, boolean trim) { List<String> list = new ArrayList<String>(); if (StringUtils.isEmpty(str)) { return list; } // 過濾空白字符串 if (filterBlank && StringUtils.isBlank(str)) { return list; } String[] split = str.split(sep); for (String string : split) { if (filterBlank && StringUtils.isBlank(string)) { continue; } if (trim) { string = string.trim(); } list.add(string); } return list; } /** * 下划線轉駝峰命名 */ public static String toUnderScoreCase(String str) { if (str == null) { return null; } StringBuilder sb = new StringBuilder(); // 前置字符是否大寫 boolean preCharIsUpperCase = true; // 當前字符是否大寫 boolean curreCharIsUpperCase = true; // 下一字符是否大寫 boolean nexteCharIsUpperCase = true; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (i > 0) { preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); } else { preCharIsUpperCase = false; } curreCharIsUpperCase = Character.isUpperCase(c); if (i < (str.length() - 1)) { nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); } if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) { sb.append(SEPARATOR); } else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) { sb.append(SEPARATOR); } sb.append(Character.toLowerCase(c)); } return sb.toString(); } /** * 是否包含字符串 * * @param str 驗證字符串 * @param strs 字符串組 * @return 包含返回true */ public static boolean inStringIgnoreCase(String str, String... strs) { if (str != null && strs != null) { for (String s : strs) { if (str.equalsIgnoreCase(trim(s))) { return true; } } } return false; } /** * 將下划線大寫方式命名的字符串轉換為駝峰式。如果轉換前的下划線大寫方式命名的字符串為空,則返回空字符串。 例如:HELLO_WORLD->HelloWorld * * @param name 轉換前的下划線大寫方式命名的字符串 * @return 轉換后的駝峰式命名的字符串 */ public static String convertToCamelCase(String name) { StringBuilder result = new StringBuilder(); // 快速檢查 if (name == null || name.isEmpty()) { // 沒必要轉換 return ""; } else if (!name.contains("_")) { // 不含下划線,僅將首字母大寫 return name.substring(0, 1).toUpperCase() + name.substring(1); } // 用下划線將原始字符串分割 String[] camels = name.split("_"); for (String camel : camels) { // 跳過原始字符串中開頭、結尾的下換線或雙重下划線 if (camel.isEmpty()) { continue; } // 首字母大寫 result.append(camel.substring(0, 1).toUpperCase()); result.append(camel.substring(1).toLowerCase()); } return result.toString(); } /** * 駝峰式命名法 例如:user_name->userName */ public static String toCamelCase(String s) { if (s == null) { return null; } s = s.toLowerCase(); StringBuilder sb = new StringBuilder(s.length()); boolean upperCase = false; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == SEPARATOR) { upperCase = true; } else if (upperCase) { sb.append(Character.toUpperCase(c)); upperCase = false; } else { sb.append(c); } } return sb.toString(); } @SuppressWarnings("unchecked") public static <T> T cast(Object obj) { return (T) obj; } }
這樣整個后台接口的根據字典類型查詢字典數據的方法就實現了緩存的機制。
但是如果之前添加過一種類型的字典,后期再想添加同種類型的條數據。
因為查詢緩存時一直存在,所以新增的數據不會顯示,所以需要后台調用
單元測試方法調用緩存工具類的清除緩存的方法。
import com.ruoyi.common.utils.DictUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class TestRedis { @Test public void test(){ DictUtils.clearDictCache(); } }