Skip to main content

Dynamicity

info

The example code in this article uses a type called BookDraft, which is an interface type automatically generated by Jimmer based on the user-defined type Book.

Readers can ignore this auto-generated interface for now. The later document Draft will introduce it.

Basic Concepts

1. A few properties

Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
});

In this case, the JSON for the books object would be like follows

{"name": "Learning GraphQL"}

2. More Properties

Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
});

In this case, the JSON for the books object would be like follows

{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99
}

3. Include Association

Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
draft.applyStore(store -> {
store.setName("O'REILLY");
store.setWebsite("https://www.oreilly.com/");
});
});

In this case, the JSON for the books object would be like follows

{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"store": {
"name": "O'REILLY",
"website": "https://www.oreilly.com/"
}
}

4. More Associations (Larger Breadth)

Book book = Immutables.createBook(draft -> {
draft.setName("Learning GraphQL");
draft.setEdition(1);
draft.setPrice(new BigDecimal("49.99"));
draft.applyStore(store -> {
store.setName("O'REILLY");
store.setWebsite("https://www.oreilly.com/");
});
draft.addIntoAuthors(author -> {
author.setFirstName("Eve");
author.setLastName("Procello");
author.setGender(Gender.FEMALE);
});
draft.addIntoAuthors(author -> {
author.setFirstName("Alex");
author.setLastName("Banks");
author.setGender(Gender.MALE);
});
});

In this case, the JSON for the books object would be like follows

{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"store": {
"name": "O'REILLY",
"website": "https://www.oreilly.com/"
},
"authors": [
{
"firstName": "Eve",
"lastName": "Procello",
"gender:" "FEMALE"
},
{
"firstName": "Alex",
"lastName": "Banks",
"gender:" "MALE"
}
]
}

5. Deeper Associations (Larger Depth)

Here is the translation to English:

Unlike the previous examples, here we choose BookStore as the aggregate root for the data structure, rather than Book.

BookStore store = Immutables.createBookStore(draft -> {
draft.setName("O'REILLY");
draft.setWebsite("https://www.oreilly.com/");
draft.addIntoBooks(book -> {
book.setName("Learning GraphQL");
book.setEdition(1);
book.setPrice(new BigDecimal("49.99"));
book.addIntoAuthors(author -> {
author.setFirstName("Eve");
author.setLastName("Procello");
author.setGender(Gender.FEMALE);
});
book.addIntoAuthors(author -> {
author.setFirstName("Alex");
author.setLastName("Banks");
author.setGender(Gender.MALE);
});
});
});

In this case, the JSON for the store object would be like follows

{
"name": "O'REILLY",
"website": "https://www.oreilly.com/",
"books": [
{
"name": "Learning GraphQL",
"edition": 1,
"price": 49.99,
"authors": [
{
"firstName": "Eve",
"lastName": "Procello",
"gender:" "FEMALE"
},
{
"firstName": "Alex",
"lastName": "Banks",
"gender:" "MALE"
}
]
}
]
}

Characteristics of Dynamic Objects

Dynamic object may lack any property, or in other words, dynamic object are not required to have all properties set.

caution

In dynamic object, missing properties and properties set to null are completely different things.

  • Missing property: The value of the property of the object is unknown, the current business is not interested in it

  • Property set to null: The value of the property of the object is known, it really is nothing

In static POJOs, the two are actually indistinguishable. What's worse, developers often intentionally or unintentionally confuse the two by taking advantage of Java's lack of null safety.

The concept of dynamic object is very important and key to understanding Jimmer!

For missing properties:

  • Using code to access them directly will result in an exception org.babyfish.jimmer.UnloadedException

    note

    If the reader has Hibernate experience, think of this as org.hibernate.LazyInitializationException.

  • In Jackson serialization, they will be automatically ignored.

    info

    This requires some configuration for Jackson. Since this detail is very important, it has been made into a separate document. Please refer to Work with Jackson.

Interaction with ORM

Dynamicity is an intrinsic characteristic of Jimmer objects and is universally applicable. Both the ORM framework itself and developers can easily build dynamic objects for each other to use.

  • Jimmer creates dynamic objects and returns them to developers

    That is, query data structures of any shape. This feature is called object fetchers.

  • Developers create dynamic objects and pass them to Jimmer

    That is, save data structures of any shape. This feature is called save commands.

Object Fetchers

Jimmer creates dynamic objects and returns them to developers

info
Compare to GraphQL
  • GraphQL is based on HTTP services, which can only be experienced if it crosses the boundaries of HTTP services. In Jimmer, this is the underlying API for ORM, and you can use this capability in any code logic.
  • Until now, the GraphQL protocol does not support recursive queries on self-associated properties with infinite depth; And Jimmer supports

Save Commands

Developers create dynamic objects and pass them to Jimmer

Save instructions allow developers to save data structures of any shape, rather than save simple objects.

By default, when AssociatedSaveMode is set to REPLACE, Jimmer will completely replace the existing data structure in the database with the structure being saved, as shown in the figure:

  • Top right: Users pass in a data structure of any shape for Jimmer to write to the database.
  • Top left: Query the existing data structure from the database to compare with the new data structure passed in by users.

    Whatever shape of data structure the user passes in, the same shape will be queried from the database, ensuring the shapes of old and new data structures are identical. Therefore, the querying and comparison costs are determined by the complexity of the user-provided data structure.

  • Below:Compare the new and old data structures, find the DIFF and execute corresponding SQL operations to make them consistent:
    • Orange parts: For entity objects that exist in both new and old data structures, modify data if scalar properties have changed
    • Blue parts: For entity objects that exist in both new and old data structures, modify associations if they have changed
    • Green parts: For entity objects that exist in the old data structure but not in the new one, decouple this object, clear associations and possibly delete data
    • Red parts: For entity objects that exist in the new data structure but not in the old one, insert data and establish associations
tip

Unlike other ORMs, Jimmer doesn't require describing how data should be saved in the entity model

Jimmer adopts a completely different strategy - its entity objects are not POJOs and can flexibly control the shape of data structures.

That is, entity objects have dynamic properties - not specifying a property for an entity object and setting an entity's property to null are completely different things.

For any entity object, Jimmer will only save the specified properties while ignoring unspecified ones.

Therefore, Jimmer doesn't need to consider data saving behavior during entity modeling, but rather describes the expected behavior at runtime through the data structure being saved itself, providing absolute flexibility.