욱'S 노트

Hibernate - Persistent Classes 본문

Programming/Hibernate

Hibernate - Persistent Classes

devsun 2015. 6. 10. 09:37

퍼시스턴트 클래스는 비즈니스 요건의 엔티티를 구현한 어플리케이션의 클래스이다. 여기서 퍼시스턴트의 의미는 저장될 수 있는 클래스라는 것을 의미한다. 


하이버 네이트는 POJO 프로그래밍 모델이라고 알려진 몇가지 단순한 룰을 따르면 잘 동작한다 하지만 이것을 꼭 지켜야 하는 것은 아니다. 도메인을 모델을 Map과 같은 다른 방식으로 표현할 수도 있다.


package eg;

import java.util.Set;

import java.util.Date;


public class Cat {

    private Long id; // identifier


    private Date birthdate;

    private Color color;

    private char sex;

    private float weight;

    private int litterId;


    private Cat mother;

    private Set kittens = new HashSet();


    private void setId(Long id) {

        this.xml:id=id;

    }

    public Long getId() {

        return id;

    }


    void setBirthdate(Date date) {

        birthdate = date;

    }

    public Date getBirthdate() {

        return birthdate;

    }


    void setWeight(float weight) {

        this.weight = weight;

    }

    public float getWeight() {

        return weight;

    }


    public Color getColor() {

        return color;

    }

    void setColor(Color color) {

        this.color = color;

    }


    void setSex(char sex) {

        this.sex=sex;

    }

    public char getSex() {

        return sex;

    }


    void setLitterId(int id) {

        this.litterId = id;

    }

    public int getLitterId() {

        return litterId;

    }


    void setMother(Cat mother) {

        this.mother = mother;

    }

    public Cat getMother() {

        return mother;

    }

    void setKittens(Set kittens) {

        this.kittens = kittens;

    }

    public Set getKittens() {

        return kittens;

    }


    // addKitten not needed by Hibernate

    public void addKitten(Cat kitten) {

        kitten.setMother(this);

        kitten.setLitterId( kittens.size() );

        kittens.add(kitten);

    }


퍼시스턴트 클래스가 가져야할 4가지 메인룰은 다음과 같다.


4.1.1. Implement a no-argument constructor


Cat은 인자가 없는 생성자를 가지고 있다. 퍼시스턴트 클래스는 반드시 디폴트 생성자를 가진다. 그래야 하이버네이트가 자바 리플렉션은 newInstance()를 이용해 초기화를 수행할 수 있기 때문이다. 디폴트 생성자를 정의할 때 최소한 패키지 visiblity로 정의해야 된다. 그래야 런타임 프록시로 생성할 수 있다.


4.1.2. Provide an identifier property


Cat는 id로 명명된 프로퍼티를 가지고 있다. 이 프로퍼티는 내부 데이터베이스 테이블의 프라이머리 키와 매핑된다. 식별자 타입은 프로퍼티는 기본 타입이다. 멀티 칼럼에 대한 컴포지트 식별자를 가질 수도 있다. 


퍼시스턴트 클래스에 일관된 이름의 식별자를 정의하기를 추천하고 nullable 타입을 사용하자. (long과 같은 타입말고 Long을 사용하자.)


4.1.3. Prefer non-final classes (semi-optional)


하이버네이트의 주요한 특징은 프록시(레이지 로딩)이다. 이것은 퍼시스턴트 클래스가 non-final 또는 인터페이스에 구현했냐에 의존한다. 인터페이스를 구현하지 않은 final 클래스를 저장할 수 있지만 lazy assosiation을 위한 프록시를 사용할 수 없다. 이것은 퍼포먼스 튜닝시 문제가 될 수 있다. final 클래스를 퍼시스턴트하기 위한 프록시를 생성할 수 없기 때문이다.


Example 4.2. Disabling proxies in hbm.xml


<class name="Cat" lazy="false"...>...</class>


Example 4.3. Disabling proxies in annotations


@Entity @Proxy(lazy=false) public class Cat { ... }


적절한 인터페이스를 구현한 final 클래스라면 프록시를 생성할때 인터페이스를 대신 사용하라고 하이버네이트에게 요청할 수 있다. 


Example 4.4. Proxying an interface in hbm.xml


<class name="Cat" proxy="ICat"...>...</class>


Example 4.5. Proxying an interface in annotations


@Entity @Proxy(proxyClass=ICat.class) public class Cat implements ICat { ... }


4.1.4. Declare accessors and mutators for persistent fields (optional)


Cat은 모든 퍼시스턴트 필드에 대한 접근자 메소드를 정의하고 있다. 많은 다른 ORM툴들은 객체의 변수들을 직접 퍼시스턴트한다. 이 방법은 RDB와 클래스의 내부적인 데이터구조간에 우회를 하기에 좋다. 기본적으로 하이버네에트는 JavaBeans 스타일 프로퍼티 즉 getFoo, isFoo 그리고 setFoo와 같은 메소드 명을 인지하여 퍼시스트한다. 필요하다면 특정 프로퍼티에 대한 직접 접근으로 변경할 수 있다.


프로퍼티는 public으로 정의할 필요가 없다. 하이버네이트는 어떠한 visibility에서도 퍼시스트할 수 있다.


4.2. Implementing inheritance


서브클래스 또한 첫번째, 두번째 규칙을 따라야 한다.


package eg;


public class DomesticCat extends Cat {

        private String name;


        public String getName() {

                return name;

        }

        protected void setName(String name) {

                this.name=name;

        }

}


4.3. Implementing equals() and hashCode()


 필요하다면 equals와 hashCode를 오버라이드 해야한다.


퍼시스턴트 클래스의 객체를 Set에 넣거나 detached 객체를 다시 reattachment를 할 때 사용된다. 하이버네이트는 특정한 세션 스코프에서 객체 동등성을 보장한다. 다른 세션에서 객체들이 섞였을때 의미있는 집합을 가지고 싶다면 equals나 hasCode를 반드시 구현해야된다.


equals/hashcode를 구현할 때 가장 명백한 방법은 두 오브젝트의 identifier를 비교하는 것이다. 각 값이 같다면 데이터베이스 로우는 반드시 같을 것이다. 불행하게도 생성된 식별자는 사용할 수 없다. 하이버네이트는 퍼시스턴트를 하기위에 할당된 식별자 값을 할당한다. 새롭게 생성된 객체는 어떤 식별자도 가질 수 없다. 더욱이 만약 인스턴스가 저장되지 않고 Set에 있다면 해당 객체를 찾아낼수 없을 것이다. 이것은 하이버네이트의 이슈가 아니며 일반적인 자바 시멘틱이다.


equals나 hashCode를 구현할 때 비즈니스 키를 사용할 것을 권고한다. 비즈니스 키의 동등성은 equals 메소드로 비교되며, 이는 비즈니스 키로만 구성되어야 한다. 이 키는 실생활에서 우리 객체의 식별자를 의미한다.


public class Cat {


    ...

    public boolean equals(Object other) {

        if (this == other) return true;

        if ( !(other instanceof Cat) ) return false;


        final Cat cat = (Cat) other;


        if ( !cat.getLitterId().equals( getLitterId() ) ) return false;

        if ( !cat.getMother().equals( getMother() ) ) return false;


        return true;

    }


    public int hashCode() {

        int result;

        result = getMother().hashCode();

        result = 29 * result + getLitterId();

        return result;

    }

}



4.4. Dynamic models

기본적으로 하이버네이트는 일반적인 POJO 모드로 동작한다.  default_entity_mode 설정 옵션을 이용하여 특정한 세션팩토리를 위한 기본 엔터티 표현 모드를 세팅할 수 있다.

다음 예제는 맵을 이용하여 표현한 것이다. 먼저 매핑 파일에 엔티티-네임을 클래스 이름을 정의하는 대신에  정의해야 한다.

<hibernate-mapping>

    <class entity-name="Customer">

        <id name="id"
            type="long"
            column="ID">
            <generator class="sequence"/>
        </id>

        <property name="name"
            column="NAME"
            type="string"/>

        <property name="address"
            column="ADDRESS"
            type="string"/>

        <many-to-one name="organization"
            column="ORGANIZATION_ID"
            class="Organization"/>

        <bag name="orders"
            inverse="true"
            lazy="false"
            cascade="all">
            <key column="CUSTOMER_ID"/>
            <one-to-many class="Order"/>
        </bag>

    </class>
    
</hibernate-mapping>

비록 assosiation을 위한 타겟 클래스명을 사용했더라도 관계의 타겟 타입은 POJO 대신에 다이나믹 엔티티가 된다. 

세션팩토리의 기본 엔티티 타입을 다이나믹 맵으로 세팅한 후하면 런타임시 Maps의 Maps으로 동작할 것이다.

Session s = openSession();
Transaction tx = s.beginTransaction();

// Create a customer
Map david = new HashMap();
david.put("name", "David");

// Create an organization
Map foobar = new HashMap();
foobar.put("name", "Foobar Inc.");

// Link both
david.put("organization", foobar);

// Save both
s.save("Customer", david);
s.save("Organization", foobar);

tx.commit();
s.close();

다이나믹 매핑의 주요한 이점은 프로토타이핑을 위한 시간을 절약할 수 있다는 것이다. 엔티티 클래스 구현을 하지 않기 때문이다. 하지만 컴파일 타입의 타입 체킹을 할 수 없고 런타임에 많은 오류를 겪을 수 있다. 적절한 엔티티를 생성한다면 하이버네이트 매핑의 결과로 데이터베이스 스키마는 쉽게 정규화 되고 견고해질 수 있다. 

엔티티 표현 모드는 Session 기반으로 세팅할 수 도 있다. 

Session dynamicSession = pojoSession.getSession(EntityMode.MAP);

// Create a customer
Map david = new HashMap();
david.put("name", "David");
dynamicSession.save("Customer", david);
...
dynamicSession.flush();
dynamicSession.close();


엔티티모드를 사용한 getSession()의 호출은 세션 팩토리가 아닌 세션 API이다. 새로운 세션은 JDBC 연결, 트랜잭션 또는 다른 컨텍스트 정보를 공유한다. 이 의미는 세컨더리 세션의 flush나 close를 호출하지 말하야 한다는 것이며 주요한 단위 작업을 위해 트랜잭션과 연결 처리를 남겨두어야 한다는 것을 의미한다.



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

Hibernate - Architecture  (0) 2015.06.08
Hibernate - Mapping associations  (0) 2015.05.12
Hibernate - the first hibernate application  (0) 2015.04.30
Comments