Spring Boot 2.3.0.RELEASE をベースに、Redisのシングルノードまたはクラスタモードを統合する手順を示します。
Maven依存設定(pom.xml)
<!-- Redisサポート -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JSONシリアライズ用 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9</version>
</dependency>
アプリケーション設定(application.yml)
spring:
redis:
timeout: 6000ms
password: ''
# シングルノードの場合
# host: 127.0.0.1
# port: 6379
# クラスタモードの場合
cluster:
nodes:
- 127.0.0.1:6379
- 127.0.0.1:6380
- 127.0.0.1:6381
lettuce:
pool:
max-idle: 8
min-idle: 0
max-active: 8
max-wait: -1ms
Lettuceは非同期・ノンブロッキングな接続方式で、Nettyを基盤としており、現在推奨されています。Jedisは同期ブロッキング方式です。
Redis設定クラス
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisSetup {
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connFactory) {
var keySerializer = new StringRedisSerializer();
var valueSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
valueSerializer.setObjectMapper(mapper);
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connFactory);
template.setKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashKeySerializer(keySerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
return template;
}
}
Redis操作ユーティリティ
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisHelper {
@Autowired
private RedisTemplate<String, Object> rt;
public boolean setExpiry(String k, long sec) {
try {
if (sec > 0) rt.expire(k, sec, TimeUnit.SECONDS);
return true;
} catch (Exception ex) {
ex.printStackTrace();
return false;
}
}
public long getTTL(String k) {
return rt.getExpire(k, TimeUnit.SECONDS);
}
public boolean exists(String k) {
try { return rt.hasKey(k); }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public void remove(String... keys) {
if (keys != null && keys.length > 0) {
if (keys.length == 1) rt.delete(keys[0]);
else rt.delete(java.util.Arrays.asList(keys));
}
}
// --- 基本値操作 ---
public Object fetch(String k) { return k == null ? null : rt.opsForValue().get(k); }
public boolean store(String k, Object v) {
try { rt.opsForValue().set(k, v); return true; }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean storeWithExpiry(String k, Object v, long sec) {
try {
if (sec > 0) rt.opsForValue().set(k, v, sec, TimeUnit.SECONDS);
else store(k, v);
return true;
} catch (Exception ex) { ex.printStackTrace(); return false; }
}
public long increment(String k, long delta) {
if (delta < 0) throw new RuntimeException("増分は正数である必要があります");
return rt.opsForValue().increment(k, delta);
}
public long decrement(String k, long delta) {
if (delta < 0) throw new RuntimeException("減分は正数である必要があります");
return rt.opsForValue().increment(k, -delta);
}
// --- ハッシュ操作 ---
public Object hashGet(String k, String field) { return rt.opsForHash().get(k, field); }
public Map<Object, Object> hashFetchAll(String k) { return rt.opsForHash().entries(k); }
public boolean hashStore(String k, Map<String, Object> data) {
try { rt.opsForHash().putAll(k, data); return true; }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean hashStoreWithExpiry(String k, Map<String, Object> data, long sec) {
try {
rt.opsForHash().putAll(k, data);
if (sec > 0) setExpiry(k, sec);
return true;
} catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean hashPut(String k, String field, Object v) {
try { rt.opsForHash().put(k, field, v); return true; }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean hashPutWithExpiry(String k, String field, Object v, long sec) {
try {
rt.opsForHash().put(k, field, v);
if (sec > 0) setExpiry(k, sec);
return true;
} catch (Exception ex) { ex.printStackTrace(); return false; }
}
public void hashRemove(String k, Object... fields) { rt.opsForHash().delete(k, fields); }
public boolean hashContains(String k, String field) { return rt.opsForHash().hasKey(k, field); }
public double hashIncrement(String k, String field, double by) { return rt.opsForHash().increment(k, field, by); }
public double hashDecrement(String k, String field, double by) { return rt.opsForHash().increment(k, field, -by); }
// --- セット操作 ---
public Set<Object> setMembers(String k) { return rt.opsForSet().members(k); }
public boolean setContains(String k, Object val) { return rt.opsForSet().isMember(k, val); }
public long setAdd(String k, Object... vals) { return rt.opsForSet().add(k, vals); }
public long setAddWithExpiry(String k, long sec, Object... vals) {
Long added = rt.opsForSet().add(k, vals);
if (sec > 0) setExpiry(k, sec);
return added != null ? added : 0L;
}
public long setSize(String k) { return rt.opsForSet().size(k); }
public long setRemove(String k, Object... vals) { return rt.opsForSet().remove(k, vals); }
// --- リスト操作 ---
public List<Object> listRange(String k, long start, long end) { return rt.opsForList().range(k, start, end); }
public long listSize(String k) { return rt.opsForList().size(k); }
public Object listIndex(String k, long idx) { return rt.opsForList().index(k, idx); }
public boolean listPush(String k, Object val) {
try { rt.opsForList().rightPush(k, val); return true; }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean listPushWithExpiry(String k, Object val, long sec) {
try {
rt.opsForList().rightPush(k, val);
if (sec > 0) setExpiry(k, sec);
return true;
} catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean listPushAll(String k, List<Object> vals) {
try { rt.opsForList().rightPushAll(k, vals); return true; }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean listPushAllWithExpiry(String k, List<Object> vals, long sec) {
try {
rt.opsForList().rightPushAll(k, vals);
if (sec > 0) setExpiry(k, sec);
return true;
} catch (Exception ex) { ex.printStackTrace(); return false; }
}
public boolean listUpdate(String k, long idx, Object val) {
try { rt.opsForList().set(k, idx, val); return true; }
catch (Exception ex) { ex.printStackTrace(); return false; }
}
public long listRemove(String k, long count, Object val) { return rt.opsForList().remove(k, count, val); }
}
RedisTemplateの直接利用
ユーティリティクラスを使わずに、直接RedisTemplateをインジェクトして操作することも可能です:
@Autowired
private RedisTemplate<String, Object> redisTemplate;
テスト例
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class RedisIntegrationTest {
@Resource
private RedisHelper redisHelper;
@Test
public void verifyStorage() {
boolean success = redisHelper.store("sample-key", "Hello Redis");
if (success) {
System.out.println(redisHelper.fetch("sample-key"));
} else {
System.out.println("保存に失敗しました");
}
}
}