5.8.4.1. Implementing a Composition

Let us implement a composition using the Airport and the Terminal entities as an example:

composition recipe 1
  1. The Terminal entity contains a mandatory link to the 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. The Airport entity contains one-to-many collection of terminals. The corresponding field is annotated with @Composition in order to implement composition, and @OnDelete for cascaded soft delete:

    @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. The view of the airport editing screen should contain the terminals attributes collection:

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

    For the Terminal entity, we are using the _local view, although it contains the airport link attribute (a link to an airport). The airport attribute is set only at the creation of a new Terminal instance and never changes after that, so we do not need to load it.

  4. Next, we define the datasources for the Airport instance and its terminals in the XML descriptor of the airport editor:

    <dsContext>
        <datasource id="airportDs"
                    class="com.haulmont.sample.entity.Airport"
                    view="airport-edit">
            <collectionDatasource id="terminalsDs" property="terminals"/>
        </datasource>
    </dsContext>
  5. Define a table displaying terminals and standard actions for it in the XML descriptor of the airport editor:

    <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. It is sufficient to define the standard elements in the terminal editor: datasource for the Terminal instance and visual components related to this datasource for editing terminal attributes.

As a result, editing of an airport instance works as follows:

  • The airport edit screen shows a list of terminals.

  • A user can pick a terminal and open its editor. When OK is clicked in the terminal editor, the updated instance of the terminal is not saved to the database, but to the terminalsDs datasource of the airport editor.

  • The user can create new terminals and delete existing ones. All changes will be saved to the terminalsDs datasource.

  • When a user clicks OK in the airport edit screen, the updated Airport instance together with all the updated Terminal instances is submitted to the DataManager.commit() method on the Middleware and saved in the database within a single transaction.