DATAREDIS-246 - RedisCacheManager.getCache() does not match the logical of CompositeCacheManager.
By default RedisCacheManager lazily initializes RedisCaches when requested. To put it into static mode a defined set of cache names has to be passed to the CacheManager. We changed the implementation so it makes use of AbstractTransactionalCache introduced in Spring 3.2 which properly registers caches. Additionally the loadRemoteCachesOnStartup switch allows to retrieve and initialize existing caches by loading keys form redis server. Further on RedisCacheManager can be set in transaction aware mode so that values are only put into cache after successful commit of surrounding transaction. Original pull request: #33
This commit is contained in:
committed by
Thomas Darimont
parent
11f94b0810
commit
82578066e8
174
src/test/java/org/springframework/data/redis/cache/RedisCacheManagerUnitTests.java
vendored
Normal file
174
src/test/java/org/springframework/data/redis/cache/RedisCacheManagerUnitTests.java
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.cache;
|
||||
|
||||
import static org.hamcrest.core.Is.*;
|
||||
import static org.hamcrest.core.IsNull.*;
|
||||
import static org.hamcrest.core.IsSame.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.internal.matchers.IsCollectionContaining;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RedisCacheManagerUnitTests {
|
||||
|
||||
private @Mock RedisConnection redisConnectionMock;
|
||||
private @Mock RedisConnectionFactory redisConnectionFactoryMock;
|
||||
|
||||
@SuppressWarnings("rawtypes")//
|
||||
private RedisTemplate redisTemplate;
|
||||
private RedisCacheManager cacheManager;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(redisConnectionFactoryMock.getConnection()).thenReturn(redisConnectionMock);
|
||||
|
||||
redisTemplate = new RedisTemplate();
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactoryMock);
|
||||
redisTemplate.afterPropertiesSet();
|
||||
|
||||
cacheManager = new RedisCacheManager(redisTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testGetCacheReturnsNewCacheWhenRequestedCacheIsNotAvailable() {
|
||||
|
||||
Cache cache = cacheManager.getCache("not-available");
|
||||
assertThat(cache, notNullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testGetCacheReturnsExistingCacheWhenRequested() {
|
||||
|
||||
Cache cache = cacheManager.getCache("cache");
|
||||
assertThat(cacheManager.getCache("cache"), sameInstance(cache));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testCacheInitSouldNotRequestRemoteKeysByDefault() {
|
||||
|
||||
cacheManager.afterPropertiesSet();
|
||||
Mockito.verifyZeroInteractions(redisConnectionMock);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testCacheInitShouldFetchAllCacheKeysWhenLoadingRemoteCachesOnStartupIsEnabled() {
|
||||
|
||||
cacheManager.setLoadRemoteCachesOnStartup(true);
|
||||
cacheManager.afterPropertiesSet();
|
||||
|
||||
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(redisConnectionMock, times(1)).keys(captor.capture());
|
||||
assertThat(redisTemplate.getKeySerializer().deserialize(captor.getValue()).toString(), is("*~keys"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testCacheInitShouldInitializeRemoteCachesCorrectlyWhenLoadingRemoteCachesOnStartupIsEnabled() {
|
||||
|
||||
Set<byte[]> keys = new HashSet<byte[]>(Arrays.asList(redisTemplate.getKeySerializer()
|
||||
.serialize("remote-cache~keys")));
|
||||
when(redisConnectionMock.keys(any(byte[].class))).thenReturn(keys);
|
||||
|
||||
cacheManager.setLoadRemoteCachesOnStartup(true);
|
||||
cacheManager.afterPropertiesSet();
|
||||
|
||||
assertThat(cacheManager.getCacheNames(), IsCollectionContaining.hasItem("remote-cache"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testCacheInitShouldNotInitialzeCachesWhenLoadingRemoteCachesOnStartupIsEnabledAndNoCachesAvailableOnRemoteServer() {
|
||||
|
||||
when(redisConnectionMock.keys(any(byte[].class))).thenReturn(Collections.<byte[]> emptySet());
|
||||
cacheManager.setLoadRemoteCachesOnStartup(true);
|
||||
cacheManager.afterPropertiesSet();
|
||||
|
||||
assertThat(cacheManager.getCacheNames().isEmpty(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testCacheManagerShouldNotDynamicallyCreateCachesWhenInStaticMode() {
|
||||
|
||||
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
|
||||
assertThat(cacheManager.getCache("redis"), nullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testCacheManagerShouldRetrunRegisteredCacheWhenInStaticMode() {
|
||||
|
||||
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
|
||||
assertThat(cacheManager.getCache("spring"), notNullValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* see DATAREDIS-246
|
||||
*/
|
||||
@Test
|
||||
public void testPuttingCacheManagerIntoStaticModeShouldNotRemoveAlreadyRegisteredCaches() {
|
||||
|
||||
cacheManager.getCache("redis");
|
||||
cacheManager.setCacheNames(Arrays.asList("spring", "data"));
|
||||
assertThat(cacheManager.getCache("redis"), notNullValue());
|
||||
}
|
||||
|
||||
}
|
||||
57
src/test/java/org/springframework/data/redis/cache/TransactionalRedisCacheManagerTestBase.java
vendored
Normal file
57
src/test/java/org/springframework/data/redis/cache/TransactionalRedisCacheManagerTestBase.java
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.cache;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* The idea would have been to provide {@link Configuration} here and just set {@link Rollback} accordingly in extending
|
||||
* classes. Unfortunately this does not work when running build with gradle under java6. It was also not possible to add
|
||||
* {@link Configuration} here and import it using {@link ContextConfiguration#classes()}. <br />
|
||||
* <br />
|
||||
* Therefore {@link ContextConfiguration} had to be duplicated in
|
||||
* {@link TransactionalRedisCacheManagerWithCommitUnitTests} and
|
||||
* {@link TransactionalRedisCacheManagerWithRollbackUnitTests}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public abstract class TransactionalRedisCacheManagerTestBase {
|
||||
|
||||
static class FooService {
|
||||
|
||||
private @Autowired BarRepository repo;
|
||||
|
||||
@Transactional
|
||||
public String foo() {
|
||||
return "foo" + repo.bar();
|
||||
}
|
||||
}
|
||||
|
||||
static class BarRepository {
|
||||
|
||||
@Cacheable("bar")
|
||||
public String bar() {
|
||||
return "bar";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.cache;
|
||||
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.BarRepository;
|
||||
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.FooService;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.transaction.AfterTransaction;
|
||||
import org.springframework.test.context.transaction.TransactionConfiguration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
@TransactionConfiguration(transactionManager = "transactionManager")
|
||||
public class TransactionalRedisCacheManagerWithCommitUnitTests {
|
||||
|
||||
@SuppressWarnings("rawtypes")//
|
||||
protected @Autowired RedisTemplate redisTemplate;
|
||||
protected @Autowired FooService transactionalService;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() throws SQLException {
|
||||
|
||||
DataSourceTransactionManager txmgr = new DataSourceTransactionManager();
|
||||
txmgr.setDataSource(dataSource());
|
||||
txmgr.afterPropertiesSet();
|
||||
|
||||
return txmgr;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() throws SQLException {
|
||||
|
||||
DataSource dataSourceMock = mock(DataSource.class);
|
||||
when(dataSourceMock.getConnection()).thenReturn(mock(Connection.class));
|
||||
|
||||
return dataSourceMock;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
|
||||
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
|
||||
cacheManager.setTransactionAware(true);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
@Bean
|
||||
public RedisTemplate redisTemplate() {
|
||||
|
||||
RedisConnection connectionMock = mock(RedisConnection.class);
|
||||
RedisConnectionFactory factoryMock = mock(RedisConnectionFactory.class);
|
||||
|
||||
when(factoryMock.getConnection()).thenReturn(connectionMock);
|
||||
|
||||
RedisTemplate template = new RedisTemplate();
|
||||
template.setConnectionFactory(factoryMock);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FooService fooService() {
|
||||
return new FooService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BarRepository barRepository() {
|
||||
return new BarRepository();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTransaction
|
||||
public void assertThatValuesHaveBeenAddedToRedis() {
|
||||
|
||||
ArgumentCaptor<byte[]> keyCaptor = ArgumentCaptor.forClass(byte[].class);
|
||||
ArgumentCaptor<byte[]> valueCaptor = ArgumentCaptor.forClass(byte[].class);
|
||||
|
||||
verify(redisTemplate.getConnectionFactory().getConnection(), times(1)).zAdd(keyCaptor.capture(), eq(0D),
|
||||
valueCaptor.capture());
|
||||
|
||||
Assert.assertThat(new StringRedisSerializer().deserialize(keyCaptor.getValue()).toString(),
|
||||
IsEqual.equalTo("bar~keys"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Rollback(false)
|
||||
@Test
|
||||
public void testValuesAddedToCacheWhenTransactionIsCommited() {
|
||||
transactionalService.foo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.cache;
|
||||
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.BarRepository;
|
||||
import org.springframework.data.redis.cache.TransactionalRedisCacheManagerTestBase.FooService;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
|
||||
import org.springframework.test.annotation.Rollback;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.transaction.AfterTransaction;
|
||||
import org.springframework.test.context.transaction.TransactionConfiguration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
@TransactionConfiguration(transactionManager = "transactionManager")
|
||||
public class TransactionalRedisCacheManagerWithRollbackUnitTests {
|
||||
|
||||
@SuppressWarnings("rawtypes")//
|
||||
protected @Autowired RedisTemplate redisTemplate;
|
||||
protected @Autowired FooService transactionalService;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public PlatformTransactionManager transactionManager() throws SQLException {
|
||||
|
||||
DataSourceTransactionManager txmgr = new DataSourceTransactionManager();
|
||||
txmgr.setDataSource(dataSource());
|
||||
txmgr.afterPropertiesSet();
|
||||
|
||||
return txmgr;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() throws SQLException {
|
||||
|
||||
DataSource dataSourceMock = mock(DataSource.class);
|
||||
when(dataSourceMock.getConnection()).thenReturn(mock(Connection.class));
|
||||
|
||||
return dataSourceMock;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
|
||||
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
|
||||
cacheManager.setTransactionAware(true);
|
||||
return cacheManager;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes" })
|
||||
@Bean
|
||||
public RedisTemplate redisTemplate() {
|
||||
|
||||
RedisConnection connectionMock = mock(RedisConnection.class);
|
||||
RedisConnectionFactory factoryMock = mock(RedisConnectionFactory.class);
|
||||
|
||||
when(factoryMock.getConnection()).thenReturn(connectionMock);
|
||||
|
||||
RedisTemplate template = new RedisTemplate();
|
||||
template.setConnectionFactory(factoryMock);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FooService fooService() {
|
||||
return new FooService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BarRepository barRepository() {
|
||||
return new BarRepository();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterTransaction
|
||||
public void assertThatValuesNeverAddedToRedis() {
|
||||
|
||||
verify(redisTemplate.getConnectionFactory().getConnection(), times(0)).zAdd(any(byte[].class), eq(0D),
|
||||
any(byte[].class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-246
|
||||
*/
|
||||
@Rollback(true)
|
||||
@Test
|
||||
public void tesValuesNotAddedToCacheWhenTransactionIsRolledBack() {
|
||||
transactionalService.foo();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user