Skip to main content

Save Associations

Association Types

From the user's perspective, there are two types of associations in the data structure to be saved:

Short Association

A so-called short association indicates that only the association itself between the current object and other objects is modified, with no interest in modifying the associated objects.

Usually, the UI design will use radio buttons (associated reference) or checkboxes (associated collection).

Book Form

 

Where:

  • Single-select dropdown corresponds to the many-to-one association Book.store

  • Multiple-select dropdown corresponds to the many-to-many association Book.authors

note

In actual projects, the available data may be too much to be designed as a dropdown UI. In this case, an modal selector with filtering and pagination can be used instead of a dropdown, which is a common workaround.

Since the user only wants to modify the association itself between the current object and other objects, without further modifying the associated objects, the UI cannot have nested multi-level associations. This is why it is called a short association.

info

When passing data structures of arbitrary shapes as parameters to the save command, short associations can be specified in two ways:

  • The associated object has only the id property

  • The associated object has only the key properties

Two examples are shown below:

  • The associated object has only the id property:

    Book book = Objects.createBook(draft -> {
    draft.setName("SQL in Action");
    draft.setEdition(1);
    draft.setPrice(new BigDecimal("39.9"));
    draft.applyStore(store -> {
    // Associated object has only id property
    store.setId(2L);
    });
    draft.addIntoAuthors(author -> {
    // Associated object has only id property
    author.setId(4L);
    });
    draft.addIntoAuthors(author -> {
    // Associated object has only id property
    author.setId(5L);
    });
    });
    sqlClient.save(book);
    note

    The data structure is hard-coded here only for demonstration. In actual projects the data structure to be saved is submitted by the front-end UI.

    Of course, if the user defines the authorIds property according to Mapping/Advanced mapping/View Properties/IdView, the above code can be simplified, for example:

    draft.setAuthorIds(Arrays.asList(4L, 5L));

    For greater applicability of the examples, it is not assumed here that the user has defined IdView properties for the entity type. Subsequent documents will not be reminded again.

  • The associated object has only the key property:

    note

    In the following code, it is assumed that:

    • The key property of BookStore type is name

    • The key properties of Author type are firstName and lastName

      In actual projects, this uniqueness constraint is not reasonable. It is assumed here to simplify the example. Please don't take this detail too seriously.

    Book book = Objects.createBook(draft -> {
    draft.setName("SQL in Action");
    draft.setEdition(1);
    draft.setPrice(new BigDecimal("39.9"));
    draft.applyStore(store -> {
    // Associated object has only key property,
    // i.e. `BookStore.name`
    store.setName("MANNING");
    });
    draft.addIntoAuthors(author -> {
    // Associated object has only key properties,
    // i.e. `Author.firstName` and `Author.lastName`
    author.setFirstName("Boris").setLastName("Cherny");
    });
    draft.addIntoAuthors(author -> {
    // Associated object has only key properties,
    // i.e. `Author.firstName` and `Author.lastName`
    author.setFirstName("Samer").setLastName("Buna");
    });
    });
    sqlClient.save(book);

Long Association

A so-called long association indicates that in addition to modifying the association itself between the current object and other objects, the associated objects need to be further modified.

Usually, the UI design will use nested tables, for example:

CommodityQuantityUnit priceItem priceDelete
14.6929.38
3030
Total price:59.379999999999995
 

Since the user wants not only to modify the association between the current object and other objects, but also to further modify the associated objects, and the associated objects can contain deeper associations, theoretically the UI can have nested multi-level associations. This is why it is called a long association.

note

Although designers deliberately avoid nesting deeper nested tables in nested tables to ensure UI simplicity, there are still scenarios in actual projects that require maintaining multi-level nested associations on the UI, such as:

  • The form itself is a tree structure that is edited and then saved as a whole.

  • Visual UI design, because the UI components themselves are tree structures. After a series of visual drag-and-drop designs by the user, the UI component tree is saved as a whole.

info

When passing data structures of arbitrary shapes as parameters to the save command, long associations can be specified in two ways:

  • If the id property has already been specified for the associated object, continue to specify any non-id properties for the object (including key properties)

  • Otherwise, all key properties and other properties must be been specified for the associated object.

Regardless of which approach above, specify at least one property for associated objects that is neither id nor key.

Examples:

Order order = Objects.createOrder(draft -> {
draft.applyCustomer(customer -> customer.setId(1L));
draft.setProvince("Prenzlauer Berg");
draft.setCity("Berlin");
draft.setAddress("Brandenburgische Straße 9, Prenzlauer Berg, Berlin, Germany");
draft.addIntoItems(item -> {
item.applyProduct(product -> product.setId(1L));
// Property neither id nor key
item.setQuantity(2);
});
draft.addIntoItems(item -> {
item.applyProduct(product -> product.setId(10L));
// Property neither id nor key
item.setQuantity(1);
});
});
sqlClient.save(order);
note

The data structure is hard-coded here only for demonstration. In actual projects the data structure to be saved is submitted by the front-end UI.

Speciality of One-To-Many Associations

It was mentioned before:

info

If the entity type declares the Key property (which also means the id has no business meaning other than serving as a unique identifier), then

  • For the aggregated root object to be saved, it is recommended to specify either the id property or the key properties, otherwise it will be understood as forced insertion.

  • For the associated objects to be saved that this article is discussing, either the id property or all key properties must be specified, otherwise an exception will occur.

However, there is one exceptional case for one-to-many relationships.

Let's look at an example:

Entity Definition

TreeNode.java
@Entity
public interface TreeNode {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();

@Key
String name();

@Key
@Nullable
@ManyToOne
TreeNode parent();

...other properties omitted...
}

Using Save Command

TreeNode treeNode = Objects.createTreeNode(food -> {
food
.setParent(null)
.setName("Food")
.addIntoChildNodes(drink -> {
drink
.setName("Drink")
.addIntoChildNodes(cococola -> {
cococola.setName("Cococola");
})
.addIntoChildNodes(fanta -> {
fanta.setName("Fanta");
});
;
})
.addIntoAuthors(bread -> {
bread
.setName("Bread")
.addIntoChildNodes(daguette -> {
daguette.setName("Baguette");
})
.addIntoChildNodes(ciabatta -> {
ciabatta.setName("Ciabatta");
})
});
;
});
sqlClient.save(treeNode);

The key properties of the TreeNode type are name and parent:

  • For the root node, if the id property is not specified, it is recommended to specify the name and parent properties, otherwise it will be understood as forced insertion.

  • For the child nodes, if the id property is not specified, the name and parent properties must be specified, otherwise an exception will occur.

However, in this example, except for the root node object, the parent property of all other objects is not specified. This code does not seem to execute normally.

In fact, this code can run correctly, because the save command makes a special provision for one-to-many associations:

tip

If the data structure to be saved contains some child objects held by the parent object through a one-to-many association (TreeNode.childNodes in this example), then the reverse many-to-one association of these child objects (TreeNode.parent in this example) will be deemed by the save command to have been set.

So in the above example, the save command will consider the parent property of all child nodes to have already been set. That is, all child objects have both the name and parent properties at the same time, so the save command can run smoothly.

Dissociate Operations

For associated objects, insert or update operations are not much different from saving the aggregated root object operations introduced earlier. The user just needs to remember that associated objects are not controlled by Save Mode (or it can be considered that the save mode for associated objects is only UPSERT).

However, associated objects have a very special operation: dissociation.

Dissociation is a very important concept. Due to limited space, we will discuss it in detail in the next document.