春のブートキャッシュ初めての経験

春のブートキャッシュ初めての経験

構築する1.プロジェクト

春ブーツ統合MyBatisのは、データベースを操作するので、キャッシュコンポーネントspringboot使用し、データベースとして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の Javaのキャッシュは、5つのコアインタフェースがCachingProvider、のCacheManager、キャッシュ、入力され定義され、キャッシュ仕様
と有効期限。

  • CachingProviderは
    、作成、構成、取得、管理、および複数のCacheManagerを制御する定義します。アプリケーションは、実行時に複数のCachingProviderにアクセスすることができます。
  • CacheManagerは、
    これらのCacheManagerのコンテキストに存在キャッシュ、一意キャッシュ名前、作成、設定、取得、管理、および複数の制御定義します。唯一CachingProvider所有のCacheManager。
  • キャッシュは、
    地図のキーに類似して一時的指標の値として格納されたデータ構造です。キャッシュが所有する唯一の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

基本的なキャッシュの様々な構成要素のキャッシュ操作のためのキャッシュ・インタフェース仕様は、ばねが一般的に使用されるxxxCacheの多様を提供例えば、実装:RedisCache、EhCacheCache、ConcurrentMapCacheが好き。

現在では依存の追加、およびキャッシュを達成するために見つけることができます

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

たびメソッドはキャッシュ関数呼び出しを必要とし、春には、ターゲットメソッド指定されたパラメータが呼び出されたかどうかを確認すること、そしてそれがキャッシュから直接利用可能な場合、結果がキャッシュと呼ばれている方法はありません後にユーザーに返さ、後になりますデータはキャッシュから直接取得されています。

したがって、次の側面を考慮するためにキャッシュを使用します。

  • あなたがメソッドをキャッシュしてよろしいです
  • (例えばキー設定などの、キャッシュされたデータはJSON形式またはJavaシリアライゼーションの使用で)キャッシュポリシーを決定するための方法
  • キャッシュとデータベースは、データの整合性を確保する方法
  • 各キャッシュから以前にキャッシュされたデータを読み取ります

まず、すべてのメソッドはキャッシュを必要としない、一般的には、頻繁にアクセスして頻繁に変更されていないデータのみをキャッシュする必要があります。

鍵生成戦略はキーを指定する属性を直接使用することができ、あなたもするKeyGeneratorを指定することができます

シリアル番号の場合の方法は、我々は、プロジェクトのニーズを参照して、JSON形式として保存することができ、デフォルトでJavaキャッシュデータを使用しています。

缓存的一致性,这个比较复杂,本文不涉及到高并发情况下缓存和数据库一致的讨论,只是保证在数据修改或删除时,及时地更新缓存中的数据。换句话说,就是数据在缓存之后,如果之后调用了修改的方法,把数据修改了,需要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のですか?

Redisのを見て

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

デフォルトでは、キーの戦略は、パラメータキャッシングネーム学生+メソッドを生成することで、パラメータの更新方法があるので、データの元の第2改正は、デフォルトのキャッシュキーオブジェクトを使用することを、なぜ、学生はオブジェクトことを発見し、二つは矛盾キーであるため、テストは、更新後のデータを取得することはできません。

だから、長いキーはまだ更新方法としてすることはできません指定されているよう

@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