实现步骤
导pom文件坐标
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
yaml主配置文件,配置redis端口号
spring:
redis:
host: localhost
port: 6379
测试类
字符串专用类:StringRedisTemplate stringRedisTemplate
@Autowired
public RedisTemplate redisTemplate;
@Test
public void stringTest(){
// 各种类型支持
stringRedisTemplate.opsForValue();
stringRedisTemplate.opsForList();
stringRedisTemplate.opsForSet();
stringRedisTemplate.opsForHash();
stringRedisTemplate.opsForZSet();
// 字符串
stringRedisTemplate.opsForValue().set("teacher","刘老板");
String teacher = stringRedisTemplate.opsForValue().get("teacher");
System.out.println("stringRedisTemplate输出结果"+teacher);
// 操作list列表
stringRedisTemplate.opsForList().leftPush("tang","李白");
stringRedisTemplate.opsForList().leftPush("tang","杜甫");
stringRedisTemplate.opsForList().leftPushAll("songAll","欧阳修","苏轼","苏辙");
List<String> songAll = stringRedisTemplate.opsForList().range("songAll", 0, 2);
songAll.forEach(System.out::println);
}
对象专用类:RedisTemplate redisTemplate
@Autowired(required = false)
public RedisTemplate redisTemplate;
@Test
public void redisTemplate(){
// 各种类型支持
redisTemplate.opsForValue();
redisTemplate.opsForList();
redisTemplate.opsForSet();
redisTemplate.opsForHash();
redisTemplate.opsForZSet();
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("panda","花花");
String panda = (String) valueOperations.get("panda");
System.out.println(panda);
Student student = new Student(1,"惠晨怡","女");
redisTemplate.opsForValue().set("stu",student);
Student student1 = (Student) redisTemplate.opsForValue().get("stu");
System.out.println(student1);
redisTemplate.opsForList().leftPushAll("animal","狗","猫","龙","鼠");
List animal = redisTemplate.opsForList().range("animal", 0, 3);
animal.forEach(System.out::println);
}
在可视化页面中查看对象存入的键和值,看不明白,没有可读性,可以使用自定义类
自定义类实现步骤:
pom文件导入fastJson
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.3version>
dependency>
<dependency>
<groupId>com.colobugroupId>
<artifactId>fastjson-jaxrs-json-providerartifactId>
<version>0.3.1version>
dependency>
添加配置类RedisConfig
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
// 创建自定义模板
RedisTemplate<Object, Object> template = new RedisTemplate<>();
//配置json类型的序列化工具
template.setKeySerializer(new StringRedisSerializer());
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
测试:装配的redis模板类需要和自定义的同名
@Autowired
public RedisTemplate jsonRedisTemplate;
@Test
public void test03(){
jsonRedisTemplate.opsForValue();
jsonRedisTemplate.opsForList();
jsonRedisTemplate.opsForSet();
jsonRedisTemplate.opsForHash();
jsonRedisTemplate.opsForZSet();
Student student1 = new Student(2,"惠晨怡","男");
Student student2 = new Student(3,"尚恒通","男");
Student student3 = new Student(4,"李竟坡","男");
ArrayList<Student> students = new ArrayList<>(Arrays.asList(student1,student2,student3));
jsonRedisTemplate.opsForValue().set("stus",students);
Object stus = jsonRedisTemplate.opsForValue().get("stus");
String s = JSON.toJSONString(stus);
List<Student> list = JSONObject.parseArray(s, Student.class);
list.forEach(System.out::println);
}
redis在项目中起到缓存作用,案例演示redis在项目中的实现
导入pom.xml文件
springboot版本2.7.14
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.29version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
yaml配置文件配置数据源和redis
# 配置数据源
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring?serverTimezone=GMT
username: root
password: 123456
# 配置redis
redis:
host: localhost
port: 6379
mybatis:
configuration:
map-underscore-to-camel-case: true
用到了mybatis,所以配置了一个自动驼峰映射
Redis自定义模板配置类
@Component
public class RedisConfig {
@Bean
public RedisTemplate<Object,Object> jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setKeySerializer(new StringRedisSerializer());
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
导入redisUtil工具类,工具栏中封装了大量redis操作代码,一般真实开发环境中都可以看到一个公司自己封装的RedisUtil
@Component
public class RedisUtil {
@Autowired(required = false)
private RedisTemplate jsonRedisTemplate;
// =========================================================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
jsonRedisTemplate.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 jsonRedisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return jsonRedisTemplate.hasKey(key);
} catch (Exception e) {
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
jsonRedisTemplate.delete(key[0]);
} else {
jsonRedisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : jsonRedisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
jsonRedisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
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) {
jsonRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return jsonRedisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return jsonRedisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return jsonRedisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return jsonRedisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
jsonRedisTemplate.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 {
jsonRedisTemplate.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 {
jsonRedisTemplate.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 {
jsonRedisTemplate.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) {
jsonRedisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return jsonRedisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return jsonRedisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return jsonRedisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return jsonRedisTemplate.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 jsonRedisTemplate.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 jsonRedisTemplate.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 = jsonRedisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return jsonRedisTemplate.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 = jsonRedisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return jsonRedisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return jsonRedisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return jsonRedisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
jsonRedisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
jsonRedisTemplate.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 {
jsonRedisTemplate.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 {
jsonRedisTemplate.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 {
jsonRedisTemplate.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 = jsonRedisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
实体类POJO
public class Student {
private int stuId;
private String stuName;
private String stuSex;
// get,set,构造,toString等
}
Dao层:Mapper
@Mapper
public interface StudentMapper {
@Delete("delete from student where stu_id = #{id}")
public int delete(Integer id);
@Select("select * from student where stu_id = #{id}")
public Student find(Integer id);
}
业务层:
接口
public interface IStudentService {
public void delete(int id);
public Student find(int id);
}
实现类
@Service
public class StudentServiceImp implements IStudentService {
@Autowired(required = false)
private StudentMapper mapper;
@Autowired
private RedisUtil redisUtil;
// 删除用户策略:删除数据表中数据,然后删除缓存
@Override
public void delete(int id) {
// 删除数据库
int res = mapper.delete(id);
String key = "student:id:"+id;
// 判断数据库是否删除成功
if(res != 0){
boolean hasKey = redisUtil.hasKey(key);
if(hasKey){
redisUtil.del(key);
System.out.println("删除了缓存中的key:" + key);
}
}
}
// 获取用户策略:先从缓存中获取用户,没有则取数据表中数据,再将数据写入缓存
@Override
public Student find(int id) {
String key = "student:id:" + id;
//1.1判断key在redis中是否存在
boolean hasKey = redisUtil.hasKey(key);
if (hasKey) {
//1.2存在缓存则直接获取
Object stu = redisUtil.get(key);
ObjectMapper change = new ObjectMapper();
Student student = change.convertValue(stu,Student.class);
System.out.println("==========从缓存中获得数据=========");
System.out.println(student.getStuName());
System.out.println("==============================");
return student;
} else {
//1.3不存在缓存,先从数据库中获取,在保存至redis,最后返回用户
Student student = mapper.find(id);
System.out.println("==========从数据表中获得数据=========");
System.out.println(student.getStuName());
System.out.println("==============================");
if (student != null){
redisUtil.set(key, student);//写入缓存
}
return student;
}
}
}
控制器
@RestController
public class StudentController {
@Autowired
IStudentService service;
@RequestMapping("/delete/{id}")
public Integer delete(@PathVariable("id") int id){
service.delete(id);
return id;
}
@RequestMapping("/find/{id}")
public Student find(@PathVariable("id") int id){
Student student = service.find(id);
return student;
}
}
启动服务,地址栏中测试查看控制台打印结果,(测试时一定要保证redis服务正在运行,否则存不进redis)
第一次访问localhost:8080/find/1显示从数据表中获得的数据,第二次访问显示缓存中获取的数据
SpringBoot Cache介绍
Spring Cache是一个框架, 实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。
Spring Cache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过CacheManager接口来统一不同的缓存技术。
CacheManager缓存管理器是Spring提供的各种缓存技术抽象接口
针对不同的缓存技术需要实现不同的CacheManager:
CacheManager | 描述 |
---|---|
EhCacheCacheManager | 使用EhCache作为缓存技术(Spring Cache框架操作的默认缓存) |
GuavaCacheManager | 使用Google的GuavaCache作为缓存技术 |
RedisCacheManager | 使用Redis作为缓存技术 |
SpringBoot Cache常用注解
注解 | 说明 |
---|---|
@EnableCaching | 开启缓存注解功能 |
@Cacheable | 在方法执行前spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法返回值放到缓存中 |
@CachePut | 将方法的返回值放到缓存中 |
@CacheEvict | 将一条或多条数据从缓存中删除 |
使用步骤:
引入缓存启动器:spring-boot-starter-cache,spring-boot-starter-data-redis
@EnableCaching:在启动类上,开启基于注解的缓存
@Cacheable : 标在方法上,返回的结果会进行缓存
属性: value/cacheNames缓存的名字
key : 作为缓存中的Key值,可自已使用 SpEL表达式指定(不指定就是参数值), 缓存结果是
方法返回值
名字 | 描述 | 示例 |
---|---|---|
methodName | 当前被调用的方法名 | #root.methodName |
target | 当前被调用的目标对象 | #root.target |
targetClass | 当前被调用的目标对象类 | #root.targetClass |
args | 当前被调用的方法的参数列表 | #root.args[0] |
caches | 当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”,“cache2”})),则有两个cache | #root.caches[0].name |
argument name | 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; | #iban 、 #a0 、 #p0 |
result | 方法执行后的返回值(仅当方法执行之后的判断有效,在@CachePut 使用于更新数据后可用) | #result |
代码实现演示:
pom文件导坐标
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.0.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
yaml或properties主配置文件
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring?serverTimezone=GMT%2B8
#开启日志管理,可以查看sql语句
logging.level.com.apesource.springboot_redis03.mapper=debug
debug=true
#配置要连接redis的地址
spring.redis.host=localhost
spring.redis.port=6379
POJO实体类
根据要操作的表写,演示为student
public class Student implements Serializable{
private Integer stuId;
private String stuName;
private String stuSex;
// get、set、toString、有参、无参构造
}
Dao层StudentMapper
public interface StudentMapper {
@Select("select * from Student where stu_id = #{id}")
public Student getStudentById(Integer id);
@Delete("delete from student where stu_id = #{id}")
public int deleteStudentById(Integer id);
@Update("update student set stu_name=#{stuName},stu_sex=#{stuSex} where stu_id = #{stuId}")
public int updateById(Student student);
}
Service层
演示Cache简化redis实现,业务直接写实现类,没写接口
@Service
public class StudentService {
@Autowired(required = false)
StudentMapper mapper;
//根据@Cacheable注解中的cacheNames+key拼接后的值为key
@Cacheable(cacheNames = "students",key = "#id")
public Student findById(Integer id){
return mapper.getStudentById(id);
}
@CacheEvict(cacheNames = "students",key = "#id")
public void deleteStudentById(Integer id){
mapper.deleteStudentById(id);
}
@CachePut(cacheNames = "students",key = "#result.stuId")
public Student updateById(Student student){
mapper.updateById(student);
return student;
}
}
在业务层的方法中,加SpringBoot Cache的注解:
cacheNames会在缓存中开辟一块儿叫"students"的空间,以键值对的形式存放数据,键是cacheNames+key拼接组成,value就是被标注注解的方法返回值
控制器StudentController
@RestController
public class UserController {
@Autowired
StudentService userService;
@GetMapping("/findById/{id}")
public Student findById(@PathVariable("id") Integer id) {
Student stu = userService.findById(id);
return stu;
}
@GetMapping("/delete/{id}")
public Integer delete(@PathVariable("id") Integer id) {
userService.deleteStudentById(id);
return id;
}
@GetMapping("/update/{id}/{name}/{hobby}")
public Integer update(@PathVariable Integer id,@PathVariable String name,@PathVariable String sex) {
userService.updateById(new Student(id,name,sex));
return id;
}
}
启动类上添加注解
@SpringBootApplication
@MapperScan("com.apesource.springboot_redis03")
@EnableCaching
public class SpringbootRedis03Application {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedis03Application.class, args);
}
}
启动服务,浏览器访问localhost:8080/findById/1,访问之后刷新再次访问
注意启动程序服务之前,需要先把redis运行起来
第一次访问时,从数据库中获取的数据
第二次访问,没有SQL语句,但是也得到了数据,证明cache实现了缓存的作用