4. Spring Data Redis

http://projects.spring.io/spring-data/


1) 왜 스프링 데이터 Redis?

  • Spring Framework는 최고의 풀 스택 Java / JEE 애플리케이션 프레임 워크.
    가벼운 컨테이너와 의존성 삽입, AOP 및 편리한 서비스 추상화 제공.
  • Spring Data Redis는 Spring 애플리케이션의 Redis에 대한 쉬운 구성과 액세스를 제공.
    저장소와의 상호 작용을 위한 하위 수준 및 높은 수준의 추상화를 모두 제공하여 사용자를 인프라 관련 문제로부터 자유롭게 함.

2) 요구사항

  • JDK 레벨 6.0 이상
  • Spring Framework 5.0.0.M5 이상
  • 키 값 저장소의 경우 Redis 2.6.x 이상
  • 스프링 데이터 Redis는 현재 최신 3.2 릴리스에 대해 테스트.
  • Spring 알기(이해하기)
  • NoSQL 및 Key Value 저장소

3) Redis 연결

dependencies {
	compile('org.springframework.boot:spring-boot-starter-data-redis')
}

 -  RedisTemplate, RedisConnectionFactory 두개만 설정하면 됨.

 가. RedisConnectionFactory

  - Redis 서버와의 통신을 위한 low-level 추상화를 제공

  - 설정에 따라서 새로운 RedisConnection 또는 이미 존재하는 RedisConnection을 리턴.
    RedisConnection 은 Redis 서버와의 통신 추상화를 제공하며, exception 발생시 Spring DataAccessException으로 전환.

  - RedisConnection 은 binary value를 인자로 받고 결과를 리턴하는 low-level method를 리턴.

  - Jedis, Jredis(1.7 Deprecated), Lettuce, SRP, RJC 등의 클라이언트 라이브러리가 있음. 

    Jedis 를 보편적으로 많이 사용하는거 같음

 나. RedisTemplate

  - Redis 서버에 Redis Command를 수행하기 위한 high-level 추상화를 제공

  - 오브젝트 serialization 과 connection management를 수행

  - Redis 서버에 데이터 CRUD를 위한 Key Type Operations 와 Key Bound Operations 인터페이스를 제공

     Key Type Operations

  • ValueOperations - 

    Redis string (or value) operations 
  • ListOperations - Redis list operations
  • SetOperations - Redis set operations
  • ZSetOperations - Redis zset (or sorted set) operations
  • HashOperations - Redis hash operations
  • HyperLogLogOperations - Redis HyperLogLog operations like (pfadd, pfcount,…?)
  • GeoOperations - Redis geospatial operations like GEOADD, GEORADIUS,…?)

    Key Bound Operations

  • BoundValueOperations - Redis string (or value) key bound operations
  • BoundListOperations - Redis list key bound operations
  • BoundSetOperations - Redis set key bound operations
  • BoundZSetOperations - Redis zset (or sorted set) key bound operations
  • BoundHashOperations - Redis hash key bound operations
  • BoundGeoOperations - Redis key bound geospatial operations

 
  - thread-safe 하며, 재 사용이 가능

  - 대부분의 기능에 RedisSerializer 인터페이스를 사용.

    StringRedisSerializer, JdkSerializationRedisSerializer, JacksonJsonRedisSerializer, Jackson2JsonRedisSerializer, GenericJackson2JsonRedisSerializer, OxmSerializer를 사용할 수 있음.

  - Redis에 저장된 키와 값이 java.lang.String이 되도록하는 것이 일반적이므로 StringRedisTemplate 확장 기능을 제공.

    StringRedisSerializer를 사용하며, 저장된 키와 값은 사람이 읽을 수 있음.

 @Configuration
public class RedisConfig {

	@Bean
	public JedisConnectionFactory jedisConnectionFactory() {
		JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
		jedisConnectionFactory.setHostName("localhost");
		jedisConnectionFactory.setPort(6379);
		jedisConnectionFactory.setTimeout(0);
		jedisConnectionFactory.setUsePool(true);

		return jedisConnectionFactory;
	}

	@Bean
	public RedisTemplate<String, Object> redisTemplate() {
		RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
		redisTemplate.setKeySerializer(new StringRedisSerializer());
		redisTemplate.setValueSerializer(new StringRedisSerializer());
		redisTemplate.setConnectionFactory(jedisConnectionFactory());
		return redisTemplate;
	}

	@Bean
	public StringRedisTemplate stringRedisTemplate() {
		StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();

		stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
		stringRedisTemplate.setValueSerializer(new StringRedisSerializer());

		stringRedisTemplate.setConnectionFactory(jedisConnectionFactory());
		return stringRedisTemplate;
	}
}

4) 예제
 
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootRedisApplicationTests {
        --- 전체 소스는 github ---

		@Resource(name="redisTemplate")
	private ValueOperations<String, String> valueOperations;
	
	@Resource(name="redisTemplate")
	private ValueOperations<String, User> userOperations;
	
	/**
	 * Redis string (or value) operations
	 */
	@Resource(name="redisTemplate")
	private ListOperations<String, String> listOperations;
	/**
	 * Redis string (or value) operations
	 */
	@Resource(name="redisTemplate") 
	private SetOperations<String, String> setOperations;
	/**
	 * Redis string (or value) operations
	 */
	@Resource(name="redisTemplate")
	private ZSetOperations zSetOperations;
	/**
	 * Redis string (or value) operations
	 */
	@Resource(name="redisTemplate")
	private HashOperations hashOperations;
	
	@Before
	public void 깨끗한_상태로() {
//		Set<String> keys = redisTemplate.keys("*");
//		log.info("@@@@@@@@@ 현재 키가 {} 개 들어 있습니다.", keys.size());
//		for (String key : keys) {
//			redisTemplate.delete(key);
//		}
	}
	@After
	public void 원래대로_되돌림() {
		System.out.println();
//		try {
//			redisTemplate.execute(new RedisCallback() { 
//				@Override 
//				public Object doInRedis(RedisConnection connection) throws DataAccessException { 
//					connection.flushAll(); 
//					return null; 
//				} 
//			}); 
//		} catch (Exception e) { 
//			log.warn("모든 캐시를 삭제하는데 실패했습니다.", e); 
//		} 	
	}
	
	@Test
	public void valueOperations() {
		log.info("----------- valueOperations start --------");
		
		String key1 = "key1";
		String key2 = "허균";
		
		valueOperations.set(key1, "value1");
		valueOperations.set(key2, "홍길동, 허생전");
		
		log.info("{}의 값은 = {}, 사이즈는 = {} 입니다.", key1, valueOperations.size(key1));
		log.info("{}의 값은 = {}, 사이즈는 = {} 입니다.", key2, valueOperations.size(key2));
		
		log.info("----------- valueOperations end --------");
	}
	
	@Test
	public void listOperations() {
		log.info("----------- listOperations start --------");
		
		String key1 = "여자랭킹";
		listOperations.rightPush(key1, "이나영");
		listOperations.rightPush(key1, "소녀시대 윤아");
		
		String key2 = "남자랭킹";
		listOperations.leftPush(key2, "이승환");
		listOperations.leftPush(key2, "송광호");
		
		log.info(" {} 의 왼쪽 값은 {} ", key1, listOperations.leftPop(key1));
		log.info(" {} 의 오른쪽 값은 {} ", key2, listOperations.rightPop(key2));
		
		log.info("----------- listOperations end --------");
	}
	
	@Test
	public void setOperations() {
		log.info("----------- setOperations start --------");
		
		String key1 = "user:gt1000:intro";
		
		setOperations.add(key1, "잘 생긴 훈남");
		setOperations.add(key1, "영화감삼");
		setOperations.add(key1, "감성적");
		setOperations.add(key1, "제일 중요한건.... 소고기 함 쏴 알려 주께...");
		
		Set<String> intro = setOperations.members(key1);
		intro.forEach((value) -> log.info(" 나는 = {} ", value));
		
//		for(String value : intro) {
//			log.info(" 나는 = {} ", value);
//		}
		
		log.info("----------- setOperations end --------");
	}
	
	@Test
	public void hashOperations() {
		log.info("----------- hashOperations start --------");
		
		String key1 = "user:gt1000:detail";
		hashOperations.put(key1, "age", 43);
		hashOperations.put(key1, "재산", "개털이다");
		hashOperations.put(key1, "job", "언제 짤릴지 모르는 프로그래머");
		hashOperations.put(key1, "술은 쇠주", 1);

		Map<String, Object> entry = hashOperations.entries(key1);
		log.info(" 그이 나이 {} 세", entry.get("age"));
		log.info(" 모아둔 재산은 {}", entry.get("재산"));
		log.info(" 직업은 {}", entry.get("job"));
		log.info(" 주량은 소주 {} 병이다.", entry.get("술은 쇠주"));
		
		log.info("----------- hashOperations end --------");
	}
	
	@Test
	public void userTest() {
		String userId1 = "gt1000";
		String userId2 = "gt1003";
		String userId3 = "gt1005";
		
		User user1 = new User(userId1, "엄청 멋진 놈");
		user1.setAge(30);
		User user2 = new User(userId2, "당신의 천사가 되고 싶은 사람 == gt1000");
		user2.setAge(40);
		User user3 = new User(userId3, "천사 보다 더 좋은 사람 == gt1000");
		user3.setAge(43);
		
		userOperations.set(userId1, user1);
		userOperations.set(userId2, user2);
		userOperations.set(userId3, user3);
		
		log.info("key = {}, value = {}", userId1,  userOperations.get(userId1));
		log.info("key = {}, value = {}", userId2, userOperations.get(userId2));
		log.info("key = {}, value = {}", userId3, userOperations.get(userId3));
	}
}