5.8.3.1. Реализация композиции

Рассмотрим реализацию композиции на примере сущностей Airport и Terminal:

composition recipe 1
  1. Сущность Terminal содержит обязательную ссылку на Airport:

    @Entity(name = "sample$Terminal")
    @Table(name = "SAMPLE_TERMINAL")
    public class Terminal extends StandardEntity {
    ...
        @ManyToOne(optional = false, fetch = FetchType.LAZY)
        @JoinColumn(name = "AIRPORT_ID")
        private Airport airport;
    
        public Airport getAirport() {
            return airport;
        }
    
        public void setAirport(Airport airport) {
            this.airport = airport;
        }
    }
  2. Сущность Airport содержит one-to-many коллекцию терминалов. Соответствующее поле помечается аннотацией @Composition для огранизации композиции и @OnDelete для каскадного мягкого удаления:

    @Entity(name = "sample$Airport")
    @Table(name = "SAMPLE_AIRPORT")
    public class Airport extends StandardEntity {
    ...
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "airport")
        @OnDelete(DeletePolicy.CASCADE)
        @Composition
        protected List<Terminal> terminals;
    
        public List<Terminal> getTerminals() {
            return terminals;
        }
    
        public void setTerminals(List<Terminal> terminals) {
            this.terminals = terminals;
        }
    }
  3. Представление, используемое в экране редактирования аэропорта, должно содержать атрибут-коллецию terminals:

    <view entity="sample$Airport" name="airport-edit" extends="_local">
        <property name="terminals" view="_local"/>
    </view>

    Для сущности Terminal здесь выбрано представление _local, хотя она содержит ссылочный атрибут airport - ссылку на аэропорт. Дело в том, что атрибут airport устанавливается только при создании нового экземпляра Terminal, и не меняется в дальнейшем, поэтому загружать его не обязательно.

  4. В XML-дескрипторе экрана редактирования аэропорта определяем источники данных для экземпляра Airport и коллекции его терминалов:

    <dsContext>
        <datasource id="airportDs"
                    class="com.haulmont.sample.entity.Airport"
                    view="airport-edit">
            <collectionDatasource id="terminalsDs" property="terminals"/>
        </datasource>
    </dsContext>
  5. В XML-дескрипторе экрана редактирования аэропорта определяем таблицу, отображающую терминалы, и стандартные действия для нее:

    <table id="terminalsTable">
        <actions>
            <action id="create"/>
            <action id="edit"/>
            <action id="remove"/>
        </actions>
        <buttonsPanel>
            <button action="terminalsTable.create"/>
            <button action="terminalsTable.edit"/>
            <button action="terminalsTable.remove"/>
        </buttonsPanel>
        <columns>
            <column id="code"/>
            <column id="name"/>
            <column id="address"/>
        </columns>
        <rows datasource="terminalsDs"/>
    </table>
  6. В экране редактирования терминала достаточно определить стандартные элементы: datasource для экземпляра Terminal и визуальные компоненты, связанные с этим datasource, для редактирования атрибутов терминала.

В результате редактирование экземпляра аэропорта работает следующим образом:

  • В экране редактирования аэропорта отображается таблица терминалов.

  • Пользователь может выбрать терминал и открыть экран его редактирования. При нажатии OK в экране редактирования терминала измененный экземпляр терминала сохраняется не в базу данных, а в источник данных terminalsDs экрана редактирования аэропорта.

  • Пользователь может создавать новые или удалять терминалы - все изменения сохраняются в источнике данных terminalsDs.

  • Пользователь нажимает OK в экране редактирования аэропорта, и измененный Airport вместе со всеми измененными экземплярами Terminal отправляется на Middleware в метод DataManager.commit() и сохраняется в базе данных в рамках одной транзакции.