Commit ea217d2a4ed16ecb90d35733173c84deea72c294
Committed by
Andrew Shvayka
1 parent
79034ddc
cache: Transaction aware cache to synchronize cache put/evict operations with on…
…going Spring-managed transactions.
Showing
5 changed files
with
100 additions
and
7 deletions
@@ -77,13 +77,13 @@ | @@ -77,13 +77,13 @@ | ||
77 | <artifactId>logback-classic</artifactId> | 77 | <artifactId>logback-classic</artifactId> |
78 | </dependency> | 78 | </dependency> |
79 | <dependency> | 79 | <dependency> |
80 | - <groupId>junit</groupId> | ||
81 | - <artifactId>junit</artifactId> | 80 | + <groupId>org.springframework.boot</groupId> |
81 | + <artifactId>spring-boot-starter-test</artifactId> | ||
82 | <scope>test</scope> | 82 | <scope>test</scope> |
83 | </dependency> | 83 | </dependency> |
84 | <dependency> | 84 | <dependency> |
85 | - <groupId>org.mockito</groupId> | ||
86 | - <artifactId>mockito-core</artifactId> | 85 | + <groupId>org.awaitility</groupId> |
86 | + <artifactId>awaitility</artifactId> | ||
87 | <scope>test</scope> | 87 | <scope>test</scope> |
88 | </dependency> | 88 | </dependency> |
89 | </dependencies> | 89 | </dependencies> |
@@ -27,6 +27,7 @@ import org.springframework.cache.CacheManager; | @@ -27,6 +27,7 @@ import org.springframework.cache.CacheManager; | ||
27 | import org.springframework.cache.annotation.EnableCaching; | 27 | import org.springframework.cache.annotation.EnableCaching; |
28 | import org.springframework.cache.caffeine.CaffeineCache; | 28 | import org.springframework.cache.caffeine.CaffeineCache; |
29 | import org.springframework.cache.support.SimpleCacheManager; | 29 | import org.springframework.cache.support.SimpleCacheManager; |
30 | +import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy; | ||
30 | import org.springframework.context.annotation.Bean; | 31 | import org.springframework.context.annotation.Bean; |
31 | import org.springframework.context.annotation.Configuration; | 32 | import org.springframework.context.annotation.Configuration; |
32 | 33 | ||
@@ -47,9 +48,14 @@ public class CaffeineCacheConfiguration { | @@ -47,9 +48,14 @@ public class CaffeineCacheConfiguration { | ||
47 | 48 | ||
48 | private Map<String, CacheSpecs> specs; | 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 | @Bean | 56 | @Bean |
51 | public CacheManager cacheManager() { | 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 | SimpleCacheManager manager = new SimpleCacheManager(); | 59 | SimpleCacheManager manager = new SimpleCacheManager(); |
54 | if (specs != null) { | 60 | if (specs != null) { |
55 | List<CaffeineCache> caches = | 61 | List<CaffeineCache> caches = |
@@ -59,7 +65,11 @@ public class CaffeineCacheConfiguration { | @@ -59,7 +65,11 @@ public class CaffeineCacheConfiguration { | ||
59 | .collect(Collectors.toList()); | 65 | .collect(Collectors.toList()); |
60 | manager.setCaches(caches); | 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 | private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) { | 75 | private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) { |
@@ -79,13 +79,19 @@ public abstract class TBRedisCacheConfiguration { | @@ -79,13 +79,19 @@ public abstract class TBRedisCacheConfiguration { | ||
79 | 79 | ||
80 | protected abstract JedisConnectionFactory loadFactory(); | 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 | @Bean | 86 | @Bean |
83 | public CacheManager cacheManager(RedisConnectionFactory cf) { | 87 | public CacheManager cacheManager(RedisConnectionFactory cf) { |
84 | DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService(); | 88 | DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService(); |
85 | RedisCacheConfiguration.registerDefaultConverters(redisConversionService); | 89 | RedisCacheConfiguration.registerDefaultConverters(redisConversionService); |
86 | registerDefaultConverters(redisConversionService); | 90 | registerDefaultConverters(redisConversionService); |
87 | RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().withConversionService(redisConversionService); | 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 | @Bean | 97 | @Bean |
common/cache/src/test/java/org/thingsboard/server/cache/CaffeineCacheConfigurationTest.java
0 → 100644
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 | +} |
common/cache/src/test/resources/logback.xml
0 → 100644
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> |