SpringBoot 缓存

一、SpringBoot与缓存

1、基础概念

缓存:缓存是指可以进行高速数据交换的存储器,它先于内存与CPU交换数据,因此速率很快。
缓存作用:缓存的工作原理是当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理;没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。(例:在数据库中利用缓存机制可以大大降低数据库的负压能力,当我们查询相同的数据时不用每一次都在数据库中查询,这时我们就可以利用缓存机制将第一次在数据库中查询的内容保存在缓存中,这样不仅提高了效率而且减少了数据库的负担)

2、搭建环境
2.1、引入缓存的相关依赖
<!--缓存的依赖-->
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
 </dependency>
 <!--我们要连接数据库进行测试引入数据库驱动-->
<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
</dependency>
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
</dependency>
2.2、数据库配置文件
spring.datasource.url=jdbc:mysql://localhost:3306/spring_cache?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234567890
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.redis.host=xxx.xxx.xx.xxx
2.3、整合MyBatis操作数据库

[详情见博客SpringBoot与数据访问(MyBatis)]j(https://blog.csdn.net/qq_43775034/article/details/104151952)

2.4、编写实体类(bean)

Employee.java

package com.atorg.cache.bean;
import java.io.Serializable;
public class Employee implements Serializable {
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;    //性别 1男 0女
    private Integer dId;

    public Employee() {
        super();
    }

    public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.dId = dId;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public Integer getdId() {
        return dId;
    }

    public void setdId(Integer dId) {
        this.dId = dId;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                ", dId=" + dId +
                '}';
    }
}

Department.java

package com.atorg.cache.bean;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.io.Serializable;
public class Department implements Serializable {
    private Integer id;
    private String departmentName;

    public Department() {
        super();
    }

    public Department(Integer id, String departmentName) {
        this.id = id;
        this.departmentName = departmentName;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }

    @Override
    public String toString() {
        return "Department {id="+id+",departmentName="+departmentName+"}";
    }
}
2.5、编写mapper操作数据库

DepartmentMapper.java

package com.atorg.cache.mapper;
import com.atorg.cache.bean.Department;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface DepartmentMapper {
    @Select("SELECT * FROM department WHERE id=#{id}")
    Department getDeptById(Integer id);
}

EmployeeMapper.java

package com.atorg.cache.mapper;
import com.atorg.cache.bean.Employee;
import org.apache.ibatis.annotations.*;
@Mapper
public interface EmployeeMapper {
//查询数据
    @Select("SELECT * FROM employee WHERE id=#{id}")
    public Employee getEmpById(Integer id);
//更新数据
    @Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
    public void updateEmp(Employee employee);
//删除数据
    @Delete("DELETE FROM employee WHERE id=#{id}")
    public void deleteEmpById(Integer id);
//插入数据
    @Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
    public void insertUser(Employee employee);
//按名查询
    @Select("SELECT * FROM employee WHERE lastName=#{lastName}")
    public Employee  getEmpByLastName(String lastName);
}

注意@Mapper扫描mapper接口 (扫描操作数据库所在的接口),在每一个mapper接口写一个@Mapper注解很麻烦,所以我们在主配置类中使用 @MapperScan(“mapper所在的包名”) 将扫描所有操作数据库的接口在每一个接口上默认加上@Mapper注解

3、快速体验缓存
3.1、开启基于注解的缓存

我们要使用基于注解的缓存必须要在主配置类中使用 @EnableCaching注解

3.2、编写基于注解缓存的service类

EmployeeService.java

package com.atorg.cache.service;
import com.atorg.cache.bean.Employee;
import com.atorg.cache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    public Employee getEmpById(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp=employeeMapper.getEmpById(id);
        return emp;
    }

    @CachePut(value = "emp")
    public Employee updateEmp(Employee employee){
        System.out.println("updateEmp:"+employee);
        employeeMapper.updateEmp(employee);
        return employee ;
    }

    @CacheEvict(/*value = "emp"*//*,key = "#id",*/allEntries = true)
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp"+id);

    }

    //@Caching  定义复杂的缓存规则
    @Caching(
            cacheable = {
                    @Cacheable(/*value = "emp",key="#lastName"*/)
            },
            put = {
                    @CachePut(/*value = "emp",key = "#result.id"*/),
                    @CachePut(/*value = "emp",key = "#result.email"*/)
            }
    )
    public Employee getEmpByLastName(String lastName){
        return employeeMapper.getEmpByLastName(lastName);
    }
}
3.3、**@Cacheable注解

Cacheable的几个属性详讲
1、 cacheNames/value
指定缓存的名字,将方法的返回结果放在那个缓存中,是数组的形式,可以指定多个缓存
2、 key(在缓存中是以键值对的形式存在)
缓存使用的key由于数据在缓存中是以键值对的形式存在,故key可以用它来指定,

1)、默认是使用方法参数的值如果方法参数是1则值为:方法的返回值
2)、编写SpEl #id;参数id的值 《=======》 #a0 #p0 #root.args[0]

3、keyGenerator:
key的生成器,可以自己指定key的生成器的组件id keyGenerator/key:二选一使用
4、cacheManager
指定缓存管理器或者指定缓存解析器cacheResolver
cacheManager/cacheResolver二选一使用
5、condition
指定符合条件的情况下才缓存 例:condition = "#id>1"当id>1时进行缓存
6、unless
否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存,可以获取到结果进行判断
例:unless = “#result==null” 如果结果为null时不缓存

例:unless = "#a0==2"如果第一个参数的值是2,结果就不缓存
7、sync
是否使用异步模式
Cacheable运行流程
1、方法运行之前先去查询Cache组件,按照cacheNames指定的名字存取(cacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
2、去Cache中查找缓存的内容,使用一个key,key默认就是方法的参数 key是按照某种策略生成的;是使用keyGenerator生成的,默认使用simpleKeyGenerator生成key simpleKeyGenerator生成的key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数;key=参数的值;
如果有多个参数;key=new SimpleKey(params)
3、没有查到缓存就调用目标方法
4、将目标方法返回的结果放入缓存中
5、自定义KeyGenerator

package com.atorg.cache.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;

@Configuration
public class MyCacheConfig {
    @Bean("myKeyGenerator")  //id为myKeyGenerator
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName()+"["+ Arrays.asList(objects)+"]";
            }
        };
    }
}

使用自定义的KeyGenerator只需在缓存注解中声明自定义KeyGenerator的id

@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")

核心
@Cacheable方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值key去查询缓存,如果没有就去运行这哥方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据

3.4、@CachePut注解

即调用方法又同步更新了缓存数据【即修改数据库的数据时同时同步更新了缓存】(注意取缓存与放缓存的key必须是同一个值
运行时机:
1、先调用目标方法
2、将目标方法的结果缓存起来

3.4.1、测试步骤

1、查询1号员工(第一次查询查询数据库并将数据放入数据库中)
在浏览器中输入请求地址:http://localhost:8080/emp/1
第一次查询查询数据库并将数据存入缓存
2、查询1号员工(第二次不通过数据库而是通过缓存)
在浏览器中输入请求地址:http://localhost:8080/emp/1
第二次直接通过缓存得到数据
3、更新1号员工【lastName=‘zhangsan’, gender=0】
在浏览器中输入请求地址:http://localhost:8080/emp?lastName=zhangsan&gender=0
更新后的控制台打印
4、查询1号员工此时查询到的数据还是汉字张三而我们重新编写的为英文zhangsan
查询数据还是张三
然而数据库信息已经更新为什么我们得到的还是原来的值???
数据库信息已经更新
控制台打印更新数据
经过分析我们得到结果:我们在存入缓存时的 key默认为方法的参数值在调用查询方法时查询员工时key的值为id(在此例中为Employee的id的值即为 key=#employee.id而我们在更新缓存时默认的key为方法的返回值即为:key=employee为Employee的对象 存与更新缓存的key值不一样造成了最终取出的结果不一样,而此时的缓存也已更新,当我们再次查询缓存时调用查询方法使用的key的值为id因此查询出的结果时key的值按照id存入的缓存,故我们看到查到原来的数据
缓存更新
解决方法:
我们将查询与更新数据的缓存的key值设置为相同的,这样保证了我们再存缓存与取缓存时的key值相同,更新以后查处的数据也是更新以后的

    @Cacheable(cacheNames = {"emp"})//默认key的值为方法的参数id
    public Employee getEmpById(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp=employeeMapper.getEmpById(id);
        return emp;
    }
    @CachePut(value = "emp",key = "#result.id")//指定方法的参数是返回值的id
    public Employee updateEmp(Employee employee){
        System.out.println("updateEmp:"+employee);
        employeeMapper.updateEmp(employee);
        return employee ;
    }

注意:@Cacheable的key是不能用#result的,因为Cacheable是先查缓存如果缓存没有再去执行方法,然而一开始的缓存肯定是空的,因为没有调用方法所以没有返回值此时返回值为null因此#result为null会报错
此时向浏览器发送:http://localhost:8080/emp?id=1&lastName=zhangsan&gender=0
更新数据查询1号员工:http://localhost:8080/emp/1
查询1号员工
控制打印语句也没有数据库查询语句说明去查缓存了
控制台打印
缓存更新
缓存更新

3.5、@CacheEvict注解

几个重要的属性
1、key:指定要清除的数据
2、 allEntries = true:指定清除这个缓存中所有的数据
3、beforeInvocation=false:缓存的清除是否在方法之前执行 默认代表缓存清除操作是在方法之后;如果出现异常缓存就不会被清除
4、beforeInvocation=true代表缓存清除操作在方法执行之前,无论方法是否出现异常,缓存都清除。
此时缓存中有数据
缓存中有数据
向浏览器发送:http://localhost:8080/delemp?id=1删除1号员工
删除1号员工成功
此时的缓存中的数据被清空了
缓存的数据被清空

4、整合Redis作为缓存(序列化)

1)、引入redis的starter,容器中保存的是 RedisCacheManager
2)、RedisCacheManager帮我们创建 redisCache作为缓存组件,RedisCache通过操作redis缓存数据
3)、默认保存数据k-v都是object;利用序列化保存,如何保存为json

  • 1|引入了redis的starter,CacheManager变为RedisCacheManager
<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
  • 2、默认创建的RedisCacheManager操作Redis的时候使用的是RedisTemplate<Object,Object>
  • 3.RedisTemplate<Object,Object>是默认使用jdk的序列化机制
    默认使用jdk的序列化机制

4)、自定义CacheManager(SpringBoot2.2.x以上版本

package com.atorg.cache.config;
import com.atorg.cache.bean.Department;
import com.atorg.cache.bean.Employee;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.net.UnknownHostException;

@Configuration
public class MyRedisConfig {
    //CacheManagerCustomizers可以来定制缓存的一些规则
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //定义value序列化方式
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());//定义key序列化方式
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return  redisTemplate;
    }

    @Primary//将某个缓存管理器设置为默认
    @Bean
    public RedisCacheManager empCacheManager(RedisConnectionFactory redisConnectionFactory){
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Employee.class);
        RedisCacheConfiguration config =  RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration1 = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration1)
                .build();
        return  redisCacheManager;
    }

    @Bean
    public RedisCacheManager deptCacheManager(RedisConnectionFactory redisConnectionFactory){
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Department.class);
        RedisCacheConfiguration config =  RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration1 = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration1)
                .build();
        return  redisCacheManager;
    }
}

有问题希望大家能多多斧正

.net EXT学习资料与源码 ext是一个强大的js类库,以前是基于YAHOO-UI,现在已经完全独立了,   主要包括data,widget,form,grid,dd,menu,其中最强大的应该算grid了,编程思想是基于面对对象编程(oop),扩展性相当的好.可以自己写扩展.自己定义命名空间.web应用可能感觉太大.不过您可以根据需要按需加载您想要的类库就可以了.   主要包括三个大的文件ext-all.css,ext-base.js,ext-all.js(包括所有的类库,您可以根据需要进行删减.官方网站提供这一接口),在引用ext类库的时候.这三个文件必不可少.   它提供了丰富的,非常漂亮的外观体验,成为众多界面层开发人员的追捧!其核心的组件基本覆盖了我们构建富客户端的常用的组件。   从 Ext 2开始,商业版(针对那些以盈利为目的的开发方)要收费了。这可能影响一些他的应用前景。   目前的最新版本为3.0RC,该版本是在2009年4月15日发布的。 三层架构,   三层架构(3-tier application) 通常意义上的三层架构就是将整个业务应用划分为:表现层(UI)、业务逻辑层(BLL)、数据访问层(DAL)。区分层次的目的即为了“高内聚,低耦合”的思想。   1、表现层(UI):通俗讲就是展现给用户的界面,即用户在使用一个系统的时候他的所见所得。   2、业务逻辑层(BLL):针对具体问题的操作,也可以说是对数据层的操作,对数据业务逻辑处理。   3、数据访问层(DAL):该层所做事务直接操作数据库,针对数据的增添、删除、修改、更新、查找等。 [编辑本段]概述   在软件体系架构设计中,分层式结构是最常见,也是最重要的一种结构。微软推荐的分层式结构一般分为三层,从下至上分别为:数据访问层、业务逻辑层(又或成为领域层)、表示层。   三层结构原理:   3个层次中,系统主要功能和业务逻辑都在业务逻辑层进行处理。   所谓三层体系结构,是在客户端与数据库之间加入了一个“中间层”,也叫组件层。这里所说的三层体系,不是指物理上的三层,不是简单地放置三台机器就是三层体系结构,也不仅仅有B/S应用才是三层体系结构,三层是指逻辑上的三层,即使这三个层放置到一台机器上。   三层体系的应用程序将业务规则、数据访问、合法性校验等工作放到了中间层进行处理。通常情况下,客户端不直接与数据库进行交互,而是通过COM/DCOM通讯与中间层建立连接,再经由中间层与数据库进行交互。   表示层    位于最外层(最上层),离用户最近。用于显示数据和接收用户输入的数据,为用户提供一种交互式操作的界面。   业务逻辑层    业务逻辑层(Business Logic Layer)无疑是系统架构中体现核心价值的部分。它的关注点主要集中在业务规则的制定、业务流程的实现等与业务需求有关的系统设计,也即是说它是与系统所应对的领域(Domain)逻辑有关,很多时候,也将业务逻辑层称为领域层。例如Martin Fowler在《Patterns of Enterprise Application Architecture》一书中,将整个架构分为三个主要的层:表示层、领域层和数据源层。作为领域驱动设计的先驱Eric Evans,对业务逻辑层作了更细致地划分,细分为应用层与领域层,通过分层进一步将领域逻辑与领域逻辑的解决方案分离。   业务逻辑层在体系架构中的位置很关键,它处于数据访问层与表示层中间,起到了数据交换中承上启下的作用。由于层是一种弱耦合结构,层与层之间的依赖是向下的,底层对于上层而言是“无知”的,改变上层的设计对于其调用的底层而言没有任何影响。如果在分层设计时,遵循了面向接口设计的思想,那么这种向下的依赖也应该是一种弱依赖关系。因而在不改变接口定义的前提下,理想的分层式架构,应该是一个支持可抽取、可替换的“抽屉”式架构。正因为如此,业务逻辑层的设计对于一个支持可扩展的架构尤为关键,因为它扮演了两个不同的角色。对于数据访问层而言,它是调用者;对于表示层而言,它却是被调用者。依赖与被依赖的关系都纠结在业务逻辑层上,如何实现依赖关系的解耦,则是除了实现业务逻辑之外留给设计师的任务。   数据层    数据访问层:有时候也称为是持久层,其功能主要是负责数据库的访问,可以访问数据库系统、二进制文件、文本文档或是XML文档。   简单的说法就是实现对数据表的Select,Insert,Update,Delete的操作。如果要加入ORM的元素,那么就包括对象和数据表之间的mapping,以及对象实体的持久化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值