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

news2024/12/23 11:31:17

场景: 访问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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/193686.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

懂了委托,才算真正入门C#

文章目录委托的概念多播委托拖动按钮前文提要&#xff1a;超快速成&#xff0c;零基础掌握C#开发中最重要的概念抽丝剥茧&#xff0c;C#面向对象快速上手Winform&#xff0c;最友好的桌面GUI框架 委托的概念 委托这个名字取的神乎其神的&#xff0c;但实质是函数式编程&#…

网络攻防技术--第五次作业

文章目录作业五一、 什么是恶意代码&#xff1f;恶意代码主要类型有哪些&#xff1f;二、 恶意代码的基本技术主要包括哪几种&#xff1f;三、 什么是特洛伊木马&#xff1f;有什么特点&#xff1f;四、 什么是计算机病毒&#xff1f;其有哪几个基本组成部分&#xff1f;五、 什…

项目管理工具能做什么 它给企业带来哪些作用

一个项目经理&#xff0c;如果要想管理好项目&#xff0c;那么一定要掌握项目管理的方法与工具。在项目管理过程中总会借助一些工具来掌控项目点点滴滴&#xff0c;这不仅可以提高团队的生产力和效率&#xff0c;还可以让项目组织在不同项目带来的影响变化中做好准备。 项目管…

每天一道大厂SQL题【Day03】订单量统计

每天一道大厂SQL题【Day03】订单量统计 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&#xff0c…

SAP 自定义SQL编辑器

导语&#xff1a;自定义SQL编辑器&#xff0c;可使用SAP ABAP的OPEN SQL语法进行编辑&#xff0c;快速出具简单报表&#xff0c;基本具备与SAP中DB02相似的功能。 此资源其中有两个版本&#xff0c;一个版本是网上的代码资料&#xff0c;另一个版本是本人优化过后的&#xff0…

Gerrit3.4.1安装使用

环境依赖jdk11 安装jdk11&#xff1a; 下载&#xff1a; wget https://download.java.net/openjdk/jdk11/ri/openjdk-1128_linux-x64_bin.tar.gz解压&#xff1a; tar zxvf openjdk-1128_linux-x64_bin.tar.gzsudo update-alternatives --install /usr/bin/java java /home/je…

Fiddler抓包工具配置+Jmeter基本使用

目录 一、Fiddler抓包工具的配置和使用 局域网络配置# Fiddler配置# Fiddler抓包实例# 二、Jmeter的基本使用 Jmeter的安装配置# 第一个Jmeter脚本# 一、Fiddler抓包工具的配置和使用 在编写网关自动化脚本之前&#xff0c;得先学会如何抓包&#xff0c;这里以Fiddler为…

Docker容器及安装 01

前言 1.1 从环境配置说起 环境配置是软件开发的一大难题。开发、测试及运维人员需要相同的代码运行环境&#xff0c;如此一来就需要多次搭建环境&#xff0c;想想就觉得麻烦&#xff0c;实际上&#xff0c;在不了解docker等容器技术以前&#xff0c;还真就是这么干的&#xff…

2023年FOF/MOM基金研究报告

第一章 概况 FOF 是 Fund of Funds 的简称&#xff0c;即“基金中的基金”&#xff0c;是一种专门投资于其他投资基金的基金。FOF 并不直接投资股票或债券&#xff0c;其投资范围主要限于其他基金。按照募集方式&#xff0c;FOF 分为公募 FOF 和私募 FOF。 公募 FOF 是指将80…

python小游戏——飞机大战代码开源

♥️作者&#xff1a;小刘在这里 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽放&#xff0c;愿所有的美好&#…

明细打印重影方案

一、问题描述生产上出现明细查询打印业务&#xff0c;部分客户打印数据时出现数据重叠现象&#xff0c;不利于客户使用&#xff0c;影响客户体验。二、问题原因对方户名公司名称字段目前没有限制&#xff0c;按照现有的分页处理机制&#xff0c;如果一页纸出现多个公司名称较长…

python中值传递、引用传递、global关键字

结论1、函数中&#xff0c;数值、字符、元组的传递是值传递 b 5 print(id b ,id(b))def change(b):print(\n)#print(在函数change中 修改前 id b ,id(b))b 0print(在函数change中 修改后 id b ,id(b))def print_b():print(\n)print(在函数print b中 b , b)print(在函数pri…

【C++修炼之路】C++入门(下)

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录一、前言二、内联函数1、概念2、特性三、auto&#xff08;C 11)1、概念2、价值3、三个不能四、范围for循环(C11)1、基本使用2、使…

MySQL基础(3)—— MySQL数据类型

文章目录数值类型1、整数类型2、浮点数类型3、定点数类型日期和时间类型1、YEAR2、DATE、TIME、DATETIME3、TIMESTAMP字符串类型1、CHAR(M)2、VARCHAR(M)3、各种 TEXT 类型4、ENUM 类型和 SET 类型二进制类型1、BIT 类型2、BINARY(M) 与 VARBINARY(M)3、BLOB 类型MySQL说到底就…

移动出行2023:聊以新颜待今朝

兔年春节期间&#xff0c;城市再现浓浓烟火气。预订全满的年夜饭、排不到号的奶茶店以及火爆的电影票房等&#xff0c;证明着“吃、游、购、娱”等需求集中释放的“威力”。根据国家税务总局发布的最新数据&#xff0c;今年春节假期&#xff0c;全国消费相关行业销售收入与上年…

最长上升子序列问题(LIS问题)与最长不上升子序列问题的四种方法(c++ 模板代码)

文章目录动态规划树状数组线段树二分查找最大上升子序列问题也叫做LIS问题&#xff0c;与最大公共子序列LCS问题是一类经典问题&#xff0c;在本章我们将总结一下求解LIS最大上升子序列的几种方法&#xff0c;同时也会给出对应的最大不上升子序列的求解方法。 关于LCS问题&…

【表格单元格可编辑】vue-elementul简单实现table表格点击单元格可编辑,点击单元格变成输入框修改数据

前言 这是最近遇到的功能&#xff0c;经常会需要一个表格可以编辑数据 类似于excel那种点击一下单元格就可以编辑数据&#xff0c;修改后鼠标移动出去 光标消失就会保存数据给后台 这里记录一下实现方法&#xff0c;其实也比较简单 就是通过角标来判断显示隐藏的 效果图 代码…

[Android开发基础4] 点击事件的响应与处理

文章目录 方法一&#xff1a;控件的onClick属性 方法二&#xff1a;内部类 方法一&#xff1a;控件的onClick属性 利用控件自带的onClick属性&#xff0c;指定事件处理函数名称即可实现控件点击事件的处理 这里有个小技巧就是当设置完控件的onClick属性后&#xff0c;它会报没…

XXE漏洞常见利用点总结

目录 知识点小结 常用payload 本地文件读取 SSRF 引入外部实体 dtd 信息探测 XXE漏洞攻击 案例演示 案例一&#xff08;有回显&#xff09; 案例二&#xff08;无回显读取本地敏感文件(Blind OOB XXE)&#xff09; XXE 防御 使用语言中推荐的禁用外部实体的方法 知…

08-linux网络管理-iftop命令详解

文章目录1. 安装2. 基本使用2.1 命令2.2 输出2.3 说明3. 选项3.1 选项说明3.2 几个示例-n&#xff08;不查找主机名&#xff09;-i &#xff08;查看指定网卡流量&#xff09;-P&#xff08;显示主机端口&#xff09;-t&#xff08;不使用ncurses 界面&#xff09;4. ncurses界…