욱'S 노트

Hibernate - the first hibernate application 본문

Programming/Hibernate

Hibernate - the first hibernate application

devsun 2015. 4. 30. 14:12

1.1 The first Hibernate Application


이번 예제에서 우리는 특정 이벤트를 저장하는 작은 데이터베이스 어플리케이션을 만들 것이다.  


1.1.1. Setup


먼저 개발환경을 세팅할 필요가있다. 우리는 Maven과 같은 빌드 툴을 추천한다. 이 튜토리얼은 웹 어플리케이션으로 작성될 것이므로, src/main/java, src/main/resources 그리고 src/main/webapp 디렉토리를 만들 것이다. 


그리고 다음과 같이 pom.xml 파일을 세팅해보자.


<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">


    <modelVersion>4.0.0</modelVersion>


    <groupId>org.hibernate.tutorials</groupId>

    <artifactId>hibernate-tutorial</artifactId>

    <version>1.0.0-SNAPSHOT</version>

    <name>First Hibernate Tutorial</name>


    <build>

         <!-- we dont want the version to be part of the generated war file name -->

         <finalName>${artifactId}</finalName>

    </build>


    <dependencies>

        <dependency>

            <groupId>org.hibernate</groupId>

            <artifactId>hibernate-core</artifactId>

        </dependency>


        <!-- Because this is a web app, we also have a dependency on the servlet api. -->

        <dependency>

            <groupId>javax.servlet</groupId>

            <artifactId>servlet-api</artifactId>

        </dependency>


        <!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->

        <dependency>

            <groupId>org.slf4j</groupId>

            <artifactId>slf4j-simple</artifactId>

        </dependency>


        <dependency>

            <groupId>javassist</groupId>

            <artifactId>javassist</artifactId>

        </dependency>

    </dependencies>


</project>


1.1.2. The first class


다음 데이터베이스에 저장되는 event를 표현하는 클래스를 하나 만들어 보자. 몇 개의 프로퍼티를 가지는 심플한 자바빈이다.


package org.hibernate.tutorial.domain;


import java.util.Date;


public class Event {

    private Long id;


    private String title;

    private Date date;


    public Event() {}


    public Long getId() {

        return id;

    }


    private void setId(Long id) {

        this.id = id;

    }


    public Date getDate() {

        return date;

    }


    public void setDate(Date date) {

        this.date = date;

    }


    public String getTitle() {

        return title;

    }


    public void setTitle(String title) {

        this.title = title;

    }

}


이 클래스는 프라이빗 플로퍼티에 대한 getter 및 setter 메소드를 위해 표준 JavaBean 네이밍 컨벤션을 사용한다. 이러한 것은 디자인에서 추천사항이지만 필수는 아닌다. 하이버네이트에서도 직접 필드를 접근할 수 있지만 리팩토링을 위한 강건함 측면에서 accessor 메소드의 이점을 사용하자.


id 프러퍼티는 특정 이벤트에 대한 유일한 식별자를 저장한다. 하이버네이트에서 제공하는 모든 기능을 사용하기 위해서는 모든 퍼시스턴트 엔터티 클래스는 식별자 프로퍼티가 필요하다. 사실 대부분의 어플리케이션 특히 웹 어플리케이션에서는 식별자로 오브젝트를 구분할 필요가 있다. 하지만 일반적으로 오브젝트의 식별자는 조작하지 않으므로, setter 메소드는 private이 되어야 한다. 하이버네이트에서는 오브젝트가 저장될때에만 식별자를 할당한다. 하이버네이트에서는 public 뿐만 아니라 private, protected 접근자 메소드 또는 필드를 직접적으로 접근할 수 있다. 그래서 선택은 어플리케이션 디자인에 달려 있다.


모든 퍼시스턴트 클래스는 인자가 없는 샐성자가 필요하다. 하이버네이트는 자바 리플렉션을 이용해 오브젝트 클래스는 생성하기 때문이다. 생성자는 private이 될 수 있지만, 바이트코드 instrumentation 없이 효율적으로 데이터를 검색하고 런타임 프록시 제너레이션을 위해선 패키지 혹은 퍼블릭 접근자가 필요하다.


1.1.3. The mapping file


하이버네이트는 퍼시스턴트 클래스로 부터 어떻게 데이터를 로드하고 저장할지를 알아야 한다. 이러한 역할을 하는것이 하이버네이트 매핑 파일이다. 매핑 파일에는 데이터에 접근을 하기 위해 어떤 데이터베이스의 테이블을 이용하는지 데이터를 사용하기 위해 테이블의 어떤 칼럼을 이용하는지 등이 명시되어 있다.


매핑 파일의 기본 구조는 다음과 같다.


<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC

        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">


<hibernate-mapping package="org.hibernate.tutorial.domain">

[...]

</hibernate-mapping>


하이버네이트 DTD는 복잡하다. IDE나 에디터의 auto-completion 기능을 활용할 수 있다. DTD 파일을 살펴보면 전체 엘리먼트와 어트리뷰트에 대한 개요를 살펴볼 수 있는데, 설명들도 볼 수 있다. 하이버네이트는 classpath 어플리케이션에서 DTD 파일을 찾으면 웹으로 부터 로드하지 않을 것이다. DTD 파일은 hibernate-core.jar 파일에 포함되어 있다.


<hibernate-mapping package="org.hibernate.tutorial.domain">


    <class name="Event" table="EVENTS">


    </class>


</hibernate-mapping>


위와 같은 설정은 Event 클래스의 오브젝트를 저장하거나 로드하는 것을 EVENTS 테이블로부터 하겠다는 설정이다. 각 인스턴스는 테이블에서 로우로 표현된다. 다음 우리는 테이블의 프라이머리 키와 식별자 프로퍼티를 매핑하겠다. 우리는 식별자를 관리하지 않는 것을 원하므로, 하이버네이트의 하이버네이트 식별자 제너레이션 전략을 설정하였다.


<hibernate-mapping package="org.hibernate.tutorial.domain">


    <class name="Event" table="EVENTS">

        <id name="id" column="EVENT_ID">

            <generator class="native"/>

        </id>

    </class>


</hibernate-mapping>


id 엘레먼트는 식별자 프로퍼티에 대한 정의이다. name="id"의 의미는 자바빈 프로퍼티 id를 setter와 getter메소드를 이용해서 접근하라는 의미이다. 컬럼 어트리뷰트는 EVENTS 테이블에 프라이머리 키를 저장할 칼럼의 명이다.


내부의 generator 엘레먼트는 식별자 제너레이션 전략을 지정한다. 이 경우 native라고 지정했는데 설정된 데이터베이스 dialect에 따라 적정한 방법을 제공한다. 하이버네이트는 데이터베이스에서 생성을 지원할 뿐만아니라 목적에 따라 어플리케이션 할당, 식별자 값 생성 등 많은 확장 포인트를 제공한다. 


<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">

        <id name="id" column="EVENT_ID">

            <generator class="native"/>

        </id>

        <property name="date" type="timestamp" column="EVENT_DATE"/>

        <property name="title"/>

    </class>

</hibernate-mapping>


id 엘레먼트와 유사하게 프로퍼티의 name 어트리뷰트도 동작한다.


매핑 파일을  src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml에 저장하자.


1.1.4. Hibernate configuration


이번에는 하이버네이트 설정을 해보도록 하겠다. 먼저 HSQLDB를 서버 모드로 구동하자.


하이버네이트 설정을 위해서는 간단하게 hibernate.properties 파일을 이용할 수 있지만 더 정교한 작업을 위해서는 hibernate.cfg.xml 파일 또는 프로그램에서 모든 설정을 할 수도 있다. 대부분의 유저는 xml 파일을 선호한다.


<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"

        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">


<hibernate-configuration>


    <session-factory>


        <!-- Database connection settings -->

        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>

        <property name="connection.url">jdbc:hsqldb:hsql://localhost</property>

        <property name="connection.username">sa</property>

        <property name="connection.password"></property>


        <!-- JDBC connection pool (use the built-in) -->

        <property name="connection.pool_size">1</property>


        <!-- SQL dialect -->

        <property name="dialect">org.hibernate.dialect.HSQLDialect</property>


        <!-- Enable Hibernate's automatic session context management -->

        <property name="current_session_context_class">thread</property>


        <!-- Disable the second-level cache  -->

        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>


        <!-- Echo all executed SQL to stdout -->

        <property name="show_sql">true</property>


        <!-- Drop and re-create the database schema on startup -->

        <property name="hbm2ddl.auto">update</property>


        <mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>


    </session-factory>


</hibernate-configuration>


하이버네이트의 SessiongFactory를 설정한 것이다. SessionFactory는 특정 데이터베이스에 대한 공용 팩토리이다. 만약 여러대의 데이터베이스와 연결을 한다면 여러개의 설정 파일에 여러개의 session-factory 설정을 하면 된다.


처음 4개의 프로퍼티는 JDBC 커넥션 설정을 위해 필요하다. dialect 프로퍼티는 특정 하이버네이트가 만든 변형 SQL을 명시한 것이다.


하이버네이트의 자동 세션 관리는 매우 유용하다. hbm2ddl.auto 옵션은 자동으로 데이터베이스 스키마를 생성하도록 해준다. 마지막으로 퍼시스턴트 클래스들이 정의된 매핑 파일을 설정에 추가해 준다.


파일명 hibernate.cfg.xml으로  src/main/resources 디렉토리에 저장하자.


1.1.5. Startup and helpers


이제 Event 오브젝트를 저장하고 로딩해보자. 그러나 먼저 셋업을 완료하기 위해서 약간의 인프라스트럭쳐 코드를 만들어보자. 글로벌 org.hibernate.SessionFactory 오브젝트를 생성함으로써 Hibernate를 시작할 수 있고, 어플리케이션 코드 어드에서나 쉽게 접근할 수 있다. SessionFactory는 Session 인스턴스를 획득하기 위해 사용된다. 세션은 싱글 스레드 단위 작업을 의미한다. SessionFactory는 처음에 한번 초기화되는 스레드 세이트한 글로벌 오브젝트이다.


SessionFactory를 시작하고 쉽게 접근하기 위해서 HibernateUtil을 만들 것이다.


package org.hibernate.tutorial.util;


import org.hibernate.SessionFactory;

import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

import org.hibernate.cfg.Configuration;


public class HibernateUtil {


    private static final SessionFactory sessionFactory = buildSessionFactory();


    private static SessionFactory buildSessionFactory() {

        try {

            // Create the SessionFactory from hibernate.cfg.xml

            Configuration configuration = new Configuration();

            configuration.configure("hibernate.cfg.xml");

            ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()

                    .applySettings(configuration.getProperties()).build();

            SessionFactory sessionFactory = configuration

                    .buildSessionFactory(serviceRegistry);

            return sessionFactory;

        }

        catch (Throwable ex) {

            // Make sure you log the exception, as it might be swallowed

            System.err.println("Initial SessionFactory creation failed." + ex);

            throw new ExceptionInInitializerError(ex);

        }

    }


    public static SessionFactory getSessionFactory() {

        return sessionFactory;

    }


}


이 클래스는 글로벌 SessionFactory를 생성하여 제공한다.


만약 설정에 SessionFactory의 이름을 지정하였으면, 하이버네이트는 생성한 후에 JNDI를 바인딩할 것이다. 


1.1.6. Loading and storing objects


이제 실제적으로 동작시킬 준비를 해보자. 


package org.hibernate.tutorial;


import org.hibernate.Session;


import java.util.*;


import org.hibernate.tutorial.domain.Event;

import org.hibernate.tutorial.util.HibernateUtil;


public class EventManager {


    public static void main(String[] args) {

        EventManager mgr = new EventManager();


        if (args[0].equals("store")) {

            mgr.createAndStoreEvent("My Event", new Date());

        }


        HibernateUtil.getSessionFactory().close();

    }


    private void createAndStoreEvent(String title, Date theDate) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();


        Event theEvent = new Event();

        theEvent.setTitle(title);

        theEvent.setDate(theDate);

        session.save(theEvent);


        session.getTransaction().commit();    

}

}


createAndStoreEvent 메소드에서 우리는 새로운 이벤트 오브벡트를 생성하고 Hibernate로 처리를 넘길 것이다. 이 시점에 하이버네이트는 SQL을 관리하고 데이터베이스에 INSERT를 수행할 것이다.


Session은 하나의 단위 작업을 표현하기 위해 설계 되었다. 우리는 세션과 데이터베이스 트랜잭션은 1대1관계를 가졌다고 생각하겠다. 그리고 실제적인 트랜잭션 시스템으로 부터 우리의 코드를 보호하기 위해 우리는 하이버네이트 트랜젹션 API를 사용한다. 특별한 경우 우리는 JDBC기반 트랜잭션 문법을 사용할 수 있고, 또한 JTA를 사용할 수 있다.


sessionFactory.getCurrentSesson()이 하는 일은 무엇인가? 일단 SessionFactory로 부터 한번 가지고 오면 여러번 어디에서나 호출할 수 있다. getCurrentSesson() 메소드는 현재 단위 업무를 항상 리턴한다.  현재 단위 업무의 컨텍스트는 실행중인 어플리케이션의 하나의 스레드에 바운딩된다.


중요한 점


하이버네이트에서는 현재 세션을 트랙킹하기 위해 세가지 방법은 제공한다. 스레드 기반 방법은 프로덕션에서 사용하기에 적합하지 않다. : 프로토타이핑이나 튜토리얼에 적합하다. 현재 세션 트랙킹에 대한 논의는 다음에 하겠다.


Session은 getCurrentSession()이 처음 호출될때 현재 스레드를 위해 만들어진다. 하이버네이트에 의해 세션은 현재 스레드에 바운딩된다. 트랜잭션이 커밋이나 롤백으로 끝나면, 하이버네이트는 스레드로부터 세션을 언바인드 시키고 세션을 닫는다. 만약 getCurrentSession을 다시 호출한다면 새로운 세션이 생성되고 새로운 단위 작업을 시작한다.


단위작업의 범위와 연관하여 하이버네이트에서 하나 또는 여러개의 데이터베이스 작업을 수행할 수 있을까? 위의 예제를 보면 하나의 오퍼레이션만을 수행하였다. 하지만 읻것은 우연의 일치이다. 세션의 스코프는 유연하다. 모든 데이터베이스 오퍼레이션을 위해 새로운 세션을 사용할 필요는 없다. 만약 하나의 세션에서 하나의 오퍼레이션만 수행한다면 안티패턴이다. 


[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)


하이버네이트에 의해 인서트가 수행되었다.


이벤트가 저장된 결과를 확인하기 위해서는 다음과 같은 코드를 작성해 보자.


        if (args[0].equals("store")) {

            mgr.createAndStoreEvent("My Event", new Date());

        }

        else if (args[0].equals("list")) {

            List events = mgr.listEvents();

            for (int i = 0; i < events.size(); i++) {

                Event theEvent = (Event) events.get(i);

                System.out.println(

                        "Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()

                );

            }

        }


새로운 listEvent() 메소드도 추가하자.


private List listEvents() {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        List result = session.createQuery("from Event").list();

        session.getTransaction().commit();

        return result;

    }


우리는 EVENTS 테이블에 저장된 모든 데이터를 객체로 로딩하기 위해 HQL(Hibernate Query Language)를 사용한다. 하이버네이트는 적당한 SQL을 생성할 것이다.


'Programming > Hibernate' 카테고리의 다른 글

Hibernate - Persistent Classes  (0) 2015.06.10
Hibernate - Architecture  (0) 2015.06.08
Hibernate - Mapping associations  (0) 2015.05.12
Comments