Feature Introduction
Concept
Upper right corner: The user passes in a data structure of any shape, and asks Jimmer to save it.
There is an essential difference between this and the save method of other ORM frameworks. Taking JPA/Hibernate as an example, whether the scalar properties of the entity need to be saved is controlled byColumn.insertable andColumn.updatable, and whether association properties need to be saved is controlled byOneToOne.cascade,ManyToOne.cascade,OenToMany.cascade andManyToOne.cascade. However, no matter how the developer configures it, the shape of the data structure that JPA/Hibernate can save for you is fixed.
Jimmer adopts a completely different approach. Although the saved jimmer object is strongly typed, it is dynamic (that is, not setting the object property and setting the object property to null are completely different things), Properties that are set are saved and properties that are not set are ignored, so that data structures of any shapes can be saved.
Upper left corner: Query the existing data structure from the database for comparison with the new data structure specified by the user.
The shape of the data structure queried from database is same with the shape of new data structure give by user. Therefore, the query cost and comparison cost are determined by the complexity of the data structure specified by the user.
Below: Compare the old and new data structures, find
DIFF
and execute the corresponding SQL operations:- Orange part: For entity objects that exist in both old and new data structures, if some scalar properties change, modify the data
- Blue part: For entity objects that exist in both old and new data structures, if some associations change, modify the association
- Green part: For entity objects that exist in the new data structure but do not exist in the old data structure, insert data and create the association
- Red part: For entity objects that exist in the old data structure but not in the new data structure, dissociate this object, clear the association and possibly delete the data
The purpose of this function: take the data structure of any shape as a whole, and use one line of code to write it into the database, no matter how complicated the intermediate details are, you don't have to care.
If you know React or Vue in the web field, it is not difficult to see that this function is very similar to `Virtual DOM diff`.
Scenarios
The UI design for modifying data in an application can be divided into two styles:
-
Fully Commit
This type of UI often has complex forms and provides a final button. After editing, the user submits all the information in the form at once.
-
Incremental Commit
This type of UI does not have a submit button. Each time the user completes a local operation, the page automatically submits the changed part, which is a fragmented commit mode.
The greatest value of the Save Command lies in simplifying the development of fully commit mode functionality. For the two different modes, the usage is different.
Fully Commit | Incremental Commit |
---|---|
Jimmer automatically handles the internal details, comparing the new and old data to find all differences and executing the relevant modification operations (Jimmer's unique perspective) Use the Save Command (parameters are often complex data structures) | Business code uses a combination of multiple simple operations to implement complex operations, and the user handles the internal details (the same as traditional methods). Comprehensively use multiple methods:
|
Developers need to analyze their business scenarios to determine whether the current modification operation is a fully commit or an incremental commit, and make the right choice accordingly, without abuse.
Demo
In actual development, the data to be saved is always submitted by the client and can be passively accepted by the server (for example, @RequestBody
in Spring).
However, to simplify the discussion here, we directly hard code the object to be saved, so the code for the saved parameters is relatively more.
-
Save simple object
- Java
- Kotlin
sqlClient.save(
Immutables.createBook(draft -> {
draft.setName("GraphQL in Action");
draft.setEdition(4);
draft.setPrice(new BigDecimal("59.9"));
})
);sqlClient.save(
Book {
name = "GraphQL in Action"
edition = 4
price = BigDecimal("59.9")
}
)noteHere, the id property of the object to be saved is not specified. Jimmer will determine whether related data exists in the database according to the
name
andedition
properties,
so as to decide whether toINSERT
orUPDATE
.This is because in the entity definition,
Book.name
andBook.edition
are annotated with@org.babyfish.jimmer.sql.Key
.
This article is just a quick preview and does not go deep into it. Interested parties can view Mapping Part/Advanced Mapping/Key and Mutation Part/Save Command. -
Save data structures formed by multiple objects
- Java
- Kotlin
sqlClient.save(
Immutables.createBook(draft -> {
draft.setName("GraphQL in Action");
draft.setEdition(4);
draft.setPrice(new BigDecimal("59.9"));
draft.applyStore(store -> {
store.setName("MANNING");
store.setWebsite("https://www.manning.com");
});
draft.addIntoAuthors(author -> {
author.setFirstName("Bob");
author.setLastName("Rockefeller");
author.setGender(Gender.MALE);
});
draft.addIntoAuthors(author -> {
author.setFirstName("Eve");
author.setLastName("Procello");
author.setGender(Gender.FEMALE);
});
})
);sqlClient.save(
Book {
name = "GraphQL in Action"
edition = 4
price = BigDecimal("59.9")
store {
name = "MANNING"
website = "https://www.manning.com"
}
authors().addBy {
firstName = "Bob"
lastName = "Rockefeller"
gender = Gender.MALE
}
authors().addBy {
firstName = "Eve"
lastName = "Procello"
gender = Gender.FEMALE
}
}
);
Essential Difference from Other ORM
In the previous text, we demonstrated two examples, one describing how to save a simple object, and the other describing how to save an aggregate root object and cascade save more associated objects.
It is obvious that Jimmer's save directive can appear both simple and complex, depending on whether the data structure expressed by the dynamic entity passed by the user is simple or complex.
Jimmer does not provide configuration cascade
options for association properties like traditional ORM, because it is not necessary at all. The dynamic entity gives Jimmer's save capability unlimited possibilities, so there is no need to limit it to some fixed configuration.
This absolute flexibility has many wonderful uses. For example, changing the price of book with id 100 to 60, the traditional ORM and Jimmer approaches are different:
-
Traditional ORM (take JPA as an example) adopts find first and then modify, which is intuitive but wastes performance
Book book = entityManager.find(Book.class, 100L);
if (book != null) {
book.setPrice(new BigDecimal(60));
// entityManager.merge(book); //Omit if the current JPA transaction context exists
} -
Jimmer's approach, make up a mutilated object and directly update
- Java
- Kotlin
boolean matched = sqlClient.update(
Immutables.createBook(draft -> {
draft.setId(100L);
draft.setPrice(new BigDecimal(60));
// No other properties except `id` and `price` are specified
// So no other properties except `price` will be affected
})
).getTotalAffectedRowCount() != 0;val matched = sqlClient.update(
Book {
id = 100L
price = BigDecimal(60)
// No other properties except `id` and `price` are specified
// So no other properties except `price` will be affected
}
).totalAffectedRowCount != 0
Note: Cannot Expose Directly
The ability to save data structures of arbitrary shapes is too powerful so that it cannot be exposed directly, otherwise, it will lead to huge security vulnerabilities. For example:
- Java
- Kotlin
@RestController
public class BookController {
private final JSqlClient sqlClient;
public BookController(JSqlClient sqlClient) {
this.sqlClient = sqlClient;
}
@PutMapping("/book")
pubic int saveBook(
@RequestBody Book book
) {
return sqlClient
.save(book)
.getTotalAffectedRowCount();
}
}
class BookController(
private val sqlClient: KSqlClient
) {
@PutMapping("/book")
fun saveBook(
@RequestBody book: Book
): Int =
sqlClient
.save(book)
.totalAffectedRowCount
}
This method can work and is very powerful. The client can upload any data structure with Book
as the aggregate root for the server to save.
But this is also dangerous. You cannot limit the complexity of the data structure uploaded by the client. The client can arbitrarily modify associated data of any depth through this API.
Even if you try to verify and limit the shape of the book
parameter, it is still very easy to overlook and make mistakes.
Therefore, the powerful Jimmer data saving capability can only be used as an underlying support internally in the service, and cannot expose this capability directly to remote clients by using dynamic entities as input parameters, because this will result in the security door wide open.
To safely expose Jimmer's data saving capabilities, please continue reading the next article: Exposing Features