Caching with Spring Data Redis
In the example below, I’ll show you how to use the Spring Data – Redis project as a caching provider for the Spring Cache Abstraction that was introduced in Spring 3.1. I get a lot of questions about how to use Spring’s Java based configuration so I’ll provide both XML and Java based configurations for your review.
Dependencies
The following dependencies were used in this example:
pom.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <? xml version = "1.0" encoding = "UTF-8" ?>
< project xmlns = "http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" >
< modelVersion >4.0.0</ modelVersion >
< groupId >com.joshuawhite.example</ groupId >
< artifactId >spring-redis-example</ artifactId >
< version >1.0</ version >
< packaging >jar</ packaging >
< name >Spring Redis Example</ name >
< dependencies >
< dependency >
< groupId >org.springframework.data</ groupId >
< artifactId >spring-data-redis</ artifactId >
< version >1.0.2.RELEASE</ version >
</ dependency >
< dependency >
< groupId >cglib</ groupId >
< artifactId >cglib</ artifactId >
< version >2.2.2</ version >
</ dependency >
< dependency >
< groupId >redis.clients</ groupId >
< artifactId >jedis</ artifactId >
< version >2.0.0</ version >
< type >jar</ type >
< scope >compile</ scope >
</ dependency >
< dependency >
< groupId >log4j</ groupId >
< artifactId >log4j</ artifactId >
< version >1.2.14</ version >
</ dependency >
</ dependencies >
< build >
< plugins >
< plugin >
< groupId >org.apache.maven.plugins</ groupId >
< artifactId >maven-compiler-plugin</ artifactId >
< configuration >
< source >1.6</ source >
< target >1.6</ target >
</ configuration >
</ plugin >
</ plugins >
</ build >
</ project >
|
Code and Configuration
The HelloService
example below is very simple. As you will see in the implementation, it simply returns a String with “Hello” prepended to the name that is passed in.
HelloService.java1 2 3 4 5 6 7 | package com.joshuawhite.example.service;
public interface HelloService {
String getMessage(String name);
}
|
Looking at the HelloServiceImpl
class (below), you can see that I am leveraging Spring’s @Cacheable annotation to add caching capabilities to the getMessage
method. For more details on the capabilities of this annotation, take a look at theCache Abstraction documentation. For fun, I am using the Spring Expression Language (SpEL) to define a condition. In this example, the methods response will only be cached when the name passed in is “Joshua”.
HelloServiceImpl.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package com.joshuawhite.example.service;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service ( "helloService" )
public class HelloServiceImpl implements HelloService {
/**
* Using SpEL for conditional caching - only cache method executions when
* the name is equal to "Joshua"
*/
@Cacheable (value= "messageCache" , condition= "'Joshua'.equals(#name)" )
public String getMessage(String name) {
System.out.println( "Executing HelloServiceImpl" +
".getHelloMessage(\"" + name + "\")" );
return "Hello " + name + "!" ;
}
}
|
The App
class below contains our main
method and is used to select between XML and Java based configurations. Each of the System.out.println
‘s are used to demonstrate when caching is taking place. As a reminder, we only expect method executions passing in “Joshua” to be cached. This will be more clear when we look at the programs output later.
App.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | package com.joshuawhite.example;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import com.joshuawhite.example.config.AppConfig;
import com.joshuawhite.example.service.HelloService;
public class App {
public static void main(String[] args) {
boolean useJavaConfig = true ;
ApplicationContext ctx = null ;
if (useJavaConfig ) {
ctx = new AnnotationConfigApplicationContext(AppConfig. class );
}
else {
ctx = new GenericXmlApplicationContext( "/META-INF/spring/app-context.xml" );
}
HelloService helloService = ctx.getBean( "helloService" , HelloService. class );
System.out.println( "message: " + helloService.getMessage( "Josh" ));
System.out.println( "message: " + helloService.getMessage( "Josh" ));
System.out.println( "message: " + helloService.getMessage( "Joshua" ));
System.out.println( "message: " + helloService.getMessage( "Joshua" ));
System.out.println( "Done." );
}
}
|
Notice that component scanning is still used when using the XML based configuration. You can see that I am using the@Service
annotation on line 6 of HelloServiceImpl.java
above.
Next we will take a look at how to configure a jedisConnectionFactory
, redisTemplate
and cacheManager
.
Configuring the JedisConnectionFactory
For this example, I chose to use Jedis as our Java client of choice because it is listed on the Redis site as being the“recommended” client library for Java. As you can see, the setup is very straight forward. While I am explicitly setting use-pool=true, it the source code indicates that this is the default. The JedisConnectionFactory also provides the following defaults when not explicitly set:
- hostName=”localhost”
- port=6379
- timeout=2000 ms
- database=0
- usePool=true
Note: Though the database index is configurable, the JedisConnectionFactory
only supports connecting to one Redis database at a time. Because Redis is single threaded, you are encouraged to set up multiple instances of Redis instead of using multiple databases within a single process. This allows you to get better CPU/resource utilization. If you plan to use redis-cluster, only a single database is supported.
For more information about the defaults used in the connection pool, take a look at the implementation of JedisPoolConfig
or the Apache Commons Pool org.apache.commons.pool.impl.GenericObjectPool.Config
and it’s enclosingorg.apache.commons.pool.impl.GenericObjectPool
class.
Configuring the RedisTemplate
As you would expect from a Spring “template” class, the RedisTemplate
takes care of serialization and connection management and (providing you are using a connection pool) is thread safe.
By default, the RedisTemplate uses Java serialization (JdkSerializationRedisSerializer
). Note that serializing data into Redis essentially makes Redis an “opaque” cache. While other serializers allow you to map the data into Redis, I have found serialization, especially when dealing with object graphs, is faster and simpler to use. That being said, if you have a requirement that other non-java applications be able to access this data, mapping is your best out-of-the-box option.
I have had a great experience using Hessian and Google Protocol Buffers/protostuff. I’ll share some sample implementations of the RedisSerializer
in a future post.
Configuring the RedisCacheManager
Configuring the RedisCacheManager
is straight forward. As a reminder, the RedisCacheManager
is dependent on aRedisTemplate
which is dependent on a connection factory, in our case JedisConnectionFactory
, that can only connect to a single database at a time.
As a workaround, the RedisCacheManager has the capability of setting up a prefix for your cache keys.
Warning: When dealing with other caching solutions, Spring’s CacheManger usually contains a map of
Cache
(each implementing map like functionality) implementations that are backed by separate caches. Using the default
RedisCacheManager
configuration, this is not the case. Based on the javadoc comment on the
RedisCacheManager
, its not clear if this is a bug or simply incomplete documentation.
“…By default saves the keys by appending a prefix (which acts as a namespace).”
While the DefaultRedisCachePrefix which is configured in the RedisCacheManager
certainly supports this, it is not enabled by default. As a result, when you ask the RedisCacheManager
for a Cache
of a given name, it simply creates a new Cache
instance that points to the same database. As a result, the Cache
instances are all the same. The same key will retrieve the same value in all Cache
instances.
As the javadoc comment alludes to, prefixs can be used to setup client managed (Redis doesn’t support this functionality natively) namespaces that essentially create “virtual” caches within the same database. You can turn this feature on by calling redisCacheManager.setUsePrefix(true)
either using the Spring XML or Java configuration.
app-context.xml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | <? xml version = "1.0" encoding = "UTF-8" ?>
< beans
xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xmlns:c = "http://www.springframework.org/schema/c"
xmlns:p = "http://www.springframework.org/schema/p"
xmlns:cache = "http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beansvhttp://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
< context:component-scan base-package = "com.joshuawhite.example.service" />
< context:property-placeholder location = "classpath:/redis.properties" />
< cache:annotation-driven />
< bean
id = "jedisConnectionFactory"
class = "org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name = "${redis.host-name}"
p:port = "${redis.port}"
p:use-pool = "true" />
< bean
id = "redisTemplate"
class = "org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref = "jedisConnectionFactory" />
< bean
id = "cacheManager"
class = "org.springframework.data.redis.cache.RedisCacheManager"
c:template-ref = "redisTemplate" />
</ beans >
|
The Java configuration below is equivalent to the XML configuration above. People usually get hung up on using aPropertySourcesPlaceholderConfigurer
. To do that, you need to use both the @PropertySource
annotation and define aPropertySourcesPlaceholderConfigurer
bean. The PropertySourcesPlaceholderConfigurer
will not be sufficient on its own.
AppConfig.java1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | package com.joshuawhite.example.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
@EnableCaching
@ComponentScan ( "com.joshuawhite.example" )
@PropertySource ( "classpath:/redis.properties" )
public class AppConfig {
private @Value ( "${redis.host-name}" ) String redisHostName;
private @Value ( "${redis.port}" ) int redisPort;
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory factory = new JedisConnectionFactory();
factory.setHostName(redisHostName);
factory.setPort(redisPort);
factory.setUsePool( true );
return factory;
}
@Bean
RedisTemplate<Object, Object> redisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
return redisTemplate;
}
@Bean
CacheManager cacheManager() {
return new RedisCacheManager(redisTemplate());
}
}
|
Here is the properties file that is used by both configurations. Replace the values below with the host and port that you are using.
redis.properties1 2 | redis.host-name=yourHostNameHere
redis.port= 6379
|
Output
Finally, here is the output from our brief example application. Notice that no matter how many times we callgetHelloMessage("Josh")
, the methods response does not get cached. This is because we defined a condition (seeHelloServiceImpl.java
, line 13) where we only cache the methods response when the name equals “Joshua”.
When we call getHelloMessage("Joshua")
for the first time, the method is executed. The second time however, it is not.
Output1 2 3 4 5 6 7 8 | Executing HelloServiceImpl.getHelloMessage("Josh")
message: Hello Josh!
Executing HelloServiceImpl.getHelloMessage("Josh")
message: Hello Josh!
Executing HelloServiceImpl.getHelloMessage("Joshua")
message: Hello Joshua!
message: Hello Joshua!
Done.
|
This concludes our brief over view of caching with Spring Data Redis.
source - http://blog.joshuawhite.com/java/caching-with-spring-data-redis/
03.23.2012
This article will cover the following topics:
- How to install Redis on a *nix machine.
- How to write a simple app using Spring-Data and Redis.
Before moving forward let's understand what Redis is.
Redis is an open source, advanced key-value store. It is often referred to as a data structure serversince keys can contain strings, hashes, lists, sets and sorted sets.
In this article I'm not going to talk how fast is redis and how it works. But believe me it's a very fast key-value store.
Now let's install redis on *nix machine
2.
tar
xzf redis-2.4.8.
tar
.gz
3.
cd
redis-2.4.8
4.
make
5.
6.
src/redis-server
That's all! Your redis is ready to use.
Now let's create a simple maven application to work with redis.
Currently Spring Redis has support for Jedis and JRedis (Jedis and JRedis are Redis bindings for Java).
In this article we are going to use Jedis. Here are the Maven dependencies:
01.
02.
<
dependency
>
03.
<
groupId
>redis.clients</
groupId
>
04.
<
artifactId
>jedis</
artifactId
>
05.
<
version
>2.0.0</
version
>
06.
</
dependency
>
07.
08.
09.
<
dependency
>
10.
<
groupId
>org.springframework.data</
groupId
>
11.
<
artifactId
>spring-data-redis</
artifactId
>
12.
<
version
>1.0.0.RELEASE</
version
>
13.
</
dependency
>
You can find the full application on GitHub here: spring-data-redis example
Now let's define and configure our beans:
01.
02.
<
bean
id
=
"connectionFactory"
class
=
"org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
03.
p:use-pool
=
"true"
p:host-name
=
"${app.config.redis.host}"
p:port
=
"${app.config.redis.port}"
p:password
=
"${app.config.redis.password}"
/>
04.
05.
06.
<
bean
id
=
"redisTemplate"
class
=
"org.springframework.data.redis.core.RedisTemplate"
07.
p:connection-factory-ref
=
"connectionFactory"
/>
08.
09.
10.
<
bean
id
=
"redisStringTemplate"
class
=
"org.springframework.data.redis.core.StringRedisTemplate"
11.
p:connection-factory-ref
=
"connectionFactory"
/>
12.
13.
14.
<
bean
id
=
"userRedisMap"
class
=
"org.springframework.data.redis.support.collections.DefaultRedisMap"
>
15.
<
constructor-arg
ref
=
"redisTemplate"
/>
16.
<
constructor-arg
value
=
"USER"
/>
17.
</
bean
>
RedisTemplate provides high level abstraction for redis operations. If your application stores and retrieves strings, then you may use StringRedisTemplate.
Let's create a simple domain object.
01.
public
interface
Cachable
extends
Serializable {
02.
03.
public
String getKey();
04.
05.
public
String getObjectKey();
06.
}
07.
08.
public
class
User
implements
Cachable {
09.
10.
private
static
final
long
serialVersionUID = -7898194272883238670L;
11.
12.
public
static
final
String OBJECT_KEY =
"USER"
;
13.
14.
public
User() {
15.
}
16.
17.
public
User(String id) {
18.
}
19.
20.
public
User(String id, String name) {
21.
this
.id = id;
22.
this
.name = name;
23.
}
24.
25.
private
String id;
26.
27.
private
String name;
28.
29.
public
String getId() {
30.
return
id;
31.
}
32.
33.
public
void
setId(String id) {
34.
this
.id = id;
35.
}
36.
37.
public
String getName() {
38.
return
name;
39.
}
40.
41.
public
void
setName(String name) {
42.
this
.name = name;
43.
}
44.
45.
@Override
46.
public
String toString() {
47.
return
"User [id="
+ id +
", name="
+ name +
"]"
;
48.
}
49.
50.
@Override
51.
public
String getKey() {
52.
return
getId();
53.
}
54.
55.
@Override
56.
public
String getObjectKey() {
57.
return
OBJECT_KEY;
58.
}
59.
}
Now let's create Service layer classes.
01.
public
interface
Service<V
extends
Cachable> {
02.
03.
public
void
put(V obj);
04.
05.
public
V get(V key);
06.
07.
public
void
delete(V key);
08.
}
09.
10.
@Service
(
"userService"
)
11.
public
class
UserService
implements
co.sdr.service.Service<User> {
12.
13.
@Autowired
14.
RedisTemplate<String, Cachable> redisTemplate;
15.
16.
@Override
17.
public
void
put(User user) {
18.
redisTemplate.opsForHash().put(user.getObjectKey(), user.getKey(), user);
19.
}
20.
21.
@Override
22.
public
void
delete(User key) {
23.
redisTemplate.opsForHash().delete(key.getObjectKey(), key.getKey());
24.
}
25.
26.
@Override
27.
public
User get(User key) {
28.
return
(User) redisTemplate.opsForHash().get(key.getObjectKey(), key.getKey());
29.
}
30.
}
So domain and service classes are ready. Now let's use them.
Here is the simple Main class which uses the above-mentioned classes.
01.
public
class
Main
02.
{
03.
public
static
void
main( String[] args )
04.
{
05.
ApplicationContext context =
new
ClassPathXmlApplicationContext(
"springapp.xml"
);
06.
07.
@SuppressWarnings
(
"unchecked"
)
08.
Service<User> userService = (Service<User>)context.getBean(
"userService"
);
09.
10.
User user1 =
new
User(
"user1ID"
,
"User 1"
);
11.
User user2 =
new
User(
"user2ID"
,
"User 2"
);
12.
13.
System.out.println(
"==== getting objects from redis ===="
);
14.
System.out.println(
"User is not in redis yet: "
+ userService.get(user1));
15.
System.out.println(
"User is not in redis yet: "
+ userService.get(user2));
16.
17.
System.out.println(
"==== putting objects into redis ===="
);
18.
userService.put(user1);
19.
userService.put(user2);
20.
21.
System.out.println(
"==== getting objects from redis ===="
);
22.
System.out.println(
"User should be in redis yet: "
+ userService.get(user1));
23.
System.out.println(
"User should be in redis yet: "
+ userService.get(user2));
24.
25.
System.out.println(
"==== deleting objects from redis ===="
);
26.
userService.delete(user1);
27.
userService.delete(user2);
28.
29.
System.out.println(
"==== getting objects from redis ===="
);
30.
System.out.println(
"User is not in redis yet: "
+ userService.get(user1));
31.
System.out.println(
"User is not in redis yet: "
+ userService.get(user2));
32.
33.
}
34.
}
Wasn't it simple? Spring makes everything much more easy to use.
And this isn't the end. spring-data-redis's org.springframework.data.redis.support package provides List, Set, Map implemetations.
Let's try to use a map:
01.
@Service
(
"userMapService"
)
02.
public
class
UserMapService
implements
co.sdr.service.Service<User> {
03.
04.
@Autowired
05.
Map<String, Cachable> userRedisMap;
06.
07.
@Override
08.
public
void
put(User user) {
09.
userRedisMap.put(user.getKey(), user);
10.
}
11.
12.
@Override
13.
public
void
delete(User key) {
14.
userRedisMap.remove(key.getKey());
15.
}
16.
17.
@Override
18.
public
User get(User key) {
19.
return
(User) userRedisMap.get(key.getKey());
20.
}
21.
}
This may help you with unit testing, and you may change the datastore without the changing service layer.
You may also use RedisCacheManager as a backing implementation for spring cache abstraction.
Find the full version of this application on GitHub here: spring-data-redis example
source - http://java.dzone.com/articles/spring-data-redis-0
Spring 3.1 + Redis 를 이용한 Cache
이번 주에는 Spring 3.1 에서 지원하는 Cache 관련해서 많은 글을 썼는데, 요즘 가장 많이 사용되는 Redis 를 저장소로 사용하는 Cache 를 만들겠습니다.
Redis 를 구현하기 위해서 spring-data-redis 와 jedis 라이브러리를 사용했습니다.
jedis 만으로도 구현할 수 있지만, 편하게 spring-data-redis 의 RedisTemplate 를 사용하기로 했습니다.
우선 Redis 를 캐시 저장소로 사용하기 위해 환경설정을 합니다.
1. RedisCacheConfiguration.java
12345678910111213141516171819202122232425262728293031 | @Configuration @EnableCaching @ComponentScan(basePackageClasses = UserRepository.class) // @PropertySource("classpath:redis.properties") public class RedisCacheConfiguration { @Autowired Environment env; @Bean public JedisShardInfo jedisShardInfo() { return new JedisShardInfo("localhost"); } @Bean public RedisConnectionFactory redisConnectionFactory() { return new JedisConnectionFactory(jedisShardInfo()); } @Bean public RedisTemplate redisTemplate() { RedisTemplate<String,Object> template = new RedisTemplate<String,Object>(); template.setConnectionFactory(redisConnectionFactory()); return template; } @Bean public RedisCacheManager redisCacheManager() { return new RedisCacheManager(redisTemplate(), 300); } }
|
한가지 RedisCacheFactory를 생성할 때 주의할 점은 JedisShardInfo 로 생성해야지, JedisPoolingConfig나 기본 생성자로 생성 시에 RedisTemplate 에서 connection을 제대로 생성 못하는 버그가 있더군요 ㅠ.ㅠ 이 것 때문에 반나절을 허비...
2. RedisCacheManager.java
1234567891011121314151617181920212223242526272829303132333435363738 | @Slf4j public class RedisCacheManager extends AbstractTransactionSupportingCacheManager { private RedisTemplate redisTemplate; private int expireSeconds; public RedisCacheManager(RedisTemplate redisTemplate) { this(redisTemplate, 300); } public RedisCacheManager(RedisTemplate redisTemplate, int expireSeconds) { Guard.shouldNotBeNull(redisTemplate, "redisTemplate"); this.redisTemplate = redisTemplate; this.expireSeconds = expireSeconds; } @Override protected Collection<? extends Cache> loadCaches() { Collection<Cache> caches = Lists.newArrayList(); for (String name : getCacheNames()) { caches.add(new RedisCache(name, redisTemplate, expireSeconds)); } return caches; } @Override public Cache getCache(String name) { synchronized (this) { Cache cache = super.getCache(name); if (cache == null) { cache = new RedisCache(name, redisTemplate, expireSeconds); addCache(cache); } return cache; } } }
|
3. RedisCache.java
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293 | @Slf4j public class RedisCache implements Cache { @Getter private String name; @Getter private int expireSeconds; private RedisTemplate redisTemplate; public RedisCache(String name, RedisTemplate redisTemplate) { this(name, redisTemplate, 300); } public RedisCache(String name, RedisTemplate redisTemplate, int expireSeconds) { Guard.shouldNotBeEmpty(name, "name"); Guard.shouldNotBeNull(redisTemplate, "redisTemplate"); this.name = name; this.redisTemplate = redisTemplate; if (log.isDebugEnabled()) log.debug("MongoCache를 생성합니다. name=[{}], mongodb=[{}]", name, redisTemplate); } @Override public Object getNativeCache() { return redisTemplate; } public String getKey(Object key) { return name + ":" + key; } @Override public ValueWrapper get(Object key) { Guard.shouldNotBeNull(key, "key"); if (log.isDebugEnabled()) log.debug("캐시 키[{}] 값을 구합니다...", key); Object result = redisTemplate.opsForValue().get(getKey(key)); SimpleValueWrapper wrapper = null; if (result != null) { if (log.isDebugEnabled()) log.debug("캐시 값을 로드했습니다. key=[{}]", key); wrapper = new SimpleValueWrapper(result); } return wrapper; } @Override @SuppressWarnings("unchecked") public void put(Object key, Object value) { Guard.shouldNotBeNull(key, "key"); if (log.isDebugEnabled()) log.debug("캐시에 값을 저장합니다. key=[{}], value=[{}]", key, value); redisTemplate.opsForValue().set(getKey(key), value, expireSeconds); } @Override @SuppressWarnings("unchecked") public void evict(Object key) { Guard.shouldNotBeNull(key, "key"); if (log.isDebugEnabled()) log.debug("지정한 키[{}]의 캐시를 삭제합니다...", key); try { redisTemplate.delete(key); } catch (Exception e) { log.error("캐시 항목 삭제에 실패했습니다. key=" + key, e); } } @Override @SuppressWarnings("unchecked") public void clear() { if (log.isDebugEnabled()) log.debug("모든 캐시를 삭제합니다..."); try { redisTemplate.execute(new RedisCallback() { @Override public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.flushAll(); return null; } }); } catch (Exception e) { log.warn("모든 캐시를 삭제하는데 실패했습니다.", e); } } }
|
RedisCache의 get / put 은 일반적으로 쓰는 opsForValue() 를 사용했습니다. 다른 것을 사용할 수도 있을텐데, 좀 더 공부한 다음에 다른 것으로 변경해 봐야 할 듯 합니다.
마지막에 clear() 메소드도 jedis 에는 flushDB(), flushAll() 메소드를 지원하는데, RedisTemplate에서는 해당 메소드를 expose 하지 않아 코드와 같이 RedisCallback 을 구현했습니다.
4. RedisCacheTest.java
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647 | @Slf4j @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {RedisCacheConfiguration.class}) public class RedisCacheTest { @Autowired RedisCacheManager redisCacheManager; @Autowired UserRepository userRepository; @Test public void clearTest() { Assert.assertNotNull(redisCacheManager); Cache cache = redisCacheManager.getCache("user"); Assert.assertNotNull(cache); cache.clear(); Assert.assertNotNull(cache); } @Test public void getUserFromCache() { Stopwatch sw = new Stopwatch("initial User"); sw.start(); User user1 = userRepository.getUser("debop", 100); sw.stop(); sw = new Stopwatch("from Cache"); sw.start(); User user2 = userRepository.getUser("debop", 100); sw.stop(); Assert.assertEquals(user1.getUsername(), user2.getUsername()); } @Test public void componentConfigurationTest() { Assert.assertNotNull(redisCacheManager); Cache cache = redisCacheManager.getCache("user"); Assert.assertNotNull(cache); cache.evict("debop"); Assert.assertNotNull(userRepository); } }
|
UserRepository는 전에 쓴 Spring 3.1 + EhCache 등의 글과 같은 코드라 생략했습니다.
Redis 관련은 Windows 에서는 구 버전만 지원하고, 신 버전은 linux 만 가능하더군요...
그래도 성능은 정평이 나있으니, HA 구성 시에는 가장 먼저 고려되어야 할 캐시 저장소라 생각됩니다.
저는 앞으로 hibernate 2nd cache provider for redis, hibernate-ogm-redis 를 만들어 볼 예정입니다.
출처 - http://debop.blogspot.kr/2013/03/spring-31-redis-cache.html
Using Redis with Spring
As NoSQL solutions are getting more and more popular for many kind of problems, more often the modern projects consider to use some (or several) of NoSQLs instead (or side-by-side) of traditional RDBMS. I have already covered my experience with MongoDB in
this,
this and
this posts. In this post I would like to switch gears a bit towards
Redis, an advanced key-value store.
Aside from very rich key-value semantics,
Redis also supports pub-sub messaging and transactions. In this post I am going just to touch the surface and demonstrate how simple it is to integrate
Redis into your Spring application. As always, we will start with Maven POM file for our project:
04 | < modelversion >4.0.0</ modelversion > |
05 | < groupid >com.example.spring</ groupid > |
06 | < artifactid >redis</ artifactid > |
07 | < version >0.0.1-SNAPSHOT</ version > |
08 | < packaging >jar</ packaging > |
11 | < project.build.sourceencoding >UTF-8</ project.build.sourceencoding > |
12 | < spring.version >3.1.0.RELEASE</ spring.version > |
17 | < groupid >org.springframework.data</ groupid > |
18 | < artifactid >spring-data-redis</ artifactid > |
19 | < version >1.0.0.RELEASE</ version > |
23 | < groupid >cglib</ groupid > |
24 | < artifactid >cglib-nodep</ artifactid > |
25 | < version >2.2</ version > |
29 | < groupid >log4j</ groupid > |
30 | < artifactid >log4j</ artifactid > |
31 | < version >1.2.16</ version > |
35 | < groupid >redis.clients</ groupid > |
36 | < artifactid >jedis</ artifactid > |
37 | < version >2.0.0</ version > |
42 | < groupid >org.springframework</ groupid > |
43 | < artifactid >spring-core</ artifactid > |
44 | < version >${spring.version}</ version > |
48 | < groupid >org.springframework</ groupid > |
49 | < artifactid >spring-context</ artifactid > |
50 | < version >${spring.version}</ version > |
Spring Data Redis is the another project under Spring Data umbrella which provides seamless injection of
Redis into your application. The are several
Redis clients for Java and I have chosen the
Jedis as it is stable and recommended by
Redis team at the moment of writing this post.
We will start with simple configuration and introduce the necessary components first. Then as we move forward, the configuration will be extended a bit to demonstrated pub-sub capabilities. Thanks to Java config support, we will create the configuration class and have all our dependencies strongly typed, no XML anymore:
01 | package com.example.redis.config; |
03 | import org.springframework.context.annotation.Bean; |
04 | import org.springframework.context.annotation.Configuration; |
05 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; |
06 | import org.springframework.data.redis.core.RedisTemplate; |
07 | import org.springframework.data.redis.serializer.GenericToStringSerializer; |
08 | import org.springframework.data.redis.serializer.StringRedisSerializer; |
11 | public class AppConfig { |
13 | JedisConnectionFactory jedisConnectionFactory() { |
14 | return new JedisConnectionFactory(); |
18 | RedisTemplate< String, Object > redisTemplate() { |
19 | final RedisTemplate< String, Object > template = new RedisTemplate< String, Object >(); |
20 | template.setConnectionFactory( jedisConnectionFactory() ); |
21 | template.setKeySerializer( new StringRedisSerializer() ); |
22 | template.setHashValueSerializer( new GenericToStringSerializer< Object >( Object. class ) ); |
23 | template.setValueSerializer( new GenericToStringSerializer< Object >( Object. class ) ); |
That’s basically everything we need assuming we have single
Redis server up and running on localhost with default configuration. Let’s consider several common uses cases: setting a key to some value, storing the object and, finally, pub-sub implementation. Storing and retrieving a key/value pair is very simple:
1 | @Autowired private RedisTemplate< String, Object > template; |
3 | public Object getValue( final String key ) { |
4 | return template.opsForValue().get( key ); |
7 | public void setValue( final String key, final String value ) { |
8 | template.opsForValue().set( key, value ); |
Optionally, the key could be set to expire (yet another useful feature of Redis), f.e. let our keys expire in 1 second:
1 | public void setValue( final String key, final String value ) { |
2 | template.opsForValue().set( key, value ); |
3 | template.expire( key, 1 , TimeUnit.SECONDS ); |
Arbitrary objects could be saved into Redis as hashes (maps), f.e. let save instance of some class User
into Redis using key pattern “user:<id>”:
01 | public void setUser( final User user ) { |
02 | final String key = String.format( "user:%s" , user.getId() ); |
03 | final Map< String, Object > properties = new HashMap< String, Object >(); |
05 | properties.put( "id" , user.getId() ); |
06 | properties.put( "name" , user.getName() ); |
07 | properties.put( "email" , user.getEmail() ); |
09 | template.opsForHash().putAll( key, properties); |
Respectively, object could easily be inspected and retrieved using the id.
1 | public User getUser( final Long id ) { |
2 | final String key = String.format( "user:%s" , id ); |
4 | final String name = ( String )template.opsForHash().get( key, "name" ); |
5 | final String email = ( String )template.opsForHash().get( key, "email" ); |
7 | return new User( id, name, email ); |
There are much, much more which could be done using
Redis, I highly encourage to take a look on it. It surely is not a silver bullet but could solve many challenging problems very easy. Finally, let me show how to use a pub-sub messaging with
Redis. Let’s add a bit more configuration here (as part of AppConfig class):
02 | MessageListenerAdapter messageListener() { |
03 | return new MessageListenerAdapter( new RedisMessageListener() ); |
07 | RedisMessageListenerContainer redisContainer() { |
08 | final RedisMessageListenerContainer container = new RedisMessageListenerContainer(); |
10 | container.setConnectionFactory( jedisConnectionFactory() ); |
11 | container.addMessageListener( messageListener(), new ChannelTopic( "my-queue" ) ); |
The style of message listener definition should look very familiar to Spring users: generally, the same approach we follow to define JMS message listeners. The missed piece is our RedisMessageListener class definition:
01 | package com.example.redis.impl; |
03 | import org.springframework.data.redis.connection.Message; |
04 | import org.springframework.data.redis.connection.MessageListener; |
06 | public class RedisMessageListener implements MessageListener { |
08 | public void onMessage(Message message, byte [] paramArrayOfByte) { |
09 | System.out.println( "Received by RedisMessageListener: " + message.toString() ); |
Now, when we have our message listener, let see how we could push some messages into the queue using
Redis. As always, it’s pretty simple:
01 | @Autowired private RedisTemplate< String, Object > template; |
03 | public void publish( final String message ) { |
05 | new RedisCallback< Long >() { |
06 | @SuppressWarnings ( "unchecked" ) |
08 | public Long doInRedis( RedisConnection connection ) throws DataAccessException { |
09 | return connection.publish( |
10 | ( ( RedisSerializer< String > )template.getKeySerializer() ).serialize( "queue" ), |
11 | ( ( RedisSerializer< Object > )template.getValueSerializer() ).serialize( message ) ); |
That’s basically it for very quick introduction but definitely enough to fall in love with Redis.
Reference: Using Redis with Spring from our JCG partner Andrey Redko at the Andriy Redko {devmind} blog.
source - http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html
As you’ve read about Redis Installation & Configuration, you’re mostly understand the main concept of Redis database. Redis is an extremely high-performed, lightweight data store. It provides key/value data access to persistent byte arrays, lists, sets and hash data structures. So, the insertion, deletion and retrieve operations inside Redis becomes very simple how we have worked with using of Map, List , Set and an Array data structures how it is used inside normal Java program; although some minimal modifications that you’ve probably never care about them.
As you’ve been doing once it comes to deal with any database platform, you have to download/install the database driver. For Redis four driver libraries are provided for allowing your application to get connected to Redis. Spring Data supports connecting to Redis using either the Jedis (That will be considered in this tutorial), JRedis, RJC or SRP driver libraries. It doesn’t matter which library have been used, cause the Spring Data has abstracted the differences between those drivers into common set of APIs and template-style helpers.
1. Spring Context Configuration
To connect to Redis using Jedis, you need to create an instance of org.springframework.data.redis.connection.jedis.JedisConnectionFactory. The other driver libraries have corresponding ConnectionFactory subclasses. Below the proper Spring Context (SpringContext.xml) that should be used for initializing and connecting the Redis database through using of Spring.
SpringContext.xml
1 | <? xml version = "1.0" encoding = "UTF-8" ?> |
12 | < context:component-scan base-package = "net.javabeat.springdata.beans" ></ context:component-scan > |
14 | < bean id = "jedisConnFactory" class = "org.springframework.data.redis.connection.jedis.JedisConnectionFactory" |
17 | < bean id = "redisTemplate" class = "org.springframework.data.redis.core.RedisTemplate" |
18 | p:connection-factory-ref = "jedisConnFactory" /> |
- For searching about all Spring component (beans), you’ve provided the component-scan element.
- For connecting the Redis database, you’ve provided the Jedis connection factory bean.
- The central abstraction you’re likely to use when accessing Redis via Spring Data Redis is the org.springframework.data.redis.core.RedisTemplate class.
Since the feature set of Redis is really too large to effectively encapsulate into a single class, the various operations on data are split into separate operations classes as follows:
- ValueOperations:Returns the operations performed on simple values (or Strings in Redis terminology.
- ListOperations:Returns the operations performed on list values.
- SetOperations:Returns the operations performed on set values.
- ZSetOperations:Returns the operations performed on zset values (also known as sorted sets.
- HashOperations:Returns the operations performed on hash values.
- BoundValueOperations:Returns the operations performed on hash values bound to the given key.
- BoundListOperations:Returns the operations performed on list values bound to the given key.
- BoundSetOperations:Returns the operations performed on set values bound to the given key.
- BoundZSetoperations:Returns the operations performed on zset values (also known as sorted sets) bound to the given key.
- BoundHashOperations:Returns the operations performed on hash values bound to the given key.
2. Maven Dependencies for Spring Data Redis
The required libraries and dependencies for making the connecting of Redis is achievable are listed inside the below pom.xml file.
pom.xml
1 | <? xml version = "1.0" encoding = "UTF-8" ?> |
4 | < modelVersion >4.0.0</ modelVersion > |
5 | < groupId >net.javabeat.springdata</ groupId > |
6 | < artifactId >SpringData</ artifactId > |
8 | < packaging >jar</ packaging > |
9 | < name >Spring Data</ name > |
13 | < groupId >org.apache.maven.plugins</ groupId > |
14 | < artifactId >maven-compiler-plugin</ artifactId > |
15 | < version >3.1</ version > |
19 | < encoding >UTF-8</ encoding > |
27 | < groupId >org.slf4j</ groupId > |
28 | < artifactId >slf4j-log4j12</ artifactId > |
29 | < version >1.6.1</ version > |
33 | < groupId >org.springframework.data</ groupId > |
34 | < artifactId >spring-data-redis</ artifactId > |
35 | < version >1.2.1.RELEASE</ version > |
39 | < groupId >redis.clients</ groupId > |
40 | < artifactId >jedis</ artifactId > |
41 | < version >2.4.2</ version > |
45 | < groupId >org.springframework</ groupId > |
46 | < artifactId >spring-core</ artifactId > |
47 | < version >4.0.0.RELEASE</ version > |
51 | < groupId >org.springframework.data</ groupId > |
52 | < artifactId >spring-data-commons</ artifactId > |
53 | < version >1.5.0.RELEASE</ version > |
56 | < groupId >org.springframework</ groupId > |
57 | < artifactId >spring-core</ artifactId > |
58 | < version >4.0.3.RELEASE</ version > |
61 | < groupId >org.apache.commons</ groupId > |
62 | < artifactId >commons-pool2</ artifactId > |
63 | < version >2.2</ version > |
66 | < groupId >org.springframework</ groupId > |
67 | < artifactId >spring-beans</ artifactId > |
68 | < version >4.0.3.RELEASE</ version > |
3. Spring Beans (RegisterationBean)
For accessing the RedisTemplate that defined above, you have to use @Autowired annotation for being RedisTemplate available at your Spring Beans.
RegistrationBean.java
1 | package net.javabeat.springdata.beans; |
3 | import net.javabeat.springdata.jpa.data.User; |
5 | import org.springframework.beans.factory.annotation.Autowired; |
6 | import org.springframework.data.redis.core.RedisTemplate; |
7 | import org.springframework.stereotype.Component; |
10 | public class RegistrationBean { |
12 | private RedisTemplate<String,User> redisTemplate; |
14 | public RedisTemplate<String, User> getRedisTemplate() { |
18 | public void setRedisTemplate(RedisTemplate<String, User> redisTemplate) { |
19 | this .redisTemplate = redisTemplate; |
4. Java Beans
The business domain is very simple, it’s just a User entity associated with an instance of Address. Look below.
User.java
1 | package net.javabeat.springdata.jpa.data; |
3 | import java.io.Serializable; |
5 | public class User implements Serializable{ |
7 | private static final long serialVersionUID = 1L; |
11 | private String fullName; |
15 | private String status; |
17 | private Address address; |
19 | public String getFullName() { |
23 | public void setFullName(String fullName) { |
24 | this .fullName = fullName; |
27 | public String getAge() { |
31 | public void setAge(String age) { |
35 | public String getStatus() { |
39 | public void setStatus(String status) { |
43 | public String getId() { |
47 | public void setId(String id) { |
51 | public Address getAddress() { |
55 | public void setAddress(Address address) { |
56 | this .address = address; |
59 | public String toString() { |
60 | return this .id + "," + this .fullName; |
Address.java
1 | package net.javabeat.springdata.jpa.data; |
3 | import java.io.Serializable; |
5 | public class Address implements Serializable{ |
7 | private static final long serialVersionUID = 1L; |
9 | private Long addressId; |
11 | private String addressValue; |
13 | public Long getAddressId() { |
16 | public void setAddressId(Long addressId) { |
17 | this .addressId = addressId; |
19 | public String getAddressValue() { |
22 | public void setAddressValue(String addressValue) { |
23 | this .addressValue = addressValue; |
5. Spring Data Redis Example Application
The below Java Class, is just an executable application that developed for persisting an User entity associated with an Address inside Redis key/value database. Note that the using of this concept entity is just theoretical uses and it doesn’t mean anything when it comes to apply it inside the Redis.
Executable.java
1 | package net.javabeat.springdata.executable; |
3 | import net.javabeat.springdata.beans.RegistrationBean; |
4 | import net.javabeat.springdata.jpa.data.Address; |
5 | import net.javabeat.springdata.jpa.data.User; |
7 | import org.springframework.context.support.ClassPathXmlApplicationContext; |
9 | public class Executable { |
10 | public static RegistrationBean registrationBean; |
11 | public static ClassPathXmlApplicationContext context; |
15 | context = new ClassPathXmlApplicationContext( "SpringContext.xml" ); |
18 | public static void main(String [] args) throws Exception{ |
23 | public static void createUser(){ |
24 | User user = new User(); |
25 | user.setId( "20011202" ); |
26 | user.setFullName( "Susa Richard" ); |
29 | Address address = new Address(); |
30 | address.setAddressValue( "UK/Manchester" ); |
31 | user.setAddress(address); |
32 | RegistrationBean bean = (RegistrationBean)context.getBean( "registrationBean" ); |
34 | bean.getRedisTemplate().opsForHash().put( "UserA" , user.hashCode(),user); |
36 | User x = (User)bean.getRedisTemplate().opsForHash().get( "UserA" , user.hashCode()); |
37 | System.out.println(x); |
6. Query Redis Persisted Store
The below snapshot shows you the User object that already persisted inside the Hash persistent store inside the Redis Key/Value database.![Redis Persisted Data](http://cdn.javabeat.net/wp-content/uploads/2014/05/RedisPersistedData.png)
- See more at: http://www.javabeat.net/spring-data-redis-example/#sthash.fFpl5LKF.dpuf
source - http://www.javabeat.net/spring-data-redis-example/