메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

하이버네이트 인터셉터 소개(1)

한빛미디어

|

2007-05-23

|

by HANBIT

10,509

제공 : 한빛 네트워크
저자 : Shunmuga Raja
역자 : 백기선
원문 : Hibernate Interceptors - An Introduction

1. 도입

본 기사는 하이버네이트 인터셉터에 대한 것입니다. 하이버네이트는 ORM 솔루션을 제공하는 오픈 소스 프로젝트 입니다. 하이버네이트에 대한 좀 더 많은 정보를 원하신다면 javabeat에 올라왔던 An Introduction to Hibernate를 참조 하시기 바랍니다.

핵심 기능 로직을 수행하기 전/후에 필수로 수행해야 할 것들(pre-requisite/post-requisite)을 처리하기 위한 솔루션이 필여합니다. 이럴 때 interceptor 를 현재 비즈니스 기능을 확장하거나 추가적인 기능을 제공하기 위해 인터셉트 할 때 사용할 수 있습니다. 이런 기능들은 플러거블(pluggable) 한 아키텍처를 제공하고 일반화된 콜백(callback) 메소드들을 제공하여 특정 이벤트나 액션에 반응하여 프레임워크가 자동으로 호출하도록 합니다. 일반적인 인터셉터 패턴을 따르고 있으며 애플리케이션 설계에 다양한 장점을 제공합니다. 이것들을 사용하여 애플리케이션에 전달되는 입력 값들을 검증 하기 위한 모니터링을 할 수도 있습니다. 심지어 모듈의 핵심 기능 로직을 재정의 할 수 있는 능력도 있습니다.

예를 들어, 주문을 하면 고객의 배송 주소지로 상품을 보내주는 온라인 쇼핑몰을 생각해 봅시다. 애플리케이션에서 주문 요청이 들어 왔을 때 스팸인지 아닌지 검증을 해야 하고 고객에게 e-mail(또는 모바일)로 상품을 성공적으로 전달 했다는 것을 알려주어야 하다는 개선 사항이 있다고 생각해 보겠습니다. 이 두 개선사항은 애플리케이션의 핵심 로직에 끼어들어야 합니다.

일반적인 핵심 로직의 수행 과정을 개략적으로 본다면 다음과 같을 것입니다.
  • 사용자의 요청을 검증합니다.
  • 상품을 고객에게 보내줍니다.
  • 고객에게 성공적으로 배송되었다는 것을 알려줍니다.
위에서 볼 수 있듯이, 두 개의 개선사항은 핵심 로직의 전과 후에 끼어들어야 하기 때문에 코드가 변하게 됩니다. 그러나 만약 인터셉터를 사용하여 애플리케이션을 잘 설계 한다면 코드의 변화는 최대한으로 줄어들게 됩니다.

2. 하이버네이트의 인터셉터

하이버네이트는 데이터베이스의 데이터에 대한 영속성과 쿼리를 위한 ORM 솔루션을 제공합니다. 하이버네이트 애플리케이션은 특정 메소드를 특정한 라이프 사이클 이벤트가 발생할 때 호출 되도록 작성할 수 있습니다. 애플리케이션이 필요로 하고 요구하는 모든 API를 제공하는 소프트웨어 또는 제품은 없습니다. 하이버네이트도 이 가정에 크게 벗어나지 않습니다. 따라서 하이버네이트 API는 인터셉터를 통해서 확장성(pluggable)이 좋은 프레임워크를 제공하도록 설계하였습니다.

계층화된 애플리케이션(multi-tiered application)에서 인터셉터는 어떤 계층에도 포함될 수 있습니다. 클라이언트 계층, 서버 계층 그리고 심지어 영속성 계층((persistence layer)에도 인터셉터가 끼어들 수 있습니다. 직원들의 기록을 데이터베이스에 저장하는 애플리케이션 있고 이 애플리케이션이 DBA에게 insert와 update 작업에 대한 히스토리를 보여줘야 한다고 생각해 봅시다.

간단하게 로직을 살펴보면 다음과 같습니다.
  • 데이터베이스에 레코드를 Insert/Update 합니다.
  • Insert/Update 할 때 파일에 로그 정보를 기록합니다.
위에서 살펴본 것처럼 로깅 정보를 기록하는 것은 데이터베이스에 Insert/Update 작업이 발생할 때 마다 수행해야 합니다. 이렇게 로깅을 하는 인터셉터는 하이버네이트의 유연한(flexible) 설계 덕분에 약간의 코드 변경만으로도 쉽게 애플리케이션에 끼어 넣을 수 있습니다.

2-1. 인터셉터의 타입

스콥(scope)을 기준으로 하이버네이트의 인터셉터는 두 개의 범주로 나눌 수 있습니다.
  • Application-scoped 인터셉터
  • Session-scoped 인터셉터
2-1-1. Application-scoped 인터셉터

애플리케이션은 하나 이상의 데이터베이스 세션을 Session 인터페이스 타입으로 얻을 수 있습니다. 만약에 애플리케이션이 Global Interceptors 를 사용하도록 설정 되었다면, 모든 세션에 있는 persistent 상태의 객체에게 영향을 주게 됩니다. 다음의 코드는 global 인터셉터를 설정하고 있습니다.
Configuration configuration = new Configuration();
configuration.setInterceptor(new MyInterceptor());

SessionFactory sessionFactory = configuration.buildSessionFactory();

Session session1 = sessionFactory.openSession();
Employee e1, e2 = null;
// Assume e1 and e2 objects are associated with session1.

Session session2 = sessionFactory.openSession();
User u1, u2 = null
//Assume u1 and u2 objects are associated with session1.
global-scoped 인터셉터는 Configuration.setInterceptor(Interceptor) 메소드를 사용하여 애플리케이션에 설정할 수 있습니다. 위의 코드에 두 개의 세션 객체인 ‘session1’과 ‘session2’가 있습니다. Employee 타입의 객체인 e1과 e2는 ‘session1’과 연관되어 있고 User 타입의 객체인 u1과 u2는 ‘session2’와 연관되어 있다고 가정해 봅시다. 이 때 application-scoped 인터셉터는 비록 다른 세션에 담겨있지만 모든 객체들(e1, e2, u1, u2)에 영향을 줄 것입니다.

2-1-2. Session-scoped 인터셉터

session-scoped interceptor 는 특정 session에 관련된 persistent 상태의 객체에만 영향을 줍니다. 다음은 session-scoped 인터셉터를 설정하는 코드입니다.
Configuration configuration = new Configuration();

SessionFactory sessionFactory = configuration.buildSessionFactory();

MyInterceptor myInterceptor = new MyInterceptor();
Session session1 = sessionFactory.openSession(myInterceptor);
Employee e1, e2 = null;
// Assume e1 and e2 objects are associated with session "session1".

MyAnotherInterceptor myAnotherInterceptor = new MyAnotherInterceptor ();
Session session2 = sessionFactory.openSession(myAnotherInterceptor);
User u1, u2 = null;
// Assume u1 and u2 objects are associated with session "session2".
위 코드를 보면 session-scoped 인터셉터는 SessionFactory.openSession(Interceptor). 메소드를 호출하여 설정할 수 있다는 것을 알 수 있습니다. 위의 코드에서 두 개의 session 객체인 ‘session1’과 ‘session2’는 각각 MyInterceptor and MyAnotherInterceptor 가 설정되었습니다. 따라서 e1과 e2 객체는 MyInterceptor 의 영향을 받고 u1과 u2는 MyAnotherInterceptor 의 영향을 받습니다.

3. 인터셉터 API

인터셉텨와 관련된 세 개의 인터페이스를 사용할 수 있습니다. 그 중 두 개는 클래식 인터페이(org.hibernate.classic 패키지에 포함된 인터페이스)스인 Lifecycle 와 Validatable 이고 Interceptor 는 org.hibernate 패키지에 있습니다.

다음 섹션에서 인터셉터 인터페이스에 대해 좀 더 자세히 살펴보겠습니다.

3-1. ‘Validatable’ 인터페이스

이 클래식 인터페이스는 퍼시스턴트 자바 클레스에서 구현하여 영속성 객체의 상태를 검증할 때 사용합니다. 이 인터페이스는 Validatable.validate() 메소드를 사용하여 객체의 상태를 검증할 수 있도록 구현해 줍니다. 아래의 코드를 보겠습니다.
import java.util.Date;
import org.hibernate.classic.Validatable;
import org.hibernate.classic.ValidationFailure;

public class ProjectDuration implements Validatable{

	private Date startDate;
	private Date endDate;

	/// Other Code here.

	public void validate(){
		if (startDate.after(endDate)){
			throw new ValidationFailure(
"Start Date cannot be greater than the End Date.")
		}
	}
}
위의 퍼시스턴트 클래스인 ProjectDuration 은 Validatable 인터페이스를 구현하였고 validate 메소드 안에 startDate가 endDate 이후가 될 수 없다는 간단한 검증 룰을 구현하였습니다. 이 Validatable.validate() 메소드는 프레임워크에 의해 저장 기능을 수행 할 때 호출 될 것입니다. 저장은 Session.save(), Session.update(), Session.saveOrUpdate(), Session.flush() 메소드를 호출할 때 발생합니다.

3-2. ‘Lifecycle’ 인터페이스

영속성 객체는 각기 여러 라이프 사이클 단계를 거치게 됩니다. 새로 만들어 진 뒤에 데이터베이스에 유지 되고, 차후에 다시 로드 될 수도 있으며 필요하면 수정할 수도 있고 최종적으로 삭제 될 것입니다. 이러한 영속성 객체의 삶의 각 단계들을 Lifecycle 인터페이스에 캡슐화 했습니다. 다음의 메소드들을 Lifecycle 인터페이스에서 사용 가능합니다.

onLoad() 이 메소드는 영속성 객체를 로딩하기 전에 호출 됩니다. 예) Session.load()를 호출 했을 때
onSave() 이 메소드는 Session.save() 또는 Session.saveorUpdate() 같은 저장하는 기능을 수행하기 전에 호출 됩니다.
onUpdate() 이 메소드는 영송석 객체의 속성을 수정(update)하기 전에 호출 됩니다. 예) Session.update() 를 호출 했을 때
onDelete() 이 메소드는 삭제하기(delete) 전에 실행 됩니다. 예) Session.delete() 를 호출 했을 때


네 개의 메소드들이 영속성 객체가 연관되어 있는 Session 객체에게 전달 됩니다. 영속성 클레스는 위의 인터셉터를 사용하여 아래와 같이 커스터마이징 할 수 있습니다.
import org.hibernate.Session;
import org.hibernate.classic.Lifecycle;

class MyCustomizedPeristentClass implements Lifecycle{

	}
	
	public boolean onDelete(Session s) throws CallbackException {
		return false;
	}

	public void onLoad(Session s, Serializable id) {
		System.out.println("Loading");
	}

	public boolean onSave(Session s) throws CallbackException {
		return false;
	}

	public boolean onUpdate(Session s) throws CallbackException {
		return false;
	}

}
3-3. ‘Interceptor’ 인터페이스

이 인터페이스를 사용하여 객체를 영속화 할 때 다양한 커스터마이징을 할 수 있습니다. 심지어 영속성 객체의 상태를 수정할 수도 있습니다. 15개 이상의 여러 메소드를 제공하고 있기 때문에 기본으로 비어있는 구현체로 Interceptor 인터페이스를 구현한 EmptyInterceptor 클래스를 하이버네이트 설계자가 만들어 뒀습니다. 애플리케이션에서는 Interceptor 인터페이스 대신에 EmptyInterceptor 클레스를 사용할 수 있습니다.

아래에 있는 것들이 Interceptor 인터페이스에서 가장 자주 사용되는 것들입니다.

afterTransactionBegin() 트랜잭션을 시작하기 전에 호출됩니다. 예) Session.startTransaction() 메소드가 호출 될 때
beforeTransactionCompletion() 트랜잭션이 끝나기(커밋 또는 롤백) 직전에 호출 됩니다. 예) Transaction.commit() 또는 Transaction.rollback()
afterTransactionCompletion() 트랜잭션이 끝난(커밋 또는 롤백) 뒤에 호출 됩니다.
onSave() onSave() 에 정의한 내용은 엔티티의 속성 이름, 그것들의 값, 그것들의 상태 등 다양한 정보를 인자로 받게 됩니다. 그리고 저장하는 메소드인 Session.save(), Session.saveOrUpdate(), Session.flush(), Transaction.commit() 이 수행 될 때 호출 됩니다.
onUpdate() 개정된 onUpdate() 메소드는 엔티티 속성의 이름, 그것들의 값, 그것들의 상태 등 다양한 정보를 인자로 받게 됩니다. 그리고 영속성 객체의 속성이 수정(update) 되려고 할 때 호출 됩니다. 수정하기 작업은 Session.update(), Session.saveOrUpdate(), Session.flush(), Transaction.commit()에 의해 수행합니다.
onLoad() onLoad() 메소드는 엔티티 속성의 이름, 그것들의 값, 그것들의 상태 등 다양한 정보를 인자로 받게 됩니다. 그리고 영속성 객체가 불러지려고 할 때 호출 됩니다. 예) Session.load() 를 호출 할 때
onDelete() onDelete()메소드는 엔티티 속성의 이름, 그것들의 값, 그것들의 상태 등 다양한 정보를 인자로 받게 됩니다. 그리고 영속성 객체가 삭제 되기 전에 호출 됩니다. 예) Session.delete() 를 호출 할 때

역자 백기선님은 AJN(http://agilejava.net)에서 자바 관련 스터디를 하고 있는 착하고 조용하며 점잖은 대학생입니다. 요즘은 특히 Spring과 Hibernate 같은 오픈소스 프레임워크를 공부하고 있습니다. 공부한 내용들은 블로그(http://whiteship.tistory.com)에 간단하게 정리하고 있으며 장래 희망은 행복한 개발자입니다.
TAG :
댓글 입력
자료실

최근 본 책0