场景
前端使用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(); } }