IBM®
메인 컨텐츠로 가기
    Korea [국가변경]    이용약관
 
 
   
        제품    서비스 & 솔루션    고객지원 & 다운로드    회원 서비스    
메인 컨텐츠로 가기

한국 developerWorks  >  자바 | 오픈 소스  >

Google App Engine for Java: Part 3: 영속성과 관계

Java 기반 영속성 및 Google App Engine 데이터 저장소

developerWorks
문서 옵션
PDF format - Fits A4 and Letter

PDF - Fits A4 and Letter
42KB (13 pages)

Get Adobe® Reader®

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

영어원문

영어원문


제안 및 의견
피드백

난이도 : 초급

Richard Hightower, CTO, Mammatus Inc.

원문 게재일 : 2009 년 8 월 25 일
번역 게재일 : 2009 년 10 월 06 일

데이터 지속성은 엔터프라이즈 환경에서 확장 가능한 애플리케이션을 전달하기 위해 반드시 갖추고 있어야 하는 능력입니다. Google App Engine for Java™를 소개하는 Rick Hightower의 시리즈 중 마지막 기사인 이 기사에서는 App Engine의 최신 Java 기반 지속성 프레임워크에 대해 설명합니다. 현재 프리뷰 릴리스의 Java 지속성이 아직 충분한 기능을 제공하지 못하는 이유를 살펴본 후 App Engine for Java 애플리케이션의 데이터를 지속적으로 유지하기 위해 수행할 수 있는 작업의 데모를 살펴봅니다. 이 기사에서는 JDO API를 사용하여 Contact 오브젝트를 지속적으로 유지하고, 쿼리하고, 업데이트하고, 삭제하는 방법을 다루므로 Part 2에서 작성한 연락처 관리 애플리케이션이 실행되고 있어야 합니다.

App Engine for Java에서 확장 가능한 웹 애플리케이션을 위한 지속성 계층의 작성과 관련된 문제를 해결하기 위해 노력하고 있기는 하지만 이 목표가 얼마나 달성되고 있을까? 이 기사에서는 JDO(Java Data Objects)와 JPA(Java Persistence API)를 기반으로 작성된 지속성 프레임워크에 대한 개요를 끝으로 App Engine for Java에 대한 소개를 마칠 것이다. 초기의 기대와는 달리 App Engine의 Java 기반 지속성 기능에는 아직까지 몇 가지 심각한 단점이 남아 있으며 이 기사에서 이러한 단점에 대해 설명하고 그 예를 살펴본다. App Engine for Java 지속성의 작동 방식과 안고 있는 문제점 및 Java 개발자를 위한 Google의 클라우드 플랫폼에서 작업할 때 선택할 수 있는 지속성 옵션에 대해 설명한다.

이 기사를 읽고 예제를 실행해 보는 동안 한 가지 유의해야 할 점은 App Engine for Java가 아직까지는 프리뷰 릴리스라는 점이다. Java 기반 지속성이 현재까지는 사용자의 기대나 요구를 모두 충족하지 못하고 있지만 앞으로 충족될 수 있을 것이며 또 그렇게 되어야 할 것이다. 이 기사를 쓰면서 필자는 보수적인 개발자의 경우 App Engine for Java를 사용하여 확장 가능한 데이터 집약적 Java 애플리케이션을 개발하는 것이 적합하지 않다고 느꼈다. 이 작업은 마치 구조 요원도 보이지 않는 상황에서 수영장의 깊은 곳으로 들어가는 것과 비슷하다. 물 속 깊이 가라앉게 될지 아니면 안전하게 헤엄쳐 나오게 될 것인지 여부가 수영하는 자신에게 달려 있듯이 프로젝트의 성공 여부도 개발자 자신에게 달려 있다.

이 기사의 예제 애플리케이션은 Part 2에서 개발한 연락처 관리 애플리케이션을 기반으로 한다. 이 기사의 예제를 진행하려면 Part 2의 애플리케이션을 빌드하여 실행 가능하게 만들어야 한다.

기본 메커니즘과 추상화 약점

원래의 Google App Engine과 마찬가지로 App Engine for Java 또한 확장 가능한 애플리케이션 개발의 3대 기능인 배포, 복제 및 로드 밸런싱을 지원하는 Google의 내부 인프라를 활용한다. Google 인프라에서 작업하기 때문에 이러한 뛰어난 기능의 대부분은 인식되지 않은 상태에서 수행되며 Java의 표준 기반 API용 App Engine을 통해 액세스할 수 있다. 데이터 저장소 인터페이스는 오픈 소스 DataNucleus 프로젝트를 기반으로 하는 JDO와 JPA를 기반으로 한다. 또한 App Engine for Java는 App Engine for Java 데이터 저장소를 직접 처리할 수 있는 하위 레벨 어댑터 API도 제공한다. 이 API는 Google의 BigTable 구현을 기반으로 하고 있다. BigTable에 대한 자세한 정보는 Part 1에서 볼 수 있다.

하지만 App Engine for Java 데이터 지속성은 Google App Engine의 지속성과는 달리 약간 복잡하다. BigTable이 관계형 데이터베이스가 아니기 때문에 JDO 및 JPA 인터페이스의 추상화 기능이 강력하지 못하다. 예를 들어, App Engine for Java에서는 조인을 수행하는 쿼리를 수행할 수 없다. JPA와 JDO의 관계를 설정할 수는 있지만 이 또한 관계를 지속적으로 유지하기 위해서만 사용할 수 있다. 그리고 오브젝트의 지속성을 유지할 경우 오브젝트는 같은 엔터티 그룹에 있을 경우에만 같은 원자성 트랜잭션 내에서 지속적으로 유지될 수 있다. 규칙에 따라 소유 관계에 있는 오브젝트 중 하위 오브젝트는 상위 오브젝트와 동일한 엔터티 그룹에 있어야 한다. 이와는 반대로 비소유 관계에 있는 오브젝트는 서로 다른 엔터티 그룹에 속하게 된다.

데이터 정규화에 대한 또 다른 생각

App Engine의 확장 가능한 데이터 저장소를 사용하려면 정규화된 데이터의 장점에 대한 기존 개념을 바꿔야 한다. 물론 실무 경력이 많다면 이미 성능을 위해 정규화를 양보한 경험이 한두 번은 있을 것이다. App Engine 데이터 저장소 작업에서의 차이점은 역정규화를 초기에 종종 수행해야 한다는 것이다. 역정규화는 더 이상 거부해야 하는 단어가 아니다. 도리어 이 단어는 앞으로 App Engine for Java 애플리케이션의 설계 단계에서 주요 개념으로 자주 등장할 것이다.

App Engine for Java의 약한 지속성으로 인한 주요 단점은 RDBMS용으로 작성된 애플리케이션을 App Engine for Java로 이식할 때 드러난다. App Engine for Java 데이터 저장소는 관계형 데이터베이스에 대한 드롭인 대체가 아니기 때문에 App Engine for Java를 사용하여 수행한 작업은 RDBMS 포트로 쉽게 변환할 수가 없다. 기존 스키마를 데이터 저장소에 이식하는 경우는 많지 않다. 레거시 Java 애플리케이션을 App Engine으로 이식하기로 결정한 경우에는 많은 주의를 기울여야 하며 철저한 분석 작업뿐만 아니라 백업도 수행해야 한다. Google App Engine은 특별히 전용 애플리케이션을 위해 설계된 플랫폼이다. Google App Engine for Java의 JDO 및 JPA 지원을 사용하면 비록 정규화되지 엔터프라이즈 애플리케이션이라 하더라도 이러한 애플리케이션을 좀 더 일반적인 방법으로 이식할 수 있다.

관계 관련 문제

현재 프리뷰 릴리스에서 볼 수 있는 App Engine for Java의 또 다른 문제는 관계를 처리하는 방법에 있다. 현재는 관계를 작성하기 위해 JDO에 대한 App Engine for Java 관련 확장을 사용해야 한다. BigTable의 아티팩트를 기반으로 키가 생성되므로(즉, "기본 키"에는 모든 하위 오브젝트에 인코딩되는 상위 오브젝트 키가 있으므로) 사용자가 비관계형 데이터베이스의 데이터를 관리해야 한다. 데이터의 지속성을 유지해야 한다는 점도 또 다른 제한이다. 비표준 AppEngine for Java Key 클래스를 사용하면 복잡한 문제가 발생한다. 먼저 모델을 RDBMS로 이식할 때 비표준 Key를 사용할 수 있는 적절한 방법이 없다. 둘째, GWT 엔진에서 Key 클래스를 변환할 수 없기 때문에 이 클래스를 사용하는 모델 오브젝트를 GWT 애플리케이션에서 사용할 수 없다.

물론 이 기사를 쓰던 당시에는 Google App Engine for Java가 프리뷰 모드로 제공되고 있었기 때문에 서비스가 정상적으로 제공되는 상황은 아니었다. JDO의 관계에 대한 설명서를 보면 명확하게 알 수 있을 것이다. 하지만 이 설명서는 흔하지도 않을 뿐만 아니라 불완전한 예제가 들어 있다는 점이 아쉬운 부분이다.

App Engine for Java 개발 킷에는 여러 가지 예제 프로그램이 들어 있다. 이들 중 많은 예제에서는 JDO를 사용하고 있으며 JPA를 사용하는 예제는 없다. 그리고 jdoexamples를 포함한 그 어떤 예제에도 단순한 관계로 구성된 단일 예제가 없다. 대신 모든 예제에서는 단 하나의 오브젝트를 사용하여 데이터 저장소에 데이터를 저장한다. Google App Engine for Java 토론 그룹에는 단순한 관계를 사용하는 방법에 대한 많은 질문과 소수의 답변이 올라와 있다. 일부 개발자의 경우 관계에 대한 문제를 해결한 것이 분명하지만 그 과정이 복잡하고 많은 노력이 필요하다는 것을 알 수 있다.

App Engine for Java에서의 관계에 대한 결론은 결국 JDO 또는 JPA의 지원이 충분하지 못하기 때문에 사용자가 관계를 관리해야 한다는 것이다. 하지만 Google의 BigTable은 확장 가능한 애플리케이션을 개발하는 데 적합한 검증된 기술이기에 이 기술을 기반으로 많은 작업을 수행할 수 있다. BigTable을 사용할 경우에는 완성도가 낮은 API를 다루지 않아도 된다. 그에 반해 하위 레벨 API를 다루어야 한다는 점도 간과해선 안된다.

AppEngine for Java의 JDO(Java Data Objects)

기존 Java 애플리케이션을 App Engine for Java로 이식할 수 없고 심지어 관계로 인해 문제가 있는 경우라도, 이 플랫폼을 사용할 수 있는 지속성 시나리오가 여전히 존재한다. 이제 마지막으로 App Engine for Java 지속성의 작동 방법을 경험할 수 있는 작업 예제를 살펴보자. 먼저 Part 2에서 작성한 연락처 관리 애플리케이션부터 시작하자. 여기에서는 App Engine for Java 데이터 저장소 기능을 사용하여 Contact 오브젝트의 지속성을 유지하기 위한 지원을 추가하는 절차에 대해 설명한다.

이전 기사에서는 Contact 오브젝트에 대해 CRUD 작업을 수행하는 간단한 GWT GUI를 작성하면서 Listing 1과 같은 간단한 인터페이스를 정의했다.


Listing 1. 간단한 ContactDAO 인터페이스

package gaej.example.contact.server;

import java.util.List;

import gaej.example.contact.client.Contact;

public interface ContactDAO {
	void addContact(Contact contact);
	void removeContact(Contact contact);
	void updateContact(Contact contact);
	List<Contact> listContacts();
}


그런 다음 Listing 2와 같이 메모리 내 컬렉션의 데이터에 대한 작업을 수행하는 모의 버전을 작성했다.


Listing 2. DAO를 모방한 ContactDAOMock

package gaej.example.contact.server;

import gaej.example.contact.client.Contact;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class ContactDAOMock implements ContactDAO {

	Map<String, Contact> map = new LinkedHashMap<String, Contact>();
	
	{
		map.put("rhightower@mammatus.com", new Contact("Rick Hightower", 
                                 "rhightower@mammatus.com", "520-555-1212"));
		map.put("scott@mammatus.com", new Contact("Scott Fauerbach", 
                                 "scott@mammatus.com", "520-555-1213"));
		map.put("bob@mammatus.com", new Contact("Bob Dean", 
                                 "bob@mammatus.com", "520-555-1214"));

	}
	
	public void addContact(Contact contact) {
		String email = contact.getEmail();
		map.put(email, contact);
	}

	public List<Contact> listContacts() {
		return Collections.unmodifiableList(new ArrayList<Contact>(map.values()));
	}

	public void removeContact(Contact contact) {
		map.remove(contact.getEmail());
	}

	public void updateContact(Contact contact) {		
		map.put(contact.getEmail(), contact);
	}

}


이제 모의 구현을 Google App Engine 데이터 저장소와 통신하는 버전의 애플리케이션으로 바꾸면 어떻게 되는지 살펴보자. 이 예제에서는 JDO를 사용하여 Contact 클래스의 지속성을 유지한다. Google Eclipse Plugin을 사용하여 작성된 애플리케이션에는 이미 JDO를 사용하는 데 필요한 모든 라이브러리가 포함되어 있으며 jdoconfig.xml 파일도 포함되어 있다. 따라서 Contact 클래스에 어노테이션을 지정하기만 하면 JDO를 사용할 준비가 완료된다.

Listing 3에서는 JDO API를 사용하여 오브젝트를 지속적으로 유지하고, 쿼리하고, 업데이트하고, 삭제할 수 있도록 확장된 ContactDAO 인터페이스를 보여 준다.


Listing 3. JDO를 사용하는 ContactDAO

package gaej.example.contact.server;

import gaej.example.contact.client.Contact;

import java.util.List;

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

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

	public static PersistenceManagerFactory getPersistenceManagerFactory() {
		return pmfInstance;
	}

	public void addContact(Contact contact) {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		try {
			pm.makePersistent(contact);
		} finally {
			pm.close();
		}
	}

	@SuppressWarnings("unchecked")
	public List<Contact> listContacts() {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		String query = "select from " + Contact.class.getName();
		return (List<Contact>) pm.newQuery(query).execute();
	}

	public void removeContact(Contact contact) {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		try {
			pm.currentTransaction().begin();

			// We don't have a reference to the selected Product.
			// So we have to look it up first,
			contact = pm.getObjectById(Contact.class, contact.getId());
			pm.deletePersistent(contact);

			pm.currentTransaction().commit();
		} catch (Exception ex) {
			pm.currentTransaction().rollback();
			throw new RuntimeException(ex);
		} finally {
			pm.close();
		}
	}

	public void updateContact(Contact contact) {
		PersistenceManager pm = getPersistenceManagerFactory()
				.getPersistenceManager();
		String name = contact.getName();
		String phone = contact.getPhone();
		String email = contact.getEmail();

		try {
			pm.currentTransaction().begin();
			// We don't have a reference to the selected Product.
			// So we have to look it up first,
			contact = pm.getObjectById(Contact.class, contact.getId());
			contact.setName(name);
			contact.setPhone(phone);
			contact.setEmail(email);
			pm.makePersistent(contact);
			pm.currentTransaction().commit();
		} catch (Exception ex) {
			pm.currentTransaction().rollback();
			throw new RuntimeException(ex);
		} finally {
			pm.close();
		}
	}

}

메소드

이제 Listing 3의 각 메소드에서 어떤 작업이 수행되는지 살펴보자. 메소드 이름이 낯설 수도 있겠지만 메소드에서 수행되는 작업은 거의 대부분 익숙할 것이다.

먼저 PersistenceManager에 액세스하기 위해 정적 PersistenceManagerFactory를 작성했다. 이전에 JPA로 작업했다면 PersistenceManager는 JPA의 EntityManager와 유사하다. Hibernate로 작업했다면 PersistenceManager는 Hibernate Session과 비슷하다. 기본적으로 PersistenceManager는 JDO 지속성 시스템에 대한 기본 인터페이스로 데이터베이스에 대한 세션을 나타낸다. getPersistenceManagerFactory() 메소드는 Listing 4와 같이 정적으로 초기화된 PersistenceManagerFactory를 리턴한다.


Listing 4. PersistenceManagerFactory를 리턴하는 getPersistenceManagerFactory()

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

public static PersistenceManagerFactory getPersistenceManagerFactory() {
	return pmfInstance;
}


addContact() 메소드는 데이터 저장소에 새 연락처를 추가한다. 이 작업을 수행하기 위해 이 메소드는 PersistenceManager 인스턴스를 작성한 다음 PersistenceManagermakePersistence() 메소드를 호출해야 한다. makePersistence() 메소드는 비어 있는 Contact 오브젝트(사용자가 GWT GUI를 통해 해당 정보를 입력함)를 받아서 지속성 오브젝트로 설정한다. Listing 5에서는 이 모든 작업을 보여 준다.


Listing 5. addContact()

public void addContact(Contact contact) {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	try {
		pm.makePersistent(contact);
	} finally {
		pm.close();
	}
}

Listing 5를 보면 persistenceManagerfinally 블록에 포함되어 있는 것을 볼 수 있다. 이 코드는 persistenceManager에 연결된 리소스를 정리하기 위한 것이다.

Listing 6의 listContact() 메소드는 persistenceManager로부터 검색을 수행할 쿼리 오브젝트를 작성한다. 그런 다음 execute() 메소드를 호출하면 데이터 저장소에 있는 Contact의 목록이 리턴된다.


Listing 6. listContact()

@SuppressWarnings("unchecked")
public List<Contact> listContacts() {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	String query = "select from " + Contact.class.getName();
	return (List<Contact>) pm.newQuery(query).execute();
}


Listing 7의 removeContact() 메소드는 ID를 기준으로 연락처를 검색한 후 dataStore에서 해당 연락처를 제거한다. 하지만 GWT GUI에서 가져온 Contact는 JDO를 알지 못하므로 연락처를 직접 삭제해서는 안되며 PersistenceManager 캐시에 연관된 Contact를 가져와야만 삭제할 수 있다.


Listing 7. removeContact()

public void removeContact(Contact contact) {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	try {
		pm.currentTransaction().begin();

		// We don't have a reference to the selected Product.
		// So we have to look it up first,
		contact = pm.getObjectById(Contact.class, contact.getId());
		pm.deletePersistent(contact);

		pm.currentTransaction().commit();
	} catch (Exception ex) {
		pm.currentTransaction().rollback();
		throw new RuntimeException(ex);
	} finally {
		pm.close();
	}
}

Listing 8의 updateContact() 메소드는 removeContact() 메소드와 마찬가지로 Contact를 검색한다. 그런 다음 updateContact() 메소드는 Contact의 특성을 복사한다. 이러한 특성은 Contact에 대한 인수로 전달된 후 PersistenceManager를 통해 검색된다. 그런 다음 PersistenceManager는 검색된 오브젝트의 변경 여부를 확인한다. 오브젝트가 변경되었으면 트랜잭션이 커미트될 때 PersistenceManager가 데이터베이스에 변경 사항을 적용한다.


Listing 8. updateContact()

public void updateContact(Contact contact) {
	PersistenceManager pm = getPersistenceManagerFactory()
			.getPersistenceManager();
	String name = contact.getName();
	String phone = contact.getPhone();
	String email = contact.getEmail();

	try {
		pm.currentTransaction().begin();
		// We don't have a reference to the selected Product.
		// So we have to look it up first,
		contact = pm.getObjectById(Contact.class, contact.getId());
		contact.setName(name);
		contact.setPhone(phone);
		contact.setEmail(email);
		pm.makePersistent(contact);
		pm.currentTransaction().commit();
	} catch (Exception ex) {
		pm.currentTransaction().rollback();
		throw new RuntimeException(ex);
	} finally {
		pm.close();
	}
}

오브젝트 지속성에 대한 어노테이션

Contact를 지속적으로 유지하기 위해서는 @PersistenceCapable 어노테이션을 사용하여 지속 가능한 오브젝트로 식별해야 한다. 그런 다음 Listing 9와 같이 지속 가능한 모든 필드에 어노테이션을 지정해야 한다.


Listing 9. 지속 가능한 Contact

package gaej.example.contact.client;

import java.io.Serializable;

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 Contact implements Serializable {

	private static final long serialVersionUID = 1L;
	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Long id;
	@Persistent
	private String name;
	@Persistent
	private String email;
	@Persistent
	private String phone;

	public Contact() {

	}

	public Contact(String name, String email, String phone) {
		super();
		this.name = name;
		this.email = email;
		this.phone = phone;
	}

	public Long getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

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

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getPhone() {
		return phone;
	}

	public void setPhone(String phone) {
		this.phone = phone;
	}

}

오브젝트 지향 프로그래밍의 특성과 인터페이스별 설계의 원칙에 따라 원래의 ContactDAOMock을 새 ContactJdoDAO로 바꿀 수 있다. 그러면 변경 없이도 GWT GUI가 JDO와 정상적으로 작동한다.

마지막으로 이렇게 변경하고 나면 DAO가 서비스에서 인스턴스화되는 방법이 Listing 10처럼 바뀌게 된다.


Listing 10. RemoteServiceServlet

public class ContactServiceImpl extends RemoteServiceServlet implements ContactService {
	private static final long serialVersionUID = 1L;
	//private ContactDAO contactDAO = new ContactDAOMock();
	private ContactDAO contactDAO = new ContactJdoDAO();
...

결론

이 세 번째 기사에서는 확장 가능한 애플리케이션 전달을 위한 초석의 하나인 지속성 기능이 현재 Google App Engine for Java에서 지원되는 모습을 살펴보았다. 발전을 거듭하고 있는 플랫폼인 점을 감안하더라도 전반적으로 기대에 미치지 못한다는 느낌을 받았다. App Engine for Java 프리뷰 릴리스용으로 작성된 애플리케이션은 JDO 또는 JPA를 사용할 수 있기는 하지만 아직까지는 App Engine의 지속성 인프라에 대한 의존도가 너무 높은 편이다. 프리뷰 버전의 App Engine for Java에는 지속성 프레임워크에 대한 설명서가 거의 없으며 App Engine for Java와 함께 제공되는 예제를 사용하여 아주 단순한 관계를 구현하는 것마저도 거의 불가능하다.

JDO 및 JPA 구현의 완성도가 높아졌다고는 하지만 아직까지는 App Engine for Java 애플리케이션을 작성해서 RDBMS 기반 엔터프라이즈 애플리케이션으로 쉽게 이식할 수 있는 단계까지는 도달하지 못했다. 적어도 이식 작업을 수행하기 위한 복잡한 코딩 작업이 필요하다.

지속성 기능은 시간이 지나면서 완성된 모습을 갖추게 될 것이다. 그러나 지금 당장 App Engine for Java로 작업해야 한다면 이 Java API를 무시하고 하위 레벨 Datastore API를 직접 사용하는 것이 좋다. App Engine for Java 플랫폼으로 작업할 수 있지만 JPA 및/또는 JDO 작업에 익숙해서 이들 API를 사용하려고 한다면 필요한 기능을 익히는 데 시간이 필요할 것이다. 왜냐하면 이 기사의 시작 부분에서 언급했듯이 추상화 기능이 강력하지 않을 뿐만 아니라 해당 기능이 정상적으로 작동하지 않을 수도 있고 기능에 대한 설명도 부족하기 때문이다.



참고자료

교육

제품 및 기술 얻기

토론


필자소개

Rick Hightower는 클라우드 컴퓨팅, GWT, Java EE, Spring 및 Hibernate 개발과 관련된 교육 서비스를 전문으로 제공하는 회사인 Mammatus Inc.의 CIO이다. 유명한 Java Tools for Extreme Programming의 공동 저자이며 TheServerSide.com에서 여러 해 동안 다운로드 수가 가장 높았던 Struts Live 초판의 저자이다. IBM developerWorks에 많은 기사와 튜토리얼을 기고하고 있는 그는 Java Developer's Journal의 편집위원으로 활동하고 있으며 DZone에도 Java 및 Groovy와 관련된 많은 글을 기고하고 있다.




기사에 대한 평가


보다 나은 서비스를 제공하기 위함이오니 잠시 짬을 내어 이 양식을 제출하여 주십시오.



 


 


 


이 문서 북마킹 하기

mar.gar.in mar.gar.in naver naver eolin eolin del.icio.us del.icio.us





위로


Java 및 모든 Java 관련 상표는 미국 또는 기타 국가에서 사용되는 Sun Microsystems, Inc.의 상표이다. Microsoft, Windows, Windows NT 및 Windows 로고는 미국 또는 기타 국가에서 사용되는 Microsoft Corporation의 상표이다. 기타 회사, 제품, 및 서비스명은 다른 상표나 서비스 마크일 수 있습니다.

developerWorks 콘텐트를 다른 사이트에 전재하기:
developerWorks 콘텐트에 대한 저작권은 IBM에 있습니다. IBM의 서면 허가나 원본 저자의 허락이 없이는 전재를 금합니다. 저희 콘텐트를 전재하시려면 IBM developerWorks 담당자 에게 문의하십시오.
    IBM 소개 개인정보 보호정책 문의