IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> SpringBoot+Mybatis+Redis缓存实战总结 -> 正文阅读

[大数据]SpringBoot+Mybatis+Redis缓存实战总结

Redis 是一个高性能的key-value内存数据库。它支持常用的5种数据结构:String字符串、Hash哈希表、List列表、Set集合、Zset有序集合 等数据类型。
Redis它解决了2个问题:
1)性能
通常数据库的读操作,一般都要几十毫秒,而redisd的读操作一般仅需不到1毫秒。通常只要把数据库的数据缓存进redis,就能得到几十倍甚至上百倍的性能提升。
2)并发
在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常,甚至卡死在数据库中。为了解决大并发卡死的问题,一般的做法是采用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。

  • 下面用Redis做缓存,整合SpringBoot+Mybatis来讲解。

1、项目环境搭建

1.1、引入pom依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xyl</groupId>
    <artifactId>string_mybatis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>string_mybatis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--通用spring boot mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger-ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

1.2、添加Redis配置信息

spring.application.name=spring-boot-mybatis-redis
server.port=9090

# 指定mapper.xml的位置
mybatis.mapper-locations=classpath*:com/xyl/redis/mapper/xml/*.xml

# 数据库驱动和ip
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/redis_user?autoReconnect=true&useUnicode=true&createDatabaseIfNotExist=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456

# 为某个包目录下  设置日志
logging.level.com.xyl=debug

# 表示是否开启Swagger,一般线上环境是关闭的
spring.swagger2.enabled=true

## Redis配置
## Redis 数据库索引(默认为0)
spring.redis.database=0
## Redis 服务器地址
spring.redis.host=localhost
## Redis 服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=

1.3、创建数据库、实体类

  • 在redis_user中创建users表
CREATE TABLE `users` (
  `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
  `sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
  `deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
  `update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8 COMMENT='用户表';
  • 注意:
    存放在Redis中的数据必须要序列化(实体类对象实现Serializable接口)
package com.xyl.redis.entity;

import java.util.Date;
import javax.persistence.*;

@Table(name = "users")  // 指定对应的数据表是users
public class User User implements Serializable{


    @Id
    @GeneratedValue(generator = "JDBC")
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 性别 0=女 1=男 
     */
    private Byte sex;

    /**
     * 删除标志,默认0不删除,1删除
     */
    private Byte deleted;

    /**
     * 更新时间
     */
    @Column(name = "update_time")
    private Date updateTime;

    /**
     * 创建时间
     */
    @Column(name = "create_time")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取用户名
     *
     * @return username - 用户名
     */
    public String getUsername() {
        return username;
    }

    /**
     * 设置用户名
     *
     * @param username 用户名
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 获取密码
     *
     * @return password - 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     *
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 获取性别 0=女 1=男 
     *
     * @return sex - 性别 0=女 1=男 
     */
    public Byte getSex() {
        return sex;
    }

    /**
     * 设置性别 0=女 1=男 
     *
     * @param sex 性别 0=女 1=男 
     */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
     * 获取删除标志,默认0不删除,1删除
     *
     * @return deleted - 删除标志,默认0不删除,1删除
     */
    public Byte getDeleted() {
        return deleted;
    }

    /**
     * 设置删除标志,默认0不删除,1删除
     *
     * @param deleted 删除标志,默认0不删除,1删除
     */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
     * 获取更新时间
     *
     * @return update_time - 更新时间
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * 设置更新时间
     *
     * @param updateTime 更新时间
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    /**
     * 获取创建时间
     *
     * @return create_time - 创建时间
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * 设置创建时间
     *
     * @param createTime 创建时间
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                '}';
    }
}
  • UserVO
package com.xyl.redis.controller;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

import java.util.Date;

@ApiModel(value = "用户信息")
public class UserVO {
    @ApiModelProperty(value = "用户ID")
    private Integer id;

    @ApiModelProperty(value = "用户名")
    private String username;

    @ApiModelProperty(value = "密码")
    private String password;

    @ApiModelProperty(value = "性别 0=女 1=男 ")
    private Byte sex;

    @ApiModelProperty(value = "删除标志,默认0不删除,1删除")
    private Byte deleted;

    @ApiModelProperty(value = "更新时间")
    private Date updateTime;

    @ApiModelProperty(value = "创建时间")
    private Date createTime;

    /**
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取用户名
     *
     * @return username - 用户名
     */
    public String getUsername() {
        return username;
    }

    /**
     * 设置用户名
     *
     * @param username 用户名
     */
    public void setUsername(String username) {
        this.username = username;
    }

    /**
     * 获取密码
     *
     * @return password - 密码
     */
    public String getPassword() {
        return password;
    }

    /**
     * 设置密码
     *
     * @param password 密码
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * 获取性别 0=女 1=男 
     *
     * @return sex - 性别 0=女 1=男 
     */
    public Byte getSex() {
        return sex;
    }

    /**
     * 设置性别 0=女 1=男 
     *
     * @param sex 性别 0=女 1=男 
     */
    public void setSex(Byte sex) {
        this.sex = sex;
    }

    /**
     * 获取删除标志,默认0不删除,1删除
     *
     * @return deleted - 删除标志,默认0不删除,1删除
     */
    public Byte getDeleted() {
        return deleted;
    }

    /**
     * 设置删除标志,默认0不删除,1删除
     *
     * @param deleted 删除标志,默认0不删除,1删除
     */
    public void setDeleted(Byte deleted) {
        this.deleted = deleted;
    }

    /**
     * 获取更新时间
     *
     * @return update_time - 更新时间
     */
    public Date getUpdateTime() {
        return updateTime;
    }

    /**
     * 设置更新时间
     *
     * @param updateTime 更新时间
     */
    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    /**
     * 获取创建时间
     *
     * @return create_time - 创建时间
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * 设置创建时间
     *
     * @param createTime 创建时间
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", sex=" + sex +
                '}';
    }
}

1.4、编写Mapper、Config配置类

  • UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xyl.redis.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.xyl.redis.entity.User">
        <!--
          WARNING - @mbg.generated
        -->
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="username" jdbcType="VARCHAR" property="username" />
        <result column="password" jdbcType="VARCHAR" property="password" />
        <result column="sex" jdbcType="TINYINT" property="sex" />
        <result column="deleted" jdbcType="TINYINT" property="deleted" />
        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
        <result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
    </resultMap>
</mapper>
  • UserMapper
package com.xyl.redis.mapper;

import com.xyl.redis.entity.User;
import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper<User> {
}
  • 配置类SwaggerConfig :
package com.xyl.redis.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value(value = "${spring.swagger2.enabled}")
    private Boolean swaggerEnabled;

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(swaggerEnabled)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.xyl.redis"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("接口文档")
                .description("SpringBoot+Redis学习总结")
                .termsOfServiceUrl("https://www.bilibili.com/video/BV1GV411U78a?p=3")
                .version("1.0")
                .build();
    }
}

1.5、编写业务处理层service

  • UserService
package com.xyl.redis.service;

import com.xyl.redis.entity.User;
import com.xyl.redis.mapper.UserMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);

    private static final String Cache_Key_User = "user:";

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    // 增加User,并更新到Redis
    public void saveUser(User user){
        // 向数据库中加入 user数据项
        this.userMapper.insertSelective(user);

        String key = Cache_Key_User + user.getId();
        user = userMapper.selectByPrimaryKey(user.getId());
        //opsForValue 对应 Redis的String数据结构, SET命令
        //  将从数据库中查询到的数据 缓存到redis
        redisTemplate.opsForValue().set(key,user);
    }

    // 修改User,并更新到Redis
    public void updateUser(User user){
        this.userMapper.updateByPrimaryKeySelective(user);

        String key = Cache_Key_User + user.getId();

        User redisUser = userMapper.selectByPrimaryKey(user.getId());
        redisTemplate.opsForValue().set(key,redisUser);
    }

    public User findUserById(Integer userId){
        ValueOperations<String,User> operations = redisTemplate.opsForValue();

        String key = Cache_Key_User + userId;

        // 先根据userId 去Redis 查找User,如果Redis中没有数据,再去数据库查找,找到数据后,更新到Redis中
       User user = operations.get(key);
        if (user == null){
            user = this.userMapper.selectByPrimaryKey(userId);
            redisTemplate.opsForValue().set(key,user);
        }
        return user;
    }

}

1.6、编写控制层UserController

package com.xyl.redis.controller;
import com.xyl.redis.entity.User;
import com.xyl.redis.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.annotation.RequestScope;

import java.beans.Beans;
import java.util.Random;

@Api(description = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @ApiOperation("数据库初始化100条数据")
    @RequestMapping(value = "/init",method = RequestMethod.GET)
    public void init(){
        for (int i = 0; i < 100; i++) {
            Random random = new Random();
            User user = new User();
            String userStr = "user" + i;
            user.setUsername(userStr);
            user.setPassword(userStr + "psd");

            int gender = random.nextInt(2);
            user.setSex((byte) gender);

            userService.saveUser(user);
        }
    }

    @ApiOperation("单个用户查询,按userid查用户信息")
    @RequestMapping(value = "/findById/{id}", method = RequestMethod.GET)
    public UserVO findById(@PathVariable Integer uid){
        User user = this.userService.findUserById(uid);
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user,userVO);
        return userVO;
    }

    @ApiOperation("修改User数据")
    @PostMapping("/updateUser")
    public void updateUser(@RequestBody UserVO userVO){
        User user = new User();
        BeanUtils.copyProperties(userVO,user);
        this.userService.updateUser(user);
    }
}

1.7、主启动类(添加@MapperScan)

package com.xyl.redis;
import tk.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

//  指定要扫描 Mapper类的包 路径
@MapperScan("com.xyl.redis.mapper")
@SpringBootApplication
public class StringMybatisApplication {

    public static void main(String[] args) {
        SpringApplication.run(StringMybatisApplication.class, args);
    }

}

注意:添加@MapperScan需要导入的包是
import tk.mybatis.spring.annotation.MapperScan;`
而非org.mybatis.spring.annotation.MapperScan。

2、Swagger体验测试

1) 测试增加数据

/user/init
数据库初始化100条数据
启动项目后,发送请求:

http://localhost:9090/swagger-ui.html#

结果如下图:
在这里插入图片描述

选中"/user/init"请求栏中的 Try it out -> Execute,向服务器发送初始化数据的请求。在这里插入图片描述

  • 请求结果如下:
    在这里插入图片描述
    数据库中增加了100条初始化数据。

  • 注意:存放在Redis中的数据必须序列化(实体类对象实现Serializable接口)

2) 测试查询数据

选中 “/user/findById/{id}“请求栏中的"Try it out”,输入User对象的Id,再"Execute”,第一次execute执行查询操作,会直接从先Redis中查找数据,如果查找不到,会再去数据库中查询数据,如果查询到数据,返回数据的同时,将数据存储到Redis中,以便在后续的查询中能够命中缓存(第一次查询id为1002的user,控制台会输出对应的sql语句,再去查询id为1002的user,则不会显示sql语句,即直接从Redis查询并返回数据)。
在这里插入图片描述
在这里插入图片描述

3)测试修改数据

在这里插入图片描述

3、优化Redis的序列化,改为Json方式

  • 为什么要重写Redis序列化方式,改为Json呢?
    因为RedisTemplate默认使用的是JdkSerializationRedisSerializer,会出现2个问题:

1)被序列化的对象必须实现Serializable接口

@Table(name = "users")
public class User implements  Serializable {  // ... }

2)被序列化的数据在Redis会出现乱码,导致value值可读性差。

在这里插入图片描述

问题解决:

1)添加Redis的配置类RedisConfig :
package com.xyl.redis.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
        
@Configuration
public class RedisConfig {
    /**
     * 重写Redis序列化方式,使用Json方式:
     * 当我们的数据存储到Redis的时候,我们的键(key)和值(value)都是通过Spring提供的Serializer序列化到Redis的。
     * RedisTemplate默认使用的是JdkSerializationRedisSerializer,
     * StringRedisTemplate默认使用的是StringRedisSerializer。
     *
     * Spring Data JPA为我们提供了下面的Serializer:
     * GenericToStringSerializer、Jackson2JsonRedisSerializer、
     * JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、
     * OxmSerializer、StringRedisSerializer。
     * 在此我们将自己配置RedisTemplate并定义Serializer。
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //创建一个json的序列化对象
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置value的序列化方式json
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        //设置hash key序列化方式string
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        //设置hash value的序列化方式json
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}
2)实体类User不用再实现序列化接口Serializable
@Table(name = "users")  //  指定对应的表名
public class User {  //... }
3)flushdb清空Redis中的数据
  • 避免Redis中之前存在的旧数据对添加RedisConfig配置类后 产生影响。
    启动项目,发送请求: http://localhost:9090/swagger-ui.html#/,在"/user/init"请求栏中,“Try it out” ->"Execute"来发送请求,数据库和Redis中均会存储对应的数据,Redis中存储的数据不再是乱码,而是JSON格式的字符串。
    在这里插入图片描述

4、实现微信文章阅读量统计

4.1、场景介绍

在微信公众号里面的文章,每个用户阅读一遍文章,该篇文章的阅读量就会加一。类似于微信这样大的并发量,一般不可能采用数据库来做计数器,通常都是用redis的incr命令来实现。如下图:在这里插入图片描述

4.2、redis INCR命令实现

  • INCR命令,它的全称是increment,用途就是计数器,每执行一次INCR命令,都将key的value自动加1;如果key不存在,那么key的值初始化为0,然后再执行INCR操作。
    例如:微信文章id=100,做阅读计算如:
127.0.0.1:6379> incr article:100
(integer) 1
127.0.0.1:6379> incr article:100
(integer) 2
127.0.0.1:6379> incr article:100
(integer) 3
127.0.0.1:6379> incr article:100
(integer) 4
127.0.0.1:6379> get article:100
"4"
  • 编写WeChatViewController :
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@Api(description = "文章接口")
@RestController
@RequestMapping("/wechat")
@Slf4j
public class WeChatViewController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping(value = "/view")
    public void view(Integer id) {
        //redis key
        String key="article:"+id;
        //调用redis的increment计数器命令
        long n=this.stringRedisTemplate.opsForValue().increment(key);
        log.info("key={},阅读量为{}",key, n);
    }
}
  • 测试
    模拟阅读 编号为2001的文章:在这里插入图片描述
    在这里插入图片描述

()## 5、SpringCache

5.1、springcache解决的问题

  • springcache 是spring3.1版本发布出来的,他是对使用缓存进行封装和抽象,通过在方法上使用annotation注解就能拿到缓存结果。
  • 正是因为用了annotation,所以它解决了业务代码和缓存代码的耦合度问题,即在不侵入业务代码的基础上,让现有代码即刻支持缓存,让开发人员无感知的使用了缓存。
  • 特别注意:
    对于redis的缓存,springcache只支持String,其他的Hash 、List、set、ZSet都不支持,这要特别注意。

5.2、模块环境搭建

  • 1)引入pom依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--通用spring boot mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--swagger-ui-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--spring cache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--spring cache连接池依赖包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>
    </dependencies>
  • 2)编写redis配置信息
  • spring、数据库的配置同 前面模块的配置信息
spring.application.name=spring-boot-mybatis-redis
server.port=9091
# 指定mapper.xml的位置
mybatis.mapper-locations=classpath*:com/xyl/redis/mapper/xml/*.xml

# 数据库驱动和ip
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/redis_user?autoReconnect=true&useUnicode=true&createDatabaseIfNotExist=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456

# 为某个包目录下  设置日志
logging.level.com.xyl=debug

# 表示是否开启Swagger,一般线上环境是关闭的
spring.swagger2.enabled=true
## Redis 配置
# Redis数据库索引(默认为0)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=192.168.1.138
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8  
# 连接池最大阻塞等待时间
spring.redis.lettuce.pool.max-wait=-1ms
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8  
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0  
# 连接超时时间(毫秒)
spring.redis.timeout=5000ms
  • 开启缓存配置,设置序列化
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

@Configuration
@EnableCaching
public class RedisConfig {
    @Primary
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration
                //设置缓存的默认超时时间:30分钟
                .entryTtl(Duration.ofMinutes(30L))
                //如果是空值,不缓存
                .disableCachingNullValues()

                //设置key序列化器
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
                //设置value序列化器
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));

        return RedisCacheManager
                .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }

    /**
     * key序列化器
     */
    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }
    /**
     * value序列化器
     */
    private RedisSerializer<Object> valueSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

5.3、业务逻辑代码编写

  • UserService.java
import com.xyl.springcache.entity.User;
import com.xyl.springcache.mapper.UserMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@CacheConfig(cacheNames = { "user" })
public class UserService {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);

    @Autowired
    private UserMapper userMapper;

    @Cacheable(key="#id")
    public User findUserById(Integer id){
        return this.userMapper.selectByPrimaryKey(id);
    }

    @CachePut(key = "#obj.id")
    public User updateUser(User obj){
        this.userMapper.updateByPrimaryKeySelective(obj);
        return this.userMapper.selectByPrimaryKey(obj.getId());
    }

    @CacheEvict(key = "#id")
    public void deleteUser(Integer id){
        User user=new User();
        user.setId(id);
        user.setDeleted((byte)1);
        this.userMapper.updateByPrimaryKeySelective(user);
    }

}
  • 用到User类、SwaggerConfig、Mapper均与前面模块相同。
  • UserController:
import com.xyl.springcache.entity.User;
import com.xyl.springcache.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@Api(description = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;
    
    @ApiOperation("单个用户查询,按userid查用户信息")
    @RequestMapping(value = "/findById/{id}", method = RequestMethod.GET)
    public UserVO findById(@PathVariable int id) {
        User user = this.userService.findUserById(id);
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    @ApiOperation("修改某条数据")
    @PostMapping(value = "/updateUser")
    public void updateUser(@RequestBody UserVO obj) {
        User user = new User();
        BeanUtils.copyProperties(obj, user);
        userService.updateUser(user);
    }

    @ApiOperation("按id删除用户")
    @RequestMapping(value = "/del/{id}", method = RequestMethod.GET)
    public void deleteUser(@PathVariable int id) {
        this.userService.deleteUser(id);
    }
}

5.4、SpringCache常用注解

1)@CacheConfig

@CacheConfig是类级别的注解,统一该类的所有 缓存前缀。

@CacheConfig(cacheNames = { "user" })
public class UserService { }

以上代码,代表了该类的所有缓存都以 “user::” 作为前缀

2)@Cacheable

@Cacheable是方法级别的注解,用于将方法的结果缓存起来

@Cacheable(key="#id")
public User findUserById(Integer id){
    return this.userMapper.selectByPrimaryKey(id);
}
  • 以上方法被调用时,先从缓存中读取数据,如果缓存没有找到数据,再执行方法体,最后把返回值添加到缓存中。

  • 注意:@Cacheable 一般是配合@CacheConfig一起使用。
    例如上文的@CacheConfig(cacheNames = { “user” }) 和@Cacheable(key="#id")一起使用时。
    调用方法传入id=100,那redis对应的key=user::100 ,value通过采用GenericJackson2JsonRedisSerializer序列化为json。

3)@CachePut

@CachePut是方法级别的注解,用于更新缓存。

@CachePut(key = "#obj.id")
public User updateUser(User obj){
    this.userMapper.updateByPrimaryKeySelective(obj);
    return this.userMapper.selectByPrimaryKey(obj.getId());
}

以上方法被调用时,先执行方法体,然后Springcache通过返回值更新缓存,即key = “#obj.id”,value=User

4)@CacheEvict(key = “#id”)

@CacheEvict是方法级别的注解,用于删除缓存

public void deleteUser(Integer id){
    User user=new User();
    user.setId(id);
    user.setDeleted((byte)1);
    this.userMapper.updateByPrimaryKeySelective(user);
}

以上方法被调用时,先执行方法体,在通过方法参数删除缓存。

5.5、Springcache需要注意的问题

  • 1)对于redis的缓存,Springcache只支持String,其他的Hash 、List、set、ZSet都不支持,所以对于Hash 、List、set、ZSet只能用RedisTemplate。
  • 2) 对于多表查询的数据缓存,Springcache是不支持的,只支持单表的简单缓存;对于多表的整体缓存,只能用RedisTemplate。
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-01-04 13:29:40  更:2022-01-04 13:31:01 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:32:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码