2010년 5월 31일 월요일

Java Decompiler jad

C와 같은 언어는 소스파일을 컴파일하면 해당 시스템에 적합한 바이너리 코드를 생성합니다.
바이너리 코드를 역컴파일 하는 것이 불가능 한것은 아니나, 이것은 지루하고 복잡한 작업입니다.
흔히들 이와같은 작업을 역공학(리버스 엔지니어링)이라 하며, 소프트웨어 보안과 라이센스 정책에 크게 위협이 될수 있는 분야이기도 합니다.

Java의 경우 C와 같은 언어와 달리 바이트 형태의 class파일을 생성하는데 'Write Once, Run Anywhere"라는 Java의 패러다임에서 알수 있듯이, JavaVM이 존재하는 모든 시스템에서 실행될수 있는 시스템 독립적인 코드입니다.
따라서, 바이트 코드와 같은 경우는 바이너리 코드와 달리 손쉽게 역컴파일이 가능합니다.

jad는 바로 Java의 class파일을 디컴파일 해주는 프로그램입니다.
즉, java소스 파일은 없고 class파일만 존재하고 있을때, jad를 이용해 class파일을 java파일로 변환할 수 있습니다.


jad를 Eclipse IDE에 통합하여 편리하게 사용할수 있게 도와주는 plugin이 있습니다.
(Eclipse 3.4에 jadclipse3.3 설치가 가능합니다.)



1. 설치하기

아래 그림과 같이 다운로드 받은 jad파일을 Eclipse디렉토리에 복사합니다.


JadClipse파일(net.sf.jadclipse_3.3.0.jar)을 Eclipse plugins디렉토리에 복사합니다.


Eclipse를 재식하고 Window > Preference > Java > JadClipse에서 아래 그림과 같이 Path to decompiler을 입력합니다.


한글이 깨지는 것은 방지하기 위해 아래 그림과 같이 마지막 항목을 체크합니다.




2. class파일 디컴파일

디컴파일하고자 하는 class파일을 더블클릭하면, 아래 그림과 같이 디컴파일된 java파일이 나타납니다.



3. 난독처리
Java 프로그램은 컴파일된 코드만 배포한다고 하여도(바이트 코드) 이로부터 소스코드를 쉽게 만들어 낼 수 있으므로 소프트웨어 보호라는 측면에서 많은 문제점을 가지고 있습니다. 이와 같은 위험으로부터 소스코드를 보호하기 위해 소스코드를 난독화 할수 있습니다. 난독화는 는프로그램 코드를 변환하는 방법의 일종으로, 프로그램에 사용된 변수명을 아무 의미없는 이름으로 변환하는 등 코드를 지저분하게 하여 사람이 읽기 어렵게 만들어주는 기술입니다.

* Java 역컴파일 방지 툴: http://proguard.sourceforge.net/

[오라클]SGA/PGA 개념

SGA Memory = Shared Pool + Data Buffer Cache + Redo LogBuffer + Large Pool + Java Pool + Streams Pool

SGA 메모리 구성은 위와 같다. 다수의 사용자가 접속하기에 DISK I/O를 줄여서 그 성능을 높이고자 제공되는 공유메모리인 것이다.
간단하게 구성되는 메모리 구조를 살펴보자

1. Shared Pool
DB에서 사용되는 모든 SQL문을 처리하는 영역

2. Data Buffer Cache
SQL문이 DML일 때 사용되는 영역

3. Redo LogBuffer
로그를 기록하는 영역, DML 문 실행 이전에 백업을 담당한다.

4. Large Pool
DB 백업 및 복원 작업 지원을 위해 사용되는 대용량 메모리 영역

5. Streams Pool
Oracle Streams가 사용하는 영역

6. Java Pool

자바로 작성된 프로그램 사용할 때 실행 계획을 저장하는 영역

PGA Memory = Session Memory + Private SQL


PGA 메모리 구성은 위와 같다. 오라클은 개개의 사용자가 DB 접속 시 Server process를 생성하며, 이 프로세스의 대한 Data와 제어정보를 저장하는 메모리 영역이다.


참고사이트

펼쳐두기..

dynamic,static sql 구분하다

dynamic,static sql 을 구분

 

1.    우선 자신의 작성한 프로그램을 수행 합니다.

 

2.    SELECT hash_value, piece, sql_text
         FROM V$SQLTEXT
       WHERE hash_value IN (SELECT hash_value
                                           FROM V$SQLTEXT
                                         WHERE sql_text like '%프로그램내에기술된 테이블명%'
                                    GROUP BY hash_value)
       GROUP BY hash_value, piece, sql_text

 

3.결과를 보고 조건절에 WHERE a = :a0 라고 되어 있는지 아니면 WHERE a = '1'

  이런식으로 되어 있는지를 확인합니다.

  이때 전자를 Static SQL 후자를 Literal SQL 이라고 합니다.

  전자는 라이브러리 캐쉬를 공유 하므로 라이브러리 캐쉬에 Keep 을 하고 Parsing 하는데

  어려움이 없지만 후자는 매번 메로리 Keep 과 Parsing 을 하게 되므로 성능에 치명 적입니다.

2010년 5월 19일 수요일

프레스토 인코딩

다음 팟인코더


핸드폰

1. 핸드폰과 컴퓨터를 USB로 연결

2. [MENU]-[Settings]-[04 시스템]-[이동 디스크 설정]

3. 이동식 디스크 [Movie]폴더에 복사

 

2010년 5월 18일 화요일

pojo란!!무엇인가!!

Java진영에서 어느날 갑자기 등장하여 개발자들을 모호하게 만들어 버렸던
POJO!!

이 녀석이 당췌 뭐야?
많은 사람들은 그럴싸한 이론으로 POJO를 포장하려 한다.
실제 강의나 책을 통해서 설명되는 POJO는 이해하기 힘듬. ㅜㅜ

본인 또한 처음 POJO란 용어를 접했을때 이게 뭐지?
직역하면
명백히 오래된 자바 객체?

아쒸 명백히 오래된 자바객체가 한두개야?
jdk 1.0 버전때 부터 제공되던 수 많은 클래스들을 통해 생성하는 객체들이 그럼 다 POJO야?


POJO는 2000년 9월에 열린 컨퍼런스(어떤 컨퍼런스인지는 모름)에서
Rebecca Parsons, Josh MacKenzie, Martin Fowler 가 처음 사용한 용어이다.

다른 개념 다 버리고

POJO = Java Beans
여기서 Java Beans는 Sun의 Java Beans나 EJB의 Bean을 뜻하는것이 아닌
순수하게 setter, getter 메소드로 이루어진 Value Object성의 Bean을 말한다.


예를 들면 아래와 같다.

public class SimpleBean {
    private String name;
    private String age;

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

    public void setAge(String age) {
        this.age = age;
    }
    public String getAge() {
        return this.age;
    }

}

우리가 열심히 코딩하거나 이클립스를 통해 자동으로 생성하던
그 깡통 빈 클래스!! 를 통해서 생성된 객체!
맞다 바로 이것이 POJO 다.


그럼 왜 이전 부터 사용하던 Beans라고 말하지 않고
사람들 헤깔리게 POJO 새로이 불렀을까?

이유인즉, Beans라는 용어로 위의 깡통 빈 클래스를 정의하기에는
Java Beans나 EJB의 Beans와 구분이 모호하고 또한 Beans라는 용어로
정의되는 여타 다른 개념들과의 확실한 분리를 위해
POJO라는 용어를 사용한것이라 볼 수 있다.

Java GAE 시작하기下 (Datastore) [펌]

이번엔 시작하기(上)에 이어, 데이터스토어에 대해 정리해 보려고 합니다.

앱 엔진 자바 룩은 썬 마이크로시스템즈의 최고 오픈소스 임원으로부터 한 마디 핀잔을 들은 것 처럼, 자바 6를 지원하기는 하지만 전체 집합을 모두 지원하지는 않습니다.(꼭 핀잔을 들을 일이었는지는 모르겠지만.. 입장 차이겠죠?^^;)

예를 들어 java.io.FileInputStream을 지원하긴 하는데, java.io.FileOutputStream을 지원하진 않습니다. 얼핏 드는 생각으로는 구글 서버에 외부로부터의 파일이 유입되거나 악의적인 코드가 작성되면 골치아프다는 측면에서 빼버린 것 같다는 생각도 듭니다만, 이런저런 이유들이 있겠죠.

그래서 앱 엔진이 제공하는 인프라 자체에 데이터를 누적하는 방법이 상당히 제약적인데, 결국 앱 엔진이 제공하는 데이터스토어(Datastore)에 의지할 수 밖에 없습니다.

몇 가지 슬픈 점을 정리해보면, 우리가 매우 익숙한 create table 같은 DDL로 엔티티를 정의하고 insert into 문장이나 update set 문장으로 데이터를 추가/수정할 수 없습니다.

하지만 기쁜 점이 여전히 남아있기에.. 시도하지 않을 수 없습니다. 무료잖아요. 꽤 많은 저장공간과 꽤 많은 트래픽이 무료.. 무료...!! -ㅅ-;;

도무지 희망찬 일인지 잘 모르겠지만 아무튼 앱 엔진은 데이터스토어의 저수준 API만 제공하는 것이 아니라, JDO(Java Data Objects)와JPA(Java Persistence API) 인터페이스를 함께 제공합니다.

내공 깊은 시스템 프로그래머 같은 분이 아니라면 왠만하면 저수준 말고 제공해주는 도구를 쓰게 되는 게 자연스러운 심리인 것 같습니다.

처음엔 익숙치 않은 JDO 쓰래서 살짝 심술이 났지만, 제공되는 샘플 보니까 OOP 개념을 알고 있으면 쉽게 적용할 수 있는 방식인 것 같아서 마음이 편해졌습니다.

JDO의 세계에서는 create table 같은 액션을 클래스 작성으로 대신할 수 있습니다.

Employee.java
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 Employee {
   
@PrimaryKey
   
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   
private Long id;

   
@Persistent
   
private String firstName;

   
@Persistent
   
private String lastName;

   
@Persistent
   
private Date hireDate;

   
public Employee(String firstName, String lastName, Date hireDate) {
       
this.firstName = firstName;
       
this.lastName = lastName;
       
this.hireDate = hireDate;
   
}

   
// 아래는 필드 접근을 위한 메소드들.  JDO는 이 메소드들을 사용하지 않지만, 우리의 애플리케이션에서 필요해요.

   
public Long getId() {
       
return id;
   
}

   
public String getFirstName() {
       
return firstName;
   
}

   
// ... other accessors...

}

좋은 샘플이죠.~
@PersistenceCapable은 작성 중인 설계가 데이터가 되었을 때 지속범위 정도가 될까요?

IdentityType.APPLICATION은 이 데이터가 구글 앱 엔진 범위 내에서 유효하다는 것을 의미하고 IdentityType.DATASTORE가 구글 앱 엔진 대시보드에서 조회도 가능하고 외부에서 접근할 수 있게(멋대로 추측 중) 하는 범위를 뜻하는 것 같습니다. 저는 .APPLICATION으로만 테스트했고, DATASTORE로 하고 싶은 간절한 소망은 아직 지원하지 않는다고 해서 잠시 마음 한 구석에 접어 두었습니다.

한 번 훑으면서 이해하셨겠지만 @Persistent는 테이블 개념에서 컬럼을 나타내고, @PrimaryKey로 주 키 설정,IdGeneratorStrategy.IDENTITY로 아이덴티티를 설정해줄 수 있는 것을 볼 수 있습니다.

이제 엔티티 선언을 살펴봤으니 자료를 조회하고 쓰는 쪽을 봐야겠죠?

우리는 PersistenceManagerFactory 객체에서 얻을 수 있는 PersistenceManager라는 녀석으로 데이터스토어와 친하게 지낼 수 있습니다. 명세를 자세히 보시면 나오지만 PersistenceManagerFactory는 메모리에 활성화하는데 비용도 크고 애플리케이션 주기 동안 딱 한 번 만들어낼 수 있어서, 이 녀석을 static final로 넣어두고 실제 일을 도와줄 PersistenceManager만 땡겨 씁니다.

PMF.java
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;
   
}
}

자, 말씀드린 내용에 대한 샘플입니다. 필요한 static 메소드와 필드가 정의되어 있습니다. 그리고 아래처럼 활용이 가능합니다.

import java.util.Date;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import Employee;
import PMF;

// ...
       
Employee employee = new Employee("Alfred", "Smith", new Date());

       
PersistenceManager pm = PMF.get().getPersistenceManager();

       
try {
            pm
.makePersistent(employee);
       
} finally {
            pm
.close();
       
}

new Employee(...)와 같은 방법으로 데이터를 설정해주고 PersistenceManagermakePersistent 메소드에 데이터를 넘겨서 insert할 수 있습니다. 만일 PrimaryKey가 같은 데이터가 이미 존재한다면 insert 대신 update를 합니다. 간단하죠?

그리고 JDO는 SQL 사촌 같은 이름을 가진 JDOQL을 제공합니다.

import java.util.List;
import Employee;

// ...
       
String query = "select from " + Employee.class.getName() + " where lastName == 'Smith'";
       
List<Employee> employees = (List<Employee>) pm.newQuery(query).execute();

어떤가요? 쉽죠? 보통 select 문과 좀 달라보이는 건, object를 얻어내기에 필드를 임의로 지정하지 못합니다. 그냥 다 가져와서 쓰면 됩니다. 비교문이 사실.. SQL 표준에 비하면 많이 빈약한데, 그래도 lastName like '%멍미%' 까지는 아니더라도 lastName like '멍미1%' 정도는 lastName >= '멍미1' and lastName < '멍미2' 정도로 표현해볼 수 있습니다.

이제 기본적인 사항들에 대해 어느 정도 살펴본 듯 한데, 지금 시점에서의 몇 가지 명세를 더 살펴봤습니다.

우리가 클래스로 정의했던 데이터 객체는 우리에게 친숙한 엔티티(entity) 정도로 볼 수 있고, 엔티티는 하나 이상의 프로퍼티를 가질 수 있습니다. 정수, 실수, 문자열, 날짜, 바이너리 등 여러가지가 될 수 있죠.

그리고 각각의 엔티티는 유일하게 구분할 수 있는 키를 가집니다. 위 샘플에서 본 것 처럼 1)데이터스토어가 제공해주는 숫자생성키를 사용하거나 2)애플리케이션에서 직접 문자열로 키를 지정해줄 수 있습니다.

JDOQL을 이용해서 특정 프로퍼티를 기준으로 정렬해서 데이터를 불러올 수 있고, 애플리케이션을 deploy하기 전에 텍스트 설정 파일을 통해 직접 인덱스를 설정해줄 수 있습니다.

트랜잭션과 관련해서 데이터스토어는 optimistic concurrency를 적용해서, 특정 애플리케이션 인스턴스가 엔티티를 수정하려는 시도를 하고 있는 동안은 다른 모든 인스턴스의 수정 시도가 실패하거나 재시도를 할 수 있습니다.

또한 어느 정도 기능과 사용량에 제약이 있으니 애플리케이션 개발에 나서기 전에, 요구사항을 충족할 수 있는지 살펴보는 것이 안전하겠습니다.

기능 부문

  • 위에 언급한 것과 같이 지금은 IdentityType.APPLICATION만 지원하며 IdentityType.DATASTORE는 지원 예정,
  • 조인이나 그룹핑 등 일부 구문,
  • 그 외 아래 연결 주소 참조

사용량 부문

  • 한 엔티티(row / record)의 최대 크기: 1MB,
  • 한 엔티티를 위한 인덱스 값은 최대 1000개,
  • 배치로 한 번에 넣고 지울 수 있는 엔티티 수는 최대 500개,
  • 불러올 수 있는 건 2배인 1000개.

보다 자세한 참조) http://code.google.com/intl/ko-KR/appengine/docs/java/datastore/usingjdo.html#Unsupported_Features_of_JDO

[펌]Java GAE 시작上 (Datastore)

저는 파이썬보다는 자바가 더 친숙하고, 자바보다는 C#이 더 친숙한 편입니다. 그래서 파이썬만 제공될 때는 해보고 싶다 생각했어도 일상에 쫓겨 마음 한 구석에 접어 두었는데, 얼마 전에 아직('09 5월 초 시점) 어설픈 상태(Early Look)긴 하지만 자바가 추가 지원 언어로 제공된 것을 보았고, 이번에 팀 회의에서 웹서비스의 일부를 구글 앱 엔진으로 빼내서 하드웨어 추가구매 없이 서비스 퍼포먼스를 향상시키자는 의견을 제안, 의사결정권자의 샘플 작성 허가를 얻어서 시도해보는 김에 이렇게 튜토리얼도 적어보고자 합니다.

리소스
  1. http://code.google.com/intl/ko-KR/appengine/
  2. http://code.google.com/intl/ko/appengine/downloads.html
  3. http://code.google.com/eclipse/docs/download.html 
  4. http://code.google.com/intl/ko/appengine/docs/java/gettingstarted/


먼저 구글 앱 엔진의 인프라를 이용하려면 구글계정으로 애플리케이션 엔진 계정에 가입하는 과정부터 시작하게 됩니다.(리소스 1 참조)

로그인을 하시면 http://appengine.google.com/으로 연결되고 애플리케이션을 관리할 수 있는 대시보드가 나타납니다.


이 곳에서 애플리케이션을 최대 10개까지 생성, 관리하실 수 있습니다. 그리고 각 애플리케이션 마다 꽤 많은 걸 할 수 있습니다.

로그 조회, cron.yaml을 활용한 작업 예약 스케쥴러 이용, 함께 애플리케이션 개발에 참여 가능한 구글 아이디 추가, 데이터 스토어의 데이터 현황 조회, 리비전 조회 등 왠만한 IT 부서에서 맨 땅에 지으려면 비용이 꽤 드는 지원요소들이 고맙게도 잘 준비되어 있습니다.


그리고 Google App Engine SDK for Java를 다운받으셔야 합니다. 학부 신입생 시절에 자바 SDK가 뭐야? 라고 떠들면서 sun.com에 접속하던 기억이 떠오르네요^^a(리소스 2 참조)

전 내려 받은 SDK를 d:\appengine-java-sdk에 저장했습니다. 커맨드라인에 d:\appengine-java-sdk\bin\dev_appserver.cmd d:\appengine-java-sdk\demos\guestbook\war를 입력하셔서 데모가 잘 동작하는지 확인할 수 있습니다.

혹시 숙련도 향상을 위한 노트패드 개발 등의 수행을 하고 계신 것이 아니라면 이클립스용 구글 플러그인(Google Plugin for Eclipse)도 설치하시는 것을 권장합니다.(리소스 3 참조)


벌써 코드를 작성하고 싶어 손이 근질근질 하실 수 있지만, 일단은 개발 프로세스를 살펴야 합니다. 먼저 프로젝트를 만드는 법을 살펴봅니다.

GAE에서 프로젝트를 운영하려면 GAE의 폴더 아키텍쳐를 이해하면 좋습니다. 그런데 이클립스와 플러그인을 이용하면 사실.. 잘 몰라도 됩니다.

어린 시절 JSP를 처음 접할 때 아파치니 톰캣이니 설정하고 서버를 올렸다가 내렸다가, admin 페이지에서 애플리케이션 릴름을 잡아 주느니 마느니 하다가... 머리에 스팀이 오르는 걸 느끼신 분도 계실 겁니다.

그만큼 뭔가 서버를 설정하고 애플리케이션을 디플로이한 다음 Hello my first jsp page! 라고 찍기까지 꽤 노력이 필요했단 얘기죠.

그런데.. 말씀드린 것처럼 이클립스 쓰시면 꽤 쉽습니다.


일단 아래처럼 프로젝트 템플릿으로 구글 웹 애플리케이션 프로젝트를 만들어 봅니다.



마법사에서 프로젝트를 고른 다음 아래처럼 간단하게 네이밍을 해줍니다. (GWT가 맘에 안들면 선택해제 하셔도 됩니다.)



'완료' 버튼을 누르시면 뭐가 그리 쉬운지 뚝딱! 구글 웹 애플리케이션 아키텍쳐를 충실히 갖춘 프로젝트가 만들어집니다.

아래처럼 콘솔에 소요된 시간도 나타납니다.
DataNucleus Enhancer (version 1.1.0) : Enhancement of classes

DataNucleus Enhancer completed with success for 0 classes. Timings : input=31 ms, enhance=0 ms, total=31 ms. Consult the log for full details
DataNucleus Enhancer completed and no classes were enhanced. Consult the log for full details



아래는 기본적인 프로젝트 폴더 구조입니다.



이제 브라우저에서 애플리케이션을 확인하려면, 디플로이 단계만 거치면 되는데 이 또한 간단합니다.

이클립스 상단(아.. 사용자마다 꼭 상단은 아니겠네요; 저의 경우~) 툴바에서 디플로이 아이콘 꾸욱 누릅니다.



그러면 애플리케이션 이름과 인증정보를 요구하는 창이 떠오릅니다.



이제 Ok를 누르면 컴파일이니 링크니 업로드니.. 다 척척 진행됩니다.



마지막으로 http://당신의-앱-이름.appspot.com/ 을 브라우저 주소창에 입력하면 예쁘장한 기본 페이지가 뜨는 것을 볼 수 있습니다.

생각보다 쉽지 않나요?^^; 무료.. 무료... 무료....-_-;;


- 下편에서는 데이터저장소(Datastore) 등 좀 더 세부적인 면을 다뤄보려고 계획 중입니다.
이제 정말.. 시스템 관리자, 개발자, DBA 같은 1~2개 카테고리의 스킬만 전문적으로 보유한 인력보다는,
여러 서비스와 트렌드, 마켓을 이해하고 엮어서 필요한 세계를 꾸려낼 수 있는, 컨버전스를 해낼 수 있는
인력의 비중과 가치가 더 높아질 것 같다는 생각도 듭니다.

Google App Engine에서 Spring3 + JPA 사용한 소녀시대 예제

앱엔진에 뭔가 제 사이트를 만들고 싶어서 이렇게 삽질을 하고 있는데, 망할 제한이 왜이렇게 많지-_-

암튼, "구글 앱 엔진"에서는 JPA를 지원합니다. 하지만, 이상하게도 잘 안됩니다-_- 굉장히 제한적으로 이것저것 막아둔 것 같습니다. 사실 구글 앱 엔진에서는 DataBase를 BigTable인지 뭐시기인지 그걸 사용하고, 직접적으로 접근을 못하기 때문에(전부 프로그래밍 또는 관리페이지(관리페이지도 매우 제한적인-_-)에서만 관리 가능), 이걸 이용하는 API에서도 엄청나게 뭔가 막아둔 것 같습니다.
뭐 좀 해보려고 하면 에러를 내뱉습니다. 검색해보면 구글앱엔진에서만 나는 에러입니다-_- 사실 아직 구글앱엔진이 프리뷰버전이기에 뭐라 따지지도 못하는 게 사실입니다^^ 정식버전(언제나오려나....Beta딱지 떼는데 10년넘게 걸리겠지-_-)나오면 매우 안정화가 되지 않을까 싶습니다^^

암튼, Spring3 + JPA의 조합으로 앱엔진에 올리는 건 성공했는데, 사실 스프링에서 제공하는 TransactionManager를 사용했어야 했는데, JPATemplate으로 뭔가 처리를 하면 잘 안되더군요-_- 일단 가져오고, persist하고, 이런건 잘 되는데, 왜 삭제가 안될까요-_- 삭제가 안되서 그냥JPATemplate빼고 했습니다-_-
JPATemplate사용해서 성공하신 분 트랙백좀 ㅠㅠ

0. 환경
Eclipse 3.5 + Google AppEngine Plugin + Spring 3.0.0
일단 스프링3다운로드 - http://www.springsource.org/download

1. 프로젝트 생성
New Project -> Google에 있는 Web Application Project 선택.
Project Name은 SosiSchedule. package는 com.mudchobo.
Use Google Web Toolkit은 체크해제. 사용안할꺼라....(이것도 언제한번 공부해야하는데-_-)
Finish.

2. 라이브러리 복사 및 build path추가
spring3에서는 spring.jar가 산산조각 났어요. 필요한 것만 넣으면 되는 듯.
일단 제가 사용한 것은....
org.springframework.asm-3.0.0.RELEASE.jar
org.springframework.beans-3.0.0.RELEASE.jar
org.springframework.context-3.0.0.RELEASE.jar
org.springframework.core-3.0.0.RELEASE.jar
org.springframework.expression-3.0.0.RELEASE.jar
org.springframework.orm-3.0.0.RELEASE.jar
org.springframework.web.servlet-3.0.0.RELEASE.jar
org.springframework.web-3.0.0.RELEASE.jar
그리고, jstl을 사용할 것이기에....
jstl.jar와 standard.jar
※이번버전에서는 lib폴더가 없습니다-_- 어디서 찾아야하는 거지-_- 암튼 그래서 2.5.6버전에서 가져왔습니다^^

앱엔진에서는 lib폴더 복사로 libpath가 잡히지 않네요. 그래서 각각 다 추가해줘야한다는...-_-
일단 war/WEB-INF/lib폴더에 복사 후에 복사한 파일 선택 후 오른쪽버튼 후, Build Path -> Add to Build Path 선택하면 됩니다^^

3. web.xml파일 수정
web.xml
<servlet>
   
<servlet-name>dispatcher</servlet-name>
   
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
   
<load-on-startup>2</load-on-startup>
</servlet>

<servlet-mapping>
   
<servlet-name>dispatcher</servlet-name>
   
<url-pattern>/sosischedule/*</url-pattern>
</servlet-mapping>

<welcome-file-list>
   
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

일단 sosischedule/*요청은 spring이 받습니다.

4. dispacher-servlet.xml파일과 persistence.xml파일 생성
war/WEB-INF/폴더에 생성
dispatcher-servlet.xml
<?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:p="http://www.springframework.org/schema/p"
   
xmlns:context="http://www.springframework.org/schema/context"
   
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:component-scan base-package="com.mudchobo" />
   
   
<bean id="entityManager"
       
factory-bean="EMF"
       
factory-method="get" />
   
   
<bean id="viewResolver"
       
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
       
p:prefix="/WEB-INF/jsp/" p:suffix=".jsp" />
</beans>


src/META-INF/ 폴더에 생성
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
   
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
   
   
<persistence-unit name="transactions-optional">
       
<provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>
       
<properties>
           
<property name="datanucleus.NontransactionalRead" value="true"/>
           
<property name="datanucleus.NontransactionalWrite" value="true"/>
           
<property name="datanucleus.ConnectionURL" value="appengine"/>
       
</properties>
   
</persistence-unit>
</persistence>


5. EMF클래스 생성.
이제 jpa접근할 수 있는 EntityManagerFactory클래스(EMF)를 생성해봅시다.
com.mudchobo.sosi.sosischedule.dao.EMF.java
package com.mudchobo.sosischedule.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.springframework.stereotype.Component;

@Component
public final class EMF {
     
private static final EntityManagerFactory emfInstance =
       
Persistence.createEntityManagerFactory("transactions-optional");

   
private EMF() {}

   
public EntityManager get() {
       
return emfInstance.createEntityManager();
   
}
}


6. Entity클래스 생성
일단 Sosi와 Schedule이라는 Entity를 생성할 건데요. 둘의 관계는 1:N관계입니다.
com.mudchobo.sosischedule.entity.Sosi.java
package com.mudchobo.sosischedule.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;

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

@Entity
public class Sosi implements Serializable {
   
private static final long serialVersionUID = 5448408922872112420L;

   
@Id
   
@GeneratedValue(strategy=GenerationType.IDENTITY)
   
private Key key;
   
   
private String sosiName;
   
   
@OneToMany(mappedBy="sosi", cascade=CascadeType.ALL)
   
private List<Schedule> scheduleList = new ArrayList<Schedule>();

   
public Key getKey() {
       
return key;
   
}

   
public void setKey(Key key) {
       
this.key = key;
   
}

   
public List<Schedule> getScheduleList() {
       
return scheduleList;
   
}

   
public void setScheduleList(List<Schedule> scheduleList) {
       
this.scheduleList = scheduleList;
   
}

   
public String getSosiName() {
       
return sosiName;
   
}

   
public void setSosiName(String sosiName) {
       
this.sosiName = sosiName;
   
}

   
public Sosi() {
       
   
}
   
   
public Sosi(Key key, String sosiName) {
       
super();
       
this.key = key;
       
this.sosiName = sosiName;
   
}
}

com.mudchobo.sosischedule.entity.Schedule.java
package com.mudchobo.sosischedule.entity;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;

@Entity
public class Schedule implements Serializable{
   
private static final long serialVersionUID = -8676837674549793653L;

   
@Id
   
@GeneratedValue(strategy = GenerationType.IDENTITY)
   
private Key key;
   
   
private String program;
   
   
@ManyToOne(fetch=FetchType.LAZY)
   
private Sosi sosi;
   
   
public Sosi getSosi() {
       
return sosi;
   
}

   
public void setSosi(Sosi sosi) {
       
this.sosi = sosi;
   
}

   
public Key getKey() {
       
return key;
   
}

   
public void setKey(Key key) {
       
this.key = key;
   
}
   
   
   
public String getKeyString() {
       
return KeyFactory.keyToString(key);
   
}
   
   
public String getProgram() {
       
return program;
   
}

   
public void setProgram(String program) {
       
this.program = program;
   
}
   
   
public Schedule() {
   
}

   
public Schedule(String program, Sosi sosi) {
       
this.program = program;
       
this.sosi = sosi;
   
}
}

일단 App Engine용 JPA에서는 ID 타입이 Long이면 관계형태를 사용할 수 없더라구요. 그래서 앱엔진에서 제공하는 Key타입이 있는데, 이걸 이용해야합니다.

7. Dao만들기
com.mudchobo.sosisochedule.SosiDao.java
package com.mudchobo.sosischedule.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.google.appengine.api.datastore.KeyFactory;
import com.mudchobo.sosischedule.entity.Schedule;
import com.mudchobo.sosischedule.entity.Sosi;

@Repository
public class SosiDao {
   
private EntityManager em;
   
   
@Autowired
   
public void setEntityManager(EntityManager em) {
       
this.em = em;
       
       
// 소시데이터 추가
        addSosi
(new Long(1), "효연");
        addSosi
(new Long(2), "윤아");
        addSosi
(new Long(3), "수영");
        addSosi
(new Long(4), "유리");
        addSosi
(new Long(5), "태연");
        addSosi
(new Long(6), "제시카");
        addSosi
(new Long(7), "티파니");
        addSosi
(new Long(8), "써니");
        addSosi
(new Long(9), "서현");
   
}
   
   
public void addSosi(Long id, String sosiName) {
        em
.getTransaction().begin();
        em
.persist(new Sosi(KeyFactory.createKey(Sosi.class.getSimpleName(), id), sosiName));
        em
.getTransaction().commit();
   
}
   
   
@SuppressWarnings("unchecked")
   
public List<Sosi> getSosiList() {
       
return em.createQuery("select s from Sosi s").getResultList();
   
}

   
public Sosi getSosi(Long sosiId) {
       
return em.find(Sosi.class, sosiId);
   
}
   
   
@SuppressWarnings("unchecked")
   
public List<Schedule> getScheduleList(final Long sosiId) {
       
Query q = em.createQuery("select s.scheduleList from Sosi s where s.key = :key");
        q
.setParameter("key", KeyFactory.createKey(Sosi.class.getSimpleName(), sosiId));
       
return (List<Schedule>) q.getSingleResult();
   
}
   
   
public void addSchedule(Long sosiId, String program) {
        em
.getTransaction().begin();
       
Sosi sosi = em.find(Sosi.class, sosiId);
        sosi
.getScheduleList().add(new Schedule(program, sosi));
        em
.getTransaction().commit();
   
}
   
   
public void deleteSchedule(String scheduleKey) {
        em
.getTransaction().begin();
       
Schedule schedule = em.find(Schedule.class, scheduleKey);
        em
.remove(schedule);
        em
.getTransaction().commit();
   
}
}

EntityManager받을 때 디폴트로 데이터를 넣어줘야 합니다(아까 위에서 말했듯이 프로그래밍적으로만 테이블을 생성할 수 있어서 이런 형태로 데이터를 넣어줘야합니다ㅠㅠ)

일단 실행해보고 데이터가 잘 생성되었는지 보려면 아래와 같은 주소로 접속해보면 됩니다.
http://localhost:8888/_ah/admin
일단 보고 삭제까지는 되는데, 테이블 생성같은 건 안되더라구요. 그리고 여기서 보여지는데에는 한글이 깨지는데 나중에 출력해보면 잘 나오니 걱정마시길-_-
8. Service 클래스 생성
com.mudchobo.sosischedule.service.SosiService.java
package com.mudchobo.sosischedule.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.mudchobo.sosischedule.dao.SosiDao;
import com.mudchobo.sosischedule.entity.Schedule;
import com.mudchobo.sosischedule.entity.Sosi;

@Service
public class SosiService {
   
   
@Autowired
   
private SosiDao sosiDao;
   
   
public List<Sosi> getSosiList()
   
{
       
return sosiDao.getSosiList();
   
}
   
   
public Sosi getSosi(Long sosiId) {
       
return sosiDao.getSosi(sosiId);
   
}
   
   
public List<Schedule> getScheduleList(Long sosiId) {
       
return sosiDao.getScheduleList(sosiId);
   
}
   
   
public void deleteSchedule(String scheduleKey) {
        sosiDao
.deleteSchedule(scheduleKey);
   
}

   
public void addSchedule(Long sosiId, String program) {
        sosiDao
.addSchedule(sosiId, program);
   
}
}

Service에서 하는 역할은 뭐 없네요-_-

9. Controller생성
스프링3.0에서 새로 추가된 기능인 REST기능입니다.
com.mudchobo.sosischedule.controller.SosiController.java
package com.mudchobo.sosischedule.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.mudchobo.sosischedule.entity.Schedule;
import com.mudchobo.sosischedule.entity.Sosi;
import com.mudchobo.sosischedule.service.SosiService;

@Controller
public class SosiController {
   
private static String PREFIX = "/sosischedule";
   
   
@Autowired
   
private SosiService sosiService;
   
   
@RequestMapping(value="/", method=RequestMethod.GET)
   
public String index(Model model) {
       
List<Sosi> sosiList = sosiService.getSosiList();
        model
.addAttribute("sosiList", sosiList);
       
       
return "index";
   
}
   
   
@RequestMapping(value="/schedule/{sosiId}", method=RequestMethod.GET)
   
public String getSchedule(
           
@PathVariable("sosiId") Long sosiId,
           
Model model) {
       
Sosi sosi = sosiService.getSosi(sosiId);
       
List<Schedule> scheduleList = sosiService.getScheduleList(sosiId);
        model
.addAttribute("scheduleList", scheduleList)
           
.addAttribute("sosi", sosi);
       
       
return "sosi";
   
}
   
   
@RequestMapping(value="/schedule/{sosiId}/add", method=RequestMethod.POST)
   
public String addSchedule(
           
@PathVariable("sosiId") Long sosiId,
           
@RequestParam("program") String program,
           
Model model
           
) {
        sosiService
.addSchedule(sosiId, program);
       
       
return "redirect:" + PREFIX + "/schedule/" + sosiId;
   
}
   
   
@RequestMapping(value="/schedule/{sosiId}/{scheduleKey}", method=RequestMethod.GET)
   
public String removeSchedule(
           
@PathVariable("sosiId") Long sosiId,
           
@PathVariable("scheduleKey") String scheduleKey,
           
Model model) {
        sosiService
.deleteSchedule(scheduleKey);
       
       
return "redirect:" + PREFIX + "/schedule/" + sosiId;
   
}
}


10. View jsp파일 생성
소시 리스트를 보여주는 index파일 입니다.
war/WEB-INF/jsp/index.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding
="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!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>소녀시대 스케줄</title>
</head>
<body>
   
<div>
        스케줄 확인하기
       
<ul>
       
<c:forEach var="sosi" items="${sosiList}">
           
<li><a href="/sosischedule/schedule/${sosi.key.id}">${sosi.key.id}. ${sosi.sosiName}</a></li>
       
</c:forEach>
       
</ul>
   
</div>
</body>
</html>


해당 소시의 스케줄을 보여주는 스케줄 파일입니다.
war/WEB-INF/jsp/sosi.jsp
<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding
="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!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>소녀시대 스케줄</title>
</head>
<body>
   
<div>
        스케줄 확인하기
       
<ul>
       
<c:forEach var="sosi" items="${sosiList}">
           
<li><a href="/sosischedule/schedule/${sosi.key.id}">${sosi.key.id}. ${sosi.sosiName}</a></li>
       
</c:forEach>
       
</ul>
   
</div>
</body>
</html>

리다이렉트를 위한 파일입니다. 기존 index.html파일 지우시고, index.jsp파일 생성
index.jsp
<% response.sendRedirect("/sosischedule/"); %>


앱엔진에 올려보았습니다.
http://2.latest.mudchobosample.appspot.com/sosischedule/