环境搭建
①、在项目pom.xml文件中导入spring-data-redis的maven坐标
<!--Spring data redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>②、在项目的配置文件中加入Redis相关配置(在Spring层级下)
redis: jedis: pool: max-idle: 5 #最大链接数,连接池中最多有10个 min-idle: 1 # 最大空闲数 max-wait: 1000ms #连接池最大阻塞等待时间 max-active: 10 #最大链接数 host: 127.0.0.1 port: 6379 database: 2 # password:
HttpSession当中的。但是实际的业务场景中,一般验证码都是需要设置过期时间的,如果存在HttpSession中就无法设置过期时间,此时我们就需要对这一块的功能进行优化UserController中注入RedisTemplate对象,用于操作RedisUserController的sendMsg方法中,将随机生成的验证码缓存到Redis中,并设置有效期为5分组UserController的login方法中,从Redis中获取缓存的验证码,如果登录成功则删除Redis中的验证码①、在UserController中注入RedisTemplate对象,用于操作Redis
@Autowired
private RedisTemplate<String, String> redisTemplate;
②、在UserController的sendMsg方法中,将生成的验证码保存到Redis中(为了测试方便,这里是直接生成了固定的验证码,没有调用真实的生成验证码的API)
// 将登录账号的信息存储在redis中
// 获取字符串的客户端
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
// 存储验证码,让验证码失效时间是一分钟
valueOperations.set("SMS_" + user.getPhone(), code, 1, TimeUnit.MINUTES);
③、在登录校验的代码中实现从Redis中取出数据
// 2. 获取正确的验证码
// String verifyCode = (String) session.getAttribute("SMS_" + inputPhone);
// 从redis中获取正确的验证码
String verifyCode = redisTemplate.opsForValue().get("SMS_" + inputPhone);




之前项目中已经实现了移动端菜品查看的功能,对应的服务端方法为DishController的list方法,此方法会根据前端提交的查询条件(categoryId)进行数据库查询操作。
在高并发的情况下,频繁查询数据库会导致系统性能下降,服务端响应时间增长;针对这个问题,可以对此方法进行缓存优化,提高系统的性能
那么应该缓存多少分数据呢?是所有的菜品缓存一份,还是需要根据分类的不同,缓存多份?
具体实现思路
list方法,先从Redis中获取分类对应的菜品数据,如果有则直接返回,无需查询数据库;如果没有,则查询数据库,并将查询到的菜品数据存入RedisDishController的save和update方法,加入清理缓存的逻辑注意事项
- 在使用缓存的过程当中,要注意保证数据库中的数据和缓存中的数据保持一致
- 如果数据库中的数据发生变化,需要及时清理缓存数据。否则就会造成缓存数据与数据库数据不一致的情况
在增加缓存之前,需要对存储进Redis中的数据进行一个简单的设计,如下所示
数据类型 key值 value值 String dish_菜品分类的id 菜品的List集合(List )
①、在DishServiceImpl中注入RedisTemplate
@Autowired
private RedisTemplate<String, String> redisTemplate;
②、在list方法中,查询数据库之前,先查询缓存,如果缓存有数据,则直接返回
// 根据分类id查询菜品列表数据
@Override
public List<DishDto> selectByCategoryIdAndStatus(Long categoryId, Integer status) {
// 0. 首先先判断Redis中是否存在缓存
// 获取redis操作字符串的客户端
ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
List<DishDto> dishDtoList = JSON.parseObject(valueOperations.get("dish_" + categoryId + "_" + status), List.class);
// 如果redis中不存在这个缓存,则查询数据库,并且将查询到的结果存储到缓存中
if (dishDtoList == null) {
// 1. 调用 dao 层对象执行sql语句查询数据
List<Dish> dishList = dishMapper.selectByCategoryIdAndStatus(categoryId, status);
// 2. 遍历dishList,查询其相对应的口味数据表
dishDtoList = dishList.stream().map(dish -> {
// 查询对应的口味表
List<DishFlavor> dishFlavorList = dishFlavorMapper.selectByDishId(dish.getId());
DishDto dishDto = new DishDto();
// 将数据封装到dishDto中
dishDto.setFlavors(dishFlavorList);
// 将基本属性复制给dishDto
BeanUtils.copyProperties(dish, dishDto);
return dishDto;
}).collect(Collectors.toList());
// 把查询到的数据,存储到Redis中
// 把dishDtoList对象转换为Json格式
String dishJson = JSON.toJSONString(dishDtoList);
valueOperations.set("dish_" + categoryId, dishJson, 2, TimeUnit.DAYS);
}
// 返回数据
return dishDtoList;
}
为了保证数据库中的数据和缓存中的数据一致,如果数据库中的数据发生变化,需要及时清理缓存数据
所以,需要在菜品的增删改中清空缓存数据
清理菜品缓存的方式有两种
A、清理所有分类下的菜品缓存
//清理所有菜品的缓存数据
Set keys = redisTemplhate.keys("dish_*"); //获取所有以dish_xxx开头的key
redisTemplate.delete(keys); //删除这些key
B、清理当前添加菜品分类下的缓存
//清理某个分类下面的菜品缓存数据
String key = "dish_" + dishDto.getCategoryId();
redisTemplate.delete(key);
两者的优劣(需要结合实际的业务场景考虑)
这里清理缓存的操作比较简单,就不演示了,只需要在数据发生变更后的代码后面添加一个删除缓存的代码即可,如下所示





Spring Cache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能,大大简化我们在业务中操作缓存的代码
Spring Cache只是提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。CacheManager是Spring提供的各种缓存技术抽象接口
针对不同的缓存技术需要实现不同的CacheManager,如下表所示
| CacheManager | 描述 |
|---|---|
| EhCacheCacheManager | 使用EhCache作为缓存技术 |
| GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
| RedisCacheManager | 使用Redis作为缓存技术 |
| spring 自己也搞了一套缓存技术,默认的缓存 spring缓存是缓存在Map集合中 |
在SpringCache中提供了很多缓存操作的注解,常见的几个如下所示
| 注解 | 说明 |
|---|---|
| @EnableCaching | 开启缓存注解功能 |
| @Cacheable | 在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,则调用方法并将方法返回值放到缓存中 |
| @CachePut | 将方法的返回值或者参数放到缓存中 |
| @CacheEvict | 将一条或多条数据从缓存中删除 |
在SpringBoot项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用@EnableCaching开启缓存支持即可
①、数据库准备
/*
SQLyog Ultimate v11.33 (64 bit)
MySQL - 5.5.40 : Database - cache_demo
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`cache_demo` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `cache_demo`;
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `user` */
LOCK TABLES `user` WRITE;
UNLOCK TABLES;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
②、导入基本工程

③、注入CacheManager
UserController注入一个CacheManager,在Debug时,我们可以通过CacheManger跟踪缓存中数据的变化

ConcurrentMapCacheManger,稍后我们可以通过断点的形式跟踪缓存数据的变化④、启动类加上@EnableCaching注解

- @CachePut注解说明
- 作用
- 将方法返回值,放入缓存
- value
- 缓存的名称,每个缓存名称下面可以有很多key
- key
- 缓存的key,支持Spring的表达式语言SPEL语法
①、在save方法上加上注解@CachePut
当前UserController的save方法是用来保存用户信息的,我们希望在该用户信息保存到数据库的同时,也往缓存中缓存一份数据,我们可以在save方法上加上注解 @CachePut,如下所示
/**
* CachePut:将方法返回值放入缓存
* value:缓存的名称,每个缓存名称下面可以有多个key
* key:缓存的key
*/
@CachePut(value = "userCache", key = "#user.id")
@PostMapping
public User save(@RequestBody User user){
userService.save(user);
return user;
}
key的写法如下:
#user.id
#user指的是方法形参的名称, id指的是user的id属性 , 也就是使用user的id属性作为key#user.name
#user指的是方法形参的名称, name指的是user的name属性 ,也就是使用user的name属性作为key#result.id
#result代表方法返回值,该表达式 代表以返回对象的id属性作为key#result.name
#result代表方法返回值,该表达式 代表以返回对象的name属性作为key
②、使用Postman进行功能测试
UserController的方法, 然后通过断点(Debug)的形式跟踪缓存数据



PS
- @CacheEvict注解说明
- 作用
- 清理指定缓存
- value
- 缓存的名称,每个缓存名称下面可以有多个key
- key
- 缓存的key,支持Spring的表达式语言SPEL语法
①、在delete方法上加@CacheEvict注解
当我们在删除数据库user表数据的时候,需要删除缓存中对应的数据,此时就可以使用@CacheEvict注解,如下所示
/**
* CacheEvict:清理指定缓存
* value:缓存的名称,每个缓存名称下面可以有多个key
* key:缓存的key
*/
@CacheEvict(value = "userCache", key = "#p0")
//#p0 代表第一个参数
//@CacheEvict(value = "userCache",key = "#root.args[0]") //#root.args[0] 代表第一个参数
//@CacheEvict(value = "userCache",key = "#id") //#id 代表变量名为id的参数
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id){
userService.removeById(id);
}
②、使用Postman进行功能测试



③、在update方法上加注解@CacheEvict
在更新数据之后,数据库的数据已经发生了变更,我们需要将缓存中对应的数据删除掉,避免出现数据库数据与缓存数据不一致的情况
//@CacheEvict(value = "userCache",key = "#p0.id") //第一个参数的id属性
//@CacheEvict(value = "userCache",key = "#user.id") //参数名为user参数的id属性
//@CacheEvict(value = "userCache",key = "#root.args[0].id") //第一个参数的id属性
@CacheEvict(value = "userCache",key = "#result.id") //返回值的id属性
@PutMapping
public User update(@RequestBody User user){
userService.updateById(user);
return user;
}
加上注解之后,重启服务,然后使用Postman进行测试,测试步骤和方法跟上述①、②差不多,这里就不再演示
- @Cacheable注解说明
- 作用
- 在方法执行前,Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放入到缓存中
- value
- 缓存的名称,每个缓存名称下面可以有多个key
- key
- 缓存的key,支持Spring的表达式语言SPEL语法
①、在getById方法上加@Cacheable注解
@Cacheable(value = "user",key = "#id")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.findById(id);
return user;
}
②、使用Postman进行功能测试
Controller的方法,查询数据库。后面再查询相同的id,就直接获取到数据库,不用再查询数据库了,就说明缓存已经生效







③、缓存非null值
在@Cacheable注解中,提供了两个属性分别为:condition、unless
condition
unless
具体实现方法如下所示
/**
* 注意: @Cacheable把方法的返回值缓存起来, 即使方法返回值为null也会被缓存,如果需要改变这个结果:
* condition : 符合指定条件则缓存,这个属性不建议使用,因为condition这个属性不能使用result。
* unless : 不符合指定条件则缓存
*/
// @Cacheable 执行方法前先判断缓存是否存在指定id的user,
// 如果存在不会执行方法,直接返回缓存中数据即可。如果不存在才会返回缓存数据
@Cacheable(value = "user",key = "#id",unless = "#result==null")
@GetMapping("/{id}")
public User getById(@PathVariable Long id){
User user = userService.findById(id);
return user;
}
这里这能使用unless,因为condition属性无法获取到结果#result
在使用上述默认的ConcurrentHashMap做缓存时,服务重启之后,之前缓存的数据就全部丢失了,操作起来并不友好。在项目中使用,我们会选择使用redis来做缓存,主要需要操作以下几步
①、添加依赖
<!--spring cache依赖导入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--spring data redis 导入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
②、配置application.yaml配置文件(Spring层级下)
redis:
host: 127.0.0.1
database: 2
cache:
redis:
time-to-live: 1800000 #单位毫秒,设置缓存过期时间,可选
③、测试

SetmealController的list方法,此方法会根据前端提交的查询条件进行数据库查询操作①、导入SpringCache和Redis相关的maven坐标(spring data redis之前已经导入过了)
<!--spring cache依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
②、在application.yml配置文件中配置缓存数据的过期时间
cache:
redis:
time-to-live: 1800000 # 设置缓存数据过期时间
③、在启动类上加入@EnableCaching注解,开启缓存注解功能
package com.coolman;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@MapperScan(basePackages = "com.coolman.mapper")
@ServletComponentScan(basePackages = "com.coolman.filters")
@EnableTransactionManagement //开启对事务管理的支持
@EnableCaching
public class ReggieApplication {
public static void main(String[] args) {
SpringApplication.run(ReggieApplication.class, args);
}
}
④、在SetmealServiceImpl的selectByCategoryIdAndStatus方法上加入@Cacheable注解
在进行套餐数据查询时,需要根据分类ID和套餐的状态进行查询,所以在缓存数据的时候,可以将套餐分类id和套餐状态组合起来作为key,如下所示
1627182182_1(1627182182是分类id,1是状态) @Override
@Cacheable(value = "setmeal", key = "#categoryId + '_' + #status")
public List<SetMeal> selectByCategoryIdAndStatus(Long categoryId, Integer status) {
return setMealMapper.selectByCategoryIdAndStatus(categoryId, status);
}
⑤、在SetmealServiceImpl的数据变更方法上加入@CacheEvict注解
为了保证数据库中数据与缓存数据的一致性,在添加套餐或者删除套餐数据之后,需要清空当前套餐缓存的全部数据
那么@CacheEvict注解如何清除某一份缓存下所有的数据呢?这里可以指定@CacheEvict中的一个属性allEnties,将其设置为true即可,其含义为setmeal名称空间下面的所有key都删除
@CacheEvict(value = "setmeal", allEntries = true) // allEntries = true 代表了setmeal名称空间下面的所有key都删除
public void update(SetMealDto setMealDto) {
// 补全数据,更新时间
setMealDto.setUpdateTime(LocalDateTime.now());
// 更新setmeal表的数据
setMealMapper.updateByIds(setMealDto, new Long[]{setMealDto.getId()});
// 更新setmeal_dish表的数据
// 先删除,再修改
// 原因: 前端返回的数据是一个集合,有多个id,同时其中的数据也不是固定不变的,且是一对多关系,处理起来非常麻烦
// 给setmeal_dish补全数据
List<SetMealDish> setmealDishes = setMealDto.getSetmealDishes();
for (SetMealDish setmealDish : setmealDishes) {
// 设置创建人和创建时间
setmealDish.setCreateUser(setMealDto.getCreateUser());
setmealDish.setCreateTime(setMealDto.getCreateTime());
// 设置更新时间和更新人
setmealDish.setUpdateUser(setMealDto.getUpdateUser());
setmealDish.setUpdateTime(LocalDateTime.now());
// 设置setmeal_id
setmealDish.setSetmealId(setMealDto.getId().toString());
// 设置sort
setmealDish.setSort(0);
}
// 根据id批量删除数据
ArrayList<String> ids = new ArrayList<>();
ids.add(setMealDto.getId().toString());
setMealDishMapper.deleteByIds(ids);
// 批量插入数据
setMealDishMapper.batchInsert(setmealDishes);
}
上述代码只是修改功能的一个方法,其他有数据变更的操作,一般都要清理缓存,以保证数据库的数据和缓存的数据一致





RedisTemplate对象的时候没有删除测试代码,所以不管传入的status值是否为null值,其都会存储到缓存中,不过在这里无伤大雅,仅作为测试,在实际应用场景中,非常不建议同时使用RedisTemplate和Spring redis data我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
我一直很高兴地使用DelayedJob习惯用法:foo.send_later(:bar)这会调用DelayedJob进程中对象foo的方法bar。我一直在使用DaemonSpawn在我的服务器上启动DelayedJob进程。但是...如果foo抛出异常,Hoptoad不会捕获它。这是任何这些包中的错误...还是我需要更改某些配置...或者我是否需要在DS或DJ中插入一些异常处理来调用Hoptoad通知程序?回应下面的第一条评论。classDelayedJobWorker 最佳答案 尝试monkeypatchingDelayed::W
前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon
三分钟集成Tap防沉迷SDK(Unity版)一、SDK介绍基于国家对上线所有游戏必须增加防沉迷功能的政策下,TapTap推出防沉迷SDK,供游戏开发者进行接入;允许未成年用户在周五、六、日以及法定节假日晚上8:00-9:00进行游戏,防沉谜时间段进入游戏会弹窗进行提示!开发环境要求:Unity2019.4或更高版本iOS10或更高版本Android5.0(APIlevel21)或更高版本🔗Unity集成Demo参考链接🔗UnityTapSDK功能体验APK下载链接二、集成前准备1.创建应用进入开发者后台,按照提示开始创建应用;2.开通服务在使用TDS实名认证和防沉迷服务之前,需要在上面创建的应
我被这个难住了。到目前为止教程中的一切都进行得很顺利,但是当我将这段代码添加到我的/spec/requests/users_spec.rb文件中时,事情开始变得糟糕:describe"success"doit"shouldmakeanewuser"dolambdadovisitsignup_pathfill_in"Name",:with=>"ExampleUser"fill_in"Email",:with=>"ryan@example.com"fill_in"Password",:with=>"foobar"fill_in"Confirmation",:with=>"foobar"cl
我需要一些指导来了解如何将Angular整合到rails中。选择Rails的原因:我喜欢他们偏执的做事方式。还有迁移,gem真的很酷。使用angular的原因:我正在研究和寻找最适合SPA的框架。Backbone似乎太抽象了。我不得不在Angular和Ember之间做出选择。我首先开始阅读Angular,它对我来说很有意义。所以我从来没有去读过关于ember的文章。使用Angular和Rails的原因:我研究并尝试使用小型框架,例如grape、slim(是的,我也使用php)。但我觉得需要坚持项目的长期范围。我个人喜欢用Rails的方式做事。这就是我需要帮助的地方,我在Rails4中有
有没有人有在Maven中运行用Ruby编写的单元测试的经验。任何输入,如要使用的库/maven插件,将不胜感激!我们已经在使用Maven+hudson+Junit。但是我们正在引入Ruby单元测试,找不到任何同样好的组合。 最佳答案 我建议让Maven使用ExecMavenPlugin启动rake测试(exec:exec目标)并使用ci_reportergem生成单元测试结果的XML文件,Hudson、Bamboo等可以读取该文件,以与JUnit测试相同的格式显示测试结果。如果您不需要使用mvntest运行Ruby测试,您也可以只使
目前我有一小套针对我的网络服务器运行的集成测试,它发出请求并断言一些关于响应应该是什么的假设。这些是用Ruby编写的,生成http请求。我一直在看Gatling作为压力测试工具,但我想知道它是否也可以用于集成测试。这样,所有端点请求都可以在压力测试和集成测试中重复使用。我可能在这里失去了一些东西,因为没有RSpec的BDD,但不必两次创建相同的测试。有没有人有这样使用gatling的经验? 最佳答案 您可以使用AssertionAPI并设置验收标准。但是,Gatling不是浏览器,不会运行/测试您的Javascript,因此这种方法
文章目录前言一、Elasticsearch版本介绍二、客户端种类三、客户端与版本兼容性四、引入Elasticsearch依赖包五、客户端配置六、Elasticsearch使用前言ElasticSearch是Elastic公司出品的一款功能强大的搜索引擎,被广泛的应用于各大IT公司,它的代码位于https://github.com/elastic/elasticsearch,目前是一个开源项目。ElasticSearch公司的另外两个开源产品Logstash、Kibana与ElasticSearch构成了著名的ELK技术栈。。他们三个共同形成了一个强大的生态圈。简单地说,Logstash负责数据
如果您希望在Spring中启用定时任务功能,则需要在主类上添加 @EnableScheduling 注解。这样Spring才会扫描 @Scheduled 注解并执行定时任务。在大多数情况下,只需要在主类上添加 @EnableScheduling 注解即可,不需要在Service层或其他类中再次添加。以下是一个示例,演示如何在SpringBoot中启用定时任务功能:@SpringBootApplication@EnableSchedulingpublicclassApplication{publicstaticvoidmain(String[]args){SpringApplication.ru