JavaServer Faces 기술로 Java Persistence 사용하기
글쓴이: Roger Kitain
Roger Kitain은 JavaServer Faces 기술의 공동 스펙 리더로, 1997년부터 서버 측 웹 기술 및 제품 분야에 폭넓게 참여해 왔다. Roger는 또한 2001년에 레퍼런스 구현 팀의 멤버로서 JavaServer Faces 기술 관련 업무를 맡기 시작했으며, Servlet 및 JSP 기술을 통해 풍부한 경험을 축적했다. 최근에 그는 JSF의 다양한 렌더링 기술에 참여하여 왕성한 활동을 벌이고 있다.
흔히 JSF로 약칭되는 JavaServer Faces 기술은 Java EE(Java Platform, Enterprise Edition)와 J2EE(Java 2 Platform, Enterprise Edition) 애플리케이션의
사용자 인터페이스(UI) 구축을 간소화시켜 주는 일종의 프레임워크를 뜻한다.
"JavaServer Faces 기술 소개(영문)"에서 이 기술에 관해 간략하게 소개하고 있으며, JSF 프레임워크에 의해 모델링되는 GUI 컴포넌트가 포함된 JSF 애플리케이션을 제작하는 방법도 소개했었다. 또한 한글 테크팁 뉴스레터에서는 JavaServer Faces 기술과 사용자 정의 컴포넌트라는 테크팁(http://kr.sun.com/developers/techtips/e2004_1123.html#2)을 통해 JavaServer Faces 기술로 커스텀 컴포넌트를 제작하는 방법에 대해 알아보았다.
본 테크팁에서는 JSF 프레임워크로 Java Persistence API를 사용하는 예제 애플리케이션을 살펴보기로 한다. 예제 애플리케이션용 코드가 포함된 패키지가 첨부되어 있으며, 본 테크팁의 코드 예제들은 패키지에 포함되어 있는 예제 소스 코드에서 뽑아온 것이다.
Java Persistence API와 JSF
Java Persistence API(또는 Java Persistence로 약칭, http://java.sun.com/javaee/technologies ··· ence.jsp)는
엔티티 퍼시스턴스를 간소화해줄 뿐 아니라 EJB(Enterprise JavaBeans) 2.1 기술에서는 이용할 수 없었던 추가 기능을 제공한다.
아울러, 관계 데이터를 자바 오브젝트에 매핑하는 방법의 세부사항을 모두 처리하고 오브젝트-관계 매핑을 표준화한다.
Java Persistence API는 EJB 3.0 스펙에 포함되어 있다. (http://jcp.org/en/jsr/detail?id=220)
웹 애플리케이션에서 Java Persistence의 사용을 편리하게 만들어주는 JSF 기능이 몇 가지 있다.
즉, 값 바인딩(Value Binding) 표현은 UI 컴포넌트를 모델 티어 데이터에 바인드하여 form submit이 이루어질 때 데이터가 모델에 push될 수 있도록 하고, 메소드 바인딩 표현은 UI 컴포넌트를 액션 메소드에 바인드하여 컴포넌트 활성화 시 컴포넌트에 첨부된 메소드가 실행될 수 있도록 한다. 예를 들어, 버튼을 누르면 작성한 폼이 제출되는 경우가 바로 그것이다. 한편, Managed Beans는 요구 시에 인스턴스화될 수 있는 JSF 애플리케이션 빈으로, JSF 애플리케이션의 모델 티어이며 이 빈의 인스턴스는 요청--세션 또는 애플리케이션 스코프--에
저장할 수 있다.
Java Persistence API에는 또한 데이터베이스에 엔티티를 퍼시스트하는 데 사용할 수 있는 EntityManager API가 포함되어 있으므로 사용자는 EntityManager 인스턴스를 JSF 매니지드 빈에 주입(inject)하여 데이터를 퍼시스트할 수 있다.
EntityManager 인스턴스는 thread safe하지 않으므로, 세션 또는 애플리케이션의 스코프를 가지는 JSF 매니지드 빈에 이를 주입해서는 안 된다.
다음은 EntityManager 인스턴스를 JSF 매니지드 빈에 주입하는 예제이다.
(예제의 빈, BookBean을 애플리케이션 스코프 JSF 매니지드 빈이라고 가정)
public class BookBean {
@PersistenceContext EntityManager em;
...
public String placeOrder() {
...
em.persist(order);
...
}
}
하지만 이는 그다지 좋은 방법이라고 볼 수는 없다. 이보다 더 좋은 방법이 있는데,
그것은 바로 EntityManagerFactory를 이용하여 EntityManager 인스턴스를 생성하는 것이다.
또한 이 때 요청 스코프 JSF 매니지드 빈을 사용하게 되는데, 이렇게 하면 thread safety가 보장된다.
public class BookBean {
@PersistenceUnit EntityManagerFactory emf;
...
public String placeOrder() {
...
EntityManager em = emf.createEntityManager();
...
em.persist(order);
...
}
}
JSF 퍼시스턴스 예제
JSF에 익숙한 사용자라면 JSF의 "Guess Number" 애플리케이션에 대해 잘 알고 있을 것이다.
이 애플리케이션은 사용자에게 일정 범위(1~10) 내에서 애플리케이션이 선택한 숫자를 추측할 것을 요구한다.
아래의 도표는 Guess Number 애플리케이션의 흐름을 나타낸다.

애플리케이션은 먼저 일정 범위의 숫자를 프롬프트하는 HTML 페이지를 디스플레이하고,
사용자는 숫자를 입력한 후에 "Guess" 버튼을 눌러 정보를 제출한다. 사용자가 범위 밖의 숫자를 제출하거나 유효하지 않은 디지트(가령 문자)를 제출하면 애플리케이션은 JSF의 검증 오류 메시지를 디스플레이한다.
사용자가 유효한 숫자를 제출하면 추측의 정확성 여부를 알려주는 '응답' 페이지가 표시된다.
응답 페이지에는 "Again" 버튼이 함께 디스플레이되는데, 이 버튼을 누르면 초기 페이지로 돌아가 다시 추측을 할 수 있게 된다. 사용자는 추측이 맞을 때까지 페이지 앞뒤로 이동할 수 있으며, 추측이 맞았을 때 사용자가 "Again" 버튼을 누르면 바로 새 게임이 시작된다.
자바 퍼시스턴스는 모든 추측 시도를 데이터베이스에 저장하거나 데이터베이스에 저장된 추측 시도를 디스플레이(또는 플레이 백)하는 데 사용된다.
Guess Number 애플리케이션을 위한 코드 소스의 일부를 살펴보기로 하자.
다음은 데이터베이스 테이블을 생성하는 스크립트이다.
create table USERGUESS
(ID INT NOT NULL,
GUESS VARCHAR(10) NOT NULL,
OUTCOME VARCHAR(10) NOT NULL,
PRIMARY KEY(ID));
다음은 데이터베이스 테이블에 상응하는 엔티티 클래스, UserGuess이다.
...
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class UserGuess implements java.io.Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String guess;
public String getGuess() {
return guess;
}
public void setGuess(String guess) {
this.guess = guess;
}
private String outcome;
public String getOutcome() {
return outcome;
}
public void setOutcome(String outcome) {
this.outcome = outcome;
}
}
UserGuess 엔티티는 데이터베이스 칼럼에 상응하는 id, guess, outcome 등의 세 가지 속성으로 구성된다.
id는 데이터 열이 데이터베이스에 추가될 때 자동으로 생성되는 기본 키(primary key)이다.
JSF 매니지드 빈, UserNumberBean.java에는 애플리케이션을 위한 처리 논리(processing logic)가 포함되어 있다.
이 빈에는 validate 등과 같은 다른 메소드도 있지만, 우리는 자바 퍼시스턴스를 사용하는 코드에 주로 초점을 맞추기로 하자.
이 애플리케이션은 컨테이너 매니지드 EntityManager를 사용하므로 반드시 JTA(Java Transaction API)를 통해 제어되어야 한다.
먼저 인스턴스를 주입해보자.
...
public class UserNumberBean {
...
// This injects the default entity manager factory
@PersistenceUnit
private EntityManagerFactory emf;
// This injects a user transaction object.
@Resource
private UserTransaction utx;
...
사용자는 어디서나 주입된 EntityManagerFactory 인스턴스를 이용하여 EntityManager를 생성하고 사용할 수 있으며, 또한 주입된 UserTransaction 인스턴스를 이용하여 트랜잭션을 제어할 수 있다.
...
public class UserNumberBean {
...
/??
? This method persists each attempted guess (number).
?/
private String sameGame() {
String message = null;
EntityManager em = null;
try {
em = emf.createEntityManager();
utx.begin();
em.persist(userGuess);
utx.commit();
} catch (Exception ee) {
try {
utx.rollback();
} catch (Exception re) {}
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, "Database Error:",
ee.getCause());
}
message += "Database Error: "+ee.getCause();
} finally {
if (em != null) {
em.close();
}
}
return message;
}
...
/??
? This method clears the database table in preparation
? of a new game.
?/
private String newGame() {
String message = null;
EntityManager em = null;
String statement = "delete from UserGuess";
try {
em = emf.createEntityManager();
utx.begin();
em.createQuery(statement).executeUpdate();
utx.commit();
} catch (Exception ee) {
try {
utx.rollback();
} catch (Exception re) { }
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, "Database Error:",
ee.getCause());
}
message = "Database Error: "+ee.getCause();
} finally {
if (em != null) {
em.close();
}
}
return message;
}
...
클라이언트에서 포스팅하기
초기 페이지에는 숫자를 입력하기 위한 텍스트 필드와 추측을 제출하기 위한 "Guess" 버튼이 있다.
다음은 greeting.jsp 페이지의 단편이다.
...
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core"
prefix="f" %>
...
<h:inputText id="userNo" label="User Number"
value="#{UserNumberBean.userNumber}"
validator="#{UserNumberBean.validate}"/>
<h:commandButton id="guess" action="success"
value="Guess" />
<h:dataTable
border="1"
rendered="#{UserNumberBean.hasGuesses}"
value="#{UserNumberBean.guessList}"
var="guessItem">
<h:column>
<f:facet name="header">
<h:outputText value="Guess" />
</f:facet>
<h:outputText value="#{guessItem.guess}"/>
</h:column>
<h:column>
<f:facet name="header">
<h:outputText value="Outcome" />
</f:facet>
<h:outputText value="#{guessItem.outcome}"/>
</h:column>
</h:dataTable>
...
<h:dataTable> 컴포넌트는 현재 게임의 데이터베이스에 저장된 추측 시도를 디스플레이한다.
초기에 페이지가 렌더링되거나 각각의 새 게임이 시작될 때는 <h:dataTable> 컴포넌트가 렌더링되지 않는데, 이는 "#{UserNumberBean.hasGuesses}" 표현으로 식별되는 UserNumberBean 빈 상의 부울(Boolean) 속성 hasGuesses에 의해
제어된다. true의 값은 추측이 시도되었음을 나타낸다.
...
public class UserNumberBean {
...
// Indicates that we have some attempted guesses.
private boolean hasGuesses = false;
...
/??
? -- Accessor methods for the hasGuesses
? property (used to control rendering of the
? DataTable of attempted guesses).
?/
public void setHasGuesses(boolean hasGuesses) {
this.hasGuesses = hasGuesses;
}
public boolean getHasGuesses() {
return hasGuesses;
}
...
"#{UserNumberBean.guessList}" 표현은 UserGuess 엔티티 빈의 자바 컬렉션을 가리킨다.
렌더링 과정에서 이 표현식을 구할 때 UserNumberBean.getGuessList 메소드는 자바 퍼시스턴스를 이용하여 데이터베이스로부터
컬렉션을 로드한다.
...
public class UserNumberBean {
...
// The attempted guesses.
private Collection <UserGuess> guessList;
...
/??
? This method loads the Collection from the
? database. The Collection is used by the
? DataTable to display attempted guesses.
?/
public Collection <UserGuess> getGuessList() {
EntityManager em = emf.createEntityManager();
guessList = em.createQuery()
"select g from UserGuess g").getResultList();
return guessList;
}
...
<h:dataTable> 컴포넌트는 결과를 렌더링한다.
예제 코드 실행하기
예제 패지키에는 팁에서 다룬 기법을 예시하는 본 팁이 포함되어 있으며,
사용자는 Servlet 2.5 API, JSP(JavaServer Pages) 2.1, JSF(JavaServer Faces) 1.2를 지원하는
어떠한 웹 컨테이너에라도 예제 패키지를 설치할 수 있다. 이 지침은 또한 Java EE 5 SDK를 사용하는 것을 전제로 한다.
예제를 설치하고 실행하려면 다음의 작업 절차를 따르도록 한다.
1. Java EE 5 SDK를 아직 구하지 못했다면 Java EE 다운로드 페이지에서 다운로드 받아 설치한다.
http://java.sun.com/javaee/downloads/index.jsp
2. 아래의 환경 변수를 설정한다.
* JAVAEE_HOME. Java EE 5 SDK가 설치된 곳을 가리켜야 한다.
* JAVA_HOME. 사용자 시스템에서의 JDK 5.0의 위치를 가리켜야 한다.
JDK는 다운로드한 Java EE 5 SDK 번들에 포함되어 있다. (Windows에서는 jdk 서브디렉토리에 위치함.)
PATH 환경변수에 $JAVA_HOME/bin과 $JAVAEE_HOME/bin을 추가한다.
해당 팁의 예제 패키지를 다운로드하여 압축을 푼다. 이 때, 새로 압축이 풀린 디렉토리가
<sample_install_dir>/guessNumber로 표시되어야 하는데, 여기서 <sample_install_dir>은
예제 패키지가 설치된 디렉토리를 나타낸다. 예를 들어, Windows의 C:\에 압축을 풀었다면
새로 생성된 디렉토리는 C:\guessNumber가 되어야 한다. guessNumber 디렉토리에는 애플리케이션을 위한
웹 아카이브 web-app.war와 소스 뷰를 위한 서브디렉토리 src 및 web이 포함되어 있다.
이와 더불어 데이터베이스 테이블 생성을 위한 위한 스크립트, db.sql도 포함되어 있다.
다음 명령어를 입력하여 Derby 데이터베이스를 시작한다.
$JAVAEE_HOME/bin/asadmin start-database
다음 명령어를 입력하여 애플리케이션 서버를 시작한다.
$JAVAEE_HOME/bin/asadmin start-domain domain1
애플리케이션의 해당 데이터베이스 테이블을 생성한다. 다음이 포함되도록 CLASSPATH를 설정한다.
$JAVAEE_HOME/javadb/lib/derbyclient.jar $JAVAEE_HOME/javadb/lib/derbytools.jar $JAVAEE_HOME/javadb/lib/derby.jar
<sample_install_dir>/guessNumber/ 디렉토리로 이동한다.
ij로 들어간다. java org.apache.derby.tools.ij
"ij" 프롬프트에서 다음 명령어를 입력한다.
ij>DRIVER 'org.apache.derby.jdbc.ClientDriver';
ij>CONNECT 'jdbc:derby://localhost:1527/sun-appserv-samples';
데이터베이스가 아직 존재하지 않을 경우에는 다음 명령어를 입력해야 한다.
CONNECT 'jdbc:derby://localhost:1527/sun-appserv-samples;create=true';
"ij" 프롬프트에서 다음을 실행한다.
ij> run 'db.sql';
다음을 복사하여 예제를 배포한다.
<sample_install_dir>/guessNumber/wep-app.war to $JAVAEE_HOME/domains/domain1/autodeploy
브라우저를 실행하고 다음 URL을 연다. http://localhost:8080/web-app/
본 테크팁은 Java EE 5 SDK를 이용하여 개발되었습니다.
JavaEE 5 SDK 다운로드?
http://java.sun.com/javaee/downloads/index.jsp
-샘플 아카이브 다운로드 받기-
JavaServer Faces 기술로 Java Persistence 사용하기
http://java.sun.com/mailers/techtips/en ··· lers.zip
"Java EE" 카테고리의 다른 글
- Attach API (댓글 18개 / 트랙백 1개) 2007/09/03
- JSP 2.0 EXPRESSION LANGUAGE (댓글 1개 / 트랙백 0개) 2004/02/05
- 환경 엔트리를 이용해서 배포의 사용자 정의하기 (댓글 2개 / 트랙백 0개) 2003/12/24
- JAX-WS를 이용한 웹 서비스 개발 (댓글 1개 / 트랙백 0개) 2006/01/18
- EJB 2.1로 메시지 구동 빈 이용하기 (댓글 1개 / 트랙백 0개) 2005/05/18
- EclipseLink를 사용하여 JPA에서 반복 불가능한 읽기 방지 (댓글 0개 / 트랙백 1개) 2008/07/09
- 컴포넌트 시스템과 클래스 로더 경계 (댓글 1개 / 트랙백 0개) 2004/10/05
- JAX-WS Dispatch 및 Provider API를 이용한 문서 처리 (댓글 4개 / 트랙백 0개) 2006/09/15
- POJO를 Persistent Entity로 변환하기 (댓글 1개 / 트랙백 0개) 2005/12/27
- SAAJ 소개 (댓글 1개 / 트랙백 0개) 2005/06/08
댓글을 달아 주세요
자바는 알면 알수록 재미가 있어지네요. 좋은 자료 잘 활용하겠습니다.
2007/09/13 22:42많은 도움 되었읍니다
2007/09/17 22:00좋은 정보 감사해요~
2007/09/19 04:04좋은 정보 얻어가요. 감사합니다.
2007/09/19 22:52