2015년 12월 2일 수요일

Spring + Redis Cache and Repository Usage

이전 포스팅에서는 간략하게 서버 설정을 진행해보았다.

상세한 서버 설정 항목인 Replication 설정과 Cluster 서비스 설정에 대해서는 추후 따로 포스팅한다.
이번 포스팅에서 캐쉬용도로 사용하는 방식과 No-SQL DB로 CRUD를 처리하는 방법을 간단하게 기술한다.

먼저 사용자 정보를 담는 Dto를 설계한다. Dto를 설계 할 때 Redis에 담겨지는 Object는 반드시 Serializable 인터페이스를 상속 받아야 한다. 직렬화 되지 않은 객체는 Redis에서 반환할 때 원래 객체로 반환 할 수 없다.
public class User implements Serializable{

 private String userName;
 private String phoneNumber;
 private String email;
 private String address;
 private int userID;
 
 
 public int getUserID() {
  return userID;
 }
 public void setUserID(int userID) {
  this.userID = userID;
 }
 public String getUserName() {
  return userName;
 }
 public void setUserName(String userName) {
  this.userName = userName;
 }
 public String getPhoneNumber() {
  return phoneNumber;
 }
 public void setPhoneNumber(String phoneNumber) {
  this.phoneNumber = phoneNumber;
 }
 public String getEmail() {
  return email;
 }
 public void setEmail(String email) {
  this.email = email;
 }
 public String getAddress() {
  return address;
 }
 public void setAddress(String address) {
  this.address = address;
 }
 
}


그리고 CRUD 작업을 수행할 간단한 인터페이스를 하나 생성한다.
public interface UserAccessService {

 public User getUser(int id);
 public void setUser(User user);
 public User deleteUser(User User);
 public void updateUser(int id, User updateUser);
}

Redis는 기본적으로 Key , Value를 저장하는 데이터 타입에서 자유롭다 개발자의 취향에 따라서 제네릭으로 인터페이스를 구성하는 방법도 가능하다.
public interface UserAccessService {

 public User getUser(K key);
 public void setUser(V user);
 public User deleteUser(V User);
 public void updateUser(K key, V updateUser);
}


이 예제에서는 제너릭을 사용하지 않고 진행한다.
UserAccessService를 상속 받는 2개의 클래스를 생성한다.
첫 번째 Dao는 캐쉬를 지원하는 형태로 구성한다.
캐쉬로 Redis를 사용할 때는 Spring 에서 지원하는 기본 Cache Annotation을 숙지해야 한다.
아래의 링크에서 기본 개념과 사용방법을 숙지하자. http://blog.outsider.ne.kr/1094
@Repository("userDaoCache")
public class UserDaoCache implements UserAccessService {

 // 임의의 Database로 가정
 private static  HashMap<Integer, User> USERMAP = new HashMap<Integer, User>();

 /*private HashMap<Integer, User> getUserMap() {
  if (userMap == null)
   userMap = new HashMap<Integer, User>();
  return userMap;
 }*/

 public UserDaoCache() {

 }

 @Override
 @Cacheable(value = "users", key = "#id")
 public User getUser(int id) {
  User user = USERMAP.get(id);
  System.out.println("This user is come from HashMap : "
    + user.getUserName());
  return user;
 }

 
 @Override
 public void setUser(User user) {
  USERMAP.put(user.getUserID(), user);
 }

 @Override
 @CacheEvict(value = "users", allEntries=true)
 public User deleteUser(User user) {
  USERMAP.remove(user.getUserID());
  return null;
 }

 @Override
 @CacheEvict(value = "users", allEntries=true)
 public void updateUser(int id, User updateUser) {
  USERMAP.replace(id, updateUser);

 }

}

두 번째 Dao는 Redis 자체를 no-sql 데이터 저장소로 사용하는 방식이다.
@Repository("userDaoTemplate")
public class UserDaoTemplate implements UserAccessService {

 @Autowired
 private RedisTemplate<Integer, User> redisTemplate;

 public UserDaoTemplate() {
  
 }

 @Override
 public User getUser(int id) {
  User user = redisTemplate.opsForValue().get(id);
  return user;
 }

 @Override
 public void setUser(User user) {
  redisTemplate.opsForValue().set(user.getUserID(), user);

 }

 @Override
 public User deleteUser(User User) {
  redisTemplate.delete(User.getUserID());
  return User;
 }

 @Override
 public void updateUser(int id, User updateUser) {
  redisTemplate.opsForValue().getAndSet(id, updateUser);
  
 }

}

Redis Template은 Redis에서 지원하는 자료형에 맞게 Ops 함수를 제공한다.
Redis에서 지원하는 자료형 종류를 먼저 확인하자
아래의 링크에서 지원하는 자료형을 확인 할 수 있다. Template 에서는 각 자료형 대로 저장할 수 있도록 오퍼레이터 함수를 지원한다.
http://redis.io/topics/data-types
Redis Crud를 하기 위한 준비가 끝났다.
Cache dao가 동작하는 지 확인하는 Junit 테스트 코드를 설계한다.
 @Autowired
 @Qualifier("userDaoCache")
 private UserAccessService redisCache;
@Test
 public void testCache() {
  
  User user = new User();
  user.setUserID(1);
  user.setUserName("john");
  redisCache.setUser(user);
  
  user = redisCache.getUser(1);
  System.out.println("Result : " + user.getUserName());
  user = redisCache.getUser(1);
  System.out.println("Result : " + user.getUserName());
  user = redisCache.getUser(1);
  System.out.println("Result : " + user.getUserName());
  
  user.setUserName("tommy");
  
  redisCache.updateUser(1, user);
  
  user = redisCache.getUser(1);
  System.out.println("Result : " + user.getUserName());
  user = redisCache.getUser(1);
  System.out.println("Result : " + user.getUserName());
  user = redisCache.getUser(1);
  System.out.println("Result : " + user.getUserName());
  
  redisCache.deleteUser(user);
 }

실행결과는 다음과 같다.


처음 데이터를 읽을 때는 선언한 hashMap 안에 데이터가 출력된다.
이후에 출력되는 데이터는 Cache 에 미리 저장된 데이터가 출력됨을 알 수 있다.

이후에 사용자 정보가 Update 되면 저장된 캐쉬가 초기화 되고 다시 저장된다.
다시 저장된 캐쉬 값이 getuser 함수를 통해서 전달 되는 것을 확인할 수 있다.



두 번째 No-SQL 저장소로 구현된 Dao 가 동작하는 테스트 코드를 설계한다.
@Autowired
 @Qualifier("userDaoTemplate")
 private UserAccessService redisTemplate;

@Test
 public void testRedis()
 {
  User user = new User();
  user.setUserID(1);
  user.setUserName("john");
  redisTemplate.setUser(user);
  
  User userfromRedis = redisTemplate.getUser(1);
  assertThat(user.getUserName(), org.hamcrest.core.Is.is(userfromRedis.getUserName()));
  
  user.setUserName("tommy");
  redisTemplate.updateUser(1, user);
  userfromRedis = redisTemplate.getUser(1);
  
  assertThat(user.getUserName(), org.hamcrest.core.Is.is(userfromRedis.getUserName()));
  
  redisTemplate.deleteUser(user);
  userfromRedis = redisTemplate.getUser(1);
  
  
  
 }
수행의 결과를 확인해보자. 설정에 문제가 없을 경우 정상적으로 Junit 테스트가 실행될 것이다.
Dao 코드를 구성하기 전에 redis cli를 이용하여 구성하려는 Dao가 정상적으로 동작하는 지 확인해보는 것이 좋다.
소스 코드 위치
https://github.com/wargen99/wargen-repo.git
다음 포스팅에서는 Spring session과 Redis를 이용하여 분산환경에서 일관된 세션 유지 방법에 대해 포스팅한다.

댓글 없음:

댓글 쓰기