2013년 4월 26일 금요일

Spring과 MongoDB


이마 다 알고 있는 사실이겠지만, 대용량 Data 입력 작업에 탁월한 mongoDB을 실제 프로젝트에 적용하는 사례가 많아 지고 있다.
이번 글에서는 국민 대표 프래임워크 Spring에 mongoDB을 사용하는 방법을 살펴보도록 하겠다.

Spring-Data-MongoDB
Spring에서는 MongoDB와 연동을 위한 Connector을 제공한다. Dependency을 추가하면 mogo-java-driver도 같이 추가된다.
<!-- MongoDB for Spring -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.2.1.RELEASE</version>
</dependency>


Spring Framework
여기서 설명하는 예제는 Spring webmvc을 기반으로 RESTFul API을 만들어봤다.

jdbc.properties
mongoDB 설치 정보를 포함하는 환경설정 파일(파일 이름을 다른 걸로 할 것을...)
mongo.host = localhost
mongo.port = 27017
mongo.db.name = expense

Spring Config
mongoDB관련 Namespace을 추가 해준다.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">

<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>


dataSource 설정 처럼 mongoDB Connection 설정 부분이다. 각 항목은 딱 보면 딱 알겠죠?
<mongo:mongo host="${mongo.host}" port="${mongo.port}">
<mongo:options connections-per-host="8"
threads-allowed-to-block-for-connection-multiplier="4"
connect-timeout="1000"
max-wait-time="1500"
auto-connect-retry="true"
socket-keep-alive="true"
socket-timeout="1500"
slave-ok="true"
write-number="1"
write-timeout="0"
write-fsync="true"/>
</mongo:mongo>

SQLTemplate와 유사한 mongoTemplate 설정 부분입니다.
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongo" />
<constructor-arg name="databaseName" value="${mongo.db.name}" />
</bean>

코드 살펴 보기
Data VO = User, "만인의 예제" 사용자 관리에 사용되는 Value Object 입니다.
mongoDB의 Document와 매핑되는 VO입니다.

public class User {

private long id;
private String userName;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}


UserDAO
  • 위에서 설정한 “mongoTemplate”을 사용하게 됩니다.
  • User Document가 저장될 Collection을 지정하게 됩니다.

@Repository
public class UserDAOImpl implements UserDAO  {

@Autowired
MongoTemplate mongoTemplate;

private static String COLLECTION_NAME = "testcollect";

@Override
public User insert(User user) {
mongoTemplate.insert(user, COLLECTION_NAME);
return user;
}

@Override
public User getUser(User user) {
return mongoTemplate.findById(user.getId(), User.class, COLLECTION_NAME);
}

@Override
public List<User> getUsers() {
return (List<User>) mongoTemplate.findAll(User.class, COLLECTION_NAME);
}

@Override
public void deleteUser(User user) {
Query query = new Query(new Criteria("id").is(user.getId()));
mongoTemplate.remove(query, COLLECTION_NAME);
}

@Override
public User updateUser(User user) {
Query query = new Query(new Criteria("id").is(user.getId()));

Update update = new Update();
update.set("userName", user.getUserName());
update.set("password", user.getPassword());

mongoTemplate.updateFirst(query, update, COLLECTION_NAME);

return user;
}
}


UserService
이 부분 부터는 MongoDB 특성이 없는 코드 입니다.
@Service
@Transactional
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);

@Autowired
private UserDAO userDAO;

public void insertUser(User user) {
logger.info("userServiceImple.insertUser >>>" + user);
userDAO.insert(user);
}

public List<User> getUsers() throws Exception {
return userDAO.getUsers();
}


public User getUser(User user) throws Exception {
logger.info("userServiceImple.getUser >>>" + user);
return (User) userDAO.getUser(user);
}


public boolean deleteUser(User user) throws Exception {
logger.info("userServiceImple.delUser >>>" + user);
try {
userDAO.deleteUser(user);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}

public User updateUser(User user) throws Exception {
logger.info("userServiceImple.updateUser >>>" + user);

return userDAO.updateUser(user);

}
}

MongoController
RESTFul Controller 부분입니다.

@Controller
public class MongoController {

@Autowired
private UserService userService;

@RequestMapping(value = "/mongo/addUser",method = RequestMethod.POST)
public @ResponseBody ResponseVO addUser(HttpServletRequest request) {
ResponseVO response = null;
Map<String, String> params = getParameterMap(request);
long id = 0;
try {
id = Long.parseLong(params.get("id"));
} catch (NumberFormatException e1) {
response = new ResponseVO(ResponseHeadVO.INVALID_REQUEST,"'ID'는 null이거나 문자형일수 없습니다.",null);
return response;
}
String userName = params.get("username");
String password = params.get("password");

if(userName == null || password == null || userName.equals("") || password.equals("")) {
response = new ResponseVO(ResponseHeadVO.INVALID_REQUEST,"전송된 파라미터 중 하나 이상이 공백이거나 null 입니다.",null);
return response;
}
try {
printParams(request, params);
User user = new User();
user.setId(id);
user.setUserName(userName);
user.setPassword(password);
userService.insertUser(user);
BooleanVO isOK = new BooleanVO();
isOK.setOK(true);

response = new ResponseVO(ResponseHeadVO.SUCCESS, "", isOK);
} catch (Exception e) {
logger.error(this.getClass().getSimpleName(), e);
response = new ResponseVO(ResponseHeadVO.SERVER_ERROR,e.getMessage(),null);
}
return response;
}
@RequestMapping(value = "/mongo/getUsers",method = RequestMethod.POST)
public @ResponseBody ResponseVO getUsers(HttpServletRequest request) {
ResponseVO response = null;
Map<String, String> params = getParameterMap(request);
try {
printParams(request, params);
List<User> users = userService.getUsers();
ListVO<User> userList = new ListVO<User>();
userList.setDataList(users);
userList.setTotalCount(users.size());;

response = new ResponseVO(ResponseHeadVO.SUCCESS, "", userList);
} catch (Exception e) {
logger.error(this.getClass().getSimpleName(), e);
response = new ResponseVO(ResponseHeadVO.SERVER_ERROR,e.getMessage(),null);
}
return response;
}

}

설명보다는 코드를 직접 보시는게 더 도움이 될 것 같습니다. 소스는 아래에.

2013년 4월 16일 화요일

Embedded jetty Server에서 Jersey 사용하기기

최근 WebService는 Mobile 단말기와 서버와의 API는 HTTP 기반의 Restful Webservice을 사용하는게 표준화되어 있다. 하지만 HTTP을 기반으로 API을 제공하기 위해서는 어떤 형태로든 WAS을 기반으로 제공되게 되어 있다. 기존에 TCP 형태의 API을 제공하는 서버가 있다고 할때 최소 비용(시간과 노력)으로 Restful을 제공할 수 있는 방법을 제안하도록 하겠다.

"Jetty"의 특징은 Tomcat처럼 독립실행도 하지만, 코드 레벨로 WAS기능을 Embedded 시킬수 있다는게 가장큰 특징이다.

JAX-RS(JSR 311)을 완벽하게 구현시킨 오픈소스 RESTFul Webservice 라이브러리다.
보통 Spring RESTful을 많이 사용했겠지만, 이식성 및 표준 준수 여부 생각해서 Jersey을 선택했다.

Jetty Server in Maven
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-webapp</artifactId>
</dependency>
Jersey in Maven
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.17.1</version>
</dependency>

<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>1.17.1</version>
</dependency>

<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-bundle</artifactId>
<version>1.17.1</version>
</dependency>

Jetty Server 코드 레벨에서 실행 하기
Jetty Server을 코드 레벨에서 실행하는 방법은 여러가지 입니다.
사용자가 만든 Servlet을 등록하여 사용하는 방법, war형태로 배포하는 방법 등 많지만 여기서는 Jersey 사용을 위한 Servlet 등록 및 초기파라미터 지정하는 방법만 살펴 봅니다.

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import com.sun.jersey.spi.container.servlet.ServletContainer;
...

protected Server createJettyServer() {
   Server server = new Server(8888);
   
   ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
   context.setContextPath("/openapi");
   server.setHandler(context);
   ServletHolder holder = new ServletHolder(ServletContainer.class);
holder.setInitParameter("com.sun.jersey.config.property.resourceConfigClass", "com.sun.jersey.api.core.PackagesResourceConfig");
holder.setInitParameter("com.sun.jersey.config.property.packages", "com.mypackage");
holder.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
   context.addServlet(holder, "/*");
   
   return server;
}


public void start() throws IOException {
   server = createJettyServer();
   try {
       server.start();
       server.join();
   } catch (Exception e) {
       throw new RuntimeException(e);
   }
}
http://localshot:8888/openapi 로 Jersey Servlet이 등록 되었습니다.
com.mypackage” --> jersey openAPI Resource가 있는 패키지 위치
"com.sun.jersey.api.json.POJOMappingFeature"을 꼭 해줘야 요청에 대하여 POJO Object을 Json으로 자동 변경해줍니다.

Jersey을 이용한 OpenAPI 만들기
위에서 정의한 “com.mypackage”에 OpenAPI Resource을 만듭니다.

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.hs.uc.protocol.http.vo.UserVO;

@Path("/user")
public class UserCertificateResource {

@POST
@Path("/hello")
@Produces({ "text/plain" })
public String seyHello() {
return "Hello UC";
}

@GET
@Path("/getUser")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public UserVO getUserJson() {
UserVO user = new UserVO();
user.setName("HandyUC User");
user.setAge(18);
return user;
}

@GET
@Path("/getUser")
@Consumes({ MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_XML })
public UserVO getUserXML() {
UserVO user = new UserVO();
user.setName("HandyUC User");
user.setAge(18);
return user;
}

@POST
@Path("/setUser")
@Produces({ MediaType.APPLICATION_JSON })
public UserVO setUser(@FormParam("name") String name,
@FormParam("age") int age) {
UserVO user = new UserVO();
user.setName(name);
user.setAge(age);
return user;
}
}

Pojo class: UserVO

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement --> 이걸 꼭해줘야 XML로도 변경됩니다.
public class UserVO {

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

private String name;
private int age;

public UserVO() {

}
}

Rest Client로 테스트 해보기
위의 Jetty 서버 코드가 있는 부분을 실행합니다.

그리고 Rest Client로 확인해보면
http://localhost:8888/openapi/user/getUser, GET, Content-Type: application/json
http://localhost:8888/openapi/user/getUser, GET, Content-Type: application/xml

ETL 솔루션 환경

ETL 솔루션 환경 하둡은 대용량 데이터를 값싸고 빠르게 분석할 수 있는 길을 만들어줬다. 통계분석 엔진인 “R”역시 하둡 못지 않게 관심을 받고 있다. 빅데이터 역시 데이터라는 점을 볼때 분산처리와 분석 그 이전에 데이터 품질 등 데이...