Data Modelling: Composition
In this guide the Composition relationship between entities will be covered. It will be shown where are the differences between Associations and Compositions. The two different kinds of Compositions: One-to-Many Compositions as well as One-to-One Compositions will be explored with different examples.
What we are going to build
This guide enhances the CUBA petclinic example to show various examples of a composition relationship between entities. In particular, the following use cases will be covered:
-
The
Owner
entity gets a relationship to anAddress
-
A
Pet
can contain multipleHealthRecord
instances, each of them can contain multipleHealthRecordAttachment
instances -
The
Owner
entityPet
can contain multipleHealthRecord
instances, each of them can contain multipleHealthRecordAttachment
instances -
The
Employee
entity has a detail entity for storing the employee record:EmployeeRecord
which will be entered during the creation process of theEmployee
Requirements
Your development environment requires to contain the following:
-
CUBA Studio as standalone IDE or as an IntelliJ IDEA plugin
Download and unzip the source repository for this guide, or clone it using git:
Example: CUBA petclinic
The project that is the basis for this example is CUBA Petclinic. It is based on the commonly known Spring Petclinic. The CUBA Petclinic application deals with the domain of a Pet clinic and the associated business workflows to manage a pet clinic.
The underlying domain model for the application looks like this:
The main entities are Pet and Visit. A Pet is visiting the petclinic and during this Visit a Vet is taking care of it. A Pet belongs to an Owner, which can hold multiple pets. The visit describes the act of a pet visiting the clinic with the help of its owner.
Composition vs. Association
CUBA platform supports two types of relationship between entities: ASSOCIATION
and COMPOSITION
. Association is a relationship between the objects that can exist separately from each other. Composition, on the other hand, is used for "master-detail" relations, when the detail instances can exist only as part of the master entity.
In the Petclinic example the relationship between an Owner
and its Addresses
may be considered as an example of composition: an Address
that does not belong to any Owner
does not make sense (in the domain of this application).
Typically, the entities belonging to a composition are edited together since it is most natural to do so. A user opens the Owner
editing screen and sees the list of addresses
, so the user can create and edit them, but all changes both for the Owner
and the Address
are saved to the database together in one transaction, and only after the user confirms saving of the master entity (the Owner
).
One-to-Many Composition
In this guide different variants of using the Composition relationship will be shown. Compositions can be ONE-TO-MANY
as well as ONE-TO-ONE
. Also it is possible to have multiple levels of a nested composition in the application.
With the release of CUBA 7, there is no limit of how many layers of nesting are defined anymore. Still, having too many levels of nesting is oftentimes the source of confusion for the user when interacting with the user interface. Therefore, it is suggested to not use the nested compositions with too many levels. |
One-to-Many: One Level of Nesting
A one-to-many composition using the Owner
and the Address
entities as an example:
-
Address.java - the
Address
entity contains a mandatory link to theOwner
.In the Studio entity designer, set the following settings for the owner
attribute: Attribute type -ASSOCIATION
, Cardinality -MANY_TO_ONE
, Mandatory -on
. -
Owner.java - the
Owner
entity contains a one-to-many collection ofaddresses
. The corresponding field is annotated with@Composition
in order to implement composition, and@OnDelete
for cascaded soft delete.In the Studio entity designer, set the following settings for the addresses
attribute: Attribute type -COMPOSITION
, Cardinality -ONE_TO_MANY
, On delete -CASCADE
. -
views.xml - the
owner-with-pets-and-addresses
view of theOwner
editing screen contains theaddresses
collection attribute.Addresses
itself are loaded with_local
view, because theowner
attribute of theAddress
entity is set only at the creation of a newAddress
instance and never changes after that, so there is no need to load it. -
owner-edit.xml - the XML descriptor of the
Owner
editor screen defines a data container for theOwner
instance and a nested collection container for itsaddresses
. It also contains a table for managing theAddress
instances. -
address-edit.xml - a standard editor for the
Address
entity.
As a result, editing of an Owner
instance works as follows:
The Owner
edit screen shows a list of addresses
.
The updated instance of the Address is not yet saved to the database, but only to the DataContext
of the Owner editor. The user can create new Address instances and delete existing ones. All changes will be saved to DataContext
.
When a user clicks OK in the Owner edit screen, the updated Owner
instance together with all the updated Address
instances is submitted to the dataManager.commit()
method and saved to the database within a single transaction.
One-to-Many: Two Levels of Nesting
Composition can also be multiple-level. The next example shows a Composition of two levels. In the Petclinic there is a need to store health records of the pets. The Pet
→ HealthRecord
relationship is another composition. However, the HealthRecord
also has another composition since there can be multiple Attachment
instances created for a given health record.
-
Pet.java - the
healthRecord
attribute of thePet
class is marked as@Composition
and@OnDelete
. -
HealthRecord.java - the
attachments
attribute of theHealthRecord
class is marked as@Composition
and@OnDelete
similarly to thehealthRecords
attribute of thePet
class. -
views.xml - the
health-record-with-attachments
view of theHealthRecord
class contains theattachments
collection attribute. This view is used in thepet-with-owner-and-type-and-health-records
view of thePet
entity which acts as the root view of the edit screen. -
pet-edit.xml - the XML descriptor of the
Pet
editor screen defines a data container for thePet
instance and a nested collection data container for itshealthRecords
. It also contains a table for managing theHealthRecord
instances. The screen uses thepet-with-owner-and-type-and-health-records
view as a root view for this edit screen.CUBA 7+ removed the previous need to define datasources for the second level of nested compositions in the editor of the top level entity with the release of data containers. In CUBA 7+ this definition is not required anymore.
-
health-record-edit.xml - the XML descriptor of the
HealthRecord
editor screen defines a data container for theHealthRecord
instance and a nested collection data container for itsattachments
. It also contains a table for managing theHealthRecordAttachment
instances.
As a result, the updated instances of the HealthRecordAttachment
, as well as the HealthRecord
instances, will be saved to the database only with the Pet
instance in the same transaction.
More Levels of Nesting
As stated above, CUBA 7+ does not have the restriction of maximal two levels of nesting for composition anymore. This means that three (or more) levels of nesting are possible. The example for two levels of nesting can be extended towards: Owner
→ Pet
→ Health Record
→ Health Record Attachment
and in fact it has been done within the demo application. It is also possible to combine the usage of One-to-Many and One-to-One compositions within a Composition chain.
Having too many levels of composition has to be treated with caution, though. It requires an additional burden on the user, to understand at which point which data is stored.
One-to-One Composition
Besides the above explained Composition of type ONE-TO-MANY
, there is also the possibility to define Composition of type ONE-TO-ONE
. This type of Composition is useful when it is only possible to have one item of the child entity. An example in the petclinic is the following scenario:
The employees of the Petclinic have to be managed (like Vets, Nurses etc.). Therefore an Employee
entity is defined. For each Employee
instance there is an associated EmployeeRecord
entity which stores information about the work related information of this employee.
-
Employee.java - the
Employee
entity contains an optional link toEmployeeRecord
annotated with@Composition
. -
EmployeeRecord.java - the
EmployeeRecord
entity.
On a UI level it is possible to deal with One-to-One composition in two different ways. Normally, One-to-One compositions are displayed as a single entry in the form of the master entity. The details of the child entity are entered in a dedicated edit screen for this entity. But it is also possible to enter values for both entites in the same edit screen. Below both options will be shown.
One-to-One with a Details Screen
-
employee-edit.xml - the employee edit screen descriptor. It contains a nested data container for the
EmployeeRecord
instance. In order to load the nested instance, the root data container uses theemployee-with-employee-record-view
view of theEmployee
entity that includes theemployeeRecord
attribute.
The definition of the employeeRecord
property in the employee-edit.xml
is defined as a pickerField
component which contains the actions OpenAction
(with special type picker_open_composition
) and ClearAction
:
<pickerField id="employeeRecordField" property="employeeRecord">
<actions>
<action id="open" type="picker_open_composition"/>
<action id="clear" type="picker_clear"/>
</actions>
</pickerField>
As a result, employee editing works as follows:
When the open action is invoked, a new instance of EmployeeRecord
is created and its edit screen is shown. When OK is clicked in the employee record editor, the employee record instance is not saved to the database, but only to the employeeRecordDc
data container of the employee edit screen.
The picker field displays the instance name of the EmployeeRecord
entity:
When a user clicks OK in the employee edit screen, the updated Employee
instance together with the EmployeeRecord
instance is submitted to the DataManager.commit()
method and saved to the database within a single transaction.
If the user invokes the clear action of the picker field, the EmployeeRecord
instance is deleted and the reference to it is cleared in the same transaction after the user commits the employee editor.
One-to-One Composition with a Single Editor
It is often convenient to edit the One-to-One composition in a single editor. The following example shows how the EmployeeRecord
can be edited within the Employee
editor screen.
The employee-single-editor-edit.xml descriptor contains the main employeeDc
and the nested employeeRecordDc
data containers:
<data>
<instance id="employeeDc"
class="com.haulmont.sample.petclinic.entity.employee.Employee"
view="employee-with-employee-record-view">
<loader/>
<instance id="employeeRecordDc" property="employeeRecord"/>
</instance>
</data>
Fields for editing both entities are defined in the same editor either a single form or multiple forms:
<form id="form" dataContainer="employeeDc">
<textField id="firstNameField" property="firstName"/>
<textField id="lastNameField" property="lastName"/>
<dateField id="birthdateField" property="birthdate"/>
</form>
<form id="employeeRecordForm" dataContainer="employeeRecordDc">
<textField id="personellNumberField" property="personellNumber" datatype="int"/>
<textField id="amountSickDaysFild" property="amountSickDays" datatype="int"/>
</form>
In the EmployeeSingleEditorEdit.java controller an EmployeeRecord
instance will be created and linked to the new Employee
instance when the latter is just created:
@Inject
protected DataContext dataContext;
@Subscribe
protected void onInitEntity(InitEntityEvent<Employee> event) { (1)
Employee employee = event.getEntity();
EmployeeRecord employeeRecord = createEmployeeRecord();
employee.setEmployeeRecord(employeeRecord);
}
private EmployeeRecord createEmployeeRecord() {
return dataContext.merge(metadata.create(EmployeeRecord.class)); (2)
}
1 | the initialization of the EmployeeRecord can be defined when the InitEntityEvent is fired |
2 | an instance of EmployeeRecord is created and merged in the current dataContext |
Now, both linked entities can be created and edited in one editor screen.
Summary
In this data modelling guide the Composition relationship was described. Compositions are valuable if a master-detail relationship between two entities should be modelled, where the detail entities can only exist if the corresponding master entity exists.
Composition structures can exist in 1:1 and 1:N form. Furthermore, nested composition can be created with multiple levels. The corresponding UI screens treat Composition relationships in a special way. Detail entities are only stored during the creation process of the master entity at the point in time when the master entity is stored.