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

한빛출판네트워크

IT/모바일

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

한빛미디어

|

2007-05-25

|

by HANBIT

10,725

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

[이전 기사 보기]
하이버네이트 인터셉터 소개(1)

4. 테스트 애플리케이션

다음 섹션에서는 예제 애플리케이션으로 위에서 살펴봤던 인터셉터들을 사용해볼 것입니다.

4-1. 준비 사항

예제 애플리케이션을 실행하려면 다음에 나열된 소프트웨어 또는 제품들이 필요합니다.
  • Java Development Kit (http://java.sun.com/javase/downloads/index.jsp)
  • Hibernate 3.2 (http://www.hibernate.org/6.html)
  • MySQL Database Server (http://dev.mysql.com/downloads/mysql/6.0.html)
  • MySQL Database Driver (http://dev.mysql.com/downloads/connector/j/3.1.html)
두 개의 인터셉터를 필요로 하는 간단한 시나리오를 살펴봅시다.

첫 번째 인터셉터는 CustomSaveInterceptor 이며, 사용자가 입력한 값과 별개로 추가적인 값을 영속성 객체에 만들어 넣습니다. “PalyersName” 이라는 데이터베이스 테이블은 각각 선수의 이름, 중간 이름, 성을 나타내는 “fName”, “mNmae”, “lName”, “completeName”을 가지고 있습니다. 사용자는 fName, mName, lName만 입력하고 completeName은 입력하지 않습니다. completeName은 CustomSaveInterceptor를 사용하여 fName, mName, lName을 각각 하나의 공백을 두고 연결하여 만들 것입니다.

두 번째 인터셉터는 LoggerInterceptor 로 데이터베이스 테이블에 입력되는 모든 정보를 기록합니다.

PlayerName.java:
package interceptor;

import java.io.Serializable;

import org.hibernate.CallbackException;
import org.hibernate.Session;
import org.hibernate.classic.Lifecycle;
import org.hibernate.classic.Validatable;
import org.hibernate.classic.ValidationFailure;

public class PlayerName implements Validatable, Lifecycle {

	private String firstName;
	private String middleName;
	private String lastName;
	private String completeName;

	private String primaryKey;

	public PlayerName(){

	}

	public String getFirstName() {
		return firstName;
	}

	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	public String getLastName() {
		return lastName;
	}

	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	public String getMiddleName() {
		return middleName;
	}

	public void setMiddleName(String middleName) {
		this.middleName = middleName;
	}

	public String getCompleteName() {
		return completeName;
	}

	public void setCompleteName(String completeName) {
		this.completeName = completeName;
	}

	public String getPrimaryKey() {
		return primaryKey;
	}

	public void setPrimaryKey(String primaryKey) {
		this.primaryKey = primaryKey;
	}

	public void validate() throws ValidationFailure {

		if ((firstName.equals(middleName)) && (middleName.equals(lastName))){
			throw new ValidationFailure("First Name, Middle Name 
				  and Last Name cannot be the same");
		}

	}

	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;
	}
}
위에 있는 ‘PlayerName’ 클래스는 데이터베이스에 저장하고 싶은 영속성 클래스를 나타냅니다. 이 클래스드는 각각 선수의 이름, 중간 이름, 성을 나타내는 firstname, middleName, lastName 속성을 가지고 있습니다. 주키를 나타내기 위해 애플리케이션에서 직접 설정하는 primaryKey 속성도 가지고 있습니다.

이 클래스는 이름의 값들을 검증을 할 수 있는 인터셉터인 Validatable 를 구현하고 있습니다. validate() 메소드를 구현하여 이름, 중간 이름, 성이 모두 유일한 값인지 검사하고 있습니다. 만약 유일하지 않다면 ValidationFailureException 을 던집니다.

PlayerName 클레스는 Lifecycle 인터페이스도 구현하고 있으며 onLoad(), onSave(), onUpdate(), onDelete() 메소드는 기본적인 형태로 구현했습니다.

CustomSaveInterceptor.java:

다음의 코드는 CustomeSaveInterceptor로 EmptyInterceptor 클래스를 상속하고 있습니다. 영속성 객체에 완전한 이름(complete name)의 값을 업데이트 하는 로직을 담고 있습니다.
package interceptor;

import java.io.Serializable;

import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;

public class CustomSaveInterceptor extends EmptyInterceptor {
	public boolean onSave(Object entity,
            Serializable id,
            Object[] state,
            String[] propertyNames,
            Type[] types)
	{
		if (entity instanceof PlayerName){
			PlayerName playerName = (PlayerName)entity;
			String completeName = playerName.getFirstName() + " " + 
				   playerName.getMiddleName() + " " + 
				   		playerName.getLastName();
			playerName.setCompleteName(completeName);
		}

		return super.onSave(entity, id, state, propertyNames, types);
	}
}
onSave() 메소드를 자세히 보면 다양한 매개변수들을 가지고 있는 것을 볼 수 있습니다. entity는 저장할 객체를 나타냅니다. id는 직렬화 가능한(Serializable 인터페이스를 구현한) 주키를 나타냅니다(여기서는 String 객체를 사용했습니다.). state 배열은 영속성 객체가 가지고 있는 속성들의 값을 나타냅니다. propertyNmaes 배열은 String 값들의 배열로 firstname, middleName, lastName, completeName을 포함하고 있습니다. PlayerName 클레스에 있는 속성의 타입들이 전부 문자열이기 때문에 Type의 배열은 String 타입을 나타낼 것입니다.

코드는 기본적으로 먼저 entity 가 PayerName 타입인지 확인 한 다음 filrstName, middleName, lastName을 연결하여completeName 속성을 수정합니다.

LoggerInterceptor.java:
package interceptor;

import java.io.Serializable;

import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;

public class LoggerInterceptor extends EmptyInterceptor{

	public boolean onSave(Object entity,
            Serializable id,
            Object[] state,
            String[] propertyNames,
            Type[] types)
	{
		System.out.println("Saving the persistent Object " + 
			entity.getClass() + " with Id " + id);
		return super.onSave(entity, id, state, propertyNames, types);
	}
}
LoggerInterceptor 클레스 구현은 onSave() 메소드를 구현하여 콘솔 창에 로그 정보를 출력하도록 재정의 하는 것이 전부 입니다.

InterceptorTest.java:
package interceptor;

import java.util.List;

import org.hibernate.*;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Validatable;

public class InterceptorTest {

	public static void main(String[] args) {

		Configuration configuration = new Configuration().configure();
		configuration.setInterceptor(new CustomSaveInterceptor());

		SessionFactory sessionFactory = configuration.buildSessionFactory();
		Session session = sessionFactory.openSession(new LoggerInterceptor());
		createPlayerNames(session);
		listPlayerNames(session);
	}

	private static void createPlayerNames(Session session){

		PlayerName rahul = createPlayerName("Rahul", "Sharad", "Dravid", 
                                                      "RSD");
		PlayerName dhoni = createPlayerName("Mahendra", "Singh", "Dhoni", 
                                                      "MSD");
		PlayerName karthik = createPlayerName("Krishnakumar", "Dinesh", 
                                                      "Karthik", "KDK");
		PlayerName same = createPlayerName("Same", "Same", "Same", "SME");

		Transaction transaction = session.beginTransaction();
		try{
			session.save(rahul);
			session.save(dhoni);
			session.save(karthik);

			Transaction innerTransaction = null;
			try{
				innerTransaction = session.beginTransaction();
				session.save(same);
			}catch(Exception exception){
				System.out.println("n" + exception.getMessage());
			}finally{
				if (innerTransaction.isActive()){
					innerTransaction.commit();
				}
			}
		}catch(Exception exception){
			System.out.println(exception.getMessage());
			transaction.rollback();
			session.clear();
		}finally{
			if (transaction.isActive()){
				transaction.commit();
			}
		}
		session.flush();
	}

	private static PlayerName createPlayerName(String fName, 
					String mName,String lName, String id){
		PlayerName playerName = new PlayerName();
		playerName.setFirstName(fName);
		playerName.setMiddleName(mName);
		playerName.setLastName(lName);
		playerName.setPrimaryKey(id);
		return playerName;
	}

	private static void listPlayerNames(Session session){
		Query query = session.createQuery("From PlayerName");
		List allPlayers = query.list();
		System.out.println("n");
		for(PlayerName player : allPlayers){
			listPlayerName(player);
		}
	}

	private static void listPlayerName(PlayerName player){
		StringBuilder result = new StringBuilder();
		result.append("First Name = ").append(player.getFirstName())
						.append(" , Middle Name = ")
						.append(player.getMiddleName()).
						append(" , Last Name = ").
						append(player.getLastName()).
		append(" , Full Name = ").append(player.getCompleteName());
		System.out.println(result.toString());
	}

}
위의 코드에서 CustomSaveInterceptor를 Configuration.setInterceptor(new CustomSaveInterceptor())를 사용하여 전역적(global-scoped)으로 설정하였습니다. LoggerInterceptor는 SessionFactory.openSession(new LoggerInterceptor())를 사용하여 세션 기반(session-scoped)으로 설정하였습니다. createPlayerNames() 메소드는 테스트 용도의 player 객체를 생성합니다. ‘same’객체는 first-name, middle-name, last-name 모두 ‘Same’으로 같게 하여 Validator 인터셉터가 포착할 수 있도록 했습니다.

모든 객체는 트랜잭션 처리와 함께 Session.save() 를 사용하여 저장하였습니다. 하지만 ‘same’ 객체는 별도의 트랜잭션에서 저장하도록 했습니다. 그렇게 해야 ‘same’객체가 저장에 실패하더라도 다른 작업들이 영향을 받지 않기 때문입니다. 이것으로 nested transaction 기능을 지원한다는 것 역시 알 수 있습니다.

그 다음 코드는 player 객체를 간단한 쿼리인 "FROM PlayerName"을 사용하여 가져옵니다. 출력된 결과는 아래와 같습니다.
        Saving the persistent Object class interceptor.PlayerName with Id RSD
        Saving the persistent Object class interceptor.PlayerName with Id MSD
        Saving the persistent Object class interceptor.PlayerName with Id KDK
        
        First Name, Middle Name and Last Name cannot be the same
        
        
        First Name = Rahul , Middle Name = Sharad , Last Name = Dravid ,
		Full Name = Rahul Sharad Dravid
        First Name = Mahendra , Middle Name = Singh , Last Name = Dhoni ,
		Full Name = Mahendra Singh Dhoni
        First Name = Krishnakumar , Middle Name = Dinesh , Last Name = Karthik , 
		Full Name = Krishnakumar Dinesh Karthik
4-2. 하이버네이트 설정과 맵핑 파일

아래에는 하이버네이트 설정과 맵핑 파일이 나와있습니다. 애플리케이션을 실행 할 때 같은 클레스 패스에 위치 해야 합니다.

Hibernate.cfg.xml:

하이버네이트 설정 파일(hibernate.cfg.xml) 을 사용하여 데이터베이스 URL, username/password 와 같은 값들을 설정할 수 있습니다. 위에서 사용한 예제 애플리케이션에서 사용한 설정 파일은 다음과 같습니다.




   	
       	com.mysql.jdbc.Driver
         
        jdbc:mysql://localhost/dbforhibernate
        
        root
        root
	org.hibernate.dialect.MySQLDialect

	
        
   	

playername.hmb.xml:

맵핑 파일 은 자바 클래스를 관계형 데이터베이스 테이블로 사상하는 정보를 담고 있습니다. 여러 개의 맵핑 파일을 애플리케이션에서 참조할 수 있습니다. 아래에 있는 맵핑 정보는 예제에서 사용한 playername 매핑 정보를 담고 있습니다.




	
		
		    
		
		
		    
		
		
		    
		
		
		    
		
		
		    
		
	

5. 요약 및 정리

본 글은 인터셉터의 정의와 하이버네이트 동작 중 어느 시점에 적용될 수 있는지 살펴봤습니다. 다음으로 global-scoped 인터셉터와 session-scoped 인터셉터의 차이를 설명했습니다. 하이버네이트와 관련된 여러 API들을 자세히 살펴봤습니다. 마지막으로 인터셉터를 사용한 간단 예제 애플리케이션을 통해 글을 마무리 하였습니다.
역자 백기선님은 AJN(http://agilejava.net)에서 자바 관련 스터디를 하고 있는 착하고 조용하며 점잖은 대학생입니다. 요즘은 특히 Spring과 Hibernate 같은 오픈소스 프레임워크를 공부하고 있습니다. 공부한 내용들은 블로그(http://whiteship.tistory.com)에 간단하게 정리하고 있으며 장래 희망은 행복한 개발자입니다.
TAG :
댓글 입력
자료실

최근 본 상품0