욱'S 노트

Hibernate - Mapping associations 본문

Programming/Hibernate

Hibernate - Mapping associations

devsun 2015. 5. 12. 16:15

이전에 우리는 하나의 퍼시스턴트 엔티티 클래스와 테이블간에 1대1 매핑의 경우에 대해서 살펴 보았다. 이제 여러 클래스간의 관계로 확장해보자. 우리는 어플리케이션에 인물들을 추가하고 각 참가자의 이벤트 리스트를 저장할 것이다.


1.2.1. Mapping the Person class


첫번째로 Person 클래스를 살펴보자.


package org.hibernate.tutorial.domain;


public class Person {


    private Long id;

    private int age;

    private String firstname;

    private String lastname;


    public Person() {}


    // Accessor methods for all properties, private setter for 'id'


}


Save this to a file named src/main/java/org/hibernate/tutorial/domain/Person.java


다음으로 src/main/resources/org/hibernate/tutorial/domain/Person.hbm.xml 파일을 만들자.


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


    <class name="Person" table="PERSON">

        <id name="id" column="PERSON_ID">

            <generator class="native"/>

        </id>

        <property name="age"/>

        <property name="firstname"/>

        <property name="lastname"/>

    </class>


</hibernate-mapping>


최종적으로 하이버네이트 정의에 새로운 매핑을 추가하자.


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

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


두 엔티티 간의 관계를 생성하자. 인물은 이벤트에 참가할 수 있다. 그리고 이벤트는 참가자를 가질 수 있다. 당신이 처리해야할 설계에 대한 질문은 다음과 같다. : directionality, multiplicity, and collection behavior.


1.2.2. A unidirectional Set-based association


Person 클래스에 이벤트 컬렉션을 추가함으로써, 특정 인물을 위한 이벤트를 탐색할 수 있다. Multi-valued assosiation은 하이버네이트에서 자바 컬렉션 프레임워크의 하나로 표현된다. : 여기서는 Set을 선택하겠다. 왜냐하면 우리의 예제에서는 중복값이 포함되지 않을 것이고 오더링이 의미가 없기 때문이다. 


public class Person {


    private Set events = new HashSet();


    public Set getEvents() {

        return events;

    }


    public void setEvents(Set events) {

        this.events = events;

    }

}


이 관계를 매핑하기 전에 고려해야 할 다른 것이 있다.  우리는 many-to-many 매핑을 사용할 것이다. 


<class name="Person" table="PERSON">

    <id name="id" column="PERSON_ID">

        <generator class="native"/>

    </id>

    <property name="age"/>

    <property name="firstname"/>

    <property name="lastname"/>


    <set name="events" table="PERSON_EVENT">

        <key column="PERSON_ID"/>

        <many-to-many column="EVENT_ID" class="Event"/>

    </set>


</class>


하이버네이트는 다양한 컬렉션 매핑을 지원한다. set이 가장 일반적이다. many-to-many 관계나 n:m 엔티티 관계에서 assosiation 테이블은 필수적이다. 테이블의 각 로우는 Person과 이벤트의 연결을 나타낸다. 테이블 이름은 set 엘리먼트의 테이블 속성을 이용해서 정의한다. 관계에서 식별자 칼럼명은 person 쪽에 위해 key element로 정의한다. event 쪽의 칼럼명은 many-to-many의 컬럼 속성이 된다. 또한 하이버네이트에게 컬렉션의 클래스도 알려주어야 한다.


그러므로, 이번 매핑의 테이블 스키마는 아래와 같다.

    _____________        __________________
   |             |      |                  |       _____________
   |   EVENTS    |      |   PERSON_EVENT   |      |             |
   |_____________|      |__________________|      |    PERSON   |
   |             |      |                  |      |_____________|
   | *EVENT_ID   | <--> | *EVENT_ID        |      |             |
   |  EVENT_DATE |      | *PERSON_ID       | <--> | *PERSON_ID  |
   |  TITLE      |      |__________________|      |  AGE        |
   |_____________|                                |  FIRSTNAME  |
                                                  |  LASTNAME   |
                                                  |_____________|
 

 

1.2.3. Working the association


이제 pepole과 event를 같이 처리 하기 위해 다음과 같은 메소드를 정의하자. 


    private void addPersonToEvent(Long personId, Long eventId) {

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

        session.beginTransaction();


        Person aPerson = (Person) session.load(Person.class, personId);

        Event anEvent = (Event) session.load(Event.class, eventId);

        aPerson.getEvents().add(anEvent);


        session.getTransaction().commit();

    }


Person과 Event를 로딩한 후에 컬렉션을 수정하자. 명시적으로 update()나 save()를 호출하지 않았다. : 하이버네이트는 자동으로 컬렉션의 변경을 감지하여 업데이트할 필요가 있다. 이것은 automatic dirty checking이라고 부른다. 또한 이름이나 날짜도 변경할 수 있다. 이들이 퍼시시턴트 상태에 있는 이상 즉, 특정 하이버네이트 세션에 바인딩된 이상 하이버네이트는 변경을 감지하고 SQL을 실행 시킨다. 메모리 상태와 데이터베이스 상태를 동기화하는 과정은 단위 작업의 종료시 발생하여 flushing이라고 부른다. 우리 코드에서 단위 작업 종료는 데이터베이스 트랜잭션의  commit 또는 rollback이다.


또한 person과 event를 다른 단위 작업에서 로드할 수 있다. 또는 퍼시스턴트 Session의 외부에서 변경 될 수 있다. 이러한 상태를 detached라고 한다. Detached 상태에서도 컬렉션을 변경 할 수 있다.


    private void addPersonToEvent(Long personId, Long eventId) {

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

        session.beginTransaction();


        Person aPerson = (Person) session

                .createQuery("select p from Person p left join fetch p.events where p.id = :pid")

                .setParameter("pid", personId)

                .uniqueResult(); // Eager fetch the collection so we can use it detached

        Event anEvent = (Event) session.load(Event.class, eventId);


        session.getTransaction().commit();


        // End of first unit of work


        aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached


        // Begin second unit of work


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

        session2.beginTransaction();

        session2.update(aPerson); // Reattachment of aPerson


        session2.getTransaction().commit();

    }


Detached 상태의 오브젝트를 새로운 단위 작업에서 다시 퍼시스턴트하기 위해 update를 호출할 수 있다.


어플리케이션을 분리하기 위해 중요한 개념이다. Event 매니져에 새로운 액션을 추가하고 커맨드 라인에서 실행해보자. 만약 peson과 event이 식별자가 필요하다면 save 메소드에서 리턴한다.


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

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

            Long personId = mgr.createAndStorePerson("Foo", "Bar");

            mgr.addPersonToEvent(personId, eventId);

            System.out.println("Added person " + personId + " to event " + eventId);

        }


이번에는 두 똑같히 중요한 클래스간의 관계를 표현한 예이다. 앞에서 언급했다시피 다른 클래스와 타입들은 일반적으로 덜 중요하다. String과 int같은 클래스들은 우리는 value 타입이라고 한다. 이러한 객체들은 특정한 엔티티에 의존한다. 이러한 타입의 인스턴스들은 자신 만의 식별자가 없다. 두 인물간에는 firstname 값이 갇더라도 동일한 firstname 객체를 레퍼런스 하지 않는다. 이러한 Value 타입을 클래스는 주소나 저축금액등 당신이 만든 클래스일 수도 있다. 사실 하이버네이트에서는 JDK 모든 클래스를 Value 타입으로 생각한다.


1.2.4. Collection of values


Person 엔티티에 email 주소 컬렉션을 추가해보자. 


    private Set<String> emailAddresses = new HashSet<String>();


    public Set<String> getEmailAddresses() {

        return emailAddresses;

    }


    public void setEmailAddresses(Set<String> emailAddresses) {

        this.emailAddresses = emailAddresses;

    }


이 Set에 대한 매핑은 다음과 같다 .


        <set name="emailAddresses" table="PERSON_EMAIL_ADDR">

            <key column="PERSON_ID"/>

            <element type="string" column="EMAIL_ADDR"/>

        </set>


이전 매핑과의 차이점은 컬렉션 부분이 다른 엔티티가 아니라는 것이다. 이러한 경우에는 켈렉션 테이블이 생성되며 다음과 같은 스키마 관계를 가지게 된다.


  _____________        __________________
 |             |      |                  |       _____________
 |   EVENTS    |      |   PERSON_EVENT   |      |             |       ___________________
 |_____________|      |__________________|      |    PERSON   |      |                   |
 |             |      |                  |      |_____________|      | PERSON_EMAIL_ADDR |
 | *EVENT_ID   | <--> | *EVENT_ID        |      |             |      |___________________|
 |  EVENT_DATE |      | *PERSON_ID       | <--> | *PERSON_ID  | <--> |  *PERSON_ID       |
 |  TITLE      |      |__________________|      |  AGE        |      |  *EMAIL_ADDR      |
 |_____________|                                |  FIRSTNAME  |      |___________________|
                                                |  LASTNAME   |
                                                |_____________|

 

컬렉션 테이블의 모든 컬럼은 composite 키로 포함된다는 것을 알 수 있다. 이러한 것은 한 사람이 중복된 이메일 주소를 가질 수 없다는 것을 의미하며 이는 자바의 set의 의미와 일치한다.


이 컬렉션에 person과 event를 연결하기 전에 새로운 엘리먼트를 추가해보자.


    private void addEmailToPerson(Long personId, String emailAddress) {

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

        session.beginTransaction();


        Person aPerson = (Person) session.load(Person.class, personId);

        // adding to the emailAddress collection might trigger a lazy load of the collection

        aPerson.getEmailAddresses().add(emailAddress);


        session.getTransaction().commit();

    }


1.2.5. Bi-directional associations


다음은 bi-directional assosiation을 매핑해 보겠다. 우리는 event에 person에 대한 assosiation을 만들어 보겠다. 데이터베이스 스키마는 변경되지 않을 것이다. 우리는 여전히 many-to-many muiltiplicity를 가지기 때문이다.


먼저 이벤트 클래스에 참가자를 세팅하자.


    private Set participants = new HashSet();


    public Set getParticipants() {

        return participants;

    }


    public void setParticipants(Set participants) {

        this.participants = participants;

    }


Event.hbm.xml에 assosiation을 설정하자.


        <set name="participants" table="PERSON_EVENT" inverse="true">

            <key column="EVENT_ID"/>

            <many-to-many column="PERSON_ID" class="Person"/>

        </set>


두 개의 매핑 도큐먼트에서 일반적인 set 매핑이 존재한다.  여기에서 가장 중요하게 추가된 사항은 inverse="true" 속성이다.


이것은 하이버네이트가 다른쪽에서 Person 클래스를 가지고 올 수 있다는 것을 의미한다. 


1.2.6. Working bi-directional links


첫번째로 하이버네이트는 자바 시멘틱에 영향을 주지 않는다는 것을 명심하자. 첫번째 Person과 Event unidirectionl 관계를 처리할 떄 Person의 인스턴스에서 Event의 인스턴스에 대한 참조를 가지고 왔었다. 만약 bidirectional 관계를 처리할려면 Person에 이벤트가 추가될 때 이벤트에 자신의 참조를 전달하면 되는 것이다. 


    protected Set getEvents() {

        return events;

    }


    protected void setEvents(Set events) {

        this.events = events;

    }


    public void addToEvent(Event event) {

        this.getEvents().add(event);

        event.getParticipants().add(this);

    }


    public void removeFromEvent(Event event) {

        this.getEvents().remove(event);

        event.getParticipants().remove(this);

    }


컬렉션에 대한 setter와 getter은 proctected로 하자. 그리고 이벤트가 추가될때 자신을 이벤트에 전달하자. 

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

Hibernate - Persistent Classes  (0) 2015.06.10
Hibernate - Architecture  (0) 2015.06.08
Hibernate - the first hibernate application  (0) 2015.04.30
Comments