[redis+springboot]缓存sql执行结果

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

场景: 访问controller层(其实是service),需要将其结果缓存到redis,下一次直接从缓存找值,从而减少sql操作,减轻数据库压力

技术: redis,springboot,jpa,mysql

1, 新建项目

2, 导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!--springboot-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <!--        <version>2.1.6.RELEASE</version>-->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>redis-cache-boot</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!--lombok⼯具-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!--jpa操作redis,可以使用redis缓存-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--Spring Data Jpa-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project> 

3, 创建mysql表

create table user
(
    id int auto_increment
        primary key,
    name varchar(10) not null,
    password varchar(15) default '123456' not null,
    address varchar(25) null,
    phone varchar(15) null,
    createTime timestamp null,
    updateTime timestamp null
)

4, 创建springboot启动类

package org.malred;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching // 开启缓存
@SpringBootApplication
public class demoApplication {
    public static void main(String[] args) {
        SpringApplication.run(demoApplication.class,args);
    }
}

5, Base通用类

package org.malred.Base;

import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 通用controller,定义通用crud方法
 */
public abstract class BaseController<T>  {
    /**
     * 获取表中所有数据
     * @return
     */
    @GetMapping("/findAll")
    public abstract List<T> findAll();

    /**
     * 根据id获取表中数据
     * @param id
     * @return
     */
    @GetMapping("/find/{id}")
    public abstract T findById(@PathVariable Long id);

    /**
     * 插入数据(没有id字段)
     * @param t
     * @return
     */
    @PostMapping("/save")
    public abstract T insert(@RequestBody T t);

    /**
     * 修改数据(要有id字段)
     * @param t
     * @return
     */
    @PutMapping("/save")
    public abstract T update(@RequestBody T t);

    /**
     * 根据id删除数据
     * @param id
     */
    @DeleteMapping("/delete/{id}")
    public abstract void delete(@PathVariable Long id);
}
package org.malred.Base;

import lombok.Data;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.io.Serializable;
import java.util.Date;

/**
 * 基础实体类
 * T是主键的数据类型(Long?String?)
 */
@Data
@MappedSuperclass // 表示是实体类的父类,jpa会识别子类里的父类属性,作为表字段
public class BaseEntity<T extends Serializable> {
    /**
     * 主键
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public T id;
    /**
     * 创建时间
     */
    @CreationTimestamp
    public Date createTime;
    /**
     * 更新时间
     */
    @UpdateTimestamp
    public Date updateTime;
}
package org.malred.Base;

import org.malred.pojo.User;

import java.util.List;

/**
 * 基础service类,对应baseController的crud
 */
public interface BaseService<T> {
    /**
     * 查询表中所有数据
     * @return
     */
    List<T> findAll();
    /**
     * 根据id获取表中数据
     * @param id
     * @return
     */
    T findById(Long id);

    /**
     * 保存或更新(有id是更新,没id是保存)
     * @param t
     * @return
     */
    T save(T t);

    /**
     * 根据id删除表内数据
     * @param id
     */
    void delete(Long id);
}

6, 创建mvc三层架构

controller层

package org.malred.controller;

import org.malred.Base.BaseController;
import org.malred.pojo.User;
import org.malred.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/demo")
public class DemoController extends BaseController<User> {
    @Autowired
    DemoService demoService;
    @Override
    public List<User> findAll() {
        return demoService.findAll();
    }
    @Override
    public User findById(@PathVariable Long id) {// 不加@PathVariable,获取不到id
        return demoService.findById(id);
    }
    @Override
    public User insert(@RequestBody User user) {// 不加@RequestBody,获取不到对象
        return demoService.save(user);
    }
    @Override
    public User update(@RequestBody User user) {
        return demoService.save(user);
    }
    @Override
    public void delete(@PathVariable Long id) {
        demoService.delete(id);
    }
}

service层

package org.malred.service;

import org.malred.Base.BaseService;
import org.malred.pojo.User;

public interface DemoService extends BaseService<User> {
}
package org.malred.service.impl;

import org.malred.dao.UserDao;
import org.malred.pojo.User;
import org.malred.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DemoServiceImpl implements DemoService {
    @Autowired
    private UserDao userDao;

    @Override
    public List<User> findAll() {
        return userDao.findAll();
    }

    @Override
    public User findById(Long id) {
        return userDao.findById(id).get();
    }

    @Override
    public User save(User user) {
        return userDao.save(user);
    }

    @Override
    public void delete(Long id) {
        userDao.deleteById(id);
    }
}

dao层: 操作数据

package org.malred.dao;

import org.malred.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserDao extends JpaRepository<User,Long> { }

pojo层: 实体类

package org.malred.pojo;

import lombok.Data;
import org.malred.Base.BaseEntity;

import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * 测试用实体类
 */
@Data
@Entity
@Table(name = "user")
// 要使用非默认缓存方式,需要实现序列号接口!
public class User extends BaseEntity<Long> implements Serializable {
    private String name;
    private String password;
    private String address;
    private String phone;

    // 需要重写tostring,包含上父类的属性
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", createTime='" + createTime + '\'' +
                ", updateTime='" + updateTime + '\'' +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", address='" + address + '\'' +
                ", phone='" + phone + '\'' +
                '}';
    }
}

*7, 缓存注解(加在service层的方法上)

*7.1, @EnableCaching

加在启动类上,表示支持缓存

*7.2, @Cacheable 表示 将该方法查询结果存放在springboot默认缓存中

通常加在从数据库取出数据的地方

*7.3, @CachePut 会将数据的变更同步到缓存

通常加在从数据库更新数据的方法上

*7.4 @CacheEvict 会删除缓存

通常放在删除数据库数据的方法上

7.5, 修改后的service

package org.malred.service.impl;

import org.malred.dao.UserDao;
import org.malred.pojo.User;
import org.malred.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DemoServiceImpl implements DemoService {
    @Autowired
    private UserDao userDao;

    // @Cacheable -> 将该方法查询结果存放在springboot默认缓存中
    // cacheNames -> 起一个缓存命名空间,对应缓存唯一标识
    // springboot缓存的map结构 ->
    //      value: 缓存结果;
    //      key: 默认只有一个参数情况下,key值就是方法参数值,如果没有参数或多个参数,会自动生成(simpleKeyGenerate类)
    @Cacheable(
            cacheNames = "user",
            // 如果结果为空就不缓存
            unless = "#result==null"
    )
    @Override
    public List<User> findAll() {
        return userDao.findAll();
    }

    @Cacheable(
            cacheNames = "user",
            // 如果结果为空就不缓存
            unless = "#result==null"
    )
    @Override
    public User findById(Long id) {
        return userDao.findById(id).get();
    }

    // 更新方法,会把变更同步到缓存
    @CachePut(
            cacheNames = "user",
            // 将修改结果的id作为缓存的key
            key = "#result.id"
    )
    @Override
    public User save(User user) {
        return userDao.save(user);
    }

    // 删除方法,会删除缓存
    @Override
    @CacheEvict(
            cacheNames = "user"
    )
    public void delete(Long id) {
        userDao.deleteById(id);
    }
}

*7.6 配置文件

server:
  port: 11111
spring:
  # Redis服务连接配置
  redis:
    # 服务地址
    host: 127.0.0.1
    # 服务器连接端口
    port: 6379
    # 服务器连接密码(默认为空)
    password:
  cache:
    redis:
      # 基于注解的redis缓存使用的缓存时间配置
      time-to-live: 60000
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
  jpa:
    database: MySQL
    show-sql: true # 显示sql
    hibernate:
      naming:
        physical-strategy:
          #避免将驼峰命名转换为下划线命名(jpa会自动把实体类的驼峰字段转为 _ 连接的字段名)
          org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

8, 启动测试

8.1, 启动redis

8.2, 启动项目

8.3, 打开redis图形化软件

8.4, 测试获取数据(findAll,findById),是否自动缓存到redis

这里的 http://bd 的bd是我在本地host配置的解析,结果是127.0.0.1, 你可以使用 http://127.0.0.1:11111来访问,没有区别

8.5, 可以看到确实缓存到了,但是为什么是这种样子呢???

*9, 配置 自定义redis缓存序列化 解决缓存数据看不懂的问题

config/redisConfig.java

package org.malred.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
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;

/**
 * redis配置类
 */
@Configuration
public class RedisConfig {
    /**
     * 自定义redisCacheManager,定义序列化方式
     */
    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 分别创建String和json序列化对象,对缓存数据进行转换
        RedisSerializer<String> strSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer objectJackson2JsonRedisSerializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        // 解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectJackson2JsonRedisSerializer.setObjectMapper(om);
        // 定制缓存序列化方式及其时效
        RedisCacheConfiguration config =
                RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofDays(1))
                        .serializeKeysWith(RedisSerializationContext.SerializationPair
                                .fromSerializer(strSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair
                                .fromSerializer(objectJackson2JsonRedisSerializer))
                        .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager
                .builder(redisConnectionFactory).cacheDefaults(config).build();
        return cacheManager;
    }
}

10, 最终测试

10.1, 测试 findAll和findById

http://127.0.0.1:11111/demo/findAll

10.2, 测试 save (新增和更新)

这里使用apifox(postman好像delete方法不能用)

10.2.1, 新增

10.2.2, 修改

10.3, 测试 delete

11, 代码仓库 已经上传到GitHub和Gitee

https://gitee.com/malguy/redis-cache-boot

https://github.com/malred/redis-cache-boot

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Springredis