2016년 1월 10일 일요일

[QueryDSL] QueryDSL 적용 및 주의사항

Query DSL을 적용하기 위해서 일단 먼저 Maven 설정을 수행한다.

                
  
   com.mysema.querydsl
   querydsl-core
   3.7.0
  
  
   com.mysema.querydsl
   querydsl-apt
   3.7.0
  

  
   com.mysema.querydsl
   querydsl-jpa
   3.7.0
  



  
   
                                com.mysema.maven
    maven-apt-plugin
    1.0.4
    
     
      
       process
      
      
       target/generated-sources/java
       com.mysema.query.apt.jpa.JPAAnnotationProcessor
      
     
    
                        
  


Maven 설정을 완료하면 <executions> 항목에 오류 표시가 뜰 것이다.

이것은 STS 가 Javaw.exe 파일의 위치를 JRE를 참고해서 생기는 문제이다.

sts.ini를 편집하여 다음의 옵션을 붙여주자.

-vm
jdk의 javaw.exe 경로
편집이 끝났으면 Maven update project를 수행하여 빌드를 수행하면 문제가 해결된다.

Plug-in 항목에서 target/generated-sources/java 경로를 확인해보면 @Entity Dto를 기준으로 Q prefix가 붙은 도메인 파일이 생성 된 것을 확인할 수 있다.


Build path 메뉴를 이용하여 자동 생성된 Q도메인 경로를 소스 경로로 지정해주자. 

본인이 원하는 경로에 도메인 파일을 생성해도 상관없다. 다만 소스 폴더 지정을 해주어야

패키지 참조가 가능하니 잊지 말자. 



2015년 12월 29일 화요일

thymeleaf + Spring 설정 및 주의사항

thymeleaf에 관한 내용을 아래를 참조

http://www.thymeleaf.org/documentation.html

Kunner 님의 블로그
http://kunner.tistory.com/1053

먼저 항상 그렇듯이...
Maven Pom 설정

<!-- Thymeleaf -->
  <dependency>
   <groupId>org.thymeleaf</groupId>
   <artifactId>thymeleaf-spring4</artifactId>
   <version>2.1.2.RELEASE</version>
  </dependency>


스프링 3 용과 스프링 4 용이 분리되어 있다! 확인 해서 적용할 것

Xml Configuration 설정


<beans:bean id="templateResolver"
  class="org.thymeleaf.templateresolver.ServletContextTemplateResolver">
  <beans:property name="prefix" value="/WEB-INF/views/template/" />
  <beans:property name="suffix" value=".html" />
  <beans:property name="templateMode" value="HTML5" />
  <!--  개발모드 일때는 필수   -->
  <beans:property name="cacheable" value="false"></beans:property>
  <beans:property name="characterEncoding" value="UTF-8"></beans:property>
 </beans:bean>

 <beans:bean id="templateEngine" class="org.thymeleaf.spring4.SpringTemplateEngine">
  <beans:property name="templateResolver" ref="templateResolver" />
 </beans:bean>

 <beans:bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
  <beans:property name="templateEngine" ref="templateEngine" />
  <beans:property name="characterEncoding" value="UTF-8"></beans:property>  
 </beans:bean>


설정 시에 2가지를 유념해야 한다.

1. cacheable 설정을 해줄 것 - 디버그를 하다보면 브라우저 새로 고침을 통해서 변경된 결과를 확인할 것이다.

캐쉬 설정이 되어 있으면 즉시 반영되지 않는다.
개발 시에만 false로 설정해두고 개발이 완료된 후에는 true로 변경하자.

2. characterEncoding 설정
ServletContextTemplateResolver 와 ThymeleafViewResolver 두 군데 다 UTF-8 인코딩을 설정해야 한다. 서버에서 넘어오는 데이터 영역은 UTF-8 인코딩이 보장되지만
HTML 내에 있는 정적 텍스트의 경우 저 설정이 없으면 한글이 깨지는 문제가 발생한다.
간단한 Controller를 하나 작성해보자.

간단한 과제 목록을 보여주는 view를 구성할 때 사용하는 controller 이다.
@Controller
public class ProjectController {

@RequestMapping(value = "/ProjectList/{uid}", method = RequestMethod.GET)
 public ModelAndView getProjectList(@PathVariable String uid, ModelAndView model)
 { 
  Project project1 = new Project("Project1", "과제1");
  Project project2 = new Project("Project2", "과제2");
  
  project1.setIdx(1);
  project2.setIdx(2);
  
  ArrayList list = new ArrayList();
  
  list.add(project1);
  list.add(project2);
  
  model.addObject("projects", list);
  model.setViewName("project_list");
  
  return model;
 }

@RequestMapping(value = "/ProjectInfo/{projectID}", method = RequestMethod.GET)
 public ModelAndView getProjectInfo(@PathVariable String projectID, ModelAndView model)
 { 
  Project project1 = new Project("Project1", "과제1");
  project1.setIdx(1);
  
  model.addObject("project", project1);
  model.setViewName("project_info");
  
  return model;
 }
}

목록을 보여주는 html을 작성해보자.


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
 xmlns:th="http://www.thymeleaf.org">
<head>
<title>Project List</title>
</head>
<body>
 <h1>Project List</h1>
 <table>
  <tr>
   <th>IDX</th>
   <th>Name</th>
  </tr>
  <tr th:each="prj : ${projects}">
   <td th:text="${prj.idx}">1</td>
   <td>
    <a
    href="/" 
    th:href="@{|/ProjectInfo/${prj.idx}|}"
    th:text="${prj.name}"    
    >라바 비콘</a>
   </td>
  </tr>
 </table>
</body>
</html>


Html 예제에서 확인해야 할 사항이 있다. Controller에서 Path 변수를 사용한 것은 이것을 보여주기 위함이다.
th:href="@{|/ProjectInfo/${prj.idx}|}"
항목에서 주의해야 할 것은 @{...} URL 출력 사용시 변수가 포함 된 URL을 출력할 경우 반드시 |...| 리터럴 지정을 해줘야 한다는 점이다.




정상적으로 처리되었다면 위와 같은 화면이 나타나고 해당 url 링크가 입력한 변수대로 표시되면 잘 설정된 것이다.

|...| 리터럴을 빼고 실행해보면 차이를 알 수 있을 것이다.

2015년 12월 15일 화요일

초보개발자 시절

초보개발자 시절에 Onesound 라는 친구의 블로그를 많이 방문했었다.


이런 주옥같은 명대사를 날리던 분 

이 친구는 나보다 어린 친구 같은데 IT 트랜드 라든가 게임 트랜드 같은 분야에

대단히 해박한 친구여서 굉장히 인상적이였던 듯...

우연히 이친구 블로그에 올려진 채팅페이지에서 대화를 나눈 적이 있다.

당시에 web 1.2 이란 만화를 연재 중이였는데 소재가 없다고 해서 개인적인 경험을

이야기 해준 적이 있다. 나는 웃으면서 설마 그리겠어 했는데......

진짜로 그려버림

심지어 개발자라면 누구나 공감할 만화로 회자되기 시작함.

아아아... 이 당시 팀장 ( 지금은 부사장 ) 님은 아직도 저 사실을 모른다....

저 당시에는 디버그에 서툴러서 재귀호출 검증을 제대로 못했기 때문에 생긴 문제라

차마 3초만에 끝난걸 사실대로 말할 수 없었다...




2015년 12월 3일 목요일

Redis Spring Session

이번 포스팅에서는 Redis Spring Session을 이용하여 클라우드 환경에서 일관된 세션 유지를 수행하는 방법을 알아본다.

이를 처리하기 위해 간단한 로그인을 수행하는 프로젝트를 생성하고 로그인 한 사용자의 세션 정보가 유지되는 환경을 구성해 본다.

먼저 Spring mvc 프로젝트를 하나 생성한다.

생성 시 클라우드 환경에서 동작하는 방식을 시뮬레이션 하기 위해 Tomcat WAS 셋팅을 아래와 같이 한다.



Spring Redis Session을 이용하기 위한 Maven 설정

          <dependency>
  <groupId>org.springframework.session</groupId>
   <artifactId>spring-session-data-redis</artifactId>
   <version>1.0.2.RELEASE</version>
   <type>pom</type>
  </dependency>
  <dependency>
   <groupId>cglib</groupId>
   <artifactId>cglib</artifactId>
   <version>3.2.0</version>
  </dependency>


Redis Spring session에 관한 정보는 아래의 사이트에서 확인이 가능하다.

http://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-redis

Spring Redis Session을 이용하기 위한 Web.xml 설정

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

 <!-- The definition of the Root Spring Container shared by all Servlets 
  and Filters -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
  /WEB-INF/spring/appServlet/sessionRedis.xml
  /WEB-INF/spring/root-context.xml
  </param-value>
 </context-param>

 <!-- Creates the Spring Container shared by all Servlets and Filters -->
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

 <!-- Processes application requests -->
 <servlet>
  <servlet-name>appServlet</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>

 <servlet-mapping>
  <servlet-name>appServlet</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>
 <!--  WAS에서 제공하는 세션 정보를 redis를 통해서 전달하게 처리하는 Filter 설정 -->
 <filter>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
 </filter>
 <filter-mapping>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
</web-app>



Redis 서버 설정 파일

<?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:p="http://www.springframework.org/schema/p"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

 <context:annotation-config />
 <bean
  class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />
 <bean
  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" 
  p:port="8000" p:hostName="192.168.0.105" />
</beans>



간단한 로그인 처리를 위한 view 와 Controller 선언

@Controller
public class HomeController {

 private static final Logger logger = LoggerFactory
   .getLogger(HomeController.class);

 @RequestMapping({ "/session", "/stats" })
 public String showSession(HttpSession session, Model model) {
  if (session.getAttribute("id") == null)
   return "redirect:/Login";

  model.addAttribute("sessionId", session.getId());
  model.addAttribute("sessionNew", session.isNew());
  model.addAttribute("name", session.getAttribute("id"));
  return "session";
 }

 @RequestMapping(value = "/", method = RequestMethod.GET)
 public String home(HttpSession session, Model model) {
  if (session.getAttribute("id") == null)
   return "redirect:/Login";

  return "home";
 }
}


@Controller
public class LogInController {

 @RequestMapping(value = "/Login", method = RequestMethod.GET)
 public String login(Locale locale, Model model) {

  return "Login";
 }

 @RequestMapping(value = "/LoginCheck", method = RequestMethod.POST)
 public String loginCheck(@RequestParam String username,
   @RequestParam String password, HttpSession session) {
  // Temporary password matching
  if (password.equals("pass")) {
   session.setAttribute("id", username);
   return "home";
  }
  return "Login";
 }
}


<%@ page language="java" contentType="text/html; charset=UTF-8"
 pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Log In page</title>
</head>
<body>
 <div id="form-login">
  <h2>login</h2>
  <form:form method="post" action="/redisSession/LoginCheck">
   <input type="text" name="username" placeholder="UserName" />
   <input type="password" name="password" placeholder="Password" />
   <input type="submit" value="login" />
  </form:form>
 </div>
</body>
</html>


<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE HTML>
<html>
<head>
    <title>Spring Session Abstraction</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<p>Current session:  ${sessionId}</p>
<p>Is new session:  ${sessionNew}</p>
  <p>your id :  ${id}</p>
</body>
</html>


모든 설정이 완료되었다. 이제 테스트를 수행해보자.

먼저 localhost:8080 주소를 가진 WAS를 띄운다.
localhost:8080/redisSession 주소를 입력하여 로그인 폼을 띄우고 로그인을 수행한다.


 로그인이 완료되었으면 localhost:8080/redisSession/session 페이지로 이동하여 현재 로그인 한 세션 정보를 확인한다.


세션 정보를 확인한 뒤 8080 포트의 WAS를 내린 후 8888 포트를 가진 WAS를 실행한다.
localhost:8888/redisSession/session로 주소를 변경하고 이동한다.



 웹 개발에 익숙한 개발자라면 서로 다른 2개의 WAS에서는 세션정보가 공유되지 않는 사실을 알고 있을 것이다.

당연히 새로 로드된 WAS에는 사용자 정보가 세션이 존재하지 않기 때문에 다시 로그인 폼이 떠야한다.  어떻게 된 것 일까? 확인해보자.

 Redis 서버가 설치된 리눅스로 접속하여 Redis cli를 실행하자.
그리고 Keys 명령어를 이용해서 현재 저장된 Key 정보를 확인해 보자.

처음 발급된 session ID를 키로 가지고 있는 session 데이터가 저장되어 있는 것을 확인할 수 있다.



원리는 간단하다. 

Web.xml에서 Filter로 선언 된 DelegatingFilterProxy를 통해서 WAS 쪽에서 Session 정보를 가져올 때 먼저 Redis 서버에 세션 정보가 들어 있는 지 확인하는 것이다.

 앞서 설명한 Cache가 동작하는 방식을 생각하면 이해가 편할 것이다.

기존의 세션 컨트롤과 동일하게 세션에 정보가 요청될 때마다 세션 Expire 시간이 초기화되며 Expire 대기 시간도 별도로 설정이 가능하다.

 여러 개의 WAS를 띄워놓고 세션 정보를 redis를 통해 공유할 수 있기 때문에 HAproxy와 같은 로드 밸런서를 이용한다면 클라우드 환경과 비슷하게 운용할 수 있다.

소스코드는 아래의 주소에서 확인할 수 있다.
https://github.com/wargen99/wargen-repo

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를 이용하여 분산환경에서 일관된 세션 유지 방법에 대해 포스팅한다.

2015년 11월 19일 목요일

Spring에서 Redis 서버 설정

이번 포스팅에서는 Spring 에서 Redis를 이용하기 위한 설정에 대해서 설명한다.

 Redis 설치는 아래의 사이트를 참고하자.

레디스 공식 홈 : http://redis.io/download

Redis를 처음 설치하면 redis cli를 사용하기 위해서 bash_profile에 다음과 같이 등록해주자.




제대로 설치가 됐는지 확인하기 위해 먼저 Redis 서버를 실행하자.

실행명령은 다음과 같다.
[solrslave@localhost redis-3.0.3]$ redis-server 




위와 같이 스탠드 얼론 모드로 실행됐다는 메시지가 나오면 정상적으로 실행된 것이다.

Cli 인터페이스를 이용하여 접속해보자.
(별도의 conf 파일을 지정하지 않으면 6379 포트가 기본 포트)

[solrslave@localhost redis-3.0.3]$ redis-cli -p 8000 

정상적으로 접속되면 문제 없이 설치된 것이다.

Spring Tool Suite 도구를 열고 Spring MVC로 간단한 Project를 생성한다. 


Maven Pom 파일에 아래의 라이브러리를 추가

  <dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.4.0</version>
  </dependency>
  <!-- spring data for jedis -->
  <dependency>
   <groupId>org.springframework.data</groupId>
   <artifactId>spring-data-redis</artifactId>
   <version>1.2.1.RELEASE</version>
  </dependency>

 
Redis xml configuration 파일을 추가
<!-- JDBC 템플릿 설정과 유사하다. -->
 <bean id="connectionFactory"
  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  <property name="hostName" value="192.168.0.105" />
  <property name="port" value="8000" />
  <property name="poolConfig" ref="jedisPoolConfig"></property>
 </bean>
 <!-- 여기서는 일반적인 RedisTemplate을 사용한다. key value가 String 인 경우 String Redis Template을 사용하자. -->
 <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
  <property name="connectionFactory" ref="connectionFactory" />
 </bean>
 <!-- 기존의 database connection 관련 Factory 클래스와 유사하게 connection pool 이 내부에 구현되어 있다. 참고하자. -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
     <property name="maxTotal" value="20"></property>
    </bean>

이제 Spring에서 Redis를 사용할 준비가 완료되었다.

다음 포스팅에서 사용방법을 확인해보자.



2015년 11월 18일 수요일

Spring AOP + AspectJ annotation + Redis 샘플 #2

이번 포스팅에서는 예제에서 사용한 Redis 설정에 대해서 기술한다.
<?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:util="http://www.springframework.org/schema/util"
 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
  http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd">

 <bean id="connectionFactory"
  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
  <property name="hostName" value="#{redisProp['hostName']}" />
  <property name="port" value="#{redisProp['port']}" />
  <property name="poolConfig" ref="jedisPoolConfig"></property>
 </bean>
 <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
  <property name="connectionFactory" ref="connectionFactory" />
 </bean>

 <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
  <property name="maxTotal" value="20"></property>
 </bean>
 <context:component-scan base-package="com.example.redis" />
 <util:properties id="redisProp" location="classpath:/properties.xml" >
 </util:properties>
</beans>

Util: properties 와 SpEL 사용방법
http://secondmemory.kr/271
Resources경로에 아래의 xml 프로퍼티를 추가하자.



 192.168.0.105
 8000

Method 정보를 저장하는 Dto 생성
package com.example.redis;

public class RankDto {
 private int rank;
 private String methodName;
 private int callCount;

 public int getRank() {
  return this.rank;
 }

 public void setRank(int rank) {
  this.rank = rank;
 }

 public String getMethodName() {
  return this.methodName;
 }

 public void setMethodName(String methodName) {
  this.methodName = methodName;
 }

 public int getCallCount() {
  return this.callCount;
 }

 public void setCallCount(int callCount) {
  this.callCount = callCount;
 }
}

호출되는 함수 Dto를 저장할 Repository 인터페이스
public abstract interface ActionHistoryRepo<Key, Value> {
 public abstract void addAction(Value paramValue);

 public abstract List<RankDto> getRank();

 public abstract void deleteAction(Key paramKey);
}

Repository 인터페이스의 구현 클래스
이 예제에서는 실행되는 함수의 실행 횟수를 저장하고 통계를 내기 위해서 Redis Zset 타입을 이용하여 구현한다.
ZSet 사용법은 아래의 사이트를 참고하자
http://www.tutorialspoint.com/redis/redis_sorted_sets.htm
@Repository("actionHistory")
public class HistorySaver implements ActionHistoryRepo<String, String> {
       //Redis ZSet을 사용하기 위한 고정키
 private final String redisKey = "MethodRank";
 
 @Autowired
 private RedisTemplate<String, String> stringRedisTemplate;

 public void addAction(String value) {
  int score = 1;
     // 개체가 없으면 자동으로 생성 한 후 스코어에 +1         
  this.stringRedisTemplate.opsForZSet().incrementScore("MethodRank",
    value, score);
 }

 public List<RankDto> getRank() {
                //Range를 사용하면 내림차순이 기본으로 설정된다. Reverse Range를 사용하여 오름차순 순서로 뽑아내자.
  Set<String> rankset = this.stringRedisTemplate.opsForZSet()
    .reverseRange(redisKey , 0L, -1L);
  List<RankDto> rankList = new ArrayList();

  Iterator<String> iters = rankset.iterator();
  int ranky = 1;
  while (iters.hasNext()) {
   RankDto dto = new RankDto();
   dto.setMethodName((String) iters.next());

   Double rank = this.stringRedisTemplate.opsForZSet().score(
     redisKey , dto.getMethodName());
   dto.setCallCount(rank.intValue());
   dto.setRank(ranky);
   rankList.add(dto);
   ranky++;
  }
  return rankList;
 }

 public void deleteAction(String key) {
  this.stringRedisTemplate.delete(key);
 }
}
구현된 결과물을 Tomcat was를 이용해서 실행해 보자.
http://localhost:8080/aop/act1

http://localhost:8080/aop/rank

소스코드 주소는 다음과 같다.
https://github.com/wargen99/wargen-repo.git

아주 간단하게 AOP를 이용하여 사이트 내의 서비스 메소드가 호출되는 통계를 저장하는 예제를 만들어 보았다.
아주 기초적인 예제지만 이를 응용하여 다양한 관점에서 데이터를 저장하고 이를 재활용할 수 있는 점을 확인하면 좋을 듯 하다.