67

SpringBoot | 第十一章:Redis 的集成和简单使用

 6 years ago
source link: http://www.importnew.com/29554.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

上几节讲了利用 Mybatis-Plus 这个第三方的ORM框架进行数据库访问,在实际工作中,在存储一些非结构化或者缓存一些临时数据及热点数据时,一般上都会用上 mongodbredis 进行这方面的需求。所以这一章节准备讲下缓存数据库 Redis 的集成,同时会介绍下基于 Redis 和注解驱动的 Spring Cache 的简单使用。

Redis 介绍

大家应该对 Redis 应该比较熟悉了。这几年也是大行其道的缓存数据库,目前的 memcached 由于使用场景及其存储数据结构的单一(不知道现在是否有改善,现在基本没有接触了),在工作中也使用的少了。引用官网的简介,Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

推荐redis中国社区: http://www.redis.cn/

SpringBoot的Redis集成

0.本章节以上一章节的示例基础上进行集成。所以大家可下载 第十章节示例 或者在章节末尾直接下载本章节示例。

1.pom依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

直接引入,相关依赖会自动加载的,这就是 springboot 让人愉悦之处呀。

2. application.properties 配置加入redis相关配置

配置自动加载类为: org.springframework.boot.autoconfigure.data.redis.RedisProperties ,可在属性文件中点击某属性快捷跳转。注意到其启动类为 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration 。这里就不介绍了,后面会写一篇关于 Springboot 自动加载配置的文章。

# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8  
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1  
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8  
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0  
# 连接超时时间(毫秒)
spring.redis.timeout=0

3.一般上通过以上两步就可使用了,但工作中一般上是通过 StringRedisTemplate (默认采用 string 的序列化,保存key和值时都是通过此序列化策略)接口进行操作,所以这里直接配置了 StringRedisTemplate bean类。

RedisConfig.java

/**
 * 
 * @author oKong
 *
 */
@Configuration
public class RedisConfig {
    
    /**
     *  定义 StringRedisTemplate ,指定序列化和反序列化的处理类
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer<Object> 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);
        //序列化 值时使用此序列化方法
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

4.编写控制类,测试集成是否生效。

RedisController.java

@RestController
@RequestMapping("/redis")
@Api(tags = "redis 测试API")
public class RedisController {

	@Autowired
	StringRedisTemplate redisTemplate;
	
	@GetMapping("set/{key}/{value}")
	@ApiOperation(value="设置缓存")
	public String set(@PathVariable("key")String key,@PathVariable("value") String value) {
		//注意这里的 key不能为null spring 内部有检验
		redisTemplate.opsForValue().set(key, value);
		return key + "," + value;
	}
	
	@GetMapping("get/{key}")
	@ApiOperation(value="根据key获取缓存")
	public String get(@PathVariable("key") String key) {
		
		return "key=" + key + ",value=" + redisTemplate.opsForValue().get(key);
	}
}

5.访问: http://127.0.0.1:8080/swagger-ui.html。 也可直接浏览器输入:

set值

zmUB7f3.jpg!web set值

get值

zuiqqqA.jpg!web get值

浏览器访问

NN736fv.jpg!web

Uj6Jfa3.jpg!web

查看redis记录:

ARVBfy2.jpg!web redis记录

至此, redis 就集成好了。实际中可根据业务需要进行相关操作,比如缓存session记录,缓存菜单列表等。

Spring Cache 和 redis 使用。

Spring CacheSpring 框架提供的对缓存使用的抽象类,支持多种缓存,比如 RedisEHCache 等,集成很方便。同时提供了多种注解来简化缓存的使用,可对方法进行缓存。

0.修改 RedisConfig 配置类,加入注解 @EnableCaching ,同时设置 CacheManager 缓存管理类,这里使用 RedisCacheManager ,其他的管理类还有: SimpleCacheManagerConcurrentMapCacheManager 等,默认提供的在类 org.springframework.cache.support 下,可自行查阅。

/**
 * 
 * @author oKong
 *
 */
@Configuration
@EnableCaching
public class RedisConfig {
    
    /**
     *  定义 StringRedisTemplate ,指定序列号和反序列化的处理类
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer<Object> 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);
        //序列化 值时使用此序列化方法
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
    
    @Bean
    public CacheManager cacheManager(RedisTemplate<String,String> redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        //使用前缀
        rcm.setUsePrefix(true);
        //缓存分割符 默认为 ":"
//        rcm.setCachePrefix(new DefaultRedisCachePrefix(":"));
        //设置缓存过期时间
        //rcm.setDefaultExpiration(60);//秒
        return rcm;
    }
}

1.改造 UserController 控制层,引入 @Cacheable 等注解。

/**
 * 用户控制层 简单演示增删改查及分页
 * 新增了swagger文档内容 2018-07-21
 * 新增了@caching使用 2018-07-23
 * @author oKong
 *
 */
@RestController
@RequestMapping("/user")
@Api(tags="用户API")
public class UserController {

    @Autowired
    IUserService userService;
    
    @PostMapping("add")
    @ApiOperation(value="用户新增")
    //正常业务时, 需要在user类里面进行事务控制,控制层一般不进行业务控制的。
    //@Transactional(rollbackFor = Exception.class)
    public Map<String,String> addUser(@Valid @RequestBody UserReq userReq){
        
        User user = new User();
        user.setCode(userReq.getCode());
        user.setName(userReq.getName());
        //由于设置了主键策略 id可不用赋值 会自动生成
        //user.setId(0L);
        userService.insert(user);
        Map<String,String> result = new HashMap<String,String>();
        result.put("respCode", "01");
        result.put("respMsg", "新增成功");
        //事务测试
        //System.out.println(1/0);
        return result;
    }
    
    @PostMapping("update")
    @ApiOperation(value="用户修改")    
    //更新时 直接删除缓存 以保证下次获取时先从数据库中获取最新数据
    @CacheEvict(value="OKONG", key="#userReq.id")
    public Map<String,String> updateUser(@Valid @RequestBody UserReq userReq){
        
        if(userReq.getId() == null || "".equals(userReq.getId())) {
            throw new CommonException("0000", "更新时ID不能为空");
        }
        User user = new User();
        user.setCode(userReq.getCode());
        user.setName(userReq.getName());
        user.setId(Long.parseLong(userReq.getId()));        
        userService.updateById(user);
        Map<String,String> result = new HashMap<String,String>();
        result.put("respCode", "01");
        result.put("respMsg", "更新成功");
        return result;
    }
    
    @GetMapping("/get/{id}")
    @ApiOperation(value="用户查询(ID)")    
    @ApiImplicitParam(name="id",value="查询ID",required=true)
    @Cacheable(value="OKONG",key="#id")
    public Map<String,Object> getUser(@PathVariable("id") String id){
        //查询
        User user = userService.selectById(id);
        if(user == null) {
            throw new CommonException("0001", "用户ID:" + id + ",未找到");
        }
        UserResp resp = UserResp.builder()
                .id(user.getId().toString())
                .code(user.getCode())
                .name(user.getName())
                .status(user.getStatus())
                .build();
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("respCode", "01");
        result.put("respMsg", "成功");
        result.put("data", resp);
        return result;
    }
    
    @GetMapping("/page")
    @ApiOperation(value="用户查询(分页)")        
    public Map<String,Object> pageUser(int current, int size){
        //分页
        Page<User> page = new Page<>(current, size);
        Map<String,Object> result = new HashMap<String,Object>();
        result.put("respCode", "01");
        result.put("respMsg", "成功");
        result.put("data", userService.selectPage(page));
        return result;
    }
        
}

2.利用 Swagger 控制页面,新增一个用户,然后获取用户,会发现缓存里已经有此id的用户数据了。

qyeQvun.jpg!web 第一次获取

redis查看:

Q7vQ7jf.jpg!web redis

再次获取,会发现这次没有直接访问数据库了,而是直接从缓存读取。大家可在观察下控制台的输出情况(可先清空控制台,然后在请求)。

3aamMbN.jpg!web 控制台

此时控制台无任何输出,但前端已经获取到值了。

vmAFBvr.jpg!web

关于SpringCache 注解的简单介绍

  • @Cacheable:标记在一个方法上,也可以标记在一个类上。主要是缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上,缓存该类所有的方法返回值。

    参数: value缓存名、 key缓存键值、 condition满足缓存条件、unless否决缓存条件

ayeQzum.jpg!web

  • @CacheEvict:从缓存中移除相应数据。

qmeQ7jN.jpg!web

  • @CachePut:方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

EFnqIzU.jpg!web

  • @Caching:多个Cache注解使用,比如新增用户时,删除用户属性等需要删除或者更新多个缓存时,集合以上三个注解。

常用的就以上几个,对于 @CacheConfig 没使用过,这里就不说明了。

对于对几个注解类的简单使用就结束了,相关的详细用法,比如自定义条件缓存,自定义注解等,这里就不阐述了,请读者自行

SpEL上下文数据

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自 互联网

名称 位置 描述 示例 methodName root对象 当前被调用的方法名 root.methodName method root对象 当前被调用的方法 root.method.name target root对象 当前被调用的目标对象 root.target targetClass root对象 当前被调用的目标对象类 root.targetClass args root对象 当前被调用的方法的参数列表 root.args[0] caches root对象 当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache root.caches[0].name argument name 执行上下文 当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数 user.id result 执行上下文 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache evict’的beforeInvocation=false) result
@CacheEvict(value = "user", key = "#user.id", condition = "#root.target.canCache() and #root.caches[0].get(#user.id).get().username ne #user.username", beforeInvocation = true) public  void  conditionUpdate(User user)

总结

本章节主要是对 redis 结合 Spring Cache 的集成和简单使用进行了说明,详细的用法,可自行搜索相关资料下,这里就不阐述了。因为对于百分之八十之上的缓存要求基本能满足了。 使用缓存时,一定要注意缓存生命周期的控制,不然容易出现数据不一致的情况,谨记!

最后

目前互联网上很多大佬都有 SpringBoot 系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是实践的。若文中有所错误之处,还望提出,谢谢。


Recommend

  • 55
    • blog.51cto.com 7 years ago
    • Cache

    第十一章 LAMP架构-Malt&#39;s Sky

    Q:httpd和php为什么需要组合在一起,不能分开部署在不同的节点使用网络来进行数据协同吗?A:因为我们使用的方式是,php作为httpd的一个模块存在的。他们两者必须要在一起,才能实现效果。当然,如果你把php做成以fastcgi的形式,这样httpd可以在另外的机器上调用它...

  • 26
    • www.cnblogs.com 6 years ago
    • Cache

    springboot~集成测试里的redis

    测试不应该访问外部资源 对于单元测试,集成测试里,如果被测试的方法中使用到了redis,你需要去模拟一个单机环境的redis server,因为只有这样,你的测试才是客观的,即不会因为网络和其它因素影响你测试的准确性!

  • 29

    python 自称 "Batteries included"(自带电池, 自备干粮?), 就是因为他提供了很多内置的模块, 使用这些模块无需安装和配置即可使用. 本章主要介绍 python 的一些内置常用核心模块 Python 常用的核心模块 一...

  • 13

    Item 78 : Synchronize access to shared mutable data 共享可变数据需要同步 synchronized关键字可以保证同时只有一个线程可以执行或者阻塞该方法; 除了long和double之外,Java语言特性都能保证对变量的...

  • 11

    [Java基础教程]第十一章-Java类和对象 浏览:1130次  出处信息 继承是面向对象的...

  • 4

    关于《算法的乐趣》历法一章演示程序错误的说明

  • 22

    工程结构图:提前说明:注意:JavaBean必须实现序列化,不然数据无法存入Redis。public class User implements Serializable1.SpringBoot集成Redis实现缓存首先需要引入SpringBoot的Redis依赖:

  • 3
    • djangobook.py3k.cn 3 years ago
    • Cache

    第十一章:通用视图

    第11章 通用视图 这里需要再次回到本书的主题: 在最坏的情况下, Web 开发是一项无聊而且单调的工作。 到目前为止,我们已经介绍了 Django 怎样在模型和模板的层面上减小开发的单调性,但是 Web 开发在视图的层面上,也经历着这种令人厌...

  • 4

    SpringBoot 2.7 .2实战基础 - 09 - 集成 Redis & 异步任务1 集成RedisSpringBoot 提供了整合 Redis 的 starter,使用非常简单。1.1 添加依赖在 pom.xml 中添加 redis 的 starter:

  • 8

    第十一章《Java实战常用类》第9节:Comparable接口和Comparator接口 精选 原创 ​对事物进行比较往往是希望对它们进行排序,因此排...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK