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

암튼, "구글 앱 엔진"에서는 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
[code]<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>[/code]
일단 sosischedule/*요청은 spring이 받습니다.

4. dispacher-servlet.xml파일과 persistence.xml파일 생성
war/WEB-INF/폴더에 생성
dispatcher-servlet.xml
[code]<?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>[/code]

src/META-INF/ 폴더에 생성
persistence.xml
[code]<?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>[/code]

5. EMF클래스 생성.
이제 jpa접근할 수 있는 EntityManagerFactory클래스(EMF)를 생성해봅시다.
com.mudchobo.sosi.sosischedule.dao.EMF.java
[code]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();
    }
}[/code]

6. Entity클래스 생성
일단 Sosi와 Schedule이라는 Entity를 생성할 건데요. 둘의 관계는 1:N관계입니다.
com.mudchobo.sosischedule.entity.Sosi.java
[code]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;
    }
}[/code]
com.mudchobo.sosischedule.entity.Schedule.java
[code]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;
    }
}[/code]
일단 App Engine용 JPA에서는 ID 타입이 Long이면 관계형태를 사용할 수 없더라구요. 그래서 앱엔진에서 제공하는 Key타입이 있는데, 이걸 이용해야합니다.

7. Dao만들기
com.mudchobo.sosisochedule.SosiDao.java
[code]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();
    }
}[/code]
EntityManager받을 때 디폴트로 데이터를 넣어줘야 합니다(아까 위에서 말했듯이 프로그래밍적으로만 테이블을 생성할 수 있어서 이런 형태로 데이터를 넣어줘야합니다ㅠㅠ)

일단 실행해보고 데이터가 잘 생성되었는지 보려면 아래와 같은 주소로 접속해보면 됩니다.
http://localhost:8888/_ah/admin
일단 보고 삭제까지는 되는데, 테이블 생성같은 건 안되더라구요. 그리고 여기서 보여지는데에는 한글이 깨지는데 나중에 출력해보면 잘 나오니 걱정마시길-_-
사용자 삽입 이미지
8. Service 클래스 생성
com.mudchobo.sosischedule.service.SosiService.java
[code]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);
    }
}[/code]
Service에서 하는 역할은 뭐 없네요-_-

9. Controller생성
스프링3.0에서 새로 추가된 기능인 REST기능입니다.
com.mudchobo.sosischedule.controller.SosiController.java
[code]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;
    }
}[/code]

10. View jsp파일 생성
소시 리스트를 보여주는 index파일 입니다.
war/WEB-INF/jsp/index.jsp
[code]<%@ 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>[/code]

해당 소시의 스케줄을 보여주는 스케줄 파일입니다.
war/WEB-INF/jsp/sosi.jsp
[code]<%@ 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>[/code]
리다이렉트를 위한 파일입니다. 기존 index.html파일 지우시고, index.jsp파일 생성
index.jsp
[code]<% response.sendRedirect("/sosischedule/"); %>[/code]

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

잘 되는 것 같네요.
 
Posted by 머드초보
,
 
Netbeans에서도 Google App Engine플러그인을 설치해서 개발할 수 있습니다.
하지만, 구글에서 공식적으로 제공하는 플러그인이 아니라 누가 만든 것 같네요.

이곳에서 보고 설치 및 샘플을 실행할 수 있습니다.

아.....이건 상관없는 그림이지만, 그림을 보면 볼수록 왠지 슬퍼지는데요.
오늘 권순선님 미투데이에서 발견했습니다-_-
사용자 삽입 이미지

Java를 낳고 돌아가신 Sun이군요-_- 오라클이 인수하는 걸 EC에서 승인했다더군요.



암튼 대충 따라해보면...

1. 플러그인 설치
Tools -> Plugins -> Settings -> Add -> Name에 App Engine이라고 하고, URL에 아래 주소를 입력
그러면 Available Plugins에 5개의 Google App Engine플러그인이 생김. 체크해서 설치 고고싱

2. SDK다운로드
여기서 자바용으로 받아서 적당한 폴더에 풀어주면 됨 ㄷㄷ

3. Google App Engine 서버 설정
넷빈즈에서 Services탭에서 Servers에서 마우스 오른쪽버튼 클릭 후 Add Server선택
Google App Engine을 선택한 뒤, 위에서 받은 sdk폴더 경로 지정. 포트는 맘에 드는걸로 하면 끝남

4. 새로운 프로젝트 생성
New 프로젝트 -> Java Web -> Web Application -> Project Name은 HelloGoogleAppEngine -> Server는 Google App Engine -> Finish하면 끝남.
F6을 눌러서 실행하면 Hello World 볼 수 있음 ㄷㄷ

5. 배포
배포 시 Web Pages -> WEB-INF -> appengine-web.xml파일에서 Application Name을 자신이 얻은 Name으로 수정하고 해야함.

그런데 배포 시 약간 문제가 있음.
아무것도 안건드렸다고해도 이런 에러를 보게 될 꺼임.
[code]java.lang.IllegalStateException: cannot find javac executable based on java.home, tried "C:\Program Files\Java\jre6\bin\javac.exe" and "C:\Program Files\Java\bin\javac.exe"
Unable to update app: cannot find javac executable based on java.home, tried "C:\Program Files\Java\jre6\bin\javac.exe" and "C:\Program Files\Java\bin\javac.exe"
Please see the logs [C:\Users\mudchobo\AppData\Local\Temp\appcfg7950519452562723725.log] for further information.[/code]
javac파일을 java\bin에서 찾는 것 같음. 거기엔 javac.exe파일이 없음. java.home위치를 수정하는 법을 몰라서, 검색해보니 그냥 그 폴더에 javac.exe를 복사하는 거임. tools.jar파일도 lib폴더에 같이 복사해야함 ㄷㄷ

제꺼기준으로
C:\Program Files\Java\jdk1.6.0_17\bin\javac.exe파일을 -> C:\Program Files\Java\jre6\bin에 복사.
C:\Program Files\Java\jdk1.6.0_17\lib\tools.jar파일을 -> C:\Program Files\Java\jre6\lib\폴더에 복사.

그러면 잘 될꺼임.

ps. 결론은.......그냥 이클립스 플러그인 쓰는 게 나아요. 그건 GWT연동도 되거든요-_- 이런 짓을 하면서 까지 넷빈즈에서 개발하고 싶진 않은 듯. 공식으로 지원하는 플러그인으로....-_- 이클립스가 짱인 듯.
 
Posted by 머드초보
,
 
아주 오래전에 Hibernate xml로 삽질했던 기억이 있는데, Annotation으로 해보겠다는 게 세월(?)이 벌써 이렇게 흘렀군요.

하이버네이트는 셋팅이 참 어렵군요. 책을 보면 그냥 hibernate함수를 이용해서 어떻게 이용하는지, 하이버네이트의 특성이 주로 나와있는데, 셋팅에 대한 삽질은 좀 자세하지 않은 듯(내가 못본 것일 수도 있음-_-)

NetBeans 6.8에서 삽질했습니다.
넷빈즈다운로드 : http://netbeans.org/downloads/index.html

일단 소녀시대 테이블만 하나 만들어 놓읍시다.
테이블은 sosi랑 schedule 2개가 1:N의 관계형태로 만드려고 합니다. (DB쪽에 취약해서 맞는지 모르겠네-_- 뭐 다취약하지만-_-)
[code]
DROP TABLE IF EXISTS `sosi`;
CREATE TABLE IF NOT EXISTS `sosi` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `birthYear` int(11) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=10 ;

INSERT INTO `sosi` (`id`, `birthYear`, `name`) VALUES
(1, 1989, '효연'),
(2, 1990, '윤아'),
(3, 1990, '수영'),
(4, 1989, '유리'),
(5, 1989, '태연'),
(6, 1989, '제시카'),
(7, 1989, '티파니'),
(8, 1990, '써니'),
(9, 1991, '서현');
[/code]

1. 프로젝트 생성
File -> New Project(Ctrl + Shift + N) -> Java -> Java Application -> Project이름은 SosiSchedule

2. 하이버네이트 설정파일 셋팅
 Ctrl + N을 눌러서 새로운 파일을 만듭니다. SosiSchedule을 선택하고, Hibernate -> Hibernate Configuration Wizard선택, File Name은 hibernate.cfg 디폴트로, Database는 자신이 셋팅하고 sosi테이블을 만들어놓은 Mysql 데이터베이스를 선택합니다. 안만들었으면 New Database Connector를 선택해서 Host, Port, Database, User Name Password를 입력하면 됩니다. 이거 하면 자동으로 Library가 추가되나봅니다.
설정 파일이 만들어졌어요. 근데, 하이버네이트에서는 src최상위 폴더에 넣으면 자동으로 설정파일을 인식하나봐요. 설정파일 경로 지정하는 부분이 없는 것 같은데-_-
테이블 자동생성 및 날라가는 sql을 보기위해 아래 두옵션을 추가합니다.
[code]<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>[/code]

3. HibernateUtl만들기
Ctrl + N을 눌러서 새로운 파일 작성 -> Hibernate -> HibernateUtl.java, ClassName은 HibernateUtl로 package는 sosischedule.util로 finish!

4. 매핑할 ENTITY클래스 작성
일단 entity클래스는 database에 table이 존재하면 자동으로 만들 수 있습니다.
Ctrl + N을 눌러서 새로운 파일 작성 -> Persistence -> Entity Classes  from Database 선택.
Database Connection에서 셋팅한 mysql선택하면  테이블명이 나오는데, sosi테이블을 Add합니다.
package는 알아보기 쉽게 sosischedule.entity로 하고 finish를...-_-
이쁘게 소스가 만들어지네요.
Sosi.java
[code]/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package sosischedule.entity;

import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;

/**
 *
 * @author mudchobo
 */
@Entity
@Table(name = "sosi")
@NamedQueries({
    @NamedQuery(name = "Sosi.findAll", query = "SELECT s FROM Sosi s"),
    @NamedQuery(name = "Sosi.findById", query = "SELECT s FROM Sosi s WHERE s.id = :id"),
    @NamedQuery(name = "Sosi.findByBirthYear", query = "SELECT s FROM Sosi s WHERE s.birthYear = :birthYear"),
    @NamedQuery(name = "Sosi.findBySosiName", query = "SELECT s FROM Sosi s WHERE s.sosiName = :sosiName")})
public class Sosi implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id")
    private Long id;
    @Basic(optional = false)
    @Column(name = "birthYear")
    private int birthYear;
    @Column(name = "sosiName")
    private String sosiName;

    public Sosi() {
    }

    public Sosi(Long id) {
        this.id = id;
    }

    public Sosi(Long id, int birthYear) {
        this.id = id;
        this.birthYear = birthYear;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getBirthYear() {
        return birthYear;
    }

    public void setBirthYear(int birthYear) {
        this.birthYear = birthYear;
    }

    public String getSosiName() {
        return sosiName;
    }

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

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Sosi)) {
            return false;
        }
        Sosi other = (Sosi) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "sosischedule.entity.Sosi[id=" + id + "]";
    }

}
[/code]
각각의 어노테이션들은 구글링을 통해 찾아보는걸로 저도 잘 몰라서-_-
Annotation기반으로 하기전에는 mapping xml파일을 작성했는데, 그걸 그냥 클래스에 보기좋게 해놓은 거라고 보면 될 듯.

스케쥴 클래스는 직접 만들어봅시다.
Ctrl + N을 통해 새로운 파일생성, Persistence -> Entity Class, Class Name은 Schedule, package는 sosischedule.entity선택 후 finish.
그러면 id만 달랑 있습니다. 여기에 칼럼을 만들어 봅시다. 많이 만들면 귀찮아 지니까-_- program명과 소시객체 연동하는 것만 만들어봅시다.
[code]private String program;
private Sosi sosi;[/code]입력하고, Alt + Insert하면 getter, setter자동생성기로 만듭니다.
사실 이렇게만 만들어 놓아도 테이블이 생성됩니다-_- 다 디폴트로 만들어서. 귀찮으니까 이렇게만 만들고 맙시다-_- 아 그리고, 생성자를 두개 추가했습니다. 기본생성자와 property를 세팅해서 만들어주는 생성자.
그리고 테이블과 관계를 맺기 위해 @ManyToOne을 넣어줘야합니다. 안 넣어주면 무슨 Blob으로 그냥 저장해버리는..-_-
Schedule.java
[code]package sosischedule.entity;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;

@Entity
public class Schedule implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String program;

    @ManyToOne
    private Sosi sosi;

    public Schedule() {
    }

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

    public Sosi getSosi() {
        return sosi;
    }

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

    public Long getId() {
        return id;
    }

    public String getProgram() {
        return program;
    }

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

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        if (!(object instanceof Schedule)) {
            return false;
        }
        Schedule other = (Schedule) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "sosischedule.entity.Schedule[id=" + id + "]";
    }
}
[/code]
오...이저 hibernate.cfg.xml파일을 열어서 매핑파일이라고 추가합니다.
hibernate.cfg.xml
[code]<mapping class="sosischedule.entity.Sosi"/>
<mapping class="sosischedule.entity.Schedule"/>[/code]
작성한 클래스 두개. 이제 hql을 날릴 수 있어요. hibernate.cfg.xml파일을 선택 후 오른쪽버튼을 누르면 "Run HQL Query"라는 메뉴가 나와요.
from Sosi때리면 소시멤버데이터가 나오네요.
※여기서 해당 프로젝트에 대해서 한번이라도 run을 때리지 않으면 Sosi is not mapped라고 나오네요. 안되면 한번 실행하고 해보세요.
사용자 삽입 이미지
이제 다 된 것 같으니 dao와 service를 만들어봅시다.

5. Dao생성
sosischedule.dao.SosiScheduleDao.java파일을 생성
SosiScheduleDao.java
[code]package sosischedule.dao;

import java.util.List;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import sosischedule.entity.Schedule;
import sosischedule.entity.Sosi;
import sosischedule.util.HibernateUtil;

public class SosiScheduleDao {

    public List<Sosi> getSosiList() {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Query q = session.createQuery("from Sosi");
        List<Sosi> list = q.list();
        session.close();

        return list;
    }

    public Sosi getSosi(int sosiId) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Sosi sosi = (Sosi) session.get(Sosi.class, new Long(sosiId));
        session.close();

        return sosi;
    }

    public List<Schedule> getSchedule(Long sosiId) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Query q = session.createQuery("from Schedule s where s.sosi.id = " + sosiId);
        List<Schedule> list = q.list();
        session.close();

        return list;
    }

    public void addSchedule(Schedule schedule) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();
        session.save(schedule);
        tx.commit();
        session.close();
    }

    public void removeSchedule(int scheduleId) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction tx = session.beginTransaction();
        Schedule schedule =
                (Schedule) session.load(Schedule.class, new Long(scheduleId));
        session.delete(schedule);
        tx.commit();
        session.close();
    }
}
[/code]
음....분명 이렇게 하는 건 아닌 것 같아-_- 암튼, 일단 셋팅이 목적이니-_- 대충 이렇게도 할 수 있다는 것을..-_-
getSosiList는 소시리스트를 가져오고, getSosi는 소시를 가져오고, getSchedule은 스케쥴리스트를 가져오고, addSchedule은 스케쥴추가하고, removeSchedule은 스케쥴을 삭제하고...

6. 서비스생성
이걸 사용할 서비스를 만들어봅시다.
sosischedule.service.SosiScheduleService.java
[code]package sosischedule.service;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import sosischedule.dao.SosiScheduleDao;
import sosischedule.entity.Schedule;
import sosischedule.entity.Sosi;

public class SosiScheduleService {

    private SosiScheduleDao sosiScheduleDao = new SosiScheduleDao();

    public void menuSosi() {
        List<Sosi> sosiList = sosiScheduleDao.getSosiList();
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        while(true){
            try {
                for (Sosi sosi : sosiList) {
                    System.out.println(sosi.getId() + "." + sosi.getSosiName());
                }
                System.out.println("번호를 입력하세요!(0은 종료) => ");
                int menuNum = Integer.parseInt(br.readLine());
                if (menuNum == 0){
                    System.out.println("종료!");
                    break;
                }
                // 소시데이터 가져오기
                Sosi sosi = sosiScheduleDao.getSosi(menuNum);
                if (sosi != null){
                    menuSchedule(sosi);
                    break;
                } else {
                    System.out.println("없는 번호입니다.");
                }

            } catch (IOException ex) {
                Logger.getLogger(SosiScheduleService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    public void menuSchedule(Sosi sosi) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        while(true){
            try {
                List<Schedule> scheduleList = sosiScheduleDao.getSchedule(sosi.getId());
                for (Schedule schedule : scheduleList) {
                    System.out.println(schedule.getId() + "." + schedule.getProgram());
                }
                if (scheduleList.size() <= 0){
                    System.out.println("스케쥴이 없습니다.\n");
                }
                System.out.println("1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>");
                int menuNum = Integer.parseInt(br.readLine());
                if (menuNum == 0) {
                    System.out.println("뒤로!");
                    menuSosi();
                    break;
                }
                else if (menuNum == 1) {
                    System.out.println("스케쥴명 입력 : ");
                    String program = br.readLine();
                    System.out.println("스케쥴 = " + program);
                    sosiScheduleDao.addSchedule(new Schedule(program, sosi));
                    System.out.println("추가완료!");
                }
                else if (menuNum == 2) {
                    System.out.println("스케쥴번호 : ");
                    int scheduleId = Integer.parseInt(br.readLine());
                    sosiScheduleDao.removeSchedule(scheduleId);
                    System.out.println("삭제완료!");
                }
            } catch (IOException ex) {
                Logger.getLogger(SosiScheduleService.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}[/code]
메인에서 이렇게 사용하면 됩니다.
Main.java
[code]package sosischedule;

import sosischedule.service.SosiScheduleService;

public class Main {

    public static void main(String[] args) {
        SosiScheduleService sosiScheduleService = new SosiScheduleService();
        sosiScheduleService.menuSosi();
    }
}
[/code]

결과
[code]Hibernate: select sosi0_.id as id0_, sosi0_.birthYear as birthYear0_, sosi0_.sos
iName as sosiName0_ from sosi sosi0_
1.효연
2.윤아
3.수영
4.유리
5.태연
6.제시카
7.티파니
8.써니
9.서현
번호를 입력하세요!(0은 종료) =>
5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
스케쥴이 없습니다.

1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>
1
스케쥴명 입력 :
태연의 친한친구
스케쥴 = 태연의 친한친구
Hibernate: insert into Schedule (program, sosi_id) values (?, ?)
추가완료!
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
3.태연의 친한친구
1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>
1
스케쥴명 입력 :
태연의 친한친구2
스케쥴 = 태연의 친한친구2
Hibernate: insert into Schedule (program, sosi_id) values (?, ?)
추가완료!
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
3.태연의 친한친구
4.태연의 친한친구2
1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>
2
스케쥴번호 :
4
Hibernate: select schedule0_.id as id1_1_, schedule0_.program as program1_1_, sc
hedule0_.sosi_id as sosi3_1_1_, sosi1_.id as id0_0_, sosi1_.birthYear as birthYe
ar0_0_, sosi1_.sosiName as sosiName0_0_ from Schedule schedule0_ left outer join
 sosi sosi1_ on schedule0_.sosi_id=sosi1_.id where schedule0_.id=?
Hibernate: delete from Schedule where id=?
삭제완료!
Hibernate: select schedule0_.id as id1_, schedule0_.program as program1_, schedu
le0_.sosi_id as sosi3_1_ from Schedule schedule0_ where schedule0_.sosi_id=5
Hibernate: select sosi0_.id as id0_0_, sosi0_.birthYear as birthYear0_0_, sosi0_
.sosiName as sosiName0_0_ from sosi sosi0_ where sosi0_.id=?
3.태연의 친한친구
1.스케쥴추가 2.스케쥴삭제 0.뒤로 =>[/code]
와....잘된다....-_-
 
Posted by 머드초보
,
 
다운로드는 여기서....-_- adobe.com 사이트에 가입하셔야 합니다.

BlazeDS버전이 4.X대여야 잘 됩니다. 3.X대면 안되더군요.
그리고 BlazeDS버전이 4.x의 Beta1버전이 있는데, 이걸로 하면 요런 에러가 뜹니다.
ERROR : XML parse error : Error on line 1 of document : cvc-elt.1: Cannot find the declaration of element 'model'. Nested exception: cvc-elt.1: Cannot find the declaration of element 'model'.>$2
그래서 전 Night Build중에 4.0.0.11030버전을 사용해서 하니 되더군요.
다운로드는 여기서 http://opensource.adobe.com/wiki/display/blazeds/download+blazeds+trunk

제 환경은 Eclipse 3.5, Flash Builder 4 Beta 2, JDK 1.6.0 U 16입니다.
플러그인으로 못깐 이유가 기존의 Flex Builder 3가 플러그인으로 깔려있는데, 왠지 꼬일 것 같아서(새가슴 ㄷㄷ)-_-

우선 blazeds.war파일을 Eclipse에서 import.
web.xml을 보면 이상한 Servlet이 하나 더 생겼는데요. RDSDispatchServlet.
이거 풀고, 인증부분을 false로 바꾸면 됩니다. 자체적으로 할 수 있는 인증이 생긴 것 같은데, 이건 더 해봐야 알겠네요.
web.xml
[code]<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>

    <display-name>BlazeDS</display-name>
    <description>BlazeDS Application</description>

    <!-- Http Flex Session attribute and binding listener support -->
    <listener>
        <listener-class>flex.messaging.HttpFlexSession</listener-class>
    </listener>

    <!-- MessageBroker Servlet -->
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <display-name>MessageBrokerServlet</display-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
       </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
   
<!-- begin rds -->
    <servlet>
        <servlet-name>RDSDispatchServlet</servlet-name>
        <display-name>RDSDispatchServlet</display-name>
        <servlet-class>flex.rds.server.servlet.FrontEndServlet</servlet-class>
        <init-param>
            <param-name>useAppserverSecurity</param-name>
            <param-value>false</param-value>
        </init-param>       
        <load-on-startup>10</load-on-startup>
    </servlet>

    <servlet-mapping id="RDS_DISPATCH_MAPPING">
        <servlet-name>RDSDispatchServlet</servlet-name>
        <url-pattern>/CFIDE/main/ide.cfm</url-pattern>
    </servlet-mapping>
<!-- end rds -->

    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
    </welcome-file-list>
</web-app>[/code]
테스트 클래스를 하나 만듭니다.
Test.java
[code]package com.mudchobo.test;

public class TestService {

    public String sayHello(String name)
    {
        return "Hello! " + name;
    }
}[/code]
remoting-config.xml에 destination을 추가.
remoting-config.xml
[code]<destination id="test">
    <properties>
          <source>com.mudchobo.test.TestService</source>
          <scope>application</scope>
     </properties>
     <adapter ref="java-object" />
</destination>[/code]
서버를 이제 작동을 시켜서 띄워둡니다.

이제 Flash Builder로...

프로젝트 생성 -> 타입은 Web, Server type은 J2EE(BlazeDS)
Server location은 Root Folder는 해당 blazeds임포트한 폴더에 있는 WebContent.
Root URL은 http://localhost:8080/blazeds
Context root는 blazeds
Validate Configuration하면 왜 안되지...-_- 암튼 그냥 Finish를 누릅니다-_-

맨 하단에 Data/Services 탭이 있는데, 거기서 Connect to Data/Service.. 클릭.
BlazeDS선택, destionation으로 설정한 test가 하나 보일꺼임. 선택 Finish.
그럼 해당 Destination에 있는 함수인 sayHello가 보이는군요. 사용해봅시다.
여러가지 기능이 있는데요. 저도 잘 몰라서 많이 안해봤는데, 우선 기존에 삽질을 덜어주기 위한 많은 기능을 넣은 것 같습니다. 테스트도 할 수 있고, 자동으로 remoteObject코드도 만들어주고, 뭐 그런 것 같습니다.
Form도 만들어주네요-_-

Test Opeartion을 했더니 파라메터를 던지니 Response value로 나오네요.
사용자 삽입 이미지
버튼 클릭 시 데이터를 요청하는 걸 만들기 위해 Design모드로 변경.
Button과 TextInput, 결과 Label을 하나 추가.
button은 id를 btn, TextInput은 input. button에 오른쪽버튼 누르면 generate Service Call클릭.
그러면 자동으로 click핸들러 함수 만들어지고, sayHello함수 파라메터만 넣으면 되는데, 여기에는 input.text를 넣으면 끝.
Label에는 text에다가 sayHelloResult.lastResult를 바인딩.

DataTest.mxml
[code]<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/halo" minWidth="1024" minHeight="768" xmlns:test="services.test.*">
    <fx:Script>
        <![CDATA[
            import mx.controls.Alert;

            protected function btn_clickHandler(event:MouseEvent):void
            {
                sayHelloResult.token = test.sayHello(input.text);
            }
        ]]>
    </fx:Script>
    <fx:Declarations>
        <s:CallResponder id="sayHelloResult"/>
        <test:Test id="test" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)" showBusyCursor="true"/>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
    </fx:Declarations>
    <s:Button x="248" y="245" label="Button" click="btn_clickHandler(event)" id="btn"/>
    <s:TextInput x="223" y="215" id="input"/>
    <s:Label x="260" y="274" text="{sayHelloResult.lastResult}"/>
   
</s:Application>
[/code]
이런 코드가 됨. 개판임-_-
사용자 삽입 이미지

PS. 자동으로 다 해줘서 좋긴 한데...왠지 익숙하지 않은 코드가.....

참고자료
http://sujitreddyg.wordpress.com/2009/06/01/building-flex-application-for-blazeds-remoting-service-using-flash-builder-4/
 
Posted by 머드초보
,
 
[code]package test;

import java.awt.Image;
import java.net.URL;

import javax.imageio.ImageIO;

public class Main {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        try {
            URL url = new URL("http://wstatic.naver.com/w9/lg_naver_v3.gif");
            Image image = ImageIO.read(url);
            int width = image.getWidth(null);
            int height = image.getHeight(null);
            System.out.println("width = " + width + ", height = " + height);
           
        } catch (Exception e) {
            System.out.println("파일이 없습니다.");
        }
    }

}
[/code]

 
Posted by 머드초보
,