一、媒介:
在现代应用中,随着数据量的增大和访问频率的提高,怎样提高数据存取的性能变得尤为告急。缓存技能作为一种常见的优化本领,被广泛应用于减少数据库访问压力、提拔体系响应速度。Redis 作为一种高效的内存缓存数据库,因其卓越的性能和丰富的数据范例支持,在开发中占据了告急位置。
本篇文章将详细先容怎样在 Spring Boot 项目中使用 Redis,创建简朴的缓存机制,并实现数据存取的时间比力。通过这个实战项目,你将学习怎样在 Redis 和 MySQL 之间存取数据,并丈量两者的性能差异,从而对缓存策略有更加深入的明确。
二、详细利用:
2.1、环境预备和项目布局
起首,你需要预备一个 Spring Boot 项目,并确保项目布局中包含以下依赖:
- Spring Web:用于构建 RESTful API
- MyBatis:数据库利用框架
- Spring Data Redis:用于 Redis 利用
- MySQL Driver:用于与 MySQL 数据库交互
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>com.mysql</groupId>
- <artifactId>mysql-connector-j</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <!-- MyBatis Starter -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>3.0.0</version>
- </dependency>
- <!-- Spring Boot Redis Starter -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
-
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <version>1.18.30</version>
- </dependency>
- </dependencies>
复制代码 同时项目布局如下,是一个非常普通的SpringBoot项目
同时还要在application.properties中配置好对应的MySQL和MyBatis信息:
- spring.datasource.url=jdbc:mysql://localhost:3306/yourDatabase
- spring.datasource.username=root
- spring.datasource.password=root
- spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
- mybatis.mapper-locations=classpath:mapper/*.xml
复制代码 2.2、创建实体类
实体类 User 用于表示用户的基本信息,包含 id 和 name 两个字段。代码如下:
- @Data
- public class User {
- private Long id;
- private String name;
- }
复制代码 2.3、配置MyBatis Mapper层
创建一个接口 UserMapper 来利用数据库,并通过 MyBatis 的 XML 文件实现具体的 SQL 利用:
- @Mapper
- public interface UserMapper {
- User selectUserById(Long id);
- }
复制代码- <mapper namespace="com.example.redis.mapper.UserMapper">
- <select id="selectUserById" resultType="com.example.redis.entity.User">
- SELECT id, name FROM user WHERE id = #{id}
- </select>
- </mapper>
复制代码 2.4、Redis配置与工具类
自界说的 Redis 配置类 RedisConfig,它基于 Jackson 的 JSON 序列化器进行 Redis 数据存储与读取,确保数据可以以对象形式生存到 Redis 中。(这个配置类和工具类是在网上找的)配置如下:
- @Configuration
- public class RedisConfig {
- // 自己定义了一个RedisTemplate
- @Bean
- @SuppressWarnings("all")
- public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
- // 我们为了自己开发方便,一般直接使用 <String, Object>
- RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
- template.setConnectionFactory(factory);
- // Json序列化配置
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
- ObjectMapper om = new ObjectMapper();
- om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- jackson2JsonRedisSerializer.setObjectMapper(om);
- // String 的序列化
- StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
- // key采用String的序列化方式
- template.setKeySerializer(stringRedisSerializer);
- // hash的key也采用String的序列化方式
- template.setHashKeySerializer(stringRedisSerializer);
- // value序列化方式采用jackson
- template.setValueSerializer(jackson2JsonRedisSerializer);
- // hash的value序列化方式采用jackson
- template.setHashValueSerializer(jackson2JsonRedisSerializer);
- template.afterPropertiesSet();
- return template;
- }
- }
复制代码 工具类 RedisUtil 提供了简朴的 Redis 利用方法,例如设置键值对、获取缓存、检查键是否存在等:
- package com.example.redis.utils;
- import jakarta.annotation.Resource;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
- import java.util.Collection;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.TimeUnit;
- @Component
- public final class RedisUtil {
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
- public Set<String> keys(String keys){
- try {
- return redisTemplate.keys(keys);
- }catch (Exception e){
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 指定缓存失效时间
- * @param key 键
- * @param time 时间(秒)
- * @return
- */
- public boolean expire(String key, long time) {
- try {
- if (time > 0) {
- redisTemplate.expire(key, time, TimeUnit.SECONDS);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 根据key 获取过期时间
- * @param key 键 不能为null
- * @return 时间(秒) 返回0代表为永久有效
- */
- public long getExpire(String key) {
- return redisTemplate.getExpire(key, TimeUnit.SECONDS);
- }
- /**
- * 判断key是否存在
- * @param key 键
- * @return true 存在 false不存在
- */
- public boolean hasKey(String key) {
- try {
- return redisTemplate.hasKey(key);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 删除缓存
- * @param key 可以传一个值 或多个
- */
- @SuppressWarnings("unchecked")
- public void del(String... key) {
- if (key != null && key.length > 0) {
- if (key.length == 1) {
- redisTemplate.delete(key[0]);
- } else {
- redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
- }
- }
- }
- /**
- * 普通缓存获取
- * @param key 键
- * @return 值
- */
- public Object get(String key) {
- return key == null ? null : redisTemplate.opsForValue().get(key);
- }
- /**
- * 普通缓存放入
- * @param key 键
- * @param value 值
- * @return true成功 false失败
- */
- public boolean set(String key, Object value) {
- try {
- redisTemplate.opsForValue().set(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 普通缓存放入, 不存在放入,存在返回
- * @param key 键
- * @param value 值
- * @return true成功 false失败
- */
- public boolean setnx(String key, Object value) {
- try {
- redisTemplate.opsForValue().setIfAbsent(key,value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 普通缓存放入并设置时间
- * @param key 键
- * @param value 值
- * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
- * @return true成功 false 失败
- */
- public boolean set(String key, Object value, long time) {
- try {
- if (time > 0) {
- redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
- } else {
- set(key, value);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 普通缓存放入并设置时间,不存在放入,存在返回
- * @param key 键
- * @param value 值
- * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
- * @return true成功 false 失败
- */
- public boolean setnx(String key, Object value, long time) {
- try {
- if (time > 0) {
- redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
- } else {
- set(key, value);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 递增
- * @param key 键
- * @param delta 要增加几(大于0)
- * @return
- */
- public long incr(String key, long delta) {
- if (delta < 0) {
- throw new RuntimeException("递增因子必须大于0");
- }
- return redisTemplate.opsForValue().increment(key, delta);
- }
- /**
- * 递减
- * @param key 键
- * @param delta 要减少几(小于0)
- * @return
- */
- public long decr(String key, long delta) {
- if (delta < 0) {
- throw new RuntimeException("递减因子必须大于0");
- }
- return redisTemplate.opsForValue().increment(key, -delta);
- }
- /**
- * HashGet
- * @param key 键 不能为null
- * @param item 项 不能为null
- * @return 值
- */
- public Object hget(String key, String item) {
- return redisTemplate.opsForHash().get(key, item);
- }
- /**
- * 获取hashKey对应的所有键值
- * @param key 键
- * @return 对应的多个键值
- */
- public Map<Object, Object> hmget(String key) {
- return redisTemplate.opsForHash().entries(key);
- }
- /**
- * HashSet
- * @param key 键
- * @param map 对应多个键值
- * @return true 成功 false 失败
- */
- public boolean hmset(String key, Map<String, Object> map) {
- try {
- redisTemplate.opsForHash().putAll(key, map);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * HashSet 并设置时间
- * @param key 键
- * @param map 对应多个键值
- * @param time 时间(秒)
- * @return true成功 false失败
- */
- public boolean hmset(String key, Map<String, Object> map, long time) {
- try {
- redisTemplate.opsForHash().putAll(key, map);
- if (time > 0) {
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 向一张hash表中放入数据,如果不存在将创建
- * @param key 键
- * @param item 项
- * @param value 值
- * @return true 成功 false失败
- */
- public boolean hset(String key, String item, Object value) {
- try {
- redisTemplate.opsForHash().put(key, item, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 向一张hash表中放入数据,如果不存在将创建
- * @param key 键
- * @param item 项
- * @param value 值
- * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
- * @return true 成功 false失败
- */
- public boolean hset(String key, String item, Object value, long time) {
- try {
- redisTemplate.opsForHash().put(key, item, value);
- if (time > 0) {
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 删除hash表中的值
- * @param key 键 不能为null
- * @param item 项 可以使多个 不能为null
- */
- public void hdel(String key, Object... item) {
- redisTemplate.opsForHash().delete(key, item);
- }
- /**
- * 判断hash表中是否有该项的值
- * @param key 键 不能为null
- * @param item 项 不能为null
- * @return true 存在 false不存在
- */
- public boolean hHasKey(String key, String item) {
- return redisTemplate.opsForHash().hasKey(key, item);
- }
- /**
- * hash递增 如果不存在,就会创建一个 并把新增后的值返回
- * @param key 键
- * @param item 项
- * @param by 要增加几(大于0)
- * @return
- */
- public double hincr(String key, String item, double by) {
- return redisTemplate.opsForHash().increment(key, item, by);
- }
- /**
- * hash递减
- * @param key 键
- * @param item 项
- * @param by 要减少记(小于0)
- * @return
- */
- public double hdecr(String key, String item, double by) {
- return redisTemplate.opsForHash().increment(key, item, -by);
- }
- /**
- * 根据key获取Set中的所有值
- * @param key 键
- * @return
- */
- public Set<Object> sGet(String key) {
- try {
- return redisTemplate.opsForSet().members(key);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 根据value从一个set中查询,是否存在
- * @param key 键
- * @param value 值
- * @return true 存在 false不存在
- */
- public boolean sHasKey(String key, Object value) {
- try {
- return redisTemplate.opsForSet().isMember(key, value);
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 将数据放入set缓存
- * @param key 键
- * @param values 值 可以是多个
- * @return 成功个数
- */
- public long sSet(String key, Object... values) {
- try {
- return redisTemplate.opsForSet().add(key, values);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- /**
- * 将set数据放入缓存
- * @param key 键
- * @param time 时间(秒)
- * @param values 值 可以是多个
- * @return 成功个数
- */
- public long sSetAndTime(String key, long time, Object... values) {
- try {
- Long count = redisTemplate.opsForSet().add(key, values);
- if (time > 0){
- expire(key, time);
- }
- return count;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- /**
- * 获取set缓存的长度
- * @param key 键
- * @return
- */
- public long sGetSetSize(String key) {
- try {
- return redisTemplate.opsForSet().size(key);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- /**
- * 移除值为value的
- * @param key 键
- * @param values 值 可以是多个
- * @return 移除的个数
- */
- public long setRemove(String key, Object... values) {
- try {
- Long count = redisTemplate.opsForSet().remove(key, values);
- return count;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- // ===============================list=================================
- /**
- * 获取list缓存的内容
- * @param key 键
- * @param start 开始
- * @param end 结束 0 到 -1代表所有值
- * @return
- */
- public List<Object> lGet(String key, long start, long end) {
- try {
- return redisTemplate.opsForList().range(key, start, end);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 获取list缓存的长度
- * @param key 键
- * @return
- */
- public long lGetListSize(String key) {
- try {
- return redisTemplate.opsForList().size(key);
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- /**
- * 通过索引 获取list中的值
- * @param key 键
- * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
- * @return
- */
- public Object lGetIndex(String key, long index) {
- try {
- return redisTemplate.opsForList().index(key, index);
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 将list放入缓存
- * @param key 键
- * @param value 值
- * @return
- */
- public boolean lSet(String key, Object value) {
- try {
- redisTemplate.opsForList().rightPush(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 将list放入缓存
- * @param key 键
- * @param value 值
- * @param time 时间(秒)
- * @return
- */
- public boolean lSet(String key, Object value, long time) {
- try {
- redisTemplate.opsForList().rightPush(key, value);
- if (time > 0){
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 将list放入缓存
- * @param key 键
- * @param value 值
- * @return
- */
- public boolean lSet(String key, List<Object> value) {
- try {
- redisTemplate.opsForList().rightPushAll(key, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 将list放入缓存
- *
- * @param key 键
- * @param value 值
- * @param time 时间(秒)
- * @return
- */
- public boolean lSet(String key, List<Object> value, long time) {
- try {
- redisTemplate.opsForList().rightPushAll(key, value);
- if (time > 0){
- expire(key, time);
- }
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 根据索引修改list中的某条数据
- * @param key 键
- * @param index 索引
- * @param value 值
- * @return
- */
- public boolean lUpdateIndex(String key, long index, Object value) {
- try {
- redisTemplate.opsForList().set(key, index, value);
- return true;
- } catch (Exception e) {
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 移除N个值为value
- * @param key 键
- * @param count 移除多少个
- * @param value 值
- * @return 移除的个数
- */
- public long lRemove(String key, long count, Object value) {
- try {
- Long remove = redisTemplate.opsForList().remove(key, count, value);
- return remove;
- } catch (Exception e) {
- e.printStackTrace();
- return 0;
- }
- }
- }
复制代码 2.5、Service层实现
在 UserService 中,创建数据时不仅将数据写入数据库,还将其缓存到 Redis 中。查询数据时,优先从 Redis 中获取,若 Redis 中不存在,则查询数据库并缓存结果。还会记载并输出 Redis 和数据库的查询时间:
- @Service
- public class UserService {
- @Autowired
- private UserMapper userMapper;
- @Autowired
- private RedisUtil redisUtil;
- public User getUserById(Long id){
- //先从Redis中读取数据
- String key = "user:"+id;
- //判断该数据是否在Redis中
- long redisStartTime = System.currentTimeMillis();
- Object testUser = redisUtil.hasKey(key)?redisUtil.get(key):null;//先判断是否有,有就得到,没有就返回null
- long redisEndTime = System.currentTimeMillis();
- //存在的话则返回数据
- if(testUser!=null){
- System.out.println("Redis 查询时间: " + (redisEndTime - redisStartTime) + " ms");
- return (User) testUser;
- }
- //不存在的话则从数据库中读取数据并将数据保存到Redis中
- long dbStartTime = System.currentTimeMillis();
- User user = userMapper.selectUserById(id);
- long dbEndTime = System.currentTimeMillis();
- System.out.println("数据库查询时间: " + (dbEndTime - dbStartTime) + " ms");
- redisUtil.set(key,user);
- return user;
- }
- }
复制代码 2.6、Controller层实现
通过 UserController 提供 RESTful 接口,用于创建用户和查询用户信息:
- @RestController
- @RequestMapping("/test")
- public class UserController {
- @Autowired
- private UserService userService;
- @GetMapping("/{id}")
- public User test1(@PathVariable Long id) {
- return userService.getUserById(id);
- }
- }
复制代码 三、Redis相关知识点整理:
1. Redis 在缓存中的作用
在本项目中,Redis 主要用于缓存用户数据。通常,在频繁的数据库读利用中,每次查询数据库都会泯灭一定的时间和资源。而 Redis 作为一个基于内存的缓存,可以极大地减少对数据库的直接访问,提拔体系性能。
在下面我展示一下使用数据库与Redis查询数据的时间对比,还是很显着的,使用Redis能很洪流平地减少查询时间
2. Redis 与数据库的数据存取逻辑
在 UserService 中,我们实现了从 Redis 获取数据的逻辑:
- Object cachedUser = redisUtil.hasKey(key) ? redisUtil.get(key) : null;
复制代码 这里,我们起首判断 Redis 中是否已经缓存了对应用户数据(通过 redisUtil.hasKey(key))。如果缓存存在,则直接从 Redis 中获取数据,从而避免了数据库查询的延时。如果缓存不存在,则实行数据库查询,并将查询结果存入 Redis:
- User user = userMapper.selectUserById(id);
- redisUtil.set(key, user);
复制代码 这种 "缓存穿透" 的模式,确保了只有在 Redis 缓存未命中的环境下才会访问数据库,从而实现了高效的数据查询。
3. Redis 数据的存储与序列化
在 Redis 中存储复杂数据范例(如 Java 对象)时,使用了 Jackson2JsonRedisSerializer 来序列化对象:
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
- template.setValueSerializer(jackson2JsonRedisSerializer);
- template.setHashValueSerializer(jackson2JsonRedisSerializer);
复制代码 这段代码确保了 Redis 可以将 User 对象序列化为 JSON 字符串并存储在 Redis 中,当需要从缓存中获取数据时,再反序列化为 Java 对象。
四、总结:
在本篇文章中,我们通过一个简朴的 Spring Boot 项目,联合 Redis 和 MySQL 实现了数据的存取,并比力了它们在时间上的差异。通过 Redis 的引入,可以明显提拔体系的性能,特别是在频繁读写的场景下,缓存策略能够有效减轻数据库的压力。
本项目展示了怎样使用 Redis 来优化应用性能,以及 Redis 在现代应用架构中的告急性。如果你正在构建一个需要高性能、低延迟的应用,Redis 绝对是你不可或缺的技能之一。
如果这篇文章有资助到你的话,就点个赞和关注吧,你的鼓励是我最大的动力!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |