Tutorial: Build a Web Application (JSF) Using JPA
 

튜토리얼: JPA를 이용한 웹 애플리케이션(JSF)의 구축

버전: 5/18/06

서론

이 튜토리얼은 EJB 3.0 Java Persistence API(JPA)를 사용한 웹 애플리케이션의 개발, 패키징, 구축을 위한 기본적인 단계를 설명하고 있습니다.

예제 애플리케이션의 Java Server Faces(JSF) 프리젠테이션 계층은 EJB 3.0 컨테이너 외부의 퍼시스턴스를 위해 JPA를 사용하고 있습니다.

그림 1-1은 튜토리얼에서 사용되는 오브젝트 모델을 보여주고 있습니다.

그림 1-1 튜토리얼 오브젝트 모델

Description of Figure 1-1 follows
"그림 1-1 튜토리얼 오브젝트 모델”에 대한 설명"

JPA에 대한 추가적인 정보는 아래에서 확인할 수 있습니다:

소프트웨어 요구사항

  • JDK 1.5

  • Ant

  • 소스 코드:

    • 예제 소스 코드 (order-jsf-jpa-example.zip) - 바로 구현, 디플로이할 수 있는 완성된 버전의 소스 코드입니다.

    • 튜토리얼 소스 코드 (order-jsf-jpa-tutorial.zip) - 튜토리얼의 시작 부분에서 사용하게 될 소스 코드입니다. 튜토리얼을 진행하면서, 예제 소스 코드와 동일한 내용을 구현해 나가게 됩니다.

  • 데이터베이스:

    어떤 관계형 데이터베이스를 사용해도 무방합니다. 이 튜토리얼에서는 Oracle Database XE를 사용하는 것으로 가정하고 있습니다:

    사용하는 데이터베이스를 위해 권장되는 JDBC 드라이버를 사용하시기 바랍니다.

    Oracle Database XE의 JDBC 드라이버는 다음 디렉토리에서 확인할 수 있습니다: <ORACLE_HOME>\jdbc\lib.


    참고:

    오라클 데이터베이스 환경에서는 다음 파일이 필요할 수도 있습니다 <ORACLE_HOME>\lib\dms.jar.

  • 웹 컨테이너:

    어떤 웹 컨테이너를 사용해도 무방합니다. 이 튜토리얼에서는 아래 두 가지 웹 컨테이너 중 하나를 사용하는 것으로 가정하고 있습니다:

  • TopLink JPA

    위 링크에서 TopLink JPA 인스톨러 JAR 파일을 다운로드할 수 있습니다. (예: glassfish-persistence-installer-X.X-bXX.jar)

셋업 및 설정

이 튜토리얼을 시작하기 전에 필요한 소프트웨어를 먼저 셋업하고 설정해야 합니다:

  1. JDK 5.0 설치

  2. Ant 설치

  3. 예제 및 튜토리얼 소스 파일을 설치합니다:

    • 예제 소스 파일의 압축을 풀 디렉토리를 생성합니다.

      이 디렉토리를 <EXAMPLE_HOME>이라 부르기로 합니다.

    • <EXAMPLE_HOME>디렉토리에 order-jsf-jpa-example.zip 파일의 압축을 풉니다.

      이 디렉토리는 완성된 튜토리얼 소스를 저장하고 있습니다.

    • 튜토리얼 소스 파일의 압축을 풀 디렉토리를 생성합니다.

      이 디렉토리를 <TUTORIAL_HOME>이라 부르기로 합니다.

    • <TUTORIAL_HOME>디렉토리에 order-jsf-jpa-tutorial.zip 파일의 압축을 풉니다.

      이 디렉토리는 튜토리얼 과정에서 사용할 튜토리얼 소스를 저장하고 있습니다.

    <EXAMPLE_HOME>, <TUTORIAL_HOME> 디렉토리의 구조와 컨텐트에 대한 자세한 정보는 튜토리얼 소스 파일의 설명섹션을 참고하시기 바랍니다.

  4. • 데이터베이스를 설치하고 셋업합니다:

  5. 웹 컨테이너를 설치, 셋업합니다:

  6. TopLink JPA의 설치:

    • TopLink JPA 인스톨러JAR 파일을 임시 디렉토리로 이동합니다.

    • o 커맨드 라인에서 아래 명령을 이용하여 다운로드한 TopLink JPA 인스톨러 JAR 파일을 실행합니다 (JDK 1.5를 사용해야 합니다):

      java -jar glassfish-persistence-installer-X.X-bXX.jar
      
      
    • o 라이센스 동의서의 마지막 부분으로 스크롤 다운한 후 Accept를 클릭합니다.

      인스톨러가 아래와 같이 README, 라이센스 파일, TopLink Essentials JAR files 파일의 압축을 풉니다:

      glassfish-persistence\README
      glassfish-persistence\3RD-PARTY-LICENSE.txt
      glassfish-persistence\toplink-essentials.jar
      glassfish-persistence\toplink-essentials-agent.jar
      glassfish-persistence\CDDLv1.0.txt
      
      
    • 아래 디렉토리에 toplink-essentials.jar 파일과 toplink-essentials-agent.jar 파일을 추가합니다.

      • <EXAMPLE_HOME>\lib

      • <TUTORIAL_HOME>\lib

  7. 설치 환경을 검증합니다:

    • Edit the <EXAMPLE_HOME>\persistence-unit\src\META-INF\persistence.xml 파일을 편집하여 데이터베이스 환경에 맞게 아래와 같이 설정합니다:

      <property name="toplink.jdbc.driver" value="<jdbc-driver>"/>
      <property name="toplink.jdbc.url" value="<jdbc-url>"/>
      <property name="toplink.jdbc.password" value="<jdbc-password>"/>
      <property name="toplink.jdbc.user" value="<jdbc-user>"/>
      
      

      위 매개변수에 대한 설명이 아래와 같습니다:

      • <jdbc-driver>는 사용 중인 JDBC 드라이버 클래스의 이름입니다. (예: oracle.jdbc.OracleDriver)

      • <jdbc-url>은 데이터베이스에 대한 JDBC 연결 URL입니다. (예: jdbc:oracle:thin:@myhost:l521:MYSID)

      • <jdbc-password는 데이터베이스에 접속하기 위해 사용하는 JDBC 패스워드입니다. (예: tiger)

      • <jdbc-user>는 데이터베이스에 접속하기 위해 사용하는 JDBC 사용자 이름입니다. (예: scott)

    • 커맨드 라인에서 <EXAMPLE_HOME>으로 디렉토리를 변경하고 아래와 같이 실행합니다.

      ant -f build.xml generate-tables
      ant -f build.xml populate-data
      
      
    • TABLE CREATE 구문이 표준 출력을 통해 출력되는 로그 메시지에 존재하는지 확인합니다.

      이 구문이 존재한다면 데이터베이스에 성공적으로 접속하였음을 의미합니다.

      위 구문이 존재하지 않는다면 데이터베이스 연결에 실패한 것입니다. persistence.xml 파일의 설정을 다시 확인하고 데이터베이스가 정상적으로 실행 중인지 확인합니다.

    • 커맨드 라인에서 <EXAMPLE_HOME> 디렉토리로 변경하고 아래와 같이 실행합니다:

      ant -f build.xml package.webapp
      
      
    • <EXAMPLE_HOME>\web-application\deploy\jpa-example.war 파일을 디플로이(deploy)합니다. 아래 섹션을 참고합니다:

    • 애플리케이션을 실행합니다. 아래 섹션을 참고합니다:

      그림 1-2와 같은 튜토리얼 메인 페이지가 표시되는지 확인합니다.

      이제 튜토리얼을 시작할 준비가 모두 완료되었습니다.(튜토리얼 단계 설명 보기).

      그림 1-2 튜토리얼 애플리케이션 메인 페이지



      Description of Figure 1-2 follows

      "그림 1-2 튜토리얼 애플리케이션 메인 페이지”에 대한 설명

튜토리얼 소스 파일의 이해

order-jsf-jpa-example.zip 파일과 order-jsf-jpa-tutorial.zip 파일의 압축을 풀면 (셋업 및 설정 참고), <EXAMPLE_HOME>,<TUTORIAL_HOME> 디렉토리가 생성됩니다.

이 디렉토리는 예제 1-1에 보여지는 것과 같은 구조를 갖고 있습니다. 표 1-1은 서브디렉토리에서 가장 중요한 컨텐트들을 설명하고 있습니다.

<EXAMPLE_HOME>은 지금 바로 디플로이, 실행할 수 있는 완성된 버전의 튜토리얼 코드를 포함하고 있습니다.

<TUTORIAL_HOME> 서브디렉토리는 이 튜토리얼을 시작하면서 사용할 소스 코드를 포함하고 있습니다. 튜토리얼을 완료하면 튜토리얼 코드와 동일한 예제 코드를 얻게 됩니다.

예제 1-1 <EXAMPLE_HOME>과 <TUTORIAL_HOME>의 구조

build.xml
extras/
    bin/
    classes/
    src/oracle/toplink/jpa/example/inventory/tools/
        DDLGenerator.java
        Populator.java
lib/
    - JSF and JSTL JARs
    - JDBC driver JARs (user-supplied)
    - TopLink JPA JARs (user-supplied)
persistence-unit/
    classes/
    deploy/
    src/META-INF
        persistence.xml
    src/oracle/toplink/jpa/example/inventory/
        model/
            Inventory.java
            Item.java
            Order.java
        nonentity/
            Category.java
web-application/
    classes/
    deploy/
    public_html/
        *.jsp
        css/
        images/
        WEB-INF/
            faces-config.xml
            web.xml
    src/oracle/toplink/jpa/example/inventory/
        services/
            InventoryService.java
            OrderService.java
        services/impl/
            JPAResourceBean.java
            ManagedInventoryBean.java
            ManagedOrderBean.java
        ui/
            InventoryManagerBean.java

표 1-1 <EXAMPLE_HOME>과 <TUTORIAL_HOME>의 중요 서브디렉토리

서브디렉토리 설명

extras/

튜토리얼 애플리케이션에 의해 직접 실행되지 않는 소스 파일들을 포함하고 있습니다.

    classes/

extra/src 소스 파일이 튜토리얼 build.xml Ant 스크립트에 의해 컴파일되는 디렉토리입니다

    src/

src/oracle/toplink/jpa/example/inventory/tools/ 디렉토리는 튜토리얼 build.xml Ant 스크립트의 타겟과 함께 추가적으로 호출되는 헬퍼 클래스를 저장하고 있습니다:

  • generate-tables - invokes DDLGenerator를 호출하여 튜토리얼 애플리케이션의 데이터베이스 테이블을 생성합니다.

  • populate-data - invokes Populator를 호출하여 튜토리얼 애플리케이션의 데이터베이스 테이블에 데이터를 입력합니다.

lib/

lib 디렉토리는 튜토리얼 애플리케이션의 의존하고 있는 JAR 파일을 포함하고 있습니다. 특히 다음과 같은 파일이 포함되어 있습니다.

  • JSF, JSTL JARs 파일 (기본 포함)

  • JDBC 드라이버 JAR 파일 (사용자 제공)

  • TopLink JPA JAR 파일 (사용자 제공)

persistence-unit/

퍼시스턴트 JPA 엔티티를 위한 소스 파일을 저장하고 있습니다

    classes/

퍼시스턴트 유닛 아카이브에 패키징하기 전에 퍼시스턴트 유닛 소스 파일이 생성되는 임시 디렉토리입니다.

    deploy/

이 디렉토리는 빌드, 패키지된 퍼시스턴스 유닛 persistence-unit.jar 파일을 포함하고 있습니다.

    src/

이 디렉토리는 도메인 클래스를 포함하는 퍼시스턴스 유닛에 요구되는 소스를 포함하고 있습니다.

또 JPA 표준에 명시된 기준에 따라 META-INF 서브디렉토리에 persistence.xml 파일을 저장하고 있습니다.

web-application/

JPA 엔티티를 사용하는 튜토리얼 애플리케이션의 서비스와 사용자 인터페이스를 구현하기 위한 소스 파일을 저장하고 있습니다.

    classes/

디플로이(deploy) 가능한 아카이브에 패키징하기 전에 소스의 빌드 작업을 수행하는 임시 디렉토리입니다.

    deploy/

빌드 완료되어 디플로이(deploy) 가능한 상태의 웹 애플리케이션(jpa-example.war)을 저장한 디렉토리입니다. 이 파일은 Ant 타겟 package.webapp을 이용하여 빌드 됩니다.

    public_html/

JSP, 스타일시트(css/), 이미지(images/), 디플로이먼트 디스크립터(WEB-INF/web.xml) 등을 포함하는 애플리케이션의 프리젠테이션 계층을 저장하고 있습니다.

    src/

애플리케이션의 컨트롤러 계층을 위한 소스를 저장한 디렉토리입니다

  • oracle/toplink/jpa/example/inventory/services 디렉토리는 퍼시스턴스 계층에 액세스를 제공하는 서비스 인터페이스의 소스를 포함하고 있습니다.

  • oracle/toplink/jpa/example/inventory/services/impl 디렉토리는 서비스 인터페이스를 구현하는 클래스들을 포함하고 있습니다.

    ManagedOrderBean 클래스는 OrderService 인터페이스 구현에 사용됩니다.

    ManagedInventoryBean 클래스는 InventoryService 인터페이스 구현에 사용됩니다.

    JPAResourceBean은 튜토리얼 애플리케이션이 퍼시스턴트 유닛으로 EntityManager 을 확보하기 위해 사용하는 헬퍼 클래스(helper class)입니다.

  • oracle/toplink/jpa/example/inventory/ui 디렉토리는 튜토리얼 애플리케이션 사용자 인터페이스의 기반을 이루는 InventoryManagerBean 클래스를 포함하고 있습니다


튜토리얼 단계

튜토리얼 애플리케이션의 생성을 위한 중요 단계가 아래와 같습니다. 각 단계별로<TUTORIAL_HOME> 소스 파일의 수정 작업이 수행됩니다.

1 단계: 엔티티의 주석 처리(Annotating)

주석(Annotation)이란 Java 소스 코드에 메타데이터를 추가하기 위한 한 가지 방법으로, 관련 Java 클래스 파일과 함께 컴파일된 후 런타임에 JPA 퍼시스턴스 프로바이더에 의해 인터프리트 방식으로 실행되어 JPA 동작(behavior)을 관리하는데 됩니다.

퍼시스턴트 클래스에 추가된 JPA 주석은 JPA 퍼시스턴스 프로바이더가 퍼시스턴스를 위해 어떤 클래스를 필요로 하는지 정의하고, 퍼시스턴트 클래스의 상세 정보를 표현하기 위해 사용됩니다.

대부분의 경우에는 JPA 디폴트를 그대로 사용해도 무방합니다. 이 튜토리얼에서는 기본적인 주석과 옵션으로 자주 활용되는 주석의 사용 방법을 예시하고 있습니다.

좀 더 자세한 정보는 JPA Annotation Reference에서 확인하실 수 있습니다.

이 섹션에서 설명되는 내용이 아래와 같습니다:

Inventory 엔티티의 주석 처리

이 섹션에서는 Inventory.java 파일을 주석 처리하는 방법을 설명하고 있습니다. 이 파일은 POJO(plain old java object)로 구성되었으며, JPA를 이용하여 관리할 퍼시스턴트 엔티티 중 하나입니다.

  1. 편집기를 이용하여 <TUTORIAL_HOME>\persistence-unit\src\oracle\toplink\jpa\example\inventory\model\Inventory.java 소스 파일을 엽니다.

  2. 예제 1-2와 같이 @Entity 주석을 사용하여 이 클래스를 퍼시스턴트 엔티티로 지정합니다.

    예제 1-2 Inventory.java의 @Entity 활용

    @Entity
    public class Inventory {
        ...
    }
    
    

    엔티티 네임은 디폴트로 클래스 네임과 같게 설정됩니다.

    또 디폴트 설정에서는 엔티티 네임이 엔티티 데이터베이스 테이블 네임과 동일하게 설정됩니다. 이름을 임의로 변경하고자 하는 경우에는 @Table 주석을 사용하면 됩니다. (Order 엔티티의 주석 처리 참고)

    엔티티의 테이블에는 모든 퍼시스턴트 필드가 저장됩니다. JPA 표준은 @Transient 주석이 붙지 않는 한, 엔티티의 모든 필드를 퍼시스턴트로 지정할 것을 명시하고 있습니다.

  3. 예제 1-3 과 같이 @Id 주석을 사용하여 엔티티의 프라이머리 키가 되는 퍼시스턴트 필드를 지정합니다.

    예제 1-3 Inventory.java의 @Id 사용

    @Entity
    public class Inventory {
        @Id
        protected long id;
        ...
        public void setItem(Item item) {
            this.item = item;
            this.id = item.getSKU();
        }
        ...
    }
    
    

    각각의 엔티티는 프라이머리 키를 가져야 합니다.

    Inventory 클래스의 id 필드가 프라이머리 키로 지정됩니다. 이 경우, Inventory 엔티티가 setItem 메소드를 통해 Item으로부터 프라이머리 키 값을 가져오기 때문에 @GeneratedValue 주석이 사용되지 않았습니다.

  4. 예제 1-4 와 같이 @Column 주석을 사용하여 프라이머리 키 필드에 해당하는 데이터베이스 컬럼의 특성을 정의합니다.

    예제 1-4 Inventory.java의 @Column 활용

    @Entity
    public class Inventory {
        @Id
        @Column(name="ITEM_SKU", insertable=false, updatable=false)
        protected long id;
        ...
        public void setItem(Item item) {
            this.item = item;
            this.id = item.getSKU();
        }
        ...
    }
    

    디폴트 환경에서 JPA 표준은 각각의 엔티티, 각각의 퍼시스턴트 필드가 엔티티의 관계형 데이터베이스 테이블에 있는 컬럼에 대응되며, 컬럼명과 타입은 필드명과 타입에 대응되도록 명시하고 있습니다.

    이러한 디폴트 동작을 무시하려면 @Column 주석을 사용해야 합니다.

    Inventory 클래스에서는 @Column 주석을 사용하여 id 필드에 대응하는 관계형 데이터베이스 컬럼의 세부 튜닝을 수행하고 있습니다. (예제 1-4 참고).

    Inventory 엔티티는 InventorysetItem 메소드에 정의된 것처럼 Item으로부터 프라이머리 키 값을 가져오므로, 값이 데이터베이스에 덮어씌워지지 않도록 주의해야 합니다. 예제에서는 @Column 주석을 사용하여 컬럼의 INSERT, UPDATE가 불가능하도록 설정하였습니다. 이는 JPA 퍼시스턴스 프로바이더가 이 컬럼을 SQL INSERT 또는 SQL UPDATE 구문에 포함시키지 않음을 의미합니다. 이 컬럼이 Inventory 클래스의 Item에 대한 외래 키를 포함하고 있으므로,@Column 주석의 name 속성을 이용하여 Item에 대하여 OneToOne 매핑을 수행하는 과정에서 컬럼 조인을 수행할 때 사용할 컬럼명을 정의합니다(5 단계와 6단계 참조).

  5. @OneToOne 주석을 사용하여 Inventory 엔티티와 Item 엔티티의 관계를 정의합니다. (예제 1-5)

    예제 1-5 Inventory.java의 @OneToOne 활용

    @Entity
    public class Inventory {
        @Id
        @Column(name="ITEM_SKU", insertable=false, updatable=false)
        protected long id;
    
        @OneToOne
        protected Item item;
        ...
        public void setItem(Item item) {
            this.item = item;
            this.id = item.getSKU();
        }
    }
    

    디폴트 환경에서, JPA 퍼시스턴스 프로바이더는 자동으로 (대부분의 Java 프리미티브 타입, 프리미티브 타입을 위한 “wrapper”, enum 등의) 기본적인 매핑을 수행합니다. 기본 매핑에 대한 세부 튜닝이 필요한 경우 @Basic 주석을 사용할 수 있습니다.

    관계를 위한 매핑을 명시합니다. @OneToOne 주석 이외에 관계 매핑을 위해 사용되는 주석이 아래와 같습니다.

    Inventory 클래스의 item 필드에는 @OneToOne 주석을 사용하여 InventoryItem 의 관계가 정의되고 있습니다.(예제 1-5 참조)

  6. @JoinColumn 주석을 사용하여 Item을 참조하는 외래 키 컬럼이 ITEM_SKU라는 이름을 갖는 Inventory의 컬럼임을 정의합니다. (예제 1-6 참조)

    예제 1-6 Inventory.java의 @JoinColumn 활용

    @Entity
    public class Inventory {
        @Id
        @Column(name="ITEM_SKU", insertable=false, updatable=false)
        protected long id;
    
        @OneToOne
        @JoinColumn(name="ITEM_SKU")
        protected Item item;
        ...
        public void setItem(Item item) {
            this.item = item;
            this.id = item.getSKU();
        }
    }
    
    
  7. @Version 주석을 사용하여 퍼시스턴트 필드 중 하나를 옵티미스틱 락킹 필드(optimistic locking field)로 지정합니다. (예제 1-7 참조)

    예제 1-7 Inventory.java의 @Version 활용

    @Entity
    public class Inventory {
        @Id
        @Column(name="ITEM_SKU", insertable=false, updatable=false)
        protected long id;
    
        @OneToOne
        @JoinColumn(name="ITEM_SKU")
        protected Item item;
        ...
        @Version
        protected int version;
        ...
        public void setItem(Item item) {
            this.item = item;
            this.id = item.getSKU();
        }
    }
    
    

    @Version 주석을 사용하여 Inventory 클래스의 version 필드 버전을 옵티미스틱 락킹 필드로 지정했습니다

    디폴트 설정에서, JPA 퍼시스턴스 프로바이더는 애플리케이션이 데이터 일관성을 책임지는 것으로 가정합니다.

    오라클은 @Version 주석을 이용하여 옵티미스틱 락으로 이용되는 버전 필드 또는 엔티티 클래스 속성을 지정함으로써, JPA 퍼시스턴스 프로바이더에 의해 관리되는 옵티미스틱 락킹을 활성화할 것을 권장하고 있습니다.

  8. @NamedQuery 주석을 사용하여 네임드 쿼리(named query)를 정의합니다.(예제 1-8 참조)

    예제 1-8 Inventory.java의 @NamedQuery 활용

    @Entity
    
    @NamedQuery(
        name="inventoryForCategory",
        query="SELECT i FROM Inventory i WHERE i.item.category = :category and i.quantity <= :maxQuantity"
    )
    public class Inventory {
        ....
    }
    
    

    JPA 애플리케이션에서 EntityManager를 사용하여 런타임에 JPA 쿼리를 다이내믹하게 생성하거나, @NamedQuery 주석을 이용하여 쿼리를 미리 정의하고 런타임에 실행하는 방법을 사용할 수 있습니다. (4 단계: JPA 쿼리의 활용 참고)이 방법은 복잡한 쿼리를 자주 사용하는 환경에서 특히 유용합니다.

    두 가지 이상의 네임드 쿼리를 사용하는 경우에는 @NamedQueries 주석을 예제 1-17Order.java처럼 활용할 수 있습니다

    또 네이티브 SQL 쿼리를 생성하는 것도 가능합니다(@NamedNativeQuery , @NamedNativeQueries 참고)

  9. 파일을 저장하고 종료합니다.

Item 엔티티의 주석 처리

이 섹션에서는 Item.java 파일을 주석처리 하는 방법을 소개하고 있습니다. 이 파일은 POJO(plain old Java object)이며, JPA를 이용하여 관리하는 퍼시스턴트 엔티티들 중 하나입니다.

  1. 편집기를 이용하여 <TUTORIAL_HOME>\persistence-unit\src\oracle\toplink\jpa\example\inventory\model\Item.java 소스 파일을 엽니다.

  2. 이 클래스를 @Entity 주석을 사용하는 퍼시스턴트 엔티티로 지정합니다.(예제 1-2 참고)

    예제 1-9 Item.java의 @Entity 활용

    @Entity
    public class Item {
        ...
    }
    
    
  3. @Id주석을 사용하여 퍼시스턴트 필드 중 하나를 엔티티의 프라이머리 키로 지정합니다(예제 1-10 참고)

    예제 1-10 @Id in Item.java

    @Entity
    public class Item {
        protected long SKU;
        ...
        @Id
        @GeneratedValue
        public long getSKU() {
            return SKU;
        }
        ...
    }
    
    

    Item 클래스의 getSKU 속성이 프라이머리 키로 지정됩니다(예제 1-10 참고) 일반적으로 필드(예: Inventory.java) 또는 필드에 연관된 속성에 주석을 처리하는 것이 가능합니다. @GeneratedValue 주석은 JPA 퍼시스턴트 프로바이더에서 시퀀싱(sequencing)을 처리하고, 이 필드를 위한 유니크한 식별자 값을 생성하여 관리하도록 하는데 사용됩니다.

  4. @Version 주석을 사용하여 옵티미스틱 락킹 필드로 사용할 퍼시스턴트 필터를 지정합니다(예제 1-11 참고)

    예제 1-11 Item.java의 @Version 활용

    @Entity
    public class Item {
        protected long SKU;
        ...
        @Id
        @GeneratedValue
        public long getSKU() {
            return SKU;
        }
        ...
        @Version
        public void setVersion(int version) {
            this.version = version;
        }
        ...
    }
    
    

    @Version 주석을 Item 클래스의 setVersion 속성에 적용하여 version 필드가 옵티미스틱 락킹 필드로 사용됨을 명시합니다

  5. 파일을 저장하고 편집기를 종료합니다

Order 엔티티의 주석 처리

이 섹션에서는 Order.java 파일의 주석을 처리하는 방법을 설명하고 있습니다. 이 파일은 POJO(plain old Java object)이며, JPA를 이용하여 관리할 퍼시스턴트 엔티티들 중 하나입니다.

  1. 임의의 편집기를 사용하여 <TUTORIAL_HOME>\persistence-unit\src\oracle\toplink\jpa\example\inventory\model\Order.java 소스 파일을 엽니다.

  2. @Entity을 이용하여 이 클래스를 퍼시스턴트 엔티티로 지정합니다. (예제 1-2 참고)

    예제 1-12 Order.java의 @Entity 활용

    @Entity
    public class Order {
        ...
    }
    
    

    디폴트 설정에서, 엔티티 네임은 클래스 네임과 동일하게 설정되며, 또 엔티티 네임과 엔티티의 데이터베이스 테이블명 역시 동일하게 설정됩니다

    Order 엔티티의 경우 ORDER라는 이름의 테이블을 생성할 수 없습니다. 이 이름이 대부분의 관계형 데이터베이스에서 예약어로 사용되고 있기 때문입니다. @Table 주석을 이용하여 디폴트 테이블 네임을 변경합니다. (예제 1-13 참고)

    예제 1-13 Order.java의 @Table활용

    @Entity
    @Table(name="ORDER_TABLE")
    public class Order {
        ...
    }
    
    
  3. @Id 주석을 사용하여 퍼시스턴트 필드 중 하나를 엔티티의 프라이머리 키로 지정합니다. (예제 1-3 참고)

    예제 1-14 Order.java의 @Id 활용

    @Entity
    @Table(name="ORDER_TABLE")
    public class Order {
        @Id
        @GeneratedValue
        protected long orderId;
        ...
    }
    
    

    Order 클래스의 orderId 필드가 프라이머리 키로 지정되었습니다. @GeneratedValue 주석은 JPA 퍼시스턴스 프로바이더가 시퀀싱을 처리하고, 이 필드를 위한 유니크한 식별자 값을 생성하여 관리하도록 지정합니다.

  4. @OneToOne 주석을 이용하여 Order 엔티티와 Item 엔티티 간의 관계를 정의합니다. (예제 1-5 참고)

    예제 1-15 Order.java의 @OneToOne 활용

    @Entity
    @Table(name="ORDER_TABLE")
    public class Order {
        @Id
        @GeneratedValue
        protected long orderId;
        ...
        @OneToOne
        protected Item item;
        ...
    }
    
    

    Order 클래스의 item 필드는에 @OneToOne 주석 주석을 사용하여 OrderItem 간의 관계를 정의하고 있습니다. (예제 1-15 참고)

  5. @Version 주석을 사용하여 옵티미스틱 락킹 필드로 사용할 퍼시스턴트 필드를 지정합니다. (예제 1-7 참고)

    예제 1-16 Order.java의 @Version 활용

    @Entity
    @Table(name="ORDER_TABLE")
    public class Order {
        @Id
        @GeneratedValue
        protected long orderId;
        ...
        @Version
        protected int version;
    
        @OneToOne
        protected Item item;
        ...
    }
    
    

    @Version 주석을 사용하여 Order 클래스의 version 필드를 옵티미스틱 락킹 필드로 지정했습니다.

    디폴트 설정에서, JPA 퍼시스턴트 프로바이더는 애플리케이션이 데이터 일관성을 책임지는 것으로 가정합니다.

    오라클은 @Version 주석을 이용하여 옵티미스틱 락으로 이용되는 버전 필드 또는 엔티티 클래스 속성을 지정함으로써, JPA 퍼시스턴스 프로바이더에 의해 관리되는 옵티미스틱 락킹을 활성화할 것을 권장하고 있습니다

  6. @NamedQueries 주석을 이용하여 두 가지 네임드 쿼리를 정의합니다. (예제 1-17 참고)

    예제 1-17 Order.java의 @NamedQueries 활용

    @Entity
    @Table(name="ORDER_TABLE")
    @NamedQueries({
    
        @NamedQuery(
        name="shippedOrdersForItem",
        query="SELECT o FROM Order o JOIN o.item i WHERE i.sKU = :itemId and o.arrivalDate is not null"
        ),
    
        @NamedQuery(
        name="pendingOrdersForItem",
        query="SELECT o FROM Order o WHERE o.item.sKU = :itemId and o.arrivalDate is null"
        )
    })
    public class Order {
        ...
    }
    
    

    네임드 쿼리를 하나만 정의하는 경우에는 Inventory.java의 경우처럼 @NamedQuery 주석을 사용합니다. (예제 1-8).

  7. 파일을 저장하고 편집기를 종료합니다

2 단계 : 퍼시스턴스 유닛의 설정

엔티티 매니저(entity manager)는 엔티티에 대한 기본적인 퍼시스턴트 작업(생성, 읽기, 업데이트, 삭제 등)을 수행하기 위한 JPA 인터페이스입니다.

퍼시스턴스 유닛(persistence unit)은 엔티티 매니저 프로바이더, 설정 속성, 퍼시스턴트 매니지드 클래스 등의 세부 정보를 논리적으로 그룹핑하여 엔티티 매니저의 설정을 정의한 단위를 말합니다.

각각의 퍼시스턴스 유닛에는 이름이 필요합니다. EJB-JAR, WAR, EAR, 애플리케이션 클라이언트 JAR 파일에 중복된 퍼시스턴트 유닛의 이름이 존재할 수는 없습니다. 엔티티 매니저 팩토리(entity manager factor)를 확보했을 때 사용할 퍼시스턴스 유닛의 이름을 명시합니다. (엔티티 매니저 팩토리의 확보 참고)

persistence.xml 파일에 퍼시스턴스 유닛을 정의합니다

퍼시스턴스 유닛을 설정하는 방법이 아래와 같습니다.

  1. 임의의 편집기를 사용하여 <TUTORIAL_HOME>\persistence-unit\src\META-INF\persistence.xml 소스 파일을 엽니다.

  2. 각각의 퍼시스턴트 JPA 엔티티 클래스 별로 <!-- class list goes here --> 커멘트를 <class> 엘리먼트로 대체합니다.

    <class>oracle.toplink.jpa.example.inventory.model.Inventory</class>
    <class>oracle.toplink.jpa.example.inventory.model.Order</class>
    <class>oracle.toplink.jpa.example.inventory.model.Item</class>
    
    
  3. 관계형 데이터베이스의 속성에 맞게 아래 속성을 설정합니다

    <property name="toplink.jdbc.driver" value="<jdbc-driver>"/>
    <property name="toplink.jdbc.url" value="<jdbc-url>"/>
    <property name="toplink.jdbc.password" value="<jdbc-password>"/>
    <property name="toplink.jdbc.user" value="<jdbc-user>"/>
    
    

    위 코드에 대한 설명이 아래와 같습니다:

    • <jdbc-driver>는 JDBC 드라이버 클래스의 이름입니다. 예: oracle.jdbc.OracleDriver.

    • <jdbc-url>는 데이터베이스 JDBC 연결 URL입니다. 예: jdbc:oracle:thin:@myhost:l521:MYSID.

    • <jdbc-password>는 데이터베이스 연결에 사용할 JDBC 패스워드입니다. 예: tiger.

    • <jdbc-user>는 데이터베이스 연결에 사용할 JDBC 사용자 이름입니다. 예: scott

    완성된 persistence.xml 파일 내용의 일부가 예제 1-18과 같습니다.

    예제 1-18 Persistence.xml의 퍼시스턴스 유닛

    ...
        <persistence-unit name="default" transaction-type="RESOURCE_LOCAL">
            <provider>
               oracle.toplink.essentials.PersistenceProvider
            </provider>
            <class>oracle.toplink.jpa.example.inventory.model.Inventory</class>
            <class>oracle.toplink.jpa.example.inventory.model.Order</class>
            <class>oracle.toplink.jpa.example.inventory.model.Item</class>
            <properties>
                <property name="toplink.logging.level" value="FINE"/>
                <property name="toplink.jdbc.driver" value="oracle.jdbc.OracleDriver"/>  <!-- update to match database-->
                <property name="toplink.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:XE"/> <!-- update to match database-->
                <property name="toplink.jdbc.password" value="tiger"/> <!-- update to match database-->
                <property name="toplink.jdbc.user" value="scott"/> <!-- update to match database-->
            </properties>
        </persistence-unit>
    ...
    
    

    퍼시스턴스 유닛의 이름은 default로 설정되었습니다

    transaction-type의 이름은 RESOURCE_LOCAL입니다. 이는 이 퍼시스턴스 유닛의 엔티티 매니저가 JTA 트랜잭션에 참여하지 않음을 의미합니다

    oracle.toplink.essentials.PersistenceProvider가 프로바이더로 사용됩니다

    마지막으로, 퍼시스턴스 유닛 속성이 설정됩니다. 퍼시스턴스 유닛 속성을 이용하여 하부 JPA 퍼시스턴스 프로바이더를 세부 튜닝하고, 퍼시스턴스 유닛이 연관되는 관계형 데이터베이스의 세부 연결 설정을 정의할 수 있습니다.

    TopLink JPA 퍼시스턴스 프로바이더 익스텐션에 대한 자세한 정보는 TopLink JPA Extension Reference를 참고하십시오.

    persistence.xml 파일에 대한 자세한 정보는 JSR-000220 Enterprise JavaBeans v3.0 JPA 표준 스펙의 섹션 6.3을 참고하십시오.

  4. 파일을 저장하고 편집기를 종료합니다

3 단계 : JPA를 이용한 서비스의 구현

튜토리얼 사용자 인터페이스의 관리를 위한 메인 리소스는 oracle.toplink.jpa.example.inventory.ui.InventoryManagerBean입니다. 이 리소스는 이 애플리케이션에 의해 제공되는 서비스를 구현하기 위해 JPA가 사용하는 아래와 같은 클래스들에 대한 참조를 포함하고 있습니다.

  • oracle.toplink.jpa.example.inventory.services.impl.ManagedOrderBean (implements oracle.toplink.jpa.example.inventory.services.OrderService의 구현에 사용)

  • oracle.toplink.jpa.example.inventory.services.impl.ManagedInventoryBean (implements oracle.toplink.jpa.example.inventory.services.InventoryService의 구현에 사용)

이 두 가지 클래스를 통해 JPA를 이용하여 아래와 같은 작업을 수행하는 방법을 설명하기로 합니다:

엔티티 매니저 팩토리(Entity Manager Factory)의 확보

ManagedOrderBeanManagedInventoryBean은 모두 oracle.toplink.jpa.example.inventory.services.impl.JPAResourceBean 헬퍼 클래스의 인스턴스를 사용하여 앞의 persistence.xml 파일에서 정의한 default 퍼시스턴스 유닛을 위한 엔티티 매니저 팩토리의 인스턴스를 확보하고 있습니다. (2 단계: 퍼시스턴스 유닛의 설정). 예제 1-19는 엔티티 매니저 팩토리가 확보되는 과정을 보여 주고 있습니다.

예제 1-19 엔티티 매니저 팩토리의 확보

public EntityManagerFactory getEMF (){
    if (emf == null){
        emf = Persistence.createEntityManagerFactory("default");
    }
    return emf;
}

ManagedOrderBean, ManagedInventoryBean 클래스는 확보된 엔티티 매니저 팩토리를 사용하여 기본적인 퍼시스턴스 작업(생성, 읽기, 업데이트, 삭제)를 수행하기 위한 엔티티 매니저를 확보합니다.

엔티티의 생성

예제 1-20ManagedOrderBeanEntityManager를 이용하여 새로운 Order 엔티티를 생성하는 과정을 보여주고 있습니다.

예제 1-20 ManagedOrderBean의 Order 엔티티 생성

public void  createNewOrder(Order order){
    EntityManager em = jpaResourceBean.getEMF().createEntityManager();
    try{
        em.getTransaction().begin();
        em.persist(order);
        em.getTransaction().commit();
    }finally{
        em.close();
    }
}

엔티티 읽기

예제 1-21ManagedOrderBeanEntityManager를 이용하여 프라이머리 키를 기준으로 정렬된 기존 Order 엔티티를 읽어 오는 과정을 보여 주고 있습니다.

예제 1-21 ManagedOrderBean의 Order 읽기

public Order getOrderById(long orderId){
    EntityManager em = jpaResourceBean.getEMF().createEntityManager();
    try{
        return em.find(Order.class, orderId);
    }finally{
        em.close();
    }
}

엔티티의 업데이트

예제 1-22ManagedOrderBeanEntityManager를 이용하여 기존의 Order 엔티티를 업데이트하는 과정을 보여주고 있습니다. Order 엔티티에 발생한 모든 변경 사항은 로컬 트랜잭션이 커밋될 때 영구적으로 반영됩니다.

예졔 1-22 ManagedOrderBean의 Order 업데이트

public void alterOrderQuantity(long orderId, int newQuantity){
    EntityManager em = jpaResourceBean.getEMF().createEntityManager();
    try{
        em.getTransaction().begin();
        Order order = em.find(Order.class, orderId);
        order.setQuantity(newQuantity);
        em.getTransaction().commit();
    }finally{
        em.close();
    }
}

엔티티의 삭제

예제 1-23ManagedOrderBeanEntityManager를 이용하여 프라이머리 키를 기준으로 기존 Order 엔티티를 삭제하는 과정을 보여 주고 있습니다.

예제 1-23 ManagedOrderBean의 Order 삭제

public void requestCancelOrder(long orderId){
    EntityManager em = jpaResourceBean.getEMF().createEntityManager();
    try{
        em.getTransaction().begin();
        Order order = em.find(Order.class, orderId);
        em.remove(order);
        em.getTransaction().commit();
    }finally{
        em.close();
    }
}

4 단계 : JPA 쿼리의 활용

ManagedInventoryBean, ManagedOrderBean의 두 가지 클래스 모두 JPA 쿼리를 사용하고 있습니다. 이 섹션에서는 다음과 같은 내용이 설명됩니다:

ManagedInventoryBean 클래스에서 쿼리 활용하기

이 섹션에서는 ManagedInventoryBean.java 파일에서 네임드/다이내믹 쿼리를 코딩하는 방법을 설명합니다.

  1. 임의의 편집기를 사용하여 <TUTORIAL_HOME>\web-application\src\oracle\toplink\jpa\example\inventory\services\impl\ManagedInventoryBean.java 소스 파일을 엽니다.

  2. EntityManagercreateNamedQuery 메소드를 사용하여 inventoryForCategory라는 이름의 Query 인스턴스를 반환합니다. (예제 1-24 참고)

    예 1-24 ManagedInventoryBean의 네임드 쿼리 사용

    public class ManagedInventoryBean implements InventoryService{
        ...
        public Collection<Inventory> getInventoryForCategoryMaxQuantity(String category, int quantity){
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                Query query = em.createNamedQuery("inventoryForCategory");
                query.setParameter("category", category);
                query.setParameter("maxQuantity", quantity);
                return query.getResultList();
            }finally{
                em.close();
            }
        }
        ...
    }
    

    이 네임드 쿼리는 Inventory 클래스에서 미리 생성해 둔 것입니다. (예제 1-8 참고)

  3. EntityManagercreateQuery 메소드를 사용하여 다이내믹 쿼리를 생성합니다. (예제 1-25 참고)

    예 1-25 ManagedInventoryBean의 다이내믹 쿼리의 사용

    public class ManagedInventoryBean implements InventoryService{
        ...
        //Returns a list of available item categories
        public Collection<Category> getCategories(){
            //Create an EntityManager from the Factory stored in the JPAResourceBean
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
    
            try{
                //execute a JPQL query that collects summary data and stores it in a
                //non-entity class Category.  This query will pass the results  of the
                //query into the constructor of Category and return a list of Category
                //objects
                Collection<Category> result = em.createQuery("Select new oracle.toplink.jpa.example.inventory.nonentity.Category(i.category) from Item i group by i.category").getResultList();
                return result;
            }finally{
                em.close();
            }
        }
        ...
    }
    

    이 쿼리가 엔티티 클래스가 아닌 Category 클래스의 Collection을 빌드하고 반환한다는 점을 참고하시기 바랍니다. 이 쿼리는 요약 데이터를 이용하여 (엔티티가 아닌) 헬퍼 클래스를 생성합니다.

  4. 파일을 저장하고 편집기를 종료합니다.

ManagedOrderBean 클래스에서 쿼리 사용하기

이 섹션에서는 ManagedOrderBean.java 파일에서 네임드/다이내믹 쿼리를 코딩하는 방법을 설명합니다

  1. 임의의 편집기를 사용하여 <TUTORIAL_HOME>\web-application\src\oracle\toplink\jpa\example\inventory\services\impl\ManagedOrderBean.java 소스 파일을 엽니다.

  2. EntityManagercreateNamedQuery 메소드를 사용하여 inventoryForCategory라는 이름의 쿼리를 위한 Query 인스턴스를 반환합니다. (예제 1-24 참고)

    예 1-26 ManagedOrderBean의 네임드 쿼리 사용

    public class ManagedOrderBean implements OrderService{
        ...
        // Returns those orders that have a set arrival date indicating that they have shipped
        public Collection<Order> getShippedOrdersForItem(long itemId){
            //Create an EntityManager from the Factory stored in the JPAResourceBean
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                //create an instance of the NamedQuery defined in the Inventory class.
                Query query = em.createNamedQuery("shippedOrdersForItem");
                //setting the provided parameters on the query
                query.setParameter("itemId", itemId);
                //return result of query
                return query.getResultList();
            }finally{
                em.close();
            }
        }
    
        // Returns those orders that have a set arrival date indicating that they have shipped
        public Collection<Order> getPendingOrdersForItem(long itemId){
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                Query query = em.createNamedQuery("pendingOrdersForItem");
                query.setParameter("itemId", itemId);
                return query.getResultList();
            }finally{
                em.close();
            }
        }
        ...
    }
    

    이 네임드 쿼리는 Order 클래스에서 미리 생성해 둔 것입니다. (예제 1-17)

  3. EntityManagercreateQuery 메소드를 사용하여 다이내믹 쿼리를 생성합니다. (예제 1-25 참고)

    예제 1-27 ManagedOrderBean의 다이내믹 쿼리 사용

    public class ManagedInventoryBean implements InventoryService{
        ...
        //Returns a list of available item categories
        public Collection<Category> getCategories(){
            //Create an EntityManager from the Factory stored in the JPAResourceBean
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
    
            try{
                //execute a JPQL query that collects summary data and stores it in a
                //non-entity class Category.  This query will pass the results  of the
                //query into the constructor of Category and return a list of Category
                //objects
                Collection<Category> result = em.createQuery("Select new oracle.toplink.jpa.example.inventory.nonentity.Category(i.category) from Item i group by i.category").getResultList();
                return result;
            }finally{
                em.close();
            }
        }
        ...
    }
    
  4. 적절한 EntityManager 메소드를 사용하여 프라이머리 키를 기준으로 Order를 검색하고 제거합니다. (예제 1-28 참고)

    예제 1-28 ManagedOrderBean의 트랜잭션에서 Order의 검색 및 삭제

    public class ManagedInventoryBean implements InventoryService{
        ...
        // request that an order be canceled.  Assume success if no exception is thrown
        public void requestCancelOrder(long orderId){
               //Create an EntityManager from the Factory stored in the JPAResourceBean
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                //changes will be made so begin a transaction
                em.getTransaction().begin();
                //find the order that will be deleted.  This step ensures the order
                //will be managed as the specification requires the object be
                //managed before remove can be called.
                Order order = em.find(Order.class, orderId);
                //set the order to be delet4ed
                em.remove(order);
                //commit the transaction, this will cause the the delete SQL to be
                //sent to the database.
                em.getTransaction().commit();
            }finally{
                em.close();
            }
        }
    
        // request that an order be canceled assume success if no exception is thrown
        public void alterOrderQuantity(long orderId, int newQuantity){
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                em.getTransaction().begin();
                //ensure that this order is a managed object.
                Order order = em.find(Order.class, orderId);
                //update the order object directly
                order.setQuantity(newQuantity);
                //commit the transaction to have the update sent to the database
                em.getTransaction().commit();
            }finally{
                em.close();
            }
        }
    
        // Create a new order request for a particular item;
        public void  createNewOrder(Order order){
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                em.getTransaction().begin();
                //calling persist on the order object will mark the object as new
                //within the persistence context.
                em.persist(order);
                //commit the transaction to have the object data inserted to the
                //database
                em.getTransaction().commit();
            }finally{
                em.close();
            }
        }
        ...
    }
    
    
  5. EntityManagerfind 메소드를 사용하여 프라이머리 키를 기준으로 Order를 가져옵니다. (예제 1-29 참고)

    예제 1-29 ManagedOrderBean에서 프라이머리 키를 기준으로 Order 검색하기

    public class ManagedInventoryBean implements InventoryService{
        ...
        // finds a particular order by the specified order id
        public Order getOrderById(long orderId){
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                return em.find(Order.class, orderId);
            }finally{
                em.close();
            }
        }
    
        // finds a particular order by the specified order id
        public Item getItemById(long itemId){
            EntityManager em = jpaResourceBean.getEMF().createEntityManager();
            try{
                return em.find(Item.class, itemId);
            }finally{
                em.close();
            }
        }
        ...
    }
    
    
  6. 파일을 저장하고 편집기를 종료합니다

5 단계 : 패키징 및 디플로이(Deploy) 작업

이 섹션에서는 튜토리얼 애플리케이션의 패키징 및 디플로이 작업에 대해 설명합니다. 논의 되는 내용이 아래와 같습니다:

애플리케이션의 컴파일링, 패키징

튜토리얼 애플리케이션의 컴파일, 패키징 작업을 수행하기 위해, 커맨드 라인에서 <TUTORIAL_HOME> 디렉토리로 이동하여 아래와 같이 실행합니다.

ant -f buld.xml package.webapp

<TUTORIAL_HOME>\web-application\deploy\jpa-example.war파일이 생성됩니다.

이 튜토리얼에서는 jpa-example.war 파일 내의 WEB-INF\lib\persistence-unit.jar에 퍼시스턴스 유닛을 패키징합니다. 퍼시스턴스 유닛을 해당 JAR 파일에 한정적으로 정의함으로써, 다른 애플리케이션에서 쉽게 퍼시스턴스 유닛을 재활용하도록 할 수 있습니다.

OC4J의 디플로이

OC4J에 튜토리얼 애플리케이션을 디플로이 하는 방법이 아래와 같습니다.

  1. 아래의 시스템 속성이 설정되었는지 확인합니다:

    • JAVA_HOME – JDK 1.5 설치 디렉토리로 설정

      예: C:\Program Files\Java\jdk1.5.0_06

    • ORACLE_HOME - OC4J 설치 디렉토리로 설정

      예: C:\OC4JHome

    • PATH - 경로에 %JAVA_HOME%\bin이 포함되어야 함

  2. 커맨드 라인에서 OC4J를 시작합니다:

    Windows 환경:

    cd %ORACLE_HOME%
    cd bin
    oc4j.cmd -start
    
    

    UNIX 환경:

    cd %ORACLE_HOME%
    cd bin
    oc4j.sh -start
    
    
  3. Oracle Enterprise Manager Application Server Control 콘솔에 로그인합니다.

    브라우저를 시작하고 다음 URL을 입력합니다 : http://<hostname>:8888/em/console/ias/oc4j/administration

    <hostname>은 OC4J를 실행하는 컴퓨터의 이름을 의미합니다.

  4. Home > Applications 를 선택하고 Deploy 버튼을 클릭합니다.

  5. Archive 영역에서 Archive is present on local host 옆에 위치한 라디오 버튼을 클릭합니다.

  6. Browse 버튼을 클릭하고 jpa-example.war 파일을 검색합니다

  7. Next를 클릭합니다.

  8. 공백