struts로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

struts로 검색한 결과
등록일:2008-06-10 17:48:54
작성자:
제목:Spring과 Java Persistence API의 사용


개요

JPA(Java Persistence API)와 Spring Framework 버전 2.0 릴리스는 개발자들의 많은 관심 속에서 탄생하게 되었습니다. 이 기사에서는 Spring 2.0 및 JPA를 BEA WebLogic Server와 함께 사용할 수 있는 방법을 알아봅니다. 특히, Spring과 JPA를 사용하는 업데이트된 WebLogic Server 버전의 의무기록 예제 애플리케이션에 대해 설명합니다. 또한 이 기사에서는 Spring과 JPA가 어떤 방식으로 결합되어 단순화된 POJO 기반 애플리케이션 아키텍처의 토대가 되는지도 보여 줍니다. 사용된 기술로는 WebLogic Server 9.1, Spring 2.0, Kodo JPA 등이 있습니다.

소개

Medical Records 예제 애플리케이션(MedRec)은 WebLogic Server에 몇 가지 기술이 사용되고 있는지를 잘 보여 주는 포괄적인 애플리케이션입니다. 여기에는 Spring 및 struts와 같은 공개 소스 기술과 웹 서비스, JSP, 메시지 구동 빈 및 JDBC와 같은 WebLogic Server 고유의 기술이 포함됩니다.

이 기사에서는 업데이트된 버전의 MedRec에서 JPA(Java Persistence API)와 Spring Framework가 어떤 식으로 결합되어 사용되었는지를 설명합니다. 중요한 목표는 개발자들에게 Spring 2.0, WebLogic Server 9.1 및 Kodo 4.0을 함께 사용할 수 있는 방법을 보여 주는 것입니다. 이 기사를 읽은 개발자는 JPA 사용 및 Spring 2.0 릴리스에서 새롭게 지원되는 JPA에 대한 좋은 정보를 얻게 될 것입니다. 또한 이 기사에서는 JavaBean(POJO)을 엔터프라이즈 애플리케이션의 여러 계층에서 다시 사용할 때 발생할 수 있는 문제에 대해서도 논의할 것입니다. 이는 Spring 및 JPA를 기반으로 하는 애플리케이션 아키텍처의 주요 이점 중 하나가 바로 재사용이기 때문입니다.

Java Persistence API(JPA)에 대해 잘 모르는 사용자를 위해 잠깐 설명하자면, JPA는 Java 객체를 데이터베이스에 저장할 수 있는 방법을 지정하는 새로운 단순형 API입니다. JPA는 EJB 2.x 엔터티 빈을 대체하지만 J2EE 및 J2SE 애플리케이션 둘 다에 사용할 수 있기 때문에 EJB 3.0 (JSR 220)의 일부로 개발됩니다. JPA의 가장 중요한 점 중 하나는 POJO 기반이라는 것입니다. 또한 JPA는 Java 5.0 주석을 사용하여 Java 객체에서 관계형 데이터베이스로 매핑을 지정하는 방법을 단순화합니다. BEA에서는 Kodo를 기반으로 하는 공개 소스 프로젝트인 OpenJPA의 구성을 발표했습니다. Kodo를 사용하려면 조금 더 기다려야 하지만 지금 즉시 사용하고 싶다면 Kodo 4.0 조기 액세스 릴리스를 통해 바로 시작할 수 있습니다.

이 기사에서는 먼저 POJO 및 Spring 데이터 접근에 대한 일반적인 사항을 살펴 봅니다. 다음 섹션에서는 MedRec의 아키텍처 개요를 살펴 본 후 MedRec의 데이터 접근 계층에 대해 자세히 설명합니다. 그런 다음 JPA 지속 클래스를 자세히 알아보고 이 클래스에 필요한 디자인 패턴에 대해 논의해 봅니다. 마지막에는 모든 것을 종합해 Spring 및 Kodo JPA의 XML 구성에 대해 다룹니다. MedRec의 전체 소스 코드는 이 기사에서 다운로드할 수 있습니다.

POJO 및 Spring 데이터 접근

Spring Framework는 단순화된 비지니스 컴포넌트로 가장 유명합니다. Spring에 익숙한 사용자라면 IoC(Inversion of Control) 및 AOP(Aspect Oriented Programming)를 통해 개발자가 일반 JavaBean 또는 POJO(Plain Old Java Object)인 강력한 비지니스 컴포넌트를 작성할 수 있다는 것을 알 것입니다.

데이터베이스 접근이 필요한 엔터프라이즈 애플리케이션의 경우 Spring에서는 지속 데이터에 대한 접근을 캡슐화하는 데이터 접근 객체(DAO)를 간편하게 만들 수 있는 프레임워크를 제공합니다. Spring의 POJO는 데이터 접근 영역의 객체도 포함합니다. 이는 쿼리 및 수정하는 데이터 접근 객체와 도메인 모델 객체가 모두 POJO일 수 있기 때문입니다.

Spring에서 POJO 패턴을 사용하면 실제로 중요한 몇 가지 이점이 있습니다. 첫째, 비지니스 컴포넌트와 마찬가치로 POJO는 개발자가 애플리케이션을 구현하기 위해 수행해야 하는 작업을 간소화시켜 줍니다. 포함되는 코드 행 수가 적을 뿐만 아니라 코드가 표준 Java이므로 보다 간단하기 때문입니다. 둘째, POJO를 사용한다는 것은 나머지 애플리케이션 코드, 즉 데이터 접근 객체를 호출하는 코드가 특정 지속성 기술에 의존할 필요가 없다는 것을 의미합니다. 예를 들어 애플리케이션에서 원시 JDBC 또는 JDO를 사용하는 경우 POJO를 사용하면 비교적 쉽게 JPA를 지속성 솔루션으로 사용할 수 있습니다.

세 번째 이점은 도메인 모델 객체와 관련이 있습니다. 도메인 모델 객체는 MedRec에서 환자, 의사 및 처방과 같은 엔터티를 나타내는 객체입니다. 이러한 클래스는 POJO이고, 기존의 EJB 2.x 엔터티 빈과는 달리 특정 지속성 환경과 연관이 없기 때문에 애플리케이션 코드가 도메인 모델에 접근해야 하는 여러 계층에서 재사용될 수 있습니다. MedRec에서는 이러한 계층에 웹 계층과 웹 서비스 계층이 포함됩니다. 웹 계층에서는 JSP가 도메인 모델 객체를 사용하여 사용자 인터페이스를 렌더링하고, 웹 서비스 계층은 도메인 모델 클래스를 매개 변수로 사용하여 웹 서비스 메서드에 유형을 반환합니다.

MedRec 개요

MedRec 애플리케이션의 포괄적인 아키텍처 개요 및 이 애플리케이션에서 Spring을 사용하는 방법에 대한 자세한 내용은 WebLogic Server와 Spring의 통합(dev2dev, 2005년 9월)를 참조하십시오. 이 기사는 Spring과 WebLogic Server 간의 일반적인 통합에 대해 잘 설명하고 있습니다. 그러나 여기서는 JPA가 MedRec에 적합한 방식을 살펴 볼 것이므로 MedRec 아키텍처에 대한 논의는 간략하게 마칠 것입니다.

그림 1에서는 MedRec의 전체 아키텍처를 보여 줍니다. 최상위 수준의 MedRec은 실제로 MedRec 애플리케이션과 Physician 애플리케이션이라는 두 가지 별도의 J2EE 애플리케이션(EAR 파일)입니다. 모든 데이터 접근은 MedRec 부분에서 이루어지므로 관심의 초점을 여기에 맞추도록 하겠습니다.

Figure 1
그림 1: MedRec 애플리케이션 아키텍처

그림 1에서 보는 바와 같이, MedRec 애플리케이션은 웹 서비스를 포함하는 프레젠테이션 계층, 서비스 계층, 데이터 접근을 포함하는 통합 계층 및 데이터베이스 계층의 여러 계층으로 구성되어 있습니다. 통합 계층의 일부인 Spring 데이터 접근 객체(DAO)는 주로 서비스 계층에 속해 있는 비지니스 객체에 의해 호출됩니다. DAO와 서비스 객체는 둘 다 Spring 애플리케이션 컨텍스트에 따라 구성되는 Spring 빈입니다. 서비스 객체는 Spring의 선언적 트랜잭션 지원을 사용하여 트랜잭션을 제어합니다.

MedRec 데이터 접근

Medical Records 애플리케이션에서는 지속성을 위해 필요한 각 도메인 모델 클래스(Patient, Prescription, Record 및 User)에 하나씩 모두 네 가지 DAO를 사용합니다. DAO 구현 클래스는 각각 JpaPatientDAO, JpaPrescriptionDAO, JpaRecordDAO 및 JpaUserDAO라고 하며 com.bea.medrec.dao.orm 패키지에 들어 있습니다.

그럼, Patient DAO를 통해 작동 방법에 대해 자세히 알아보겠습니다. 다음은 의무기록 애플리케이션에서 환자 기록을 검색 및 업데이트하는 데 사용되는 데이터 접근 객체의 인터페이스입니다.

public interface PatientDao {

  public Patient getById(Integer patientId) 
    throws DataAccessException;

  public List getByEmail(String email) 
    throws DataAccessException;

  public List getByLastName(String lastName) 
    throws DataAccessException;

  public List getByLastNameFirstName(String lastName,
    String firstName) throws DataAccessException;

  public List getByLastNameWild(String lastName) 
    throws DataAccessException;

  public Patient getBySsn(String ssn) 
    throws DataAccessException;

  public List getByStatus(String status) 
    throws DataAccessException;

  public Patient getByUsername(String username) 
    throws DataAccessException;

  public Patient save(Patient patient) 
    throws DataAccessException;
  
  public Patient update(Patient patient) 
    throws DataAccessException;
}

Patient DAO 인터페이스는 POJI(Plain Old Java Interface)입니다. 따라서 사용되는 지속성 기술(여기서는 JPA)에 특정한 Java 유형으로 확장되거나 해당 Java 유형을 가져오지 않습니다. 대부분의 Patient DAO 메서드는 환자 목록 또는 단일 환자 객체를 반환하는 쿼리를 수행합니다. 마지막 두 메서드는 각각 데이터베이스에서 새 환자를 저장하고 기존 환자 정보를 업데이트하는 데 사용됩니다.

인터페이스의 각 메서드는 DataAccessException을 선언하고 있습니다. 이는 Spring 프레임워크에서 정의한 런타임 예외입니다. DataAccessException에 대해서는 두 가지 사항을 기억해야 합니다. 첫째, 이 예외는 런타임 예외이므로 데이터 접근 객체를 사용하는 애플리케이션 코드에서 JDBC 및 EJB 2.x 엔터티 빈에서처럼 각 호출을 try-catch 구문에 포함할 필요가 없다는 점입니다. 둘째, DataAccessException은 기본 지속성 기술에서 사용되는 특정 예외 클래스를 포함하고, 이를 통해 애플리케이션의 나머지 부분을 지속성 계층과 독립적으로 유지하기 때문에 유용합니다.

인터페이스에 대해서는 이 정도로 마무리하고, 다음은 Patient DAO의 구현에 대해 알아보겠습니다. 다음은 MedRec의 모든 데이터 접근 객체가 확장하는 기준 클래스인 BaseDAO입니다.

package com.bea.medrec.dao.orm;

import org.springframework.orm.jpa.support.JpaDaoSupport; 

public abstract class BaseDao extends JpaDaoSupport {
  ...
}

이것은 전문가 입장에서 보면 아주 간단한 클래스이지만, 일반적으로 여러 데이터 접근 객체 구현에 공통적인 모든 코드에 사용될 수 있습니다. 위 코드를 보면 BaseDAO는 Spring의 JpaDaoSupport 클래스를 확장합니다. 이것은 DAO 구현 클래스에서 Spring의 JPA 지정 API에 접근할 수 있는 권한을 주기 때문에 반드시 필요합니다.

Patient DAO 구현 클래스인 JpaPatientDao는 BaseDao를 확장합니다.

public class JpaPatientDao extends BaseDao implements PatientDao {

   public Patient getById(Integer pId) 
      throws DataAccessException {
      return getJpaTemplate().find(Patient.class, pId);
    }

    public List getByLastName(String pLastName) 
      throws DataAccessException {
        
        List patients = getJpaTemplate().find(
            "SELECT p " + 
            "FROM " + Patient.class.getSimpleName() + " p " + 
            "WHERE p.lastName LIKE ?1", pLastName);

         return (patients.isEmpty())? 
            Collections.EMPTY_LIST : patients;
    }
    ...
}

또한 이 코드 예제에서는 두 가지 예제 쿼리 메서드 구현을 보여 줍니다. 그 중 하나는 고유 식별자 필드로 환자를 조회하는 getById()입니다. 이 메서드는 Spring의 JpaTemplate에서 Patient 클래스와 고유 ID를 매개 변수로 사용하여 해당 ID를 가진 Patient 객체를 반환하는 특정 찾기 메서드를 호출하여 실행됩니다. getByLastName() 메서드는 비슷한 성을 가진 환자 목록을 반환합니다. getByLastName()은 EJB QL 쿼리를 매개 변수로 하여 이 쿼리와 일치하는 객체 목록을 반환하는 선택적 버전의 JpaTemplate.find()를 사용합니다. 다음은 Patient DAO 저장 및 업데이트 메서드입니다.

public class JpaPatientDao extends BaseDao implements PatientDao {

   ...

   public Patient save(Patient pPatient) 
      throws DataAccessException {
      getJpaTemplate().persist(pPatient);
        
   return pPatient;
   }

   public Patient update(Patient pPatient) 
      throws DataAccessException {

      return getJpaTemplate().merge(pPatient);
   }
}

이 Patient DAO save 및 update 메서드 구현에서 save() 메서드는 데이터베이스에 새 환자 기록을 삽입하는 데 사용되고, update() 메서드는 기존 환자 기록을 수정합니다. 각 메서드는 새롭거나 업데이트된 Patient 객체에 대한 참조를 반환합니다. 여기서 persist와 merge는 데이터베이스를 즉시 업데이트하지 않음을 이해하는 것이 중요합니다. 변경 내용은 현재 트랜잭션이 커밋될 때까지 JPA에 의해 캐시된 다음 배치로 데이터베이스에 전송됩니다. 그러나 보류 상태의 변경 내용은 쿼리가 실행될 때마다 플러시되는 경우가 많을 수 있습니다.

이 예제에서는 Spring 템플릿이 모든 데이터 접근 동작에 사용되고 내부적으로 JPA로 위임하기 때문에 Patient DAO 객체의 JPA API에 대한 의존성이 거의 없습니다. 실제로 지금까지 본 JPA에 대한 직접 의존성은 getByLastName() 메서드에서 사용하는 EJB QL 쿼리 문자열이 유일합니다. 또한 앞에서는 Spring DAO가 POJO라고 했지만 이는 절대적으로 그런 것은 아닙니다. 왜냐하면 Spring의 JpaDaoSupport 클래스를 확장하는 데 DAO가 필요하기 때문입니다. 그러나 중요한 것은 DAO가 애플리케이션의 나머지 부분에 노출되는 인터페이스가 JPA 또는 Spring 유형에 의존하지 않는 POJI(Plain Old Java Interface)라는 점입니다.

JPA 지속 클래스(Persistent Class)

지금까지 쿼리 및 업데이트를 처리하는 데이터 접근 코드의 예를 살펴 보았습니다. 이번에는 지속 클래스에 대해 알아보겠습니다. 지속 클래스는 어떤 형태일까요? POJO와 메타데이터를 섞은 놓았다고 생각해 보십시오. 지속 POJO는 몇 가지 규칙 또는 디자인 패턴을 따라야 합니다. 이러한 규칙의 일부는 JPA에서 지정하고 다른 규칙은 아래에서 볼 수 있는 것처럼 전체 애플리케이션 아키텍처에 따라 결정됩니다.

먼저 메타데이터부터 살펴 봅니다. JPA에서는 지속성 및 객체 관계형 매핑과 관련된 JPA 메타데이터를 외부 XML 파일, Java 5.0 주석 또는 이 둘의 조합 중 어디에 지정할지 개발자가 선택할 수 있습니다. 의무기록 애플리케이션에서는 Java 주석을 JPA 메타데이터에 사용합니다. 이렇게 하면 메타데이터가 해당 Java 클래스, 필드 및 메서드와 함께 배열되어 이해하기 쉽다는 장점이 있습니다. 메타데이터를 코드와 별도로 유지하려면 XML을 대신 사용할 수 있습니다.

Patient 클래스 및 해당 필드 선언을 확인하여 예제 주석이 있는 JPA 클래스의 모양을 살펴 보겠습니다.

package com.bea.medrec.domain; 

import javax.persistence.*;
import kodo.persistence.jdbc.ElementJoinColumn;

@Entity()
public class Patient implements Serializable
{
  @Id()
  @GeneratedValue(strategy=IDENTITY)
  private Integer id;

  private Date dob;

  @Column(name = "first_name")
  private String firstName;

  private String gender;

  @Column(name = "last_name")
  private String lastName;

  @Column(name = "middle_name")
  private String middleName;

  private String phone;

  private String ssn;

  @ManyToOne(cascade={PERSIST, MERGE})
  @JoinColumn (name="address_id")
  private Address address;

  @OneToOne(cascade={PERSIST, MERGE})
  @JoinColumn (name="email")
  private User user;

  @OneToMany(targetEntity = Prescription.class)
  @ElementJoinColumn(name="pat_id")
  private Set prescriptions=null;

  @OneToMany (targetEntity=Record.class)
  @ElementJoinColumn(name="pat_id")
  private Set records =null; 

  ...
}

Patient 클래스는 Entity 주석으로 주석 처리되어 있습니다. 이것은 주석을 사용하는 모든 지속 클래스에 대한 필수 조건입니다. Entity 주석은 해당 클래스가 JPA 엔터티임을 지속성 엔진에 알려 줍니다. 일반적으로 엔터티 클래스는 public일 필요가 없지만 MedRec의 지속 클래스는 웹 및 웹 서비스 계층에서 사용할 수 있도록 public이어야 합니다.

또한 엔터티 클래스는 Serializable을 구현하지 않아도 되지만 Patient 클래스가 MedRec struts action에 따라 HTTP 세션에 배치되므로 직렬화되어야 합니다. 이것은 세션 상태가 클러스터의 노드 사이에서 직렬화될 수 있는 클러스터 환경에서 애플리케이션이 작동할 수 있도록 하기 위한 것입니다. 엔터티 클래스의 다른 요구 사항으로는 반드시 최상위 클래스여야 하며, 마지막 클래스여서는 안 된다는 것입니다. 전체 요구 사항 목록은 JPA 사양(JSR220)을 참조하십시오.

지속 필드

JPA 구현에서는 런타임에서 엔터티 클래스의 상태(지속 필드)를 읽고 쓸 수 있어야 합니다. JPA 사양은 구현에서 엔터티의 필드에 직접 접근하거나 JavaBean 형식의 accessor 메서드(getters/setters)를 호출하여 이러한 작업을 수행할 수 있도록 합니다. 사용되는 접근 방식은 지속성 주석이 배치되는 위치에 따라 결정됩니다. 예를 들어 클래스의 필드에 배치된 경우에는 필드 접근이 사용됩니다.

JPA에서 왜 필드 접근 방식과 메서드 접근 방식을 둘 다 지원하는 것일까요? 바로 유연성(flexibility) 때문입니다. 일부 클래스는 public accessor 메서드에서 유효성 검사를 수행합니다. 그러나 이 경우 데이터베이스의 값이 범위를 벗어나 예외가 throw되면 JPA 구현에 문제가 발생할 수 있습니다. 클래스가 해당 setters에서 유효성 검사를 수행하는 경우에는 필드를 주석 처리하는 것이 가장 좋습니다. 반면, 가상 지속 속성과 같은 속성을 지원하려는 경우에는 accessor 메서드를 주석 처리합니다.

JPA 엔터티에서 Transient 주석(JPA에서 정의하는 주석)으로 주석 처리되지 않은 모든 non-transient 필드는 지속 필드입니다. "지속(Persistent)"은 필드가 데이터베이스의 열에 매핑된다는 의미입니다. Patient 클래스의 일부 지속 필드에는 주석이 없는 경우도 있습니다. 이는 JPA에서 정의한 기본값(예: 기본 열 이름)이 이러한 필드에 알맞은 값이었기 때문입니다. 필드 이름이 매핑되는 대상 데이터베이스 열 이름과 다른 경우에는 Column 주석을 사용하여 해당 데이터베이스 열에 다른 열 이름을 지정해야 합니다.

모든 엔터티에는 기본 키(primary key)가 있습니다. JPA에서는 단순 기본 키(단일 필드)와 복합 기본 키(다중 필드)를 둘 다 지원합니다. 가능하면 항상 단일 기본 키를 사용하고, 데이터베이스에서 기본 키 값을 생성하도록 하는 것이 가장 좋습니다. 이는 객체가 지속성을 가진 후에는 JPA에서 기본 키 값이 변경되는 것을 허용하지 않기 때문입니다. 의무기록 애플리케이션에서 Patient 클래스를 포함한 대부분의 지속 클래스는 이러한 이유로 단순 기본 키를 사용합니다. 복합 기본 키가 있는 클래스의 예를 보려면 Group 클래스를 보면 됩니다. ID 주석은 기본 키, ID 또는 필드를 표시합니다.

그리고 GeneratedValue 주석은 새 Patient가 삽입될 때 데이터베이스에서 기본 키 값을 생성했음을 나타냅니다. 데이터베이스에서는 여러 가지 방법으로 기본 키 값을 생성할 수 있으며 strategy 특성을 사용하여 그 중 하나를 선택합니다. Patient 클래스는 identity strategy를 사용합니다. Identity strategy는 ID 필드가 데이터베이스의 자동 증분 열 또는 ID 열에 매핑되어야 함을 의미합니다.

Identity strategy를 사용할 때는 클래스의 equals 및 hashcode 메서드에 주는 영향을 알아야 합니다. 예를 들어 equals 메서드에서 ID 필드 값을 비교하는 경우 데이터베이스 삽입이 발생하기 전(일반적으로 트랜잭션 커밋 시)에 equals 메서드가 호출되지 않도록 객체를 사용해야 합니다. 이는 데이터베이스 삽입이 발생하기 전에는 기본 키 값이 지정되지 않기 때문입니다. 이러한 문제를 방지하는 한 가지 방법은 equals 메서드에 생성된 키(generated key) 대신 고유 키(natural key)를 사용하는 것입니다. 예를 들어 환자의 사회 보장 번호는 환자의 equals 메서드에 사용하기 좋은 고유 키입니다.

관계

대부분의 엔터티는 도메인 모델에 있는 일부 다른 엔터티와 관계를 가지며, Patient 클래스에는 예외가 없습니다. 그림 2에서는 Patient 클래스와 해당 관계에 대한 다이어그램을 보여 줍니다.

Figure 2
그림 2. Patient 클래스 관계

JPA에서는 OneToOne, ManyToOne, OneToMany 및 ManyToMany 주석을 사용하여 관계 및 관계차수(cardinality)를 지정합니다. 모든 관계 필드에는 이러한 주석 중 하나가 반드시 있어야 합니다. 예를 들어 Patient가 Address와 ManyToOne 관계가 있다면 이것은 여러 명의 환자의 주소가 같을 수 있음을 의미합니다. Patient가 환자의 사용자 이름 및 암호 정보가 포함된 User 클래스와 OneToOne 관계가 있는 경우에는 각 Patient가 하나의 User와 관계가 있음을 의미합니다. 또한 Patient는 Prescriptions 및 Records 모두와 OneToMany 관계를 가질 수도 있습니다. 이는 특정 환자가 두 가지 이상의 처방을 받고, 의사를 두 번 이상 방문(이때 의무기록이 작성됨)할 수 있기 때문입니다. 반대로 각 Prescription 또는 Record는 한 명의 Patient에만 관계가 있을 수 있습니다.

연쇄(Cascade) 작업

JPA 관계의 재미 있는 특징 중 하나는 작업의 연쇄성입니다. 대부분의 관계형 데이터베이스에서 지원하는 "연쇄 삭제(cascade delete)" 기능에 대해서는 이미 잘 아실 것입니다. JPA에서는 삭제와 같은 작업을 연쇄적으로 처리하고, 이러한 개념을 삽입 및 업데이트 같은 다른 작업에도 적용합니다. 예를 들어 Patient 클래스에서는 삽입 및 업데이트 작업을 관련된 Address 및 User에 연쇄적으로 적용합니다. 데이터베이스 삽입은 객체가 지속성을 가진 후에 수행되므로 JPA에서 삽입은 persist가 됩니다. 반면, 업데이트는 한 객체의 변경 내용을 현재 JPA 지속성 컨텍스트에 병합하므로 merge가 됩니다. 두 가지가 혼동되더라도 걱정하지 마십시오. 이미 지속성을 가진 객체를 업데이트하려는 경우에는 merge를 호출하고, 데이터베이스에 새로운 지속 객체를 삽입하려는 경우에는 persist를 호출해야 한다는 것만 기억하십시오.

persist, merge, remove 등의 특정 작업을 연쇄적으로 처리할지 여부를 결정하는 것은 어려운 일일 수 있습니다. Patient 클래스의 경우, 애플리케이션에서 새 Patient가 삽입될 때마다 새로운 Address와 User를 삽입해야 하기 때문에 Address와 User에 persist가 연쇄적으로 적용됩니다. 마찬가지로, Patient가 업데이트될 때 Address와 User도 업데이트되어야 할 수 있습니다. Prescriptions와 Records는 다르게 작동합니다. 애플리케이션에는 Prescription 또는 Record를 명시적으로 지속화하거나 업데이트하는 작업이 있으므로 이러한 작업을 Patient에서 해당 클래스 사이에 연쇄적으로 처리할 필요가 없습니다.

이 기사의 이전 섹션에서 여러 Patients가 같은 Address를 가질 수 있다(Patient와 Address의 관계가 ManyToOne인 경우)고 한 것을 기억하실 것입니다. 또한 Patient가 Address에 연쇄적으로 삽입될 수 있다는 얘기도 했습니다. 물론 이 경우에는 데이터베이스에 있는 각 환자마다 새 주소가 생성될 것입니다. 그래서 뭘 말하고 싶냐고요? 지금부터 생성된 기본 키를 사용하고, Patient와 Address 사이에 연쇄 삽입을 적용할 텐데, 아마 재미 있는 결과를 보게 될 것입니다. 주소에 대한 기본 키는 데이터베이스에서 자동으로 생성되기 때문에 모든 Address에는 고유의 기본 키가 있으며, 두 개의 다른 Address 객체가 같은 주소를 참조하여 데이터베이스에 삽입될 수도 있습니다. 이는 MedRec을 포함하여 많은 애플리케이션에 유용하지만 중복된 주소가 없도록 하려는 경우에는 DAO에서 일부 추가 논리를 수행해야 합니다.

웹 서비스 고려 사항

MedRec의 엔터티 클래스는 데이터베이스 스키마(JPA에 따름)와 XML(WebLogic Server 9.1 웹 서비스 구현에 따름) 둘 다에 매핑된다는 사실이 엔터티 클래스 디자인에 영향을 주는 문제를 발생시켰습니다.

JPA는 단방향 및 양방향 관계를 둘 다 지원하지만 MedRec에서 엔터티 클래스를 보면 모든 관계가 단방향으로 나타납니다. 이는 웹 서비스 바인딩이 양방향 관계를 지원할 수 없기 때문에 불가피한 것이었습니다.

또 다른 문제는 Java Collection 유형(예: java.util.Set)을 다중값 관계 필드에 사용할 수 있어야 한다는 JPA의 요구 사항 때문에 발생한 것이었습니다. 9.1에서의 웹 서비스 구현은 Collection 유형을 지원하지 않는 것으로 밝혀졌습니다. 그렇다면 이제 어떻게 해야 할까요?

객체 관계 매핑을 엔터티의 메서드 또는 필드 중 하나에서 정의할 수 있다는 사실을 기억해 보십시오. JPA에서 제공하는 이와 같은 유연성은 매우 중요합니다. 반면, 웹 서비스에서는 클래스의 public 속성 accessor 메서드(getXXX() 메서드)를 사용하여 XML에 배열되는 속성을 정의합니다. 따라서 필드를 사용하여 지속성 매핑을 정의하고, 클래스의 메서드에서 XML 바인딩을 정의할 수 있도록 한 결과, 두 개의 매핑을 별도로 유지할 수 있었습니다. 다중값 관계는 지속성을 위해 내부적으로 Set을 사용했지만 속성 접근 메서드를 통해 XML 바인딩에서 처리할 수 있는 배열로 노출되었습니다.

다음은 트랜잭션을 수행한 Record 클래스의 속성 접근 메서드의 예입니다. 이 예를 보면 지금까지 설명한 내용을 좀더 쉽게 이해할 수 있을 것입니다.

public Prescription[] getPrescriptions() {
   if (prescriptions == null)
      return null;

   return (Prescription[]) prescriptions.toArray(new 
      Prescription[prescriptions.size()]);
}

내부적으로 이 메서드는 java.util.Set 유형의 prescriptions 지속 필드에 접근합니다. Set은 웹 서비스 구현에서 처리할 수 있는 배열로 변환됩니다. 웹 서비스 통합에 대한 경험을 토대로 볼 때, JPA가 기존 애플리케이션과 인프라를 통합하는 기능은 정말 인상적이었습니다.

사실 이 하나로 지속 클래스에 대한 논의는 더 이상 필요 없을 것입니다. 많은 규칙과 제한 사항을 따라야 하는 것처럼 보일 수 있지만 실제로 JPA 규칙은 유용한 코딩 정보를 구체화하는 정도일 뿐이며, 아마 어느 순간 많은 고민 없이 이러한 규칙을 따르고 있는 자신을 발견하게 될 것입니다.

DAO를 사용하여 서비스 구현

지금까지 Spring 2.0 및 JPA를 통해 데이터 접근 객체가 구현되는 방법을 알아보았습니다. 이번에는 DAO를 사용하여 작업을 수행하는 MedRec 서비스 객체에 대해 간단히 살펴 보겠습니다. 다음 코드 예제에서는 PatientServiceImpl 클래스 및 이 클래스의 비지니스 메서드 중 하나인 processNewRegistration 메서드를 보여 줍니다.

public class PatientServiceImpl 
   extends BaseService implements PatientService {
 
    protected PatientDao patientDao;

    public void setPatientDao(PatientDao patientDao) {
        this.patientDao = patientDao;
    }
    
    public void processNewRegistration(Registration registration) {
      try {
         User user = registration.getUser();
         Patient patient = registration.getPatient();
         patient.setUser(user);
         user.setStatus(MedRecConstants.USER_NEW);
            
         patientDao.save(pPatient);
      } catch (Exception ex) {
        ...
      }
   }
   
   ...
}

위 예에서 processNewRegistration 메서드는 새 환자가 MedRec 웹 인터페이스를 사용하여 직접 등록할 때 호출됩니다. 이 메서드는 새 환자에 대한 등록 정보를 유지하는 Registration 객체를 매개 변수로 사용합니다. ProcessNewRegistration은 먼저 Registration 객체에서 Patient 및 User를 가져온 다음 둘 사이의 관계를 초기화합니다. User 객체에는 환자의 사용자 이름 및 암호 정보를 비롯하여 상태 정보가 들어 있습니다. 이 환자는 관리자의 승인을 받아야 하는 새 환자이므로 현재 상태 정보가 USER_NEW로 설정됩니다. 그런 다음 데이터베이스에 새 환자를 삽입하도록 Patient DAO가 호출됩니다.

Spring의 선언적 트랜잭션은 processNewRegistration 같은 비지니스 메서드가 호출되기 전에 Spring에서 트랜잭션을 시작하고 메서드가 완료되면 커밋되도록 MedRec 서비스 객체에 구성됩니다. 그러면 DAO에서는 자동으로 커밋될 수 있는 기존 트랜잭션의 일부로 작업을 수행할 수 있습니다. 또한 setPatientDao 메서드는 Spring에서 DAO 객체에 대한 참조를 서비스 빈에 주입하는 데 사용됩니다.

구성

지금까지 이 버전의 Medical Records 애플리케이션을 구성하는 몇몇 Java 코드에 대해 살펴 보았습니다. 이 섹션에서는 Spring 및 Kodo에 필요한 외부 구성에 대해 알아봅니다. 먼저 Spring부터 시작합니다.

Spring 구성

Spring ApplicationContext는 모듈 형식의 XML 구성 파일 집합을 사용하여 구성됩니다. 데이터 접근 객체의 구성이 포함된 구성 파일은 applicationContext-orm.xml로, MedRec 설치 디렉터리 아래 src\medrecEar\APP-INF\classes 디렉터리에 있습니다. 다음은 Patient DAO를 선언하는 applicationContext-orm.xml의 스탠자입니다.

<bean id="patientDao"
      class="com.bea.medrec.dao.orm.JpaPatientDao"
      autowire="byType"/>

Spring의 JPA DAO에는 주입이 필요한 하나의 의존성이 있습니다. 바로 JPA EntityManagerFactory입니다. EntityManagerFactory는 DAO에서 실제 지속성 작업을 수행하는 JPA EntityManagers를 만드는 데 사용됩니다. 여기서 와이어링(wiring)은 Spring의 autowire 기능을 사용하여 수행됩니다. 바로 이 때문에 XML에서 EntityManagerFactory에 대한 의존성을 명시적으로 불 수 없는 것입니다. autowire 기능은 모든 MedRec DAO가 확장되는 Spring JpaDaoSupport에서 상속된 DAO의 EntityManagerFactory 속성을 기반으로 해당 DAO를 EntityManagerFactory에 와이어링합니다.

아래 코드는 EntityManagerFactory를 만드는 Spring 팩토리 빈을 선언합니다. 코드를 보면 지금 설명하고 있는 두 개의 팩토리가 있어 혼동될 수 있겠지만 Spring 팩토리 빈의 용도는 단지 일부 사용자 지정 코드를 실행하여 EntityManagerFactory를 만들 수 있는 간접 지정 수준(level of indirection)을 제공하는 것입니다. DAO 객체가 인식하는 팩토리는 EntityManagerFactory뿐입니다.

<bean id="entityManagerFactory"
      class="com.bea.medrec.utils.KodoEntityManagerFactoryBean">
  <property name="jndiName">
     <value>kodo-jpa</value>
  </property>  
</bean>

이 코드를 작성할 당시에는 WebLogic Server에서 JPA와의 표준 통합을 지원하지 않았기 때문에 EntityManagerFactory는 사용자 지정 팩토리 빈을 사용하여 만듭니다. EntityManagerFactory는 JNDI에서 Kodo 리소스 어댑터를 조회할 위치를 알아야 합니다. 이 정보는 Spring 팩토리 빈에서 EntityManagerFactory를 따라 전달되는 하나의 속성으로 구성됩니다. 팩토리 빈의 역할은 EntityManagerFactory 인스턴스를 만들고, Spring에서 EntityManagerFactory를 DAO 빈에 주입할 수 있도록 이 인스턴스를 Spring에 반환하는 것입니다. 엔터티 관리자 팩토리 인스턴스를 만드는 팩토리 빈의 코드는 다음과 같습니다.

public class KodoEntityManagerFactoryBean 
   implements FactoryBean, InitializingBean {

   private String jndiName = "kodo-jpa";

   private EntityManagerFactory entityManagerFactory = null;

   public void setJndiName(String jndiName) {
      this.jndiName = jndiName;
   } 

   public Object getObject() throws Exception {
      return entityManagerFactory;
   }

   public Class getObjectType() {
      return EntityManagerFactory.class;
   }

   public boolean isSingleton() {
      return true;
   } 

   public void afterPropertiesSet() throws Exception {
      try {
         entityManagerFactory  =
            KodoPersistence.createEntityManagerFactory(
               jndiName, new InitialContext());
      } catch (Throwable throwable) {
         throw new RuntimeException(throwable);
      }
   }
}

여기서 KodoEntityManagerFactoryBean은 바로 Spring 팩토리 빈입니다. 런타임에서 Spring은 먼저 빈(bean) 인스턴스를 만들 기본 구성자를 호출합니다. 그런 다음 setJndiName() 메서드를 호출하여 Kodo 리소스 어댑터의 JNDI 이름을 설정합니다. 모든 속성이 주입되고 나면 Spring애서 EntityManagerFactory 인스턴스를 만드는 afterPropertiesSet()을 호출합니다. 마지막으로, Spring에서는 getObject()를 호출하여 DAO에 주입할 EntityManagerFactory를 가져옵니다.

복습하는 의미로, DAO에서 모든 지속성 작업을 수행하는 데 사용하는 Kodo EntityManagerFactory를 Spring DAO에 주입하는 방법에 대해 다시 한 번 살펴 보았습니다. JNDI를 통해 Kodo EntityManagerFactory를 Kodo 리소스 어댑터에 연결합니다. 다음에는 Kodo 리소스 어댑터를 구성하는 방법을 알아볼 텐데, 그 전에 모든 것을 하나로 연결할 수 있게 해 주는 Spring 구성에 대해 알아야 할 사항이 있습니다.

Spring에서는 WebLogic JTA 트랜잭션 관리자를 사용하여 트랜잭션을 시작 및 커밋합니다. 이 작업은 모두 XML 스탠자에서 이루어집니다.

<bean id="transactionManager"
      class="org.springframework.transaction.jta.WebLogicJtaTransactionManager">
  <property name="transactionManagerName"
    value="javax.transaction.TransactionManager"/>
</bean>

여기서 한 가지, Spring에서는 트랜잭션 작업을 WebLogic JTA 구현에 위임한다는 사실을 이해해야 합니다. 또한 이 기사의 뒷부분에서는 Kodo의 구성에 JTA 트랜잭션이 어떻게 사용되는지를 알아볼 것입니다. 이 설정에 따라 MedRec 서비스 객체에서는 WebLogic JTA 트랜잭션을 시작한 다음 해당 트랜잭션의 컨텍스트에서 DAO를 호출합니다. 그런 다음 DAO에서는 Kodo를 호출하여 기존 WebLogic JTA 트랜잭션의 일부로 해당 데이터베이스의 읽기/쓰기를 수행합니다.

Kodo 구성

Kodo의 JPA 구현에 쉽게 접근할 수 있는 Kodo 4.0 EA4를 사용했습니다. 이 코드를 작성할 당시 JPA를 정의하는 EJB 3.0 사양이 최종 버전이 아니었기 때문에 Kodo에서는 최종 버전 이전의 JPA 버전을 지원합니다. Kodo는 리소스 어댑터로 WebLogic Server에 배포됩니다. 리소스 어댑터는 JNDI에 등록됩니다. 또한 리소스 어댑터의 구성은 표준 ra.xml 설명자 파일에서 이루어집니다. 이 파일은 리소스 어댑터 RAR 파일의 META-INF 디렉터리에 있습니다.

ra.xml 파일을 보면 Kodo에서 다양한 구성 옵션을 지원하는 것을 알 수 있습니다. 바로 기술적으로 성숙한 제품이라는 의미입니다. 그러나 예제 애플리케이션에서는 그 중 몇 가지를 수정해야 했습니다. 예제 애플리케이션에 필요한 속성을 다음과 같습니다.

<config-property>
   <config-property-name>ConnectionFactory2Name</config-property-name>
   <config-property-type>java.lang.String</config-property-type>
   <config-property-value>jdbc/MedRecGlobalDataSource
   </config-property-value>
</config-property>

<config-property>
   <config-property-name>ConnectionFactoryName</config-property-name>
   <config-property-type>java.lang.String</config-property-type>
   <config-property-value>jdbc/MedRecGlobalDataSourceXA
   </config-property-value>
</config-property>

<config-property>
   <config-property-name>TransactionMode</config-property-name>
   <config-property-type>java.lang.String</config-property-type>
   <config-property-value>managed</config-property-value>
</config-property>

<config-property>
   <config-property-name>LicenseKey</config-property-name>
   <config-property-type>java.lang.String</config-property-type>
   <config-property-value>XXXXXXXXXXXXXXXXXXXX</config-property-value>
</config-property>

<config-property>
   <config-property-name>PersistentClasses</config-property-name>
   <config-property-type>java.lang.String</config-property-type>
   <config-property-value>
      com.bea.medrec.domain.Patient,com.bea.medrec.domain.Address,
      com.bea.medrec.domain.User,com.bea.medrec.domain.Physician,
      com.bea.medrec.domain.Prescription,com.bea.medrec.domain.Record,
      com.bea.medrec.domain.Group,com.bea.medrec.domain.VitalSigns
   </config-property-value>
</config-property>

Kodo에 필요한 한 가지는 데이터베이스와 상호 작용할 수 있는 JDBC 데이터 소스의 JNDI 위치입니다. 실제로 Kodo에서는 두 가지 데이터 소스, 즉 트랜잭션 작업을 처리할 데이터 소스와 비 트랜잭션 작업을 처리할 데이터 소스가 필요합니다. 이는 Kodo에서 데이터베이스에 접근하여 전역 트랜잭션의 일부가 되어서는 안 되는 작업을 수행하는 경우가 있기 때문입니다. 예상할 수 있겠지만, ConnectionFactoryName 및 ConnectionFactory2Name 속성이 바로 이런 용도로 사용됩니다. 각 속성 값은 WebLogic 데이터 소스의 JNDI 이름입니다. 이 경우 ConnectionFactory2Name에서 전역 JTA 트랜잭션에 연결되어서는 안 되는 데이터 소스를 참조하는지 확인해야 합니다.

Kodo에서는 애플리케이션이 JTA 관리 트랜잭션을 사용하는지 로컬 트랜잭션을 사용하는지 알아야 합니다. 트랜잭션을 관리하는 MedRec의 서비스 빈이 JTA를 사용하도록 구성되어 있기 때문에 여기서는 TransactionMode 속성 값이 관리되도록 설정합니다. 또한 리소스 어댑터 파일에서 Kodo 라이센스를 구성하고 애플리케이션에서 사용 중인 지속 클래스의 목록을 작성해야 합니다. 여기에 사용되는 속성은 구체적인 설명이 나타나는 것이어야 합니다.

JPA 사양을 알아볼 기회가 있었다면 persistence.xml 파일에 대해 들어본 적이 있을 것입니다. 이 파일은 JPA 지속성과 관련된 메타데이터가 있는 표준 구성 파일입니다. Kodo에서는 이 persistence.xml 파일도 지원합니다. 그러나 애플리케이션 서버 환경에 신속하게 접근할 수 있는 이 버전의 Kodo를 사용할 경우에는 persistence.xml 파일에 리소스 어댑터 구성에 지정된 정보의 일부만 들어 있고 Kodo Enhancer와 같은 도구에서만 사용할 수 있습니다.

다운로드

다운로드(medrec-spring-jpa.zip)에는 이 기사에서 설명한 MedRec 버전의 모든 Java 파일과 기타 소스 파일을 비롯하여 Spring 2.0 및 Kodo 이진 파일과 해당 의존성 파일이 들어 있습니다. 다운로드하려면 약 27MB의 디스크 공간이 필요합니다.

결론

이 기사에서는 Spring 2.0에서 지원되는 새로운 JPA의 사용에 대해 자세히 살펴 보았습니다. 새로운 JPA는 이 기사의 작성을 위해 MedRec 예제 애플리케이션의 데이터 접근 계층을 다시 구현하는 데 사용되었습니다. 이 기사가 새로운 API의 사용을 준비하는 데 충분한 도움이 되기를 바랍니다. 좀더 깊이 있게 알고 싶다면 이 기사에 포함된 실제 MedRec 코드를 보시기 바랍니다. 예제 애플리케이션 코드에서는 WebLogic 9.1, Spring 2.0 및 Kodo JPA를 통합된 방식으로 사용하는 방법을 보여 줍니다.

이제 새로운 JPA API는 사용이 편리하고, 직관적이며, 동시에 유연하다는 사실을 알았습니다. Java 주석을 어떻게 처리하느냐에 따라서는 데이터베이스 매핑을 지정하는 데 필요한 메타데이터의 양이 달라집니다. JPA는 새롭기는 하지만 도메인 모델 클래스가 애플리케이션의 지속성 계층, 웹 계층 및 웹 서비스 계층에서 지속적으로 사용될 수 있을 만큼 강력합니다. 이러한 재사용성 덕분에 애플리케이션 아키텍처를 극적으로 간소화하고, 개발자가 작성해야 하는 Java 클래스 수를 크게 줄일 수 있습니다.

Spring 2.0에서는 JPA를 사용하는 데이터 접근 객체를 만들 수 있는 기능을 제공합니다. Spring의 데이터 접근 아키텍처는 애플리케이션 코드의 나머지 부분을 다시 작성하지 않고도 여러 지속성 기술 간에 쉽게 전환할 수 있도록 해 줍니다.

기타 문서

Seth White는 BEA의 엔지니어로서 지난 6년 간 WebLogic Server 엔지니어링 팀에서 근무했으며, 현재는 WebLogic Server 오픈 소스 팀에 있습니다.

 

출처 : http://www.dev2dev.co.kr/pub/a/2006/03/jpa-spring-medrec.jsp