봄 부팅 캐시 첫 경험

봄 부트 캐시 첫 경험

구축 1. 프로젝트

데이터베이스를 운영, 그래서 캐시 구성 요소 springboot 사용하는 데이터베이스, 봄 부팅 통합의 MyBatis로 MySQL을 사용하여, 우리는 간단한 SSM 환경을 구축 할 필요가있다.

첫 번째는 프로젝트가 의존

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

데이터베이스 테스트 데이터

CREATE TABLE `student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gender` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, 'eric', 'male', 22);
INSERT INTO `student` VALUES (2, 'alice', 'female', 23);
INSERT INTO `student` VALUES (3, 'bob', 'male', 21);

다음과 같이 클래스 코드를 해당 법인 :

public class Student {
    private Integer id;
    private String name;
    private String gender;
    private Integer age;
    //省略构造函数,getter,setter,toString
}

대응 매퍼 :

public interface StudentMapper {
    @Select("select * from student where id = #{id}")
    Student getStudentById(Integer id);
}

해당 서비스 :

@Service
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;

    public Student getStudentById(Integer id) {
        return studentMapper.getStudentById(id);
    }
}

테스트 클래스 대응 :

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoCacheApplicationTests {
    @Autowired
    private StudentService studentService;

    /**
     * 测试mybatis是否正确配置
     */
    @Test
    public void contextLoads() {
        System.out.println(studentService.getStudentById(1));
    }

}

성공적으로 인쇄 위의 시험 방법을 실행

student{id=1, name='eric', gender='male', age=22}

기본 랙을 구축하는 프로젝트가 성공적으로, 다음 단계는 테스트를 제공 springboot 캐시 주석을 사용하는 것입니다.

이에 앞서, 먼저 몇 가지 배경.

첫 번째는이다 JSR107의 자바 캐싱은 5 개 가지 핵심 인터페이스 CachingProvider, CacheManager, 캐시, 입구입니다 정의, 캐시 사양
및 만료.

  • CachingProvider는
    구성, 생성, 수집, 관리하고 여러 CacheManager를 제어 정의합니다. 응용 프로그램은 런타임에 여러 CachingProvider에 액세스 할 수 있습니다.
  • CacheManager는
    이러한 CacheManager의 맥락에서 존재하는 캐시 구성, 생성, 수집, 관리하고 여러 고유 한 이름 캐시를 제어 정의합니다. CacheManager 만 CachingProvider 소유.
  • 캐시
    지도 키와 유사 일시적으로 지수의 값으로 저장되어있는 데이터 구조입니다. 캐시는 소유 만 CacheManager입니다.
  • 엔트리는
    캐시에 저장된 키 - 값 쌍이다.
  • 유효
    각각의 캐시 항목에 저장하는 것은 올바른 정의를 가지고있다. 일단이 시간이 지남에 만료 된 상태에 대한 항목. 만료되면 항목에 액세스, 갱신, 그리고 삭제되지 않습니다. 캐시의 유효 기간은 ExpiryPolicy 설정할 수 있습니다.

Snipaste_2019-09-23_21-58-01.png

스프링은 다른 캐쉬 기술을 통합하기 위해 3.1과 org.springframework.cache.Cache org.springframework.cache.CacheManager 인터페이스를 정의 시작하고 개발을 단순화 JCache [JSR-107 주석의 사용을 지원한다.

우리는 캐시 인터페이스의 기본 구조를 보면 :

Snipaste_2019-09-25_23-33-25.png

RedisCache, EhCacheCache, ConcurrentMapCache이 같은 기본적인 캐시의 다양한 구성 요소의 캐시 동작을위한 캐시 인터페이스 사양은 일반적으로 스프링 xxxCache는, 예를 들어 사용되는 구현의 다양한 제공한다.

현재에 의존 추가 캐시를 달성하기 위해 찾을 수 있습니다

Snipaste_2019-09-25_23-32-20.png

마다 방법은 캐시 함수 호출을 필요로 봄은 대상 메소드 지정된 매개 변수가 호출되었는지 여부를 확인하는 것, 그리고 결과가 캐시라고 방법이 후, 없다, 그것은 캐시에서 직접 확인할 수 있습니다 후 경우 사용자에게 반환 데이터가 캐시에서 직접 취득.

그래서 다음과 같은 측면을 고려 캐시를 사용합니다 :

  • 당신은 당신이 방법을 캐시 하시겠습니까?
  • 캐시 정책을 결정하는 방법 (예 : 키 설정 등을, 캐시 된 데이터는 JSON 형식 또는 Java 직렬화를 사용하는 것입니다)
  • 캐시와 데이터베이스는 데이터 일관성을 보장하는 방법
  • 캐시에서 각 읽기 이전에 캐시 된 데이터

첫째, 모든 방법은 일반적으로 자주 액세스 자주 수정 된 데이터는 캐시 할 필요가 없습니다, 캐시를 필요로하지.

키 생성 전략은 키를 지정하는 속성을 직접 사용할 수 있습니다, 당신은 또한의 KeyGenerator를 지정할 수 있습니다

일련 번호의 경우 방법은 기본적으로 자바 캐시 데이터를 사용하여, 우리는 프로젝트 요구 사항을 참조 JSON 형식으로 저장할 수 있습니다.

缓存的一致性,这个比较复杂,本文不涉及到高并发情况下缓存和数据库一致的讨论,只是保证在数据修改或删除时,及时地更新缓存中的数据。换句话说,就是数据在缓存之后,如果之后调用了修改的方法,把数据修改了,需要CachePut注解及时地把缓存里的数据也一并修改,或者,调用了删除的方法,需要使用CacheEvict注解来删除相应缓存的数据。

至于每次都从缓存中读取已经缓存过的数据,这个事情就交给Spring来自动处理吧。

Cache 缓存接口,封装缓存的基本操作
CacheManager 缓存管理器,管理各种缓存组件,一个应用程序可以有多个缓存管理器
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存,一般用于修改数据。
@EnableCaching 开启基于注解的缓存
keyGenerator 缓存数据时key生成策略
serialize 缓存数据时value序列化策略

@CachePut@Cacheable两个注解的区别是什么呢?

@CachePut:这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。

@Cacheable:当重复使用相同参数调用方法的时候,方法本身不会被调用执行,即方法本身被略过了,取而代之的是方法的结果直接从缓存中找到并返回了。

​ 对于@CachePut这个注解,它的作用是什么呢,每次方法都执行,那么缓存的意义是什么呢?答案很简单,同一个缓存实例的相同的key的缓存的数据,可以用@CachePut更新,而@Cacheable在取值的时候,是@CachePut更新后的值。但同时也要注意确保是同一个缓存实例对象,并且key要保证一致!!!

@Cacheable,@CachePut,@CacheEvict注解的常用属性如下:

属性 作用 示例
value 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”}
key 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 例如: @Cacheable(value=”testcache”,key=”#userName”)
condition 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries (@CacheEvict ) 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true)
beforeInvocation (@CacheEvict) 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 例如: @CachEvict(value=”testcache”,beforeInvocation=true)
unless (@CachePut) (@Cacheable) 用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存 例如: @Cacheable(value=”testcache”,unless=”#result == null”)

Cache SpEL available metadata

名字 位置 描述 示例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 #root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache #root.caches[0].name
argument name evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; #iban 、 #a0 、 #p0
result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) #result

这个掌握就好,没有必要去死记硬背,默认情况下的配置都是够用的。

2.缓存使用过程解析

首先需要引入spring-boot-starter-cache依赖

然后使用@EnableCaching开启缓存功能

然后就可以使用缓存注解来支持了。

先看一下官方API里面是怎么说的吧:

@Target(value=TYPE)
@Retention(value=RUNTIME)
@Documented
@Import(value=CachingConfigurationSelector.class)
public @interface EnableCaching

Enables Spring's annotation-driven cache management capability,To be used together with @Configuration classes as follows:

@Configuration
@EnableCaching
public class AppConfig {

    @Bean
    public MyService myService() {
        // configure and return a class having @Cacheable methods
        return new MyService();
    }
    @Bean
    public CacheManager cacheManager() {
        // configure and return an implementation of Spring's CacheManager SPI
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
        return cacheManager;
    }
}

@EnableCaching is responsible for registering the necessary Spring components that power annotation-driven cache management, such as the CacheInterceptor and the proxy- or AspectJ-based advice that weaves the interceptor into the call stack when @Cacheable methods are invoked.

官方文档的描述简洁明了,我们只需要开启缓存,然后定制CacheManager即可。

If the JSR-107 API and Spring's JCache implementation are present, the necessary components to manage standard cache annotations are also registered. This creates the proxy- or AspectJ-based advice that weaves the interceptor into the call stack when methods annotated with CacheResult, CachePut, CacheRemove or CacheRemoveAll are invoked.

强大的spring同样支持了JSR107缓存注解!!!当然,本文还是主要以讲解spring的缓存注解为主。

For those that wish to establish a more direct relationship between @EnableCaching and the exact cache manager bean to be used, the CachingConfigurer callback interface may be implemented. Notice the @Override-annotated methods below:

如果想要明确地定制你的CacheManager,可以像下面这样使用

 @Configuration
 @EnableCaching
 public class AppConfig extends CachingConfigurerSupport {

     @Bean
     public MyService myService() {
         // configure and return a class having @Cacheable methods
         return new MyService();
     }

     @Bean
     @Override
     public CacheManager cacheManager() {
         // configure and return an implementation of Spring's CacheManager SPI
         SimpleCacheManager cacheManager = new SimpleCacheManager();
         cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
         return cacheManager;
     }

     @Bean
     @Override
     public KeyGenerator keyGenerator() {
         // configure and return an implementation of Spring's KeyGenerator SPI
         return new MyKeyGenerator();
     }
 }

This approach may be desirable simply because it is more explicit, or it may be necessary in order to distinguish between two CacheManager

因为一个应用环境下可以有多个CacheManager,这样声明CacheManager可以更加直观。

Notice also the keyGenerator method in the example above. This allows for customizing the strategy for cache key generation, per Spring's KeyGenerator SPI. Normally, @EnableCaching will configure Spring's SimpleKeyGenerator for this purpose, but when implementing CachingConfigurer, a key generator must be provided explicitly. Return null or new SimpleKeyGenerator() from this method if no customization is necessary.

如果实现了CachingConfigurer接口,就需要明确定义keyGenerator

CachingConfigurer offers additional customization options: it is recommended to extend from CachingConfigurerSupport that provides a default implementation for all methods which can be useful if you do not need to customize everything. See CachingConfigurer Javadoc for further details.

可以通过继承CachingConfigurerSupport来实现其它的定制功能。CachingConfigurerSupport类的结构如下,可以只对你需要定制的功能进行重写,其它的一律默认返回null即可,如果返回null,那么spring boot 的自动配置就会生效。


/**
 * An implementation of {@link CachingConfigurer} with empty methods allowing
 * sub-classes to override only the methods they're interested in.
 *
 * @author Stephane Nicoll
 * @since 4.1
 * @see CachingConfigurer
 */
public class CachingConfigurerSupport implements CachingConfigurer {

    @Override
    public CacheManager cacheManager() {
        return null;
    }

    @Override
    public KeyGenerator keyGenerator() {
        return null;
    }

    @Override
    public CacheResolver cacheResolver() {
        return null;
    }

    @Override
    public CacheErrorHandler errorHandler() {
        return null;
    }

}

The mode() attribute controls how advice is applied: If the mode is AdviceMode.PROXY (the default), then the other attributes control the behavior of the proxying. Please note that proxy mode allows for interception of calls through the proxy only; local calls within the same class cannot get intercepted that way.

Note that if the mode() is set to AdviceMode.ASPECTJ, then the value of the proxyTargetClass() attribute will be ignored. Note also that in this case the spring-aspects module JAR must be present on the classpath, with compile-time weaving or load-time weaving applying the aspect to the affected classes. There is no proxy involved in such a scenario; local calls will be intercepted as well.

真是纵享丝滑。

3.实际上手

@CacheConfig注解可以定义当前类的所有使用到缓存注解(@Cacheable,@CachePut,@CacheEvict)的通用配置,下面的示例代码实际只配置了当前类的缓存名称

@Service
@CacheConfig(cacheNames = "student")
public class StudentService {
    @Autowired
    private StudentMapper studentMapper;

    @Cacheable
    public Student getStudentById(Integer id) {
        System.out.println("从数据库中查询学生:" + id);
        return studentMapper.getStudentById(id);
    }

    @CachePut
    public Student updateStudent(Student student) {
        System.out.println("更新数据库中的学生数据:" + student);
        studentMapper.updateStudent(student);
        return student;
    }

    @CacheEvict
    public void deleteStudent(Integer id) {
        System.out.println("删除数据库中的学生:"+id);
        studentMapper.delStudent(id);
    }
}

上面只是简单的使用这三个注解,更加详细的属性使用,请看后面的内容。我们先测试一下缓存的使用效果。

测试类的代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoCacheApplicationTests {
    @Autowired
    private StudentService studentService;

    @Test
    public void contextLoads() {
        System.out.println(studentService.getStudentById(1));
    }

    @Test
    public void testUpdate() {
        studentService.updateStudent(new Student(1,"gotohell","female",23));
    }

    @Test
    public void testDelete() {
        studentService.deleteStudent(1);
    }
}

首先测试@Cacheable注解,第一次调用该方法,打印的日志如下:

从数据库中查询学生:1
student{id=1, name='mmm', gender='male', age=21}

第二次调用该方法,打印的日志如下:

student{id=1, name='mmm', gender='male', age=21}

说明缓存已经生效了,没有从数据库中获取学生数据。我们看一下缓存里面的内容,

Snipaste_2019-09-26_22-52-46.png

这是默认使用jdk序列化存储的结果,我们可以选择采用json格式存储数据。另外,key的生成策略,默认是cache名称前缀加上方法参数,我觉得这个默认情况下就已经够用了,不需要再进行额外的定制。

再来测试一下修改,

打印日志如下:

更新数据库中的学生数据:student{id=1, name='gotohell', gender='female', age=23}

查看数据库中的数据,已经修改成功,redis的数据由于是序列化的,这里就不截图了,我们直接再调用一次查询看它有没有更新即可。

打印结果如下:

student{id=1, name='mmm', gender='male', age=21}

설명 캐시에 데이터를 업데이트하지 않습니다, 그것은 코멘트가 작동하지 않습니다 @CachePut입니까?

룩 레디 스

Snipaste_2019-09-26_23-01-43.png

기본적으로, 핵심 전략은 매개 변수 캐싱 네임 학생 + 방법을 생성하고, 파라미터 업데이트 방법이기 때문에 데이터의 원래의 두번째 개정, 기본 캐시 키 객체를 사용하는 것이, 왜, 학생들이 개체를 발견, 두 사람이 일치하지 않는 키 때문에 테스트, 업데이트 후 데이터를 얻을 수 없습니다.

그래서 긴 키가 아직 업데이트 방법으로 할 수 지정으로

@CachePut(key = "#result.id")
public Student updateStudent(Student student) {
    System.out.println("更新数据库中的学生数据:" + student);
    studentMapper.updateStudent(student);
    return student;
}

키가이처럼 재 할당, 그것은 표현의 봄, 특정 사용 규칙, 이전에 나열된 테이블을 지원합니다. 다음과 같이 다시 시험 후, 인쇄 로그는 다음과 같습니다

student{id=1, name='gotohell', gender='female', age=23}

핵심적인 역할을 나타내는 업데이트 후 데이터를 얻으려면.

다시 다음과 같이 인쇄 로그가 삭제 테스트 :

删除数据库中的学生:1

데이터베이스의 데이터가 성공적으로 제거 된 데이터 캐시는 삭제되었습니다.

Snipaste_2019-09-26_23-11-59.png

이 시간은 다시 다음과 같이 로그가 인쇄, 쿼리를 호출 :

从数据库中查询学生:1
null

데이터베이스 캐시와 사라,하지만 데이터가 삭제 되었기 때문에보기의 로그에서 인쇄, 그것은 null을 반환, 데이터베이스를 쿼리

4. JSON 객체 직렬화

이 CacheManager를 사용자 정의하고 새 구성 클래스를 추가하기 위해 우리를 필요

@Configuration
public class MyRedisConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        //初始化一个RedisCacheWriter
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        //设置CacheManager的值序列化方式为json序列化
        RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer());
        RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(pair).entryTtl(Duration.ofHours(1));
        //初始化RedisCacheManager
        return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
    }
}

재시험 쿼리 방법, 우리는 JSON 직렬화 형식을 사용하여 값의 캐시를 발견했다.

Snipaste_2019-09-26_23-46-09.png

원본 주소 : HTTPS : //github.com/lingEric/springboot-integration-hello

추천

출처www.cnblogs.com/ericling/p/11595222.html