23장. 예제: 여러 가지 매핑들

이 장은 몇몇 보다 복잡한 연관 매핑들을 보여준다.

23.1. Employer/Employee

EmployerEmployee 사이의 관계에 대한 다음 모형은 그 연관를 표현하는 데 실제 엔티티 클래스(Employment)를 사용한다. 동일한 두 부분들에 대해 하나 이상의 채용 주기가 존재할 수 있기 때문에 이것이 행해진다. 컴포넌트들이 화폐 값들과 종업원 이름들을 모형화 시키는데 사용된다.

다음은 가능한 매핑 문서이다:

<hibernate-mapping>
        
    <class name="Employer" table="employers">
        <id name="id">
            <generator class="sequence">
                <param name="sequence">employer_id_seq</param>
            </generator>
        </id>
        <property name="name"/>
    </class>

    <class name="Employment" table="employment_periods">

        <id name="id">
            <generator class="sequence">
                <param name="sequence">employment_id_seq</param>
            </generator>
        </id>
        <property name="startDate" column="start_date"/>
        <property name="endDate" column="end_date"/>

        <component name="hourlyRate" class="MonetaryAmount">
            <property name="amount">
                <column name="hourly_rate" sql-type="NUMERIC(12, 2)"/>
            </property>
            <property name="currency" length="12"/>
        </component>

        <many-to-one name="employer" column="employer_id" not-null="true"/>
        <many-to-one name="employee" column="employee_id" not-null="true"/>

    </class>

    <class name="Employee" table="employees">
        <id name="id">
            <generator class="sequence">
                <param name="sequence">employee_id_seq</param>
            </generator>
        </id>
        <property name="taxfileNumber"/>
        <component name="name" class="Name">
            <property name="firstName"/>
            <property name="initial"/>
            <property name="lastName"/>
        </component>
    </class>

</hibernate-mapping>

그리고 다음은 SchemaExport에 의해 생성된 테이블 스키마이다.

create table employers (
    id BIGINT not null, 
    name VARCHAR(255), 
    primary key (id)
)

create table employment_periods (
    id BIGINT not null,
    hourly_rate NUMERIC(12, 2),
    currency VARCHAR(12), 
    employee_id BIGINT not null, 
    employer_id BIGINT not null, 
    end_date TIMESTAMP, 
    start_date TIMESTAMP, 
    primary key (id)
)

create table employees (
    id BIGINT not null, 
    firstName VARCHAR(255), 
    initial CHAR(1), 
    lastName VARCHAR(255), 
    taxfileNumber VARCHAR(255), 
    primary key (id)
)

alter table employment_periods 
    add constraint employment_periodsFK0 foreign key (employer_id) references employers
alter table employment_periods 
    add constraint employment_periodsFK1 foreign key (employee_id) references employees
create sequence employee_id_seq
create sequence employment_id_seq
create sequence employer_id_seq

23.2. Author/Work

Work, Author 그리고 Person 사이의 관계들에 대한 다음 모형을 검토하자. 우리는 WorkAuthor 사이의 관계를 many-to-many 연관으로 표현한다. 우리는 AuthorPerson 사이의 관계를 one-to-one 연관으로 표현하고자 선택한다. 또 다른 가능성은 AuthorPerson을 확장하도록 하는 것일 것이다.

다음 매핑 문서는 이들 관계들을 정확하게 표현한다:

<hibernate-mapping>

    <class name="Work" table="works" discriminator-value="W">

        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <discriminator column="type" type="character"/>

        <property name="title"/>
        <set name="authors" table="author_work">
            <key column name="work_id"/>
            <many-to-many class="Author" column name="author_id"/>
        </set>

        <subclass name="Book" discriminator-value="B">
            <property name="text"/>
        </subclass>

        <subclass name="Song" discriminator-value="S">
            <property name="tempo"/>
            <property name="genre"/>
        </subclass>

    </class>

    <class name="Author" table="authors">

        <id name="id" column="id">
            <!-- The Author must have the same identifier as the Person -->
            <generator class="assigned"/> 
        </id>

        <property name="alias"/>
        <one-to-one name="person" constrained="true"/>

        <set name="works" table="author_work" inverse="true">
            <key column="author_id"/>
            <many-to-many class="Work" column="work_id"/>
        </set>

    </class>

    <class name="Person" table="persons">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping>

이 매핑에는 네 개의 테이블들이 존재한다. works, authorspersons은 각각 작업 데이터, 저자 데이터, 개인 데이터를 보관한다. author_work는 저자들을 작업들에 연결시키는 연관 테이블이다. 다음은 SchemaExport에 의해 생성된 테이블 스키마이다.

create table works (
    id BIGINT not null generated by default as identity, 
    tempo FLOAT, 
    genre VARCHAR(255), 
    text INTEGER, 
    title VARCHAR(255), 
    type CHAR(1) not null, 
    primary key (id)
)

create table author_work (
    author_id BIGINT not null, 
    work_id BIGINT not null, 
    primary key (work_id, author_id)
)

create table authors (
    id BIGINT not null generated by default as identity, 
    alias VARCHAR(255), 
    primary key (id)
)

create table persons (
    id BIGINT not null generated by default as identity, 
    name VARCHAR(255), 
    primary key (id)
)

alter table authors 
    add constraint authorsFK0 foreign key (id) references persons
alter table author_work 
    add constraint author_workFK0 foreign key (author_id) references authors
alter table author_work
    add constraint author_workFK1 foreign key (work_id) references works

23.3. Customer/Order/Product

이제 Customer, OrderLineItem 그리고 Product 사이의 관계들에 관한 모형을 검토하자. CustomerOrder 사이의 one-to-many 연관이 존재하지만, 우리는 어떻게 Order / LineItem / Product를 표현할 것인가? 나는 OrderProduct 사이의 many-to-many 연관를 나타내는 하나의 연관 클래스로서 LineItem을 매핑하기로 선택했다. Hibernate에서 이것은 composite 요소로 명명된다.

매핑 문서:

<hibernate-mapping>

    <class name="Customer" table="customers">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <set name="orders" inverse="true">
            <key column="customer_id"/>
            <one-to-many class="Order"/>
        </set>
    </class>

    <class name="Order" table="orders">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="date"/>
        <many-to-one name="customer" column="customer_id"/>
        <list name="lineItems" table="line_items">
            <key column="order_id"/>
            <list-index column="line_number"/>
            <composite-element class="LineItem">
                <property name="quantity"/>
                <many-to-one name="product" column="product_id"/>
            </composite-element>
        </list>
    </class>

    <class name="Product" table="products">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="serialNumber"/>
    </class>

</hibernate-mapping>

customers, orders, line_items 그리고 products는 각각 고객 데이터, 주문 데이터, 주문 라인 아이템 데이터, 그리고 제품 데이터를 보관한다. line_items는 또한 주문들을 제품들과 연결시키는 연관 테이블로서 동작한다.

create table customers (
    id BIGINT not null generated by default as identity, 
    name VARCHAR(255), 
    primary key (id)
)

create table orders (
    id BIGINT not null generated by default as identity, 
    customer_id BIGINT, 
    date TIMESTAMP, 
    primary key (id)
)

create table line_items (
    line_number INTEGER not null, 
    order_id BIGINT not null, 
    product_id BIGINT, 
    quantity INTEGER, 
    primary key (order_id, line_number)
)

create table products (
    id BIGINT not null generated by default as identity, 
    serialNumber VARCHAR(255), 
    primary key (id)
)

alter table orders 
    add constraint ordersFK0 foreign key (customer_id) references customers
alter table line_items
    add constraint line_itemsFK0 foreign key (product_id) references products
alter table line_items
    add constraint line_itemsFK1 foreign key (order_id) references orders

23.4. 기타 예제 매핑들

이들 예제들은 모두 Hiberante test suite로부터 취했다. 당신은 거기서 많은 다른 유용한 예제 매핑들을 발견할 것이다. Hibernate 배포본의 test 폴더를 살펴보라.

TODO: 이 내용을 둘러싼 말들을 집어넣을 것.

23.4.1. "형식화된(Typed)" one-to-one 연관

<class name="Person">
    <id name="name"/>
    <one-to-one name="address" 
            cascade="all">
        <formula>name</formula>
        <formula>'HOME'</formula>
    </one-to-one>
    <one-to-one name="mailingAddress" 
            cascade="all">
        <formula>name</formula>
        <formula>'MAILING'</formula>
    </one-to-one>
</class>

<class name="Address" batch-size="2" 
        check="addressType in ('MAILING', 'HOME', 'BUSINESS')">
    <composite-id>
        <key-many-to-one name="person" 
                column="personName"/>
        <key-property name="type" 
                column="addressType"/>
    </composite-id>
    <property name="street" type="text"/>
    <property name="state"/>
    <property name="zip"/>
</class>

23.4.2. Composite 키 예제

<class name="Customer">

    <id name="customerId"
        length="10">
        <generator class="assigned"/>
    </id>

    <property name="name" not-null="true" length="100"/>
    <property name="address" not-null="true" length="200"/>

    <list name="orders"
            inverse="true"
            cascade="save-update">
        <key column="customerId"/>
        <index column="orderNumber"/>
        <one-to-many class="Order"/>
    </list>

</class>

<class name="Order" table="CustomerOrder" lazy="true">
    <synchronize table="LineItem"/>
    <synchronize table="Product"/>
    
    <composite-id name="id" 
            class="Order$Id">
        <key-property name="customerId" length="10"/>
        <key-property name="orderNumber"/>
    </composite-id>
    
    <property name="orderDate" 
            type="calendar_date"
            not-null="true"/>
    
    <property name="total">
        <formula>
            ( select sum(li.quantity*p.price) 
            from LineItem li, Product p 
            where li.productId = p.productId 
                and li.customerId = customerId 
                and li.orderNumber = orderNumber )
        </formula>
    </property>
    
    <many-to-one name="customer"
            column="customerId"
            insert="false"
            update="false" 
            not-null="true"/>
        
    <bag name="lineItems"
            fetch="join" 
            inverse="true"
            cascade="save-update">
        <key>
            <column name="customerId"/>
            <column name="orderNumber"/>
        </key>
        <one-to-many class="LineItem"/>
    </bag>
    
</class>
    
<class name="LineItem">
    
    <composite-id name="id" 
            class="LineItem$Id">
        <key-property name="customerId" length="10"/>
        <key-property name="orderNumber"/>
        <key-property name="productId" length="10"/>
    </composite-id>
    
    <property name="quantity"/>
    
    <many-to-one name="order"
            insert="false"
            update="false" 
            not-null="true">
        <column name="customerId"/>
        <column name="orderNumber"/>
    </many-to-one>
    
    <many-to-one name="product"
            insert="false"
            update="false" 
            not-null="true"
            column="productId"/>
        
</class>

<class name="Product">
    <synchronize table="LineItem"/>

    <id name="productId"
        length="10">
        <generator class="assigned"/>
    </id>
    
    <property name="description" 
        not-null="true" 
        length="200"/>
    <property name="price" length="3"/>
    <property name="numberAvailable"/>
    
    <property name="numberOrdered">
        <formula>
            ( select sum(li.quantity) 
            from LineItem li 
            where li.productId = productId )
        </formula>
    </property>
    
</class>

23.4.3. 공유된 합성 키 속성을 가진 Many-to-many

<class name="User" table="`User`">
    <composite-id>
        <key-property name="name"/>
        <key-property name="org"/>
    </composite-id>
    <set name="groups" table="UserGroup">
        <key>
            <column name="userName"/>
            <column name="org"/>
        </key>
        <many-to-many class="Group">
            <column name="groupName"/>
            <formula>org</formula>
        </many-to-many>
    </set>
</class>
    
<class name="Group" table="`Group`">
    <composite-id>
        <key-property name="name"/>
        <key-property name="org"/>
    </composite-id>
    <property name="description"/>
    <set name="users" table="UserGroup" inverse="true">
        <key>
            <column name="groupName"/>
            <column name="org"/>
        </key>
        <many-to-many class="User">
            <column name="userName"/>
            <formula>org</formula>
        </many-to-many>
    </set>
</class>

23.4.4. 내용 기반 판별

<class name="Person"
    discriminator-value="P">
    
    <id name="id" 
        column="person_id" 
        unsaved-value="0">
        <generator class="native"/>
    </id>
    
            
    <discriminator 
        type="character">
        <formula>
            case 
                when title is not null then 'E' 
                when salesperson is not null then 'C' 
                else 'P' 
            end
        </formula>
    </discriminator>

    <property name="name" 
        not-null="true"
        length="80"/>
        
    <property name="sex" 
        not-null="true"
        update="false"/>
    
    <component name="address">
        <property name="address"/>
        <property name="zip"/>
        <property name="country"/>
    </component>
    
    <subclass name="Employee" 
        discriminator-value="E">
            <property name="title"
                length="20"/>
            <property name="salary"/>
            <many-to-one name="manager"/>
    </subclass>
    
    <subclass name="Customer" 
        discriminator-value="C">
            <property name="comments"/>
            <many-to-one name="salesperson"/>
    </subclass>
    
</class>

23.4.5. 대체 키들에 대한 연관들

<class name="Person">
    
    <id name="id">
        <generator class="hilo"/>
    </id>
    
    <property name="name" length="100"/>
    
    <one-to-one name="address" 
        property-ref="person"
        cascade="all"
        fetch="join"/>
    
    <set name="accounts" 
        inverse="true">
        <key column="userId"
            property-ref="userId"/>
        <one-to-many class="Account"/>
    </set>
    
    <property name="userId" length="8"/>

</class>

<class name="Address">

    <id name="id">
        <generator class="hilo"/>
    </id>

    <property name="address" length="300"/>
    <property name="zip" length="5"/>
    <property name="country" length="25"/>
    <many-to-one name="person" unique="true" not-null="true"/>

</class>

<class name="Account">
    <id name="accountId" length="32">
        <generator class="uuid"/>
    </id>
    
    <many-to-one name="user"
        column="userId"
        property-ref="userId"/>
    
    <property name="type" not-null="true"/>
    
</class>