无锡论坛网本地网站,深圳网站建设资讯,艾迪网络专业的网站建设公司,一套网站开发需要多少钱文章目录 1. 前言2. 缓存2.1 什么是缓存2.2 使用缓存的好处2.3 缓存的成本2.4 使用Spring Cache和Redis的优点 3. Spring Cache基础知识3.1 Spring Cache的核心概念3.2 Spring Cache的注解3.2.1 SpEL表达式3.2.2 Cacheable3.2.3 CachePut3.2.4 CacheEvict 4. 实现查询数据缓存4… 文章目录 1. 前言2. 缓存2.1 什么是缓存2.2 使用缓存的好处2.3 缓存的成本2.4 使用Spring Cache和Redis的优点 3. Spring Cache基础知识3.1 Spring Cache的核心概念3.2 Spring Cache的注解3.2.1 SpEL表达式3.2.2 Cacheable3.2.3 CachePut3.2.4 CacheEvict 4. 实现查询数据缓存4.1 准备工作4.2 添加依赖4.3 修改配置文件4.4 配置缓存管理器4.5 使用Spring Cache注解4.6 测试4.6.1 查询测试4.6.2 更新、删除测试 5. 总结 1. 前言 在现代应用程序中查询缓存的使用已经变得越来越普遍。它不仅能够显著提高系统的性能还能提升用户体验。缓存通过在内存中存储频繁访问的数据减少对数据库或其他存储系统的访问从而加快数据读取速度。在这篇文章中我们将探讨缓存的基本概念、重要性以及如何使用Spring Cache和Redis实现查询数据缓存 。 2. 缓存
2.1 什么是缓存
缓存是一种临时存储机制用于在内存中保存频繁访问的数据。它可以是硬件如CPU缓存或软件如应用程序缓存。缓存的主要目的是通过减少数据访问的延迟提高系统的响应速度。以下是缓存的一些关键特性
临时性缓存中的数据通常是临时的会在一段时间后失效或被替换。快速访问由于缓存数据存储在内存中访问速度非常快。空间有限缓存的存储空间通常有限因此需要有效的管理策略如LRU最近最少使用策略。
2.2 使用缓存的好处
提高性能缓存可以显著减少数据读取的时间因为内存访问速度比硬盘或网络存储快很多。减轻数据库负载缓存可以减少数据库的查询次数从而减轻数据库的负载提升整体系统的稳定性和可扩展性。节省资源通过减少对后端系统的访问缓存可以帮助节省带宽和计算资源。提高用户体验快速的数据访问可以显著提升用户体验特别是在需要频繁读取数据的应用场景中。
2.3 缓存的成本
内存消耗缓存需要占用系统的内存资源过多的缓存可能会影响其他应用程序的性能。数据一致性缓存中的数据可能会与数据库中的数据不一致尤其是在数据频繁更新的场景中。需要设计有效的缓存失效策略来保证数据的一致性。复杂性增加引入缓存机制会增加系统的复杂性需要处理缓存的管理、更新和失效等问题。维护成本缓存系统需要定期监控和维护以确保其高效运行。
2.4 使用Spring Cache和Redis的优点 为了实现高效的数据缓存Spring Boot提供了Spring Cache模块而Redis则是一个强大的缓存数据库。结合使用Spring Cache和Redis能够充分发挥二者的优点实现高效的数据缓存。 Spring Cache的优点 简化缓存操作Spring Cache提供了一系列注解如Cacheable、CachePut、CacheEvict简化了缓存的使用使开发者能够专注于业务逻辑。灵活的缓存管理Spring Cache支持多种缓存提供者如EhCache、Hazelcast、Redis等可以根据具体需求选择合适的缓存实现。透明的缓存机制Spring Cache使得缓存操作对业务代码透明开发者无需关心缓存的具体实现细节。 Redis的优点 高性能由于数据存储在内存中Redis的读写速度非常快能够处理每秒数百万级别的请求。丰富的数据结构Redis支持多种数据结构如字符串、哈希、列表、集合、有序集合等能够满足不同场景下的数据存储需求。持久化支持虽然Redis主要用于内存存储但它也提供了数据持久化的功能可以将数据定期保存到磁盘防止数据丢失。分布式支持Redis支持主从复制、哨兵模式和集群模式能够实现高可用性和数据的水平扩展。灵活的过期策略Redis支持为每个键设置过期时间自动删除过期数据方便实现缓存失效策略。
3. Spring Cache基础知识 在Spring Boot中Spring Cache提供了一套简洁且强大的缓存抽象机制帮助开发者轻松地将缓存集成到应用程序中。以下是Spring Cache的一些核心概念和常用注解。 3.1 Spring Cache的核心概念 CacheManager 定义CacheManager是Spring Cache的核心接口负责管理多个缓存实例。它是缓存操作的入口点提供了获取和操作缓存实例的方法。实现Spring提供了多种CacheManager实现如ConcurrentMapCacheManager、EhCacheCacheManager、RedisCacheManager等。不同的实现适用于不同的缓存存储机制。 Cache 定义Cache是缓存的具体实现负责存储和检索缓存数据。它提供了基本的缓存操作如put、get、evict等。实现具体的Cache实现依赖于底层的缓存存储机制如内存缓存、Redis缓存等。
3.2 Spring Cache的注解
3.2.1 SpEL表达式 因为Spring Cache使用SpEL表达式来动态生成缓存键所以在学习Spring Cache的注解之前我们还要先简单了解一下SpEL表达式的语法这部分可以先不看懂在后面看注解的时候回来看即可。 SpEL表达式的语法类似于Java的表达式语法支持以下几种操作
字面量 数字1, 2.5字符串hello, world布尔值true, false空值null 属性和方法 访问对象的属性#user.name调用对象的方法#user.getName() 运算符 算术运算, -, *, /, %比较运算, !, , , , 逻辑运算, ||, ! 集合和数组 访问集合元素#users[0]集合操作#users.size(), #users.isEmpty() 条件运算符 三元运算符condition ? trueValue : falseValueElvis运算符expression ?: defaultValue 变量 定义和使用变量#variableName
接下来进入Spring Cache注解的学习
3.2.2 Cacheable
作用Cacheable注解用于标注需要缓存的方法。当该方法被调用时Spring Cache会先检查缓存中是否存在对应的数据。如果存在则直接返回缓存数据如果不存在则执行方法并将结果存入缓存。示例RestController(/users)
RequiredArgsConstructor
public class UserController {private final UserService userService;Cacheable(value user, key #id)public User getUser(Long id) {// 获取用户的逻辑return userService.findById(id);}
}参数 value指定缓存的名称。key指定缓存的键可以使用SpEL表达式。
3.2.3 CachePut
作用CachePut注解用于标注需要更新缓存的方法。即使缓存中已经存在数据该方法仍然会执行并将结果更新到缓存中。示例RestController(/users)
RequiredArgsConstructor
public class UserController {private final UserService userService;CachePut(value user, key #user.id)public User updateUser(User user) {// 更新用户的逻辑return userService.save(user);}
}参数 value指定缓存的名称。key指定缓存的键可以使用SpEL表达式。
3.2.4 CacheEvict
作用CacheEvict注解用于标注需要清除缓存的方法。当该方法被调用时Spring Cache会清除对应的缓存数据。示例RestController(/users)
RequiredArgsConstructor
public class UserController {private final UserService userService;CacheEvict(value user, key #id)public void deleteUser(Long id) {// 删除用户的逻辑userService.deleteById(id);}
}参数 value指定缓存的名称。key指定缓存的键可以使用SpEL表达式。allEntries如果设置为true则清除缓存中的所有数据。
4. 实现查询数据缓存
4.1 准备工作
Redis安装与配置 这里可以自行查找文章进行安装和配置网上优质文章很多。 创建Product实体类
Data
AllArgsConstructor
public class Product implements Serializable {private Long id;private String name;private Integer category;private String description;private Integer stock;}创建枚举类ResultEnum
Getter
public enum ResultEnum {/* 成功状态码 */SUCCESS(1, 操作成功),/* 错误状态码 */FAIL(0, 操作失败),/* 参数错误10001-19999 */PARAM_IS_INVALID(10001, 参数无效),PARAM_IS_BLANK(10002, 参数为空),PARAM_TYPE_BIND_ERROR(10003, 参数格式错误),PARAM_NOT_COMPLETE(10004, 参数缺失),/* 用户错误20001-29999*/USER_NOT_LOGGED_IN(20001, 用户未登录请先登录),USER_LOGIN_ERROR(20002, 账号不存在或密码错误),USER_ACCOUNT_FORBIDDEN(20003, 账号已被禁用),USER_NOT_EXIST(20004, 用户不存在),USER_HAS_EXISTED(20005, 用户已存在),/* 系统错误40001-49999 */FILE_MAX_SIZE_OVERFLOW(40003, 上传尺寸过大),FILE_ACCEPT_NOT_SUPPORT(40004, 上传文件格式不支持),/* 数据错误50001-599999 */RESULT_DATA_NONE(50001, 数据未找到),DATA_IS_WRONG(50002, 数据有误),DATA_ALREADY_EXISTED(50003, 数据已存在),AUTH_CODE_ERROR(50004, 验证码错误),/* 权限错误70001-79999 */PERMISSION_UNAUTHENTICATED(70001, 此操作需要登陆系统),PERMISSION_UNAUTHORIZED(70002, 权限不足无权操作),PERMISSION_EXPIRE(70003, 登录状态过期),PERMISSION_TOKEN_EXPIRED(70004, token已过期),PERMISSION_LIMIT(70005, 访问次数受限制),PERMISSION_TOKEN_INVALID(70006, 无效token),PERMISSION_SIGNATURE_ERROR(70007, 签名失败);// 状态码int code;// 提示信息String message;ResultEnum(int code, String message) {this.code code;this.message message;}public int code() {return code;}public String message() {return message;}public void setCode(int code) {this.code code;}public void setMessage(String message) {this.message message;}
}创建统一返回结果封装类Result 相关文章可以看这里Spring Boot3统一结果封装 Data
NoArgsConstructor
public class ResultT implements Serializable {// 操作代码Integer code;// 提示信息String message;// 结果数据T data;public Result(ResultEnum resultCode) {this.code resultCode.code();this.message resultCode.message();}public Result(ResultEnum resultCode, T data) {this.code resultCode.code();this.message resultCode.message();this.data data;}public Result(String message) {this.message message;}//成功返回封装-无数据public static ResultString success() {return new ResultString(ResultEnum.SUCCESS);}//成功返回封装-带数据public static T ResultT success(T data) {return new ResultT(ResultEnum.SUCCESS, data);}//失败返回封装-使用默认提示信息public static ResultString error() {return new ResultString(ResultEnum.FAIL);}//失败返回封装-使用返回结果枚举提示信息public static ResultString error(ResultEnum resultCode) {return new ResultString(resultCode);}//失败返回封装-使用自定义提示信息public static ResultString error(String message) {return new ResultString(message);}
}4.2 添加依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-cache/artifactId
/dependency
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency4.3 修改配置文件
spring:data:redis:# Redis服务器地址host: ${shijun.redis.host}# Redis服务器端口port: ${shijun.redis.port}# Redis服务器认证密码password: ${shijun.redis.password}# Redis数据库索引database: ${shijun.redis.database}4.4 配置缓存管理器
/*** 配置类用于设置缓存管理器及相关配置以启用缓存功能** author shijun* date 2024/06/13*/
EnableCaching
Configuration
public class CacheConfig extends CachingConfigurerSupport {/*** 配置Redis键的序列化方式** return StringRedisSerializer用于序列化和反序列化Redis中的键*/private RedisSerializerString keySerializer() {return new StringRedisSerializer();}/*** 配置Redis值的序列化方式** return GenericJackson2JsonRedisSerializer使用Jackson库以JSON格式序列化和反序列化Redis中的值*/private RedisSerializerObject valueSerializer() {return new GenericJackson2JsonRedisSerializer();}/*** 缓存前缀用于区分不同的缓存命名空间一般以模块名或者服务名命名这里暂时写cache*/public static final String CACHE_PREFIX cache:;/*** 配置缓存管理器使用Redis作为缓存后端** param redisConnectionFactory Redis连接工厂用于创建Redis连接* return RedisCacheManagerRedis缓存管理器实例*/Beanpublic CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {// 配置序列化解决乱码的问题设置缓存名称的前缀和缓存条目的默认过期时间RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig()// 设置键的序列化器.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))// 设置值的序列化器.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))// 设置缓存名称的前缀.computePrefixWith(name - CACHE_PREFIX name :)// 设置缓存条目的默认过期时间为300秒.entryTtl(Duration.ofSeconds(300));// 创建非锁定的Redis缓存写入器RedisCacheWriter redisCacheWriter RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisConnectionFactory));// 返回Redis缓存管理器实例使用上述配置return new RedisCacheManager(redisCacheWriter, config);}} 分析 StringRedisSerializer 使用 StringRedisSerializer 将缓存的键序列化为字符串。因为Redis中的键通常是字符串类型使用字符串序列化器可以确保键在Redis中以可读的形式存储便于调试和管理。 GenericJackson2JsonRedisSerializer 使用 GenericJackson2JsonRedisSerializer 将缓存的值序列化为JSON格式可读性高并且便于人工排查数据。 4.5 使用Spring Cache注解 由于我们的缓存的数据源来自于数据库,而数据库的数据是会发生变化的,因此,如果当数据库中数据发生变化,而缓存却没有同步此时就会有数据一致性问题存在在一些并发场景会出现问题。 这里采用Cache Aside Pattern 即旁路缓存模式缓存调用者在更新完数据库后再去更新缓存也称之为双写方案。 读流程 分析 应用程序首先从缓存中查找数据。如果缓存命中则直接返回缓存中的数据。如果缓存未命中则从数据库中读取数据并将读取到的数据写入缓存以便后续请求可以直接从缓存中获取。 写流程 分析 应用程序首先更新数据库中的数据。然后使缓存中的对应数据失效 Slf4j
RestController(/products)
public class ProductController {/*** 根据ID获取产品信息* 通过Cacheable注解当请求的产品ID在缓存中存在时直接从缓存中获取产品信息减少数据库查询** param id 产品ID* return 返回查询结果包含指定ID的产品信息*/GetMapping(/getProductById)Cacheable(value productsCache, key #id)public ResultProduct getProductById(Long id) {// 当从数据库获取数据时会打印如果是从缓存中查询并不会执行到这里。log.info(从数据库获取产品: id {}, id);Product product new Product(id, product, 100, 课本, 10);return Result.success(product);}/*** 更新产品信息* 通过CacheEvict注解当更新产品时清除缓存中对应产品的数据确保获取到最新的数据* 设置allEntries为true表示清除整个缓存中的所有产品数据** param product 产品对象包含更新后的详细信息* return 返回更新结果成功更新时返回成功标志*/PutMapping(/updateProduct)CacheEvict(value productsCache, key #product.id)public Result updateProduct(RequestBody Product product) {// 更新操作return Result.success();}/*** 删除指定ID的产品* 通过CacheEvict注解当删除产品时清除缓存中对应产品的数据** param id 待删除产品的ID* return 返回删除结果成功删除时返回成功标志*/DeleteMapping(/deleteProductById)CacheEvict(value productsCache, key #id)public Result deleteProductById(Long id) {// 删除操作return Result.success();}}
4.6 测试
4.6.1 查询测试
发送查询请求 查看Redis中的缓存数据 通过观察可以发现CacheConfig类中的序列化配置起作用了Redis中的数据不再是一堆乱码并且在右上角还有我们之前配置的缓存的过期时间我们之前配置的300s。 查看控制台发现本次查询为从数据库查询 再次发送会发现数据成功的查询了 再次查询控制台发现并没有输出从数据库获取产品: id 1说明本次查询为从Redis缓存中获取数据。 4.6.2 更新、删除测试
分别发送更新请求和删除请求然后再次查看Redis中的缓存数据 可以发现Redis当中对应的缓存数据被删除了符合我们的设计 5. 总结
在本文中我们详细介绍了如何在Spring Boot项目中使用Spring Cache和Redis实现数据缓存并简单讲解了使用Cache Aside Pattern来解决数据一致性问题希望对大家学习有所帮助。如有问题大家可以私信或者在评论区询问。