Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Spring 레퍼런스] 부록 A. 고전적인 스프링 사용방법

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.



Part VII. 부록


부록 A. 고전적인 스프링 사용방법

이 부록에서는 레거시 스프링 애플리케이션을 유지 보수하는 개발자들이 참고할 수 있도록 고전적인 스프링의 몇 가지 사용 패턴을 설명한다. 이러한 사용패턴은 더는 스프링의 기능을 사용하는데 권장하는 방법이 아니고 현재 권장하는 방법은 참조 매뉴얼의 각 부분에서 다루었다.

A.1 고전적인 ORM 사용방법

이 부분에서는 레거시 스프링 애플리케이션에서 직면할 수 있는 고전적인 사용 패턴을 설명한다. 현재 권장하는 사용패턴은 Chapter 14, 객체 관계 매핑 (ORM) 데이터 접근 장을 참고하기 바란다.

A.1.1 하이버네이트(Hibernate)

현재 권장하는 하이버네이트 사용패턴은 Section 14.3, “하이버네이트(Hibernate)”를 참고해라.

A.1.1.1 HibernateTemplate

객체나 비즈니스 서비스에 접근하는 커스텀 데이터 일부가 될 수 있는 메서드에 대한 템플릿의 기본적인 프로그래밍 모델은 다음과 같다. 객체와 관련한 구현체에는 어떠한 제약도 없고 하이버네이트 SessionFactory만 제공하면 된다. SessionFactory는 어디서든 나중에 가져올 수 있지만, 스프링 IoC 컨테이너에서 빈(bean) 참조로 가져오는 걸 선호한다. (setSessionFactory(..) 빈 프로퍼티 세터로) 다음 코드에 스프링 컨테이너의 DAO 정의가 나와 있다. 이 정의는 앞에서 정의한 SessionFactory를 참조하고 있고 DAO 메서드 구현체의 예시이다.

<beans>
  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>
</beans>
public class ProductDaoImpl implements ProductDao {
  private HibernateTemplate hibernateTemplate;

  public void setSessionFactory(SessionFactory sessionFactory) {
    this.hibernateTemplate = new HibernateTemplate(sessionFactory);
  }

  public Collection loadProductsByCategory(String category) throws DataAccessException {
    return this.hibernateTemplate.find("from test.Product product where product.category=?", category);
  }
}

HibernateTemplate 클래스는 하이버네이트 Session 인터페이스가 노출하는 메서드와 같은 많은 메서드를 제공하고 추가로 앞에서 본 것처럼 다수의 편의 메서드를 제공한다. HibernateTemplate가 제공하지 않는 메서드를 호출하려고 Session에 접근해야 한다면 다음과 같이 콜백방식을 사용할 수 있다.

public class ProductDaoImpl implements ProductDao {
  private HibernateTemplate hibernateTemplate;

  public void setSessionFactory(SessionFactory sessionFactory) {
    this.hibernateTemplate = new HibernateTemplate(sessionFactory);
  }

  public Collection loadProductsByCategory(final String category) throws DataAccessException {
    return this.hibernateTemplate.execute(new HibernateCallback() {

      public Object doInHibernate(Session session) {
        Criteria criteria = session.createCriteria(Product.class);
        criteria.add(Expression.eq("category", category));
        criteria.setMaxResults(6);
        return criteria.list();
      }
    };
  }
}

하이버네이트 데이터 접근에 콜백 구현체를 효과적으로 사용할 수 있다. HibernateTemplate는 Session 인스턴스를 적절하게 여닫는 것을 보장하고 자동으로 트랜잭션에 참여하게 한다. 템플릿 인스턴스는 스레드 세이프하고 재사용성이 있어서 감싸는 클래스의 인스턴스 변수로 담고 있을 수 있다. 단일 find, load, saveOrUpdate, delete 호출처럼 간단한 하나의 작업을 위해 HibernateTemplate는 한 줄짜리 콜백 구현체를 대체할 수 있는 편의 메서드를 제공하고 있다. 게다가 스프링은 SessionFactory를 받는 setSessionFactory(..) 메서드를 제공하는 간편한 HibernateDaoSupport 기반 클래스를 제공하고 하위클래스가 사용할 수 있도록 getSessionFactory()와 getHibernateTemplate()를 제공한다. 이를 사용해서 전형적인 요구사항에 맞는 아주 간단한 DAO 구현체를 작성할 수 있다.

public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao {
  public Collection loadProductsByCategory(String category) throws DataAccessException {
    return this.getHibernateTemplate().find(
      "from test.Product product where product.category=?", category);
  }
}


A.1.1.2 콜백없이 스프링에 기반을 둔 DAO 구현하기

DAO를 구현하는데 스프링의 HibernateTemplate를 사용하는 대신 콜백에 하이버네이트 접근 코드를 랩핑하지 않고 더 전통적인 방식으로 데이터 접근 코드를 작성할 수도 있고 이러면서도 여전히 스프링의 일반적인 DataAccessException 계층을 따르고 참여할 수 있다. HibernateDaoSupport 기반 클래스는 현재 트랜잭션이 적용된 Session에 접근할 수 있는 메서드를 제공하고 이러한 시나리오에서 예외를 변환하는 메서드를 제공한다. 비슷한 메서드를 SessionFactoryUtils의 정적 헬퍼로 사용할 수도 있다. 이러한 코드는 getSession(..) 메서드의 'allowCreate' 인자 값으로 'false'를 보통 전달해서 트랜잭션 내에서 실행되도록 강제한다.(트랜잭션이 관리하는 생명주기를 관리하므로 반환된 Session을 닫을 필요가 없도록)

public class HibernateProductDao extends HibernateDaoSupport implements ProductDao {
  public Collection loadProductsByCategory(String category) throws DataAccessException, MyException {
    Session session = getSession(false);
    try {
      Query query = session.createQuery("from test.Product product where product.category=?");
      query.setString(0, category);
      List result = query.list();
      if (result == null) {
        throw new MyException("No search results.");
      }
      return result;
    }
    catch (HibernateException ex) {
      throw convertHibernateAccessException(ex);
    }
  }
}

이렇게 하이버네이트에 직접 접근하는 코드의 장법은 데이터 접근 코드 내에서 어떤 체크드 애플리케이션 익셉션이라도 던질 수 있다는 것이다. 이는 HibernateTemplate 클래스가 콜백내에서 언체크드 익셉션만 던질 수 있다는 제약과는 다른 부분이다. 때로는 HibernateTemplate에서 콜백실행 후에 던져진 애플리케이션 익셉션을 대응되는 체크드 입셉션으로 지연시킬 수도 있다. 일반적으로 HibernateTemplate 클래스의 편의 메서드는 많은 경우에 더 간단하고 훨씬 편리하다.

A.1.2 JDO

현재 권장하는 JDO 사용패턴은 Section 14.4, “JDO”를 참고해라.

A.1.2.1 JdoTemplate와 JdoDaoSupport

JDO에 기반을 둔 각 DAO는 의존성 주입으로 PersistenceManagerFactory를 받는다. 이러한 DAO는 평범한 JDO API로 작성할 수 있고 주어진 PersistenceManagerFactory와 동작할 수 있지만, 보통은 스프링 프레임워크의 JdoTemplate을 사용할 것이다.

<beans>
  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="persistenceManagerFactory" ref="myPmf"/>
  </bean>
</beans>
public class ProductDaoImpl implements ProductDao {
  private JdoTemplate jdoTemplate;

  public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
    this.jdoTemplate = new JdoTemplate(pmf);
  }

  public Collection loadProductsByCategory(final String category) throws DataAccessException {
    return (Collection) this.jdoTemplate.execute(new JdoCallback() {
      public Object doInJdo(PersistenceManager pm) throws JDOException {
        Query query = pm.newQuery(Product.class, "category = pCategory");
        query.declareParameters("String pCategory");
        List result = query.execute(category);
        // 결과 리스트로 추가 작업을 한다
        return result;
      }
    });
  }
}

콜백 구현체는 어떤 JDO 데이터 접근에도 효과적으로 사용할 수 있다. JdoTemplate는 PersistenceManager를 제대로 여닫았다는 것을 보장하고 자동으로 트랜잭션에 참여한다. 템플릿 인스턴스는 스레드 세이프하고 재사용성이 있어서 감싸는 클래스의 인스턴스 변수로 보관할 수 있다. 하나의 find, load, makePersistent, delete 호출 같은 간단한 단일 동작에 대해 JdoTemplate가 한 줄짜리 콜백 구현체로 교체할 수 있는 편의 메서드를 제공한다. 게다가 PersistenceManagerFactory를 받는 setPersistenceManagerFactory(..) 메서드와 하위 클래스가 사용하는 getPersistenceManagerFactory()와 getJdoTemplate()를 제공하는 편리한 JdoDaoSupport 기반 클래스를 스프링이 제공한다. 이를 함께 사용하면 일반적인 요구사항에 대해 아주 간단한 DAO 구현체를 작성할 수 있다.

public class ProductDaoImpl extends JdoDaoSupport implements ProductDao {
  public Collection loadProductsByCategory(String category) throws DataAccessException {
    return getJdoTemplate().find(
      Product.class, "category = pCategory", "String category", new Object[] {category});
  }
}

스프링의 JdoTemplate와 동작하도록 작성하는 대신 JDO API 수준에서 스프링에 기반을 둔 DAO를 작성할 수 있어서 명시적으로 PersistenceManager를 여닫을 수 있다. 하이버네이트 부분에서 자세히 설명한 대로 이러한 접근의 가장 큰 장점은 데이터 접근 코드에서 체크드 익셉션을 던질 수 있다는 것이다. JdoDaoSupport는 익센션을 잘 변환하면서 트랜잭션이 적용된 PersistenceManager를 가져오고 해지하면서 이러한 시나리오를 지원하는 여러 메서드를 제공한다.

A.1.3 JPA

현재 권장하는 JPA 사용패턴은 Section 14.5, “JPA”를 참고해라.

A.1.3.1 JpaTemplate와 JpaDaoSupport

JPA에 기반을 둔 각 DAO는 의존성 주입으로 EntityManagerFactory를 받을 것이다. 이러한 DAO는 평범한 JPA로 작성할 수 있고 주어진 EntityManagerFactory나 스프링의 JpaTemplate와 동작한다.

<beans>
  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="entityManagerFactory" ref="myEmf"/>
  </bean>
</beans>
public class JpaProductDao implements ProductDao {
  private JpaTemplate jpaTemplate;

  public void setEntityManagerFactory(EntityManagerFactory emf) {
    this.jpaTemplate = new JpaTemplate(emf);
  }

  public Collection loadProductsByCategory(final String category) throws DataAccessException {
    return (Collection) this.jpaTemplate.execute(new JpaCallback() {
      public Object doInJpa(EntityManager em) throws PersistenceException {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        List result = query.getResultList();
        // 결과 리스트로 추가 작업을 한다
        return result;
      }
    });
  }
}

JpaCallback 구현체는 어떤 타입의 JPA 데이터 접근이라고 가능하다. JpaTemplate는 EntityManager가 적절하게 열리고 닫히는 것을 보장하고 트랜잭션에 자동으로 참여하게 한다. 게다가 JpaTemplate는 예외를 제대로 다뤄서 리소스가 제대로 정리되고 트랜잭션이 제대로 롤백되었다는 것을 보장한다. 템플릿 인스턴스는 스레드 세이프하고 재사용성이 있어서 감싸진 클래스의 인스턴스 변수로 보관할 수 있다. JpaTemplate는 find, load, merge 등의 단일 동작을 한 줄짜리 콜백 구현체로 교체할 수 있는 편의 메서드를 제공한다.

게다가 스프링은 get/setEntityManagerFactory를 제공하고 하위 클래스가 사용하는 getJpaTemplate()를 제공하는 편리한 JpaDaoSupport 기반 클래스를 제공한다.

public class ProductDaoImpl extends JpaDaoSupport implements ProductDao {
  public Collection loadProductsByCategory(String category) throws DataAccessException {
    Map<String, String> params = new HashMap<String, String>();
    params.put("category", category);
    return getJpaTemplate().findByNamedParams("from Product as p where p.category = :category", params);
  }
}

스프링의 JpaTemplate로 작업할 때와 달리 JPA로 스프링에 기반을 둔 DAO를 작성할 수도 있다. 이때는 명시적으로 EntityManager를 다루어야 한다. 하이버네이트 부분에서 자세히 설명했듯이 이 방법의 주요 장점은 데이터 접근 코드가 체크드 익셉션을 던질 수 있다는 것이다. JpaDaoSupport는 이러한 시나리오를 지원하는 다양한 메서드를 제공해서 예외를 잘 변환하면서 트랜잭션이 적용된 EntityManager를 받고 해제한다.

JpaTemplate은 주로 JdoTemplate와 HibernateTemplate의 형제 관계로 존재해서 이에 익숙한 사람들에게 같은 방식을 제공한다.

A.2 고전적인 Spring MVC

...

A.3 JMS 사용방법

스프링 JMS 지원의 장점 중 하나는 JMS 1.0.2와 1.1 API 간의 차이점을 사용자가 느끼지 못하게 한다는 점이다.(두 API의 차이점에 대한 내용을 보려면 도메인 단일화의 사이드바를 봐라.) JMS 1.1 API만 사용하는 것이 이제는 일반적이지만 JMS 1.0.2 API에 기반을 둔 클래스를 사용하는 것은 스프링 3.0에서는 폐기(deprecated)되었다. 이번 장에서는 JMS 1.0.2의 폐기된 클래스에 대한 스프링 JMS 지원을 설명한다.

도메인 단일화(Domain Unification)
JMS 명세에는 두 가지 메이저 릴리즈가 있다.(1.0.2와 1.1)
JMS 1.0.2는 두 가지 종류의 메시지 도메인인 point-to-point (Queues)와 publish/subscribe (Topics)를 정의한다. 1.0.2 API는 도메인마다 병렬 클래스 계층을 제공해서 이 두 가지 메시징 도메인을 반영한다. 그 결과 클라이언트 애플리케이션은 JMS API를 사용할 때 도메인에 특화되게 되었다. JMS 1.1에서는 두 도메인사이에 함수적인 차이점과 클라이언트 API의 차이점을 모두 최소화하는 도메인 단일화라는 개념이 도입되었다. 제거된 함수의 차이점에 대한 예시로 JMS 1.1 프로바이더를 사용할 때 한 도메인에서 트랜잭션을 걸어서 메시지를 소비하고 같은 Session으로 다른 도메인에 메시지를 생성할 수 있다.

Note
JMS 1.1 명세는 2002년 4월에 공개되었고 2003년 11월 J2EE 1.4의 일부분이 되었다. 그래서 아직도 널리 쓰이는 일반적인 J2EE 1.3 애플리케이션 서버(BEA WebLogic 8.1이나 IBM WebSphere 5.1 같은)는 JMS 1.0.2에 기반을 두고 있다.


A.3.1 JmsTemplate

org.springframework.jms.core 패키지에 있는 JmsTemplate102 클래스는 JMS 장에서 설명한 JmsTemplate의 모든 기능을 제공하지만, JMS 1.1 API 대신 JMS 1.0.2 API에 기반을 두고 있다. 그래서 JmsTemplate102를 사용한다면 어떤 JMS 도메인을 사용하는가에 대한 정보로 JmsTemplate를 구성하기 위해 pubSubDomain 불리언 프로퍼티를 설정해야 한다. 기본적으로 이 프로퍼티의 값은 false이고 point-to-point 도메인(Queues)를 사용한다는 것을 의미한다.

A.3.2 비동기 메시지 수신

거의 모든 클래스를 메시지 주도 POJO로 노출해서 비동기 메시지 수신을 지원하려고 MessageListenerAdapter는 스프링의 메시지 리스너 컨테이너(message listener containers)와 함께 사용한다. JMS 1.0.2 API를 사용한다면 MessageListenerAdapter102, SimpleMessageListenerContainer102, DefaultMessageListenerContainer102같은 1.0.2에 특화된 클래스를 사용하고자 할 것이다. 이러한 클래스는 JSM 1.1에 기반을 둔 부분과 같은 기능을 제공하지만 JMS 1.0.2 API에만 의존한다는 것만 다르다.

A.3.3 연결

ConnectionFactory 인터페이스는 JMS 명세의 일부분이고 JMS와 함께 동작하는 진입점으로 제공된다. 스프링은 ConnectionFactory 인터페이스의 구현체인 SingleConnectionFactory102를 제공한다. 이는 JMS 1.0.2에 기반을 두고 있어서 모든 createConnection() 호출에 같은 Connection을 반환하고 close() 호출은 무시할 것이다. SingleConnectionFactory102이 항상 javax.jms.QueueConnection와 javax.jmsTopicConnection를 명시적으로 구분할 것이므로 어떤 메시징 도메인을 사용하는지 나타내기 위해 pubSubDomain 블리언 프로퍼티를 설정해야 할 것이다.

A.3.4 트랜잭션 관리

JMS 1.0.2 환경에서 JmsTransactionManager102 클래스는 하나의 Connection Factory에 대한 JMS 트랜잭션 관리를 지원한다. 이 기능에 대한 자세한 내용은 JMS 트랜잭션 관리의 참고문서를 봐라.

2014/12/28 23:49 2014/12/28 23:49