Commit ea217d2a4ed16ecb90d35733173c84deea72c294

Authored by Sergey Matvienko
Committed by Andrew Shvayka
1 parent 79034ddc

cache: Transaction aware cache to synchronize cache put/evict operations with on…

…going Spring-managed transactions.
... ... @@ -77,13 +77,13 @@
77 77 <artifactId>logback-classic</artifactId>
78 78 </dependency>
79 79 <dependency>
80   - <groupId>junit</groupId>
81   - <artifactId>junit</artifactId>
  80 + <groupId>org.springframework.boot</groupId>
  81 + <artifactId>spring-boot-starter-test</artifactId>
82 82 <scope>test</scope>
83 83 </dependency>
84 84 <dependency>
85   - <groupId>org.mockito</groupId>
86   - <artifactId>mockito-core</artifactId>
  85 + <groupId>org.awaitility</groupId>
  86 + <artifactId>awaitility</artifactId>
87 87 <scope>test</scope>
88 88 </dependency>
89 89 </dependencies>
... ...
... ... @@ -27,6 +27,7 @@ import org.springframework.cache.CacheManager;
27 27 import org.springframework.cache.annotation.EnableCaching;
28 28 import org.springframework.cache.caffeine.CaffeineCache;
29 29 import org.springframework.cache.support.SimpleCacheManager;
  30 +import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy;
30 31 import org.springframework.context.annotation.Bean;
31 32 import org.springframework.context.annotation.Configuration;
32 33
... ... @@ -47,9 +48,14 @@ public class CaffeineCacheConfiguration {
47 48
48 49 private Map<String, CacheSpecs> specs;
49 50
  51 +
  52 + /**
  53 + * Transaction aware CaffeineCache implementation with TransactionAwareCacheManagerProxy
  54 + * to synchronize cache put/evict operations with ongoing Spring-managed transactions.
  55 + */
50 56 @Bean
51 57 public CacheManager cacheManager() {
52   - log.trace("Initializing cache: {}", Arrays.toString(RemovalCause.values()));
  58 + log.trace("Initializing cache: {} specs {}", Arrays.toString(RemovalCause.values()), specs);
53 59 SimpleCacheManager manager = new SimpleCacheManager();
54 60 if (specs != null) {
55 61 List<CaffeineCache> caches =
... ... @@ -59,7 +65,11 @@ public class CaffeineCacheConfiguration {
59 65 .collect(Collectors.toList());
60 66 manager.setCaches(caches);
61 67 }
62   - return manager;
  68 +
  69 + //SimpleCacheManager is not a bean (will be wrapped), so call initializeCaches manually
  70 + manager.initializeCaches();
  71 +
  72 + return new TransactionAwareCacheManagerProxy(manager);
63 73 }
64 74
65 75 private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) {
... ...
... ... @@ -79,13 +79,19 @@ public abstract class TBRedisCacheConfiguration {
79 79
80 80 protected abstract JedisConnectionFactory loadFactory();
81 81
  82 + /**
  83 + * Transaction aware RedisCacheManager.
  84 + * Enable RedisCaches to synchronize cache put/evict operations with ongoing Spring-managed transactions.
  85 + */
82 86 @Bean
83 87 public CacheManager cacheManager(RedisConnectionFactory cf) {
84 88 DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService();
85 89 RedisCacheConfiguration.registerDefaultConverters(redisConversionService);
86 90 registerDefaultConverters(redisConversionService);
87 91 RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().withConversionService(redisConversionService);
88   - return RedisCacheManager.builder(cf).cacheDefaults(configuration).build();
  92 + return RedisCacheManager.builder(cf).cacheDefaults(configuration)
  93 + .transactionAware()
  94 + .build();
89 95 }
90 96
91 97 @Bean
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.cache;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.jupiter.api.Test;
  20 +import org.junit.jupiter.api.extension.ExtendWith;
  21 +import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  23 +import org.springframework.cache.CacheManager;
  24 +import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
  25 +import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy;
  26 +import org.springframework.test.context.ContextConfiguration;
  27 +import org.springframework.test.context.TestPropertySource;
  28 +import org.springframework.test.context.junit.jupiter.SpringExtension;
  29 +
  30 +import static org.assertj.core.api.Assertions.assertThat;
  31 +
  32 +@ExtendWith(SpringExtension.class)
  33 +@ContextConfiguration(classes = CaffeineCacheConfiguration.class)
  34 +@EnableConfigurationProperties
  35 +@TestPropertySource(properties = {
  36 + "cache.type=caffeine",
  37 + "caffeine.specs.relations.timeToLiveInMinutes=1440",
  38 + "caffeine.specs.relations.maxSize=0",
  39 + "caffeine.specs.devices.timeToLiveInMinutes=60",
  40 + "caffeine.specs.devices.maxSize=100"})
  41 +@Slf4j
  42 +public class CaffeineCacheConfigurationTest {
  43 +
  44 + @Autowired
  45 + CacheManager cacheManager;
  46 +
  47 + @Test
  48 + public void verifyTransactionAwareCacheManagerProxy() {
  49 + assertThat(cacheManager).isInstanceOf(TransactionAwareCacheManagerProxy.class);
  50 + }
  51 +
  52 + @Test
  53 + public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithTransactionAwareCacheDecorator() {
  54 + assertThat(cacheManager.getCache("relations")).isInstanceOf(TransactionAwareCacheDecorator.class);
  55 + assertThat(cacheManager.getCache("devices")).isInstanceOf(TransactionAwareCacheDecorator.class);
  56 + }
  57 +
  58 + @Test
  59 + public void givenCacheConfig_whenCacheManagerReady_thenVerifyNonExistedCaches() {
  60 + assertThat(cacheManager.getCache("rainbows_and_unicorns")).isNull();
  61 + }
  62 +}
\ No newline at end of file
... ...
  1 +<?xml version="1.0" encoding="UTF-8" ?>
  2 +
  3 +<configuration>
  4 + <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
  5 + <encoder>
  6 + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
  7 + </encoder>
  8 + </appender>
  9 +
  10 + <logger name="org.thingsboard.server.cache" level="TRACE"/>
  11 +
  12 + <root level="INFO">
  13 + <appender-ref ref="console"/>
  14 + </root>
  15 +</configuration>
... ...