원본글 : http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html

 

NOTES: 지금까지 개인적으로 Google Apps를 이용하여 간단한 Application을 개발하고,

배포해서 적용해 봤습니다. 초라하고 엉성하기 그지 없지만, 나름 Google Apps Engine에

대한 생각이 정리되기 시작했습니다. 사실 이거 2009년 말에 모두 번역에서 등록하려고
했는데, 이 데이터 저장 부분은 번역하면서 막히기도 하고, 개념도 이해가 안가, 상당히

망설인 부분 이였습니다. 그래서 직접 몸으로 체험을 해봤습니다.
하지만 여전히 내용이 무척 어렵습니다. 짧은 Java 실력 때문에,
20세기 말에서 부터 지금까지 만들어진 각종 Java 표준을 거의 모르다 보니,
전혀 생뚱맞게도 번역되는 군요. 어느정도의 원리라도 알면 쉽게 번역 될텐데...

그냥 강행 하렵니다!!!!!!

 

확장성을 갖춘 웹 응용프로그램에서 데이터를 저장하는 방법은 약간의 편법이 요구 됩니다. 사용자가 요청한 작업을 처리하기 위한 웹 서버가 여러 대 인 경우, 그 사용자의 다음 요청에 대해서도 정상적인 처리 될 수 있도록 이전 요청 내용에 대해서 모든 웹 서버들이 알고 있어야 한다는 점입니다. 이 때문에 물리적으로 완전 분리되어 다른 지역에 있는 웹 서버일지라도 이 사용자 정보를 모두 공유해서 보유 해야 될 것입니다.

 

Google Apps Engine에서는 이런 분산 환경에 대한 문제에 대해 여러분이 걱정하지 않도록 도와드립니다. App Engine의 인프라 스트럭처에서는 단순한 API 뒤에서 동작되는 분산 환경의 관리, 데이터 복제 및 부하 분산까지 고려되어 제공됩니다. 물론 단순한 API라고 해도 강력한 query 엔진과 트랜젝션 관리를 처리할 수 있도록 도와드립니다.

 

Apps Engine의 데이터 저장은 두 가지 유형의 API를 통해 각종 저장 관련 서비스를 제공합니다. 그 API의 두 가지 유형에는 표준형 API와 저 수준(low-level) API가 있습니다. 
표준형 API를 사용한다면, 실제 구현한 내용 그대로, 다른 데이터베이스 기술과 호스팅 환경으로도 쉽게 이식 적용이 가능합니다. 표준 API는 App Engine서비스와 여러분의 응용 프로그램 간의 의존성을 최대한 낮추도록 설계되어 있습니다. 
물론 Apps Engine 에서 제공되는 서비스를 직접 활용할 수 있도록 저수준(low-level) API를 제공하고 있습니다. 이 저수준 API를 사용하여 새로운 아답터 인터페이스를 별도로 구현할 수 있으며, 직접 응용 프로그램 내에서 사용할 수도 있습니다.

 

App Engine은 두 가지의 다른 데이터 저장 표준 - Java Data Object(JDO)와 Java Persistence API(JPA) - 서비스를 포함하고 있습니다. 이들 인터페이스는 DataNucleus Access Platform을 기반으로 구성되어 있습니다. 이 DataNucleus Access Platform은 다양하게 있는  Java persistence 표준들을 구현한 오픈 소스로써 App Engine 저장소에서는 이 플랫폼에 대한 아답터를 사용하고 있습니다.

 

방명록에서는 JDO 인터페이스를 사용하여 사용자가 남긴 메시지를 다시 불러올 수 있도록 구성할 것입니다.

 

DataNucleus Access Platform 구성하기.

Access Platform에는 JDO 구현에 대한 배경을 App Engine 저장소로 사용한다고 알려주기 위한 설정 파일이 필요합니다. 이 설정 내용은 최종 WAR 안에, war/WEB-INF/META-INF 디렉터리 안에 jdconfig.xml이라는 이름의 파일이 있어야 합니다. 

 

이클립스를 사용하신다면, 프로젝트 탐색기에서 src/META-INF 안 에 jdconfig.xml 파일을 생성해주시면 됩니다.

 

jdconfig.xml 파일에는 다음과 같은 내용이 담기면 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
    </persistence-manager-factory>
</jdoconfig>

 

JDO Class 확장하기.

JDO 클래스를 만들 때, 어떻게 데이터가 저장되어 구현될지, 데이터를 조회할 때 다시 인스턴스를 만들지에 대하여 Java의 문법을 활용하여 구성할 수 있습니다. Access Platform에서 데이터 클래스와 컴파일 후 처리되는 단계의 구현물 간의 연결을 수행할 때 그 DataNucleus 를 "확장된" 클래스라고 부르게 됩니다.

 

이클립스와 Google Plugin을 사용하게 되면, 플러그인에서 JDO 클래스에 대한 확장 작업을 빌드 단계 때 자동으로 수행하게 됩니다. 이클립스가 없다면...  Ant를 사용하여 하게 되는데, 그 내용은 원본 문서를 활용해 주세요.

 

JDO 클래스의 확장에 대한 더 자세한 내용은 JDO를 사용하기(Using JDO)를 참고하시기 바랍니다.

 

POJOs 와 JDO 문법

DataNucleus Access Platform과 같은 JDO호환 아답터로 Java 객체(대부분 Plan Old Java Objects 또는 POJOs 라고 불린다.)를 JDO로 저장할 수 있습니다. App Engine SDK에는 App Engine 저장소를 위한 Access Platform 플러그 인이 포함되어 있습니다. 즉, App Engine 데이터 저장소에 정의하려는 내용을 클래스 형태로 구현하여 저장 할 수 있다는 것입니다.  마찬가지로 JDO API를 사용하여 객체를 통해 데이터를 가져올 수도 있다는 것입니다. Java 문법을 사용하여 만든 클래스의 인스턴스를 저장하거나 재 생성하는 방법만 JDO에 전달 할 수 있으면 되는 것입니다.

 

방명록에 올린 개별 메시지를 표시 할 때 사용될 Greeting 클래스를 만들어보도록 하죠.

 

일단 guestbook 패키지 안에 Greeting 이라는 클래스를 생성하시기 바랍니다. 이 클래스 안에 채울 내용은 다음과 같습니다.

package guestbook;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Greeting {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private User author;

    @Persistent
    private String content;

    @Persistent
    private Date date;

    public Greeting(User author, String content, Date date) {
        this.author = author;
        this.content = content;
        this.date = date;
    }

    public Key getKey() {
        return key;
    }

    public User getAuthor() {
        return author;
    }

    public String getContent() {
        return content;
    }

    public Date getDate() {
        return date;
    }

    public void setAuthor(User author) {
        this.author = author;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

 

인사말을 저장하기 위한 3가지의 간단한 속성(author:작성자, content:내용, date:작성일)을 클래스 안에 정의했습니다. 이 세가지 private 필드들은 각각 @Persistence 라는 문법을 추가하여 App Engine 저장소에 저장될 항목들임을 알려주게 됩니다.

 

이 클래스에는 이 속성 값을 가져오고 넣을 수 있는 인터페이스도 만들어서 넣었습니다. 하지만 이것은 응용프로그램 내에서 사용하는 내용일 뿐 JDO에서는 사용하지 않습니다. ( 즉 이름이 조금 틀려도 상관 없다는 의미입니다.)

 

여기서 주목해서 봐주시면 하는 것이 바로 key 라는 필드 입니다. key에서는 @Persistence 뿐만 아니라 @PrimaryKey라는 Java 문법이 추가되어 있습니다. App Engine 저장소에는 사용되는 각 Entity(레코드)를 고유하게 구분하기 위한 키라는 개념이 있는데, 이 키를 표현하는 방법이 객체에 따라 여러 가지 다른 방법들이 있습니다. App Engine 저장소에서 각종 키들을 표현하는 모든 방법은 Key 라는 클래스에 정의되어 있습니다. 방법 중에서 일반적으로 사용되는 방법은 객체가 저장 될 때 유일한 값을 자동적으로 정의해서 넣는 숫자형 ID를 주로 사용됩니다.

 

JDO 문법에 대하여 더 자세한 내용은 Data Classes를 정의하는 방법(Defining Data Classes)를 참고하시기 바랍니다. 

 

PersistenceManagerFactory에 대해

위와 같은 구조에서는 요청이 들어올 때 마다 저장소는 PersistenceManager 클래스의 새 인스턴스를 계속 생성하게 됩니다. 이를 위해서 PersistenceManagerFactory 클래스를 만들어 사용하면 더 간단하게 구성하실 수 있습니다. PersistenceManagerFactory 인스턴스는 초기화 될 때 만들어지게 됩니다. 다행히도, 응용 프로그램 내에서는 단 한 개의 인스턴스만 있으면 되기 때문에, 여러 클래스들과 여러 번의 요청에서도 사용될 수 있는 정적 변수 하나에 저장되어 있으면 됩니다. 이런 정적 인스턴스를 들고 있을 싱글톤 형태의 클래스만 생성하면  간단하게 처리됩니다.

 

guestbook 패키지 안에 PMF라는 이름의 새로운 클래스를 생성한 뒤(src/guestbook 디렉터리 안에 PMF.java라는 파일을 만들면 됨), 다음과 같은 내용을 넣어주시면 됩니다.

package guestbook;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

 

개체를 생성하고 저장하기.

Greeting 클래스에 적절한 위치에, DataNucleus를 사용하여 저장소에 새로운 인사말을 저장하기 위한 처리 로직을 구성하면 됩니다.  

 

일단 src/guestbook/SignGuestbookServlet.java 파일을 열어 다음과 같은 내용처럼 재구성해주시면 됩니다.

package guestbook;

import java.io.IOException;
import java.util.Date;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import guestbook.Greeting;
import guestbook.PMF;


public class SignGuestbookServlet extends HttpServlet {
    private static final Logger log = Logger.getLogger(SignGuestbookServlet.class.getName());

    public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        String content = req.getParameter("content");
       
Date date = new Date();
        Greeting greeting = new Greeting(user, content, date);

        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(greeting);
        } finally {
            pm.close();
        }


        resp.sendRedirect("/guestbook.jsp");
    }
}

(굵은 글자의 표시가 불분명하여 밑줄을 추가했습니다.)

이 코드는 생성자를 호출하여 Greeting 클래스의 새로운 인스턴스를 만들 때 데이터를 채우게 됩니다. 그리고 저장소에 이 인스턴스의 내용을 저장하기 위해 PersistenceManagerFactory를 통해 PersistenceManager를 새로 생성 한 뒤, 데이터 인스턴스를 PersistenceManager의 makePersistence() 메소드에 넘기게 됩니다. 이제 클래스에서 정의된 Java 규칙에 맞게 바이너리 확장이 되고 데이터가 저장소로 전달되게 됩니다. makePersistent()를 갔다오게 되면 저장소에 데이터 내용이 저장되게 됩니다.

 

JDOQL로 데이터 조회하기.

JDO 표준에는  persistent 객체를 조회할 때 사용하는 매커니즘이 정의되어 있습니다. 이 매커니즘을 보통 JDOQL이라고 부릅니다. App Engine 저장소에 있는 entity들을 조회할 때 이 JDOQL을 사용하게 되면, JDO-확장 객체 형태의 결과 값을 돌려주게 됩니다.

여기서는 그 사용 예제를  guestbook.jsp에서 직접 쿼리 코드를 직접 다루어 처리하는 형태로 구성할 예정입니다. 하지만 일반적인 형태로 만들 경우에는 가급적 데이터 처리용 대리자 클래스를 별도로 구성하여 사용하는 것이 좋습니다.

일단 war/guestbook.jsp를 연 뒤에 다음 코드 형태대로 수정하시기 바랍니다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="javax.jdo.PersistenceManager" %>

<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ page import="guestbook.Greeting" %>
<%@ page import="guestbook.PMF" %>


<html>
  <body>

<%
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null) {
%>
<p>Hello, <%= user.getNickname() %>! (You can
<a href="<%= userService.createLogoutURL(request.getRequestURI()) %>">sign out</a>.)</p>
<%
    } else {
%>
<p>Hello!
<a href="<%= userService.createLoginURL(request.getRequestURI()) %>">Sign in</a>
to include your name with greetings you post.</p>
<%
    }
%>

<%
    PersistenceManager pm = PMF.get().getPersistenceManager();
    String query = "select from " + Greeting.class.getName();
    List<Greeting> greetings = (List<Greeting>) pm.newQuery(query).execute();
    if (greetings.isEmpty()) {
%>
<p>The guestbook has no messages.</p>
<%
    } else {
        for (Greeting g : greetings) {
            if (g.getAuthor() == null) {
%>
<p>An anonymous person wrote:</p>
<%
            } else {
%>
<p><b><%= g.getAuthor().getNickname() %></b> wrote:</p>
<%
            }
%>
<blockquote><%= g.getContent() %></blockquote>
<%
        }
    }
    pm.close();
%>


    <form action="/sign" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Post Greeting" /></div>
    </form>

  </body>
</html>

쿼리를 준비하기 위해 먼저 PersistenceManager 인스턴스의 newQuery() 메소드를 호출합니다. 이 메소드에 실제로 쿼리로 사용할 Text 내용을 건네 주게 됩니다. 이 메소드의 실행 결과 값은 쿼리에 대한 개체 입니다. 쿼리 개체의 execute() 메소드를 호출하게 되면 정의한 쿼리 내용대로 실행하게 되며, 그 결과 값은 각 유형에 맞는 결과 개체를 List<>로 돌려주게 됩니다. 쿼리 문장에는 쿼리를 할 클래스의 전체 이름 - 패키지 이름까지 포함한 - 을 반드시 넣어주어야 합니다.

 

프로젝트를 다시 빌드한 뒤, 서버를 재시작합니다. http://localhost:8080/ 에 들어갑니다. 인사말을 넣고 submit을 하도록 합니다. 폼 위에는 인사말이 표시될 것입니다. 계속 각각 다른 인사말을 넣고 submit을 하도록 합니다. 그러면 입력했던 인사말들이 계속 표시됩니다. 이제는 로그인 했을 때 다른 인사말을 submit 해보고, 로그아웃 했을 때 다시 인사말을 넣고 submit을 해보도록 하세요.

 

Tip: 실제 사용되는 응용 프로그램에서는 HTML용 문자들(태그 등등)에 대해 Escape 처리하는 것이 좋습니다. JavaServer Pages Standard Tag Library(JSTL)에는 이런 Escape 처리를 위한 도구들을 제공합니다. App Engine에는 JSTL(및 기타 JSP 관련 런타임 JAR들)을 포함하고 있어, 이런 Escape 처리를 위해 별도로 관련 JAR들을 포함시킬 필요는 없습니다.  escapeXml 함수에 대한 자세한 설명은 태그 라이브러리http://java.sun.com/jsp/jstl/functions 에서 보시기 바랍니다.

더 자세한 내용은 Sun의 J2EE 1.4 튜토리얼을 참고하시기 바랍니다.

 

JDOQL 기초

방명록 예제에서는 시스템 내에 등록된 모든 메시지들을 보여주고 있습니다.이 메시지들이 표시되는 순서는 알 수 없는 순서로 나열되고 있는데, 이를 메시지가 생성된 순서대로 나열하도록 하겠습니다. 또 너무 많은 메시지가 저장되어 있을 때, 최신의 메시지가 맨 위에 오도록 하는 것이 좋을 것입니다. 이런 조정을 쿼리를 수정함으로써 간단하게 처리가 가능합니다.

SQL과 유사하게 생긴 쿼리 문법인 JDOQL을 이용하여 JDO 인터페이스에 처리를 요청하여 원하는 데이터 개체들을 조회하는 것입니다. 앞서 편집한 JSP 페이지의 코드 내용을 보시면 다음 내용과 같은 부분을 보실 수 있습니다.

String query = "select from " + Greeting.class.getName();

이 내용을 실행하면 query라는 변수 안에 다음과 같이 담기게 됩니다.

select from guestbook.Greeting

즉 이 쿼리 문장을 통해 조회하게 되면 Greeting 클래스를 이용하여 저장한 모든 데이터를 가져오게 됩니다.

결과물로 돌려줄 객체의 속성값을 제한 값으로 쿼리에 포함하게 되면, 결과 값을 제한시켜 받을 수 있게 됩니다. 예를 들면 Greeting 객체에 있는 author라는 속성 값이 alfred@example.com 인 개체만을 가져오려면 다음과 같이 쿼리를 만들면 됩니다.

select from guestbook.Greeting where author == 'alfred@example.com'

또 특정 속성값을 기준으로 순서에 맞게 정렬 할 수도 있습니다. 최신글을 맨위로 해서 정렬을 한다고 할 때는 다음과 같이 쿼리를 만들면 됩니다.

select from guestbook.Greeting order by date desc

또 쿼리 결과물 갯수를 제한할 수 있습니다. 만일 최신 메시지 5개까지만 가져오도록 하고 싶다면, order by 와 range를 동시에 써서 표현 하면 됩니다. 그 쿼리는 다음과 같습니다.

select from guestbook.Greeting order by date desc range 0,5

guestbook.jsp 안의 쿼리를 수정하여 위에서 언급한 대로 최근 메시지 5개만 뿌리도록 설정하도록 하죠.

그러려면 guestbook.jsp 안의 쿼리 부분을 아래와 같이 수정하시면 됩니다.

String query = "select from " + Greeting.class.getName() + " order by date desc range 0,5";

그러면 가장 최근에 등록된 인사말 중 5개까지만 나타나게 될 것입니다.

JDOQL에 대한 더 자세한 내용은 Query와 Index를 참고하시기 바랍니다.

 

다음은...

모든 웹 응용 프로그램들은 템플릿이나 기타 매커니즘을 통해 동적으로 생성된 HTML을 돌려주게 됩니다.하지만 여전히 많은 웹 응용 프로그램들은 추가적으로 정적 컨텐츠 - 이미지, CSS, JavaScript 파일 등등 - 역시 같이 제공되고 있습니다. 이 때 응용 프로그램 구성을 보다 효과적으로 하기 위해서는 이러한 정적 파일과 응용프로그램은 명확하게 구분하는 것이 좋습니다.

다음 내용에서는 CSS 시트를 생성하여 응용 프로그램과 분리하여 구성하는 방법을 소개해 드리겠습니다.

 

 

정적 파일 사용하기에서 계속 됩니다.

728x90

+ Recent posts