2007년 7월 27일 금요일

Java Persistence와 함께 Generics 사용하기

Java Persistence와 함께 Generics 사용하기

Java Persistence API(또 는 간단히 Java Persistence)는 Java EE 5 애플리케이션에 대한 POJO 기반 도메인 모델을 제공한다. 이것은 관계형 데이터가 Java 개체에 매핑되는 방식에 대한 모든 세부사항을 다루며 개체 관계 매핑을 표준화한다. Java Persistence를 사용하는 애플리케이션은 개체 컬렉션을 반환하는 쿼리를 실행하는 경우가 많다. Java Platform, Standard Edition (Java SE) 5에서는 컬렉션에서 개체 유형을 지정할 수 있게 하는 새로운 기능인 generics가 소개되었다. 애플리케이션에서 Java Persistence를 사용하는 경우 generics를 이용하면 스펠링 체크 등의 추가 기능을 통해 코드를 작성할 수 있다. 이 팁에서는 generics를 사용하도록 애플리케이션을 리팩터링하는 방법을 알아보겠다.

Generics의 기본 원리
generics는 여러 기능을 가지고 있지만 무엇보다도 컬렉션에서 개체 유형을 전달하는 방법을 제공한다. generics를 사용하면 클래스를 나타내는 매개변수를 통해 컬렉션을 지정할 수 있다. 예를 들어, 다음은 getAllItems 메소드가 Item 유형의 개체 컬렉션을 반환한다고 선언한다.
   public List<Item> getAllItems(){...}
generics를 사용하면 다음과 같은 장점이 있다.

  • 컴파일러가 스펠링 체크를 수행하고 코드에서 잠재적 버그를 잡아낼 수 있다.
  • generics를 사용하지 않는다면 애플리케이션 실행 전까지 해당 버그를 찾아낼 수 없다.
  • 배포 전에 버그를 식별할 수 있다.
  • 코드의 가독성을 높일 수 있다. 다른 개발자가 내 코드를 읽을 때 컬렉션에서 어떤 개체 유형이 기대되는지 알 수 있다.
  • generics가 없으면 클라이언트 코드는 컬렉션에서 얻는 각 개체를 타입캐스트해야 한다.

Persistence 예제
Java Persistence와 함께 generics를 사용하는 방법을 알아보기 위해 간단한 Java Persistence 예제를 살펴보자. 이 예제에서는 JSP 페이지가 데이터베이스의 모든 항목에 대한 검색 요청을 하고 XML 문서에 이를 표시한다.
요청은 다음과 같이 처리된다.

사용자 삽입 이미지


  • 기본 JSP 페이지(index.jsp)가 서블릿에 요청을 보낸다.
    서블릿이 해당 요청을 파사드에 보낸다.
  • 파사드가 Java Persistence Query Language를 사용하여 데이터베이스의 모든 항목을 검색하는 쿼리를 생성한다. 파사드는 서블릿 또는 세션 빈 등의 웹 구성요소가 될 수 있다.
  • Java Persistence Runtime이 쿼리를 실행하고, 개체 관계 매핑을 수행하고, Items 목록을 작성하고, 기타 일부 작업을 수행한다.
  • 데이터베이스가 SQL 쿼리를 실행하고 결과 목록을 반환한다. 각 결과는 데이터베이스의 각 항목(즉, 각 행)을 의미한다.
  • 파사드가 List를 서블릿에 전달한다.
  • 서블릿이 List를 JSP 페이지(itemsxml.jsp)에 반환하고 이 페이지에서 모든 항목을 XML로 표시한다.

파사드를 먼저 살펴보자. 다음은 파사드에서 요청에 대한 코드의 모양을 보여준다.

   public List getAllItemsInPlainCollection(){
      EntityManager em = emf.createEntityManager();
      Query query = em.createQuery(
             "SELECT OBJECT(i) FROM Item i");
      List items = query.getResultList();
      em.close();
      return items;
   }

코드를 "generics화"하려면 다음과 같이 두 가지만 추가하면 된다.

   public List<Item> getAllItemsInTypedCollection(){
      EntityManager em = emf.createEntityManager();
      Query query = em.createQuery(
             "SELECT OBJECT(i) FROM Item i");
      List<Itemgt; items = query.getResultList();
      em.close();
      return items;
   }

변경된 코드는 반환 값에 Item 유형의 개체를 포함한다고 지정한다.

이제 서블릿에서 관련 코드를 살펴보자.

   public void doGet(HttpServletRequest request,
          HttpServletResponse response)
       ...
       if (selectedURL.equals("findallitems.do")) {
           List items = cf.getAllItems();
           request.setAttribute("items", items);
    }

이제 itemsxml.jsp 페이지는 다음과 같다.

   ...
        <Items>
           <% List items = (List)request.getAttribute("items");
           for (Object o : items) {
           Item item = (Item) o; %> 
           <Item>
               <Name><%=item.getName()%> </Name>
               <ListPrice>$<%=item.getListPrice()%> </ListPrice>
               <Description><%=item.getDescription()%>
               </Description>              
           </Item>           
           <% } %>
        </Items>
   ... 

항목 목록의 각 항목이 적절한 유형으로 어떻게 변환되어야 하는지에 유의한다. generics를 사용하면 다음 코드에서 보는 바와 같이 유형 변환의 필요가 없다.

   ... 
       <Items>
           <% List<Item> items = (List<Item>)
                   request.getAttribute("items");
           for (Item item : items) { %>
           <Item>
               <Name><%= item.getName() %></Name>
               <ListPrice>$<%=item.getListPrice()%></ListPrice>
               <Description><%=item.getDescription()%>
               </Description>
           </Item>
           <% } %>
        </Items>
     ...

마지막으로 다음은 간단한 persistence 개체인 Item 클래스에 대한 코드의 일부이다. 앞에서 언급한 바와 같이, 이 예제의 쿼리는 이러한 Item 개체의 목록을 반환하고 각 개체는 데이터베이스의 각 행을 가리킨다.

   import javax.persistence.*;
   @Entity      
   public class Item implements java.io.Serializable {  
     
       private int itemID;
       private String name;
       ...other fields listed here
     
       public Item() { }

     
       @Id
       public int getItemID() {
           return itemID;
       }  
       public String getName() {
           return name;
       }
     
       public void setItemID(int itemID) {
           this.itemID = itemID;
       }
       public void setName(String name) {
           this.name = name;
       }
       //...other getters and setters methods
   }

Generics 컬렉션은 다형적이 아님

generics를 사용할 경우 generics 컬렉션은 다형적이 아님을 알아야 한다. generics를 처음 사용하는 경우, List<Item> 유형의 개체를 List<Object> 유형의 개체 참조에 지정할 수 있다고 생각하기가 쉽다. 직관적인 생각과는 약간 배치되지만 이 가정은 성립되지 않는다. 예를 들어, 다음 코드는 컴파일할 때 오류가 발생한다.

   public List<Object> getAllItems() {
     // .... initialize the query object appropriately
     List<Item> results = query.getResultList();
     // the following return value will generate
     // a compile-time error
     return query;
   }

컬렉션의 유형 무결성을 보호하기 위해 이 코드는 컴파일할 때 오류가 발생한다. 컬렉션은 Item 개체의 목록이어야만 한다는 것에 유념한다. 위의 코드가 유효하다면 List<Object>의 add 메소드를 통해 Address 개체 등의 기타 개체 유형을 컬렉션에 추가할 수 있을 것이다.
동일한 의도를 합법적으로 표현할 수 있는 한 가지 대안은 generics를 와일드카드와 함께 사용하는 것이다. 예제 코드는 다음과 같다.

   public List<?> getAllItemsasObjects() {
     // .... initialize the query object appropriately
     List<Item> results = query.getResultList();
     return query;
   }  

여기서 반환 값은 임의 개체의 컬렉션이 아님에 유의한다. 이 값은 알 수 없는 개체의 컬렉션으로서 해당 유형에 대한 가정이 불가능하다. 사실, 특정 유형 결과가 필요한 Collections의 모든 메소드(예: add 메소드)는 컴파일 타임 오류가 발생한다. 사용할 수 있는 유일한 메소드는 유형을 java.lang.Object로 가정하는 메소드이다.

불필요한 경고 방지하기

기 억해 두어야 할 또 한 가지 사항은 Java Persistence API의 현재 버전(1.0)에서는 generics를 이용하여 스펠링 체크하는 기능을 보장할 수 없다는 것이다. 사용자 코드에서 generics를 사용한다면 불필요한 경고가 발생할 수 있다. 다음 코드는 해당 문제점을 보여준다.

   public List<Item> getAllItemsInTypedCollection(){
     EntityManager em = emf.createEntityManager();
     Query query = em.createQuery(
            "SELECT OBJECT(i) FROM Item i");
     List<Item> items = query.getResultList();
     em.close();
     return items;
   }

-Xlint:unchecked 옵션을 사용하여 코드를 컴파일하면 다음과 같은 경고가 발생한다.

   warning: [unchecked] unchecked conversion
   found : java.util.List
   required:
     java.util.List<com.sun.javaee.blueprints.autoid.model.Item>
   List<Item> items = query.getResultList();

-Xlint:unchecked 옵션에 익숙하지 않은 경우 이 옵션은 컴파일러에게 Java 언어 규격에서 지정한 체크되지 않은 변환 경고에 대한 세부 정보를 제공하도록 요구한다.
query.getResultList()는 비generic 버전의 List를 반환하므로 경고가 발생한다. 하지만 items 변수는 List<Item> 유형이다. 실제 쿼리 결과는 Item 개체의 List이므로 이 경고는 불필요하다. 컴파일러는 런타임에 대한 generics 유형 정보를 보존하지 않으므로 List<Item>의 타입캐스트를 사용해도 이 문제는 해결되지 않는다. Java Persistence API의 이후 버전에서는 generics를 더 잘 지원하도록 javax.persistence.Query 클래스가 변경될 것으로 보인다. 그 때까지는 불필요한 경고가 발생하지 않도록 하는 최선의 방법은 다음 코드 예제에 표시된 대로 @SuppressWarnings 주석을 사용하는 것이다.

    @SuppressWarnings("unchecked")  
    public List<Item> getAllItemsInTypedCollection(){
      EntityManager em = emf.createEntityManager();
      Query query = em.createQuery(
           "SELECT OBJECT(i) FROM Item i");
      List<Item> items = query.getResultList();
      em.close();
      return items;
    }


ttapr2007jpa-generics.zip

관련 소스 파일




출처 : SDN Korea

댓글 1개:

  1. 블로그 구경잘 하였습니다. 블로그에 필요한 동영상, boom4u.net 도 구경 오세요~~

    답글삭제