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 | 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 | ... | ... |
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 | +} | |
\ No newline at end of file | ... | ... |
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> | ... | ... |