Using DataManager
DataManager
is the main interface for CRUD (Create, Read, Update, Delete) operations on entities. It allows you to load graphs of entities by ID or query, to save changed instances or remove them. You can use entity event listeners to perform actions on loading and saving of particular entities. DataManager
maintains cross-datastore references for both JPA, DTO, and mixed entity graphs.
You can inject DataManager
to a Spring bean or a screen controller, for example:
@Component
public class CustomerService {
@Autowired
private DataManager dataManager;
In the examples below, we skip the definition and assume that the dataManager
variable is a reference to DataManager
.
Loading Entities
DataManager
provides a fluent API for loading entities. Use load()
methods accepting entity class or Id
as entry points to this API.
Loading Entity by Id
The following method loads an entity by its identifier value:
Customer loadById(UUID customerId) {
return dataManager.load(Customer.class) (1)
.id(customerId) (2)
.one(); (3)
}
1 | Entry point to the fluent loader API. |
2 | id() method accepts the identifier value. |
3 | one() method loads the entity instance. If there is no entity with the given identifier, the method throws IllegalStateException . |
The identifier can also be specified using Id<E>
class which contains information about the entity type. Then the application code doesn’t need to use concrete type of the entity identifier (UUID
, Long
, etc.) and the loading code becomes even shorter:
Customer loadByGenericId(Id<Customer> customerId) {
return dataManager.load(customerId).one();
}
If the entity with the given identifier may not exist, instead of one()
terminal method use optional()
which returns Optional<E>
. In the example below, if the entity not found, a new instance is created and returned:
Customer loadOrCreate(UUID customerId) {
return dataManager.load(Customer.class)
.id(customerId)
.optional() (1)
.orElse(dataManager.create(Customer.class));
}
1 | Returns Optional<Customer> . |
You can also load a list of entities by their identifiers passed to the ids()
method, for example:
List<Customer> loadByIds(UUID id1, UUID id2) {
return dataManager.load(Customer.class)
.ids(id1, id2)
.list();
}
Entities in the result list have the same order as provided identifiers.
Loading All Entities
The following method loads all instances of the entity in a list:
List<Customer> loadAll() {
return dataManager.load(Customer.class).all().list();
}
Load all instances only if you are sure that the number of rows in the corresponding table is always low. Otherwise, use a query, conditions and/or paging. |
Loading Entities by Query
When working with relational databases, use JPQL queries to load data. See the JPQL Extensions for information on how JPQL in Jmix differs from the JPA standard. Also note that DataManager
can execute only "select" queries.
The following method loads a list of entities using a full JPQL query and two parameters:
List<Customer> loadByFullQuery() {
return dataManager.load(Customer.class)
.query("select c from sample_Customer c where c.email like :email and c.grade = :grade")
.parameter("email", "%@company.com")
.parameter("grade", CustomerGrade.PLATINUM)
.list();
}
The query()
method of the fluent loader interface accepts both full and abbreviated query string. The latter should be created in accordance with the following rules:
-
You can always omit the
"select <alias>"
clause. -
If the
"from"
clause contains a single entity and you don’t need a specific alias for it, you can omit the"from <entity> <alias> where"
clause. In this case, the framework assumes that the alias ise
. -
You can use positional parameters and pass their values right to the
query()
method as additional arguments.
Below is an abbreviated equivalent of the previous example:
List<Customer> loadByQuery() {
return dataManager.load(Customer.class)
.query("e.email like ?1 and e.grade = ?2", "%@company.com", CustomerGrade.PLATINUM)
.list();
}
An example of a more complex abbreviated query with join:
List<Order> loadOrdersByProduct(String productName) {
return dataManager.load(Order.class)
.query("from sample_Order o, sample_OrderLine l " +
"where l.order = o and l.product.name = ?1", productName)
.list();
}
Loading Entities by Conditions
You can use conditions instead of a JPQL query to filter results. For example:
List<Customer> loadByConditions() {
return dataManager.load(Customer.class)
.condition( (1)
LogicalCondition.and( (2)
PropertyCondition.contains("email", "@company.com"), (3)
PropertyCondition.equal("grade", CustomerGrade.PLATINUM) (3)
)
)
.list();
}
1 | condition() method accepts a single root condition. |
2 | LogicalCondition.and() method creates AND condition with the given nested conditions. |
3 | Property conditions compare entity attributes with the specified values. |
If you need a single property condition, pass it directly to the condition()
method:
List<Customer> loadByCondition() {
return dataManager.load(Customer.class)
.condition(PropertyCondition.contains("email", "@company.com"))
.list();
}
PropertyCondition
lets you specify properties of referenced entities, for example:
List<Order> loadByCondition() {
return dataManager.load(Order.class)
.condition(PropertyCondition.contains("customer.email", "@company.com"))
.list();
}
Loading Scalar and Aggregate Values
Besides entity instances, DataManager
can load scalar and aggregate values in the form of key-value entities.
The loadValues(String query)
method loads a list of KeyValueEntity
instances populated with a given query results. For example:
String getCustomerPurchases(LocalDate fromDate) {
List<KeyValueEntity> kvEntities = dataManager.loadValues(
"select o.customer, sum(o.amount) from sample_Order o " +
"where o.date >= :date group by o.customer")
.store("main") (1)
.properties("customer", "sum") (2)
.parameter("date", fromDate)
.list();
StringBuilder sb = new StringBuilder();
for (KeyValueEntity kvEntity : kvEntities) {
Customer customer = kvEntity.getValue("customer"); (3)
BigDecimal sum = kvEntity.getValue("sum"); (3)
sb.append(customer.getName()).append(" : ").append(sum).append("\n");
}
return sb.toString();
}
1 | Specify data store where the requested entities are located. Omit this method if the entity is located in the main data store. |
2 | Specify names of the resulting key-value entity attributes. The order of the properties must correspond to the columns in the query result set. |
3 | Get loaded values from the key-value entity attributes. |
The loadValue(String query, Class valueType)
method loads a single value of the given type by the query. For example:
BigDecimal getTotal(LocalDate toDate) {
return dataManager.loadValue(
"select sum(o.amount) from sample_Order o where o.date >= :date",
BigDecimal.class (1)
)
.store("main") (2)
.parameter("date", toDate)
.one();
}
1 | The type of the returned value. |
2 | Specify data store where the requested entities are located. Omit this method if the entity is located in the main data store. |
Paging and Sorting
When you load entities using all()
, query()
or condition()
methods, you can also sort results and split them into pages.
Use firstResult()
and maxResults()
methods for paging:
List<Customer> loadPageByQuery(int offset, int limit) {
return dataManager.load(Customer.class)
.query("e.grade = ?1", CustomerGrade.BRONZE)
.firstResult(offset)
.maxResults(limit)
.list();
}
Use sort()
method for sorting results:
List<Customer> loadSorted() {
return dataManager.load(Customer.class)
.condition(PropertyCondition.contains("email", "@company.com"))
.sort(Sort.by("name"))
.list();
}
In the Sort.by()
method, you can specify properties of referenced entities, for example:
List<Order> loadSorted() {
return dataManager.load(Order.class)
.all()
.sort(Sort.by("customer.name"))
.list();
}
When loading by a JPQL query, you can also use the standard order by
clause in the query:
List<Customer> loadByQuerySorted() {
return dataManager.load(Customer.class)
.query("e.grade = ?1 order by e.name", CustomerGrade.BRONZE)
.list();
}
Saving Entities
Use save()
method to save new and modified entities in the database.
In the simplest form, the method accepts an entity instance and returns a saved instance:
Customer saveCustomer(Customer entity) {
return dataManager.save(entity);
}
Usually the passed and returned instances are not the same. The returned instance can be affected by entity event listeners, database triggers or access control rights. So if you need to save an entity and then continue working with it, always use the instance returned from the save() method.
|
The save()
method can accept multiple instances at once. In this case, it returns the EntitySet
object which can be used to easily get the saved instances. In the example below, we create and save two linked entities, and return one of them:
Order createOrderWithCustomer() {
Customer customer = dataManager.create(Customer.class);
customer.setName("Alice");
Order order = dataManager.create(Order.class);
order.setCustomer(customer);
EntitySet savedEntities = dataManager.save(order, customer); (1)
return savedEntities.get(order); (2)
}
1 | Save two linked entities. The order of save() parameters is not important. |
2 | The EntitySet.get() method lets you obtain the saved instance by its source instance. |
The most powerful form of the save()
method accepts SaveContext
object that can be used to add multiple instances and specify additional parameters of saving. In the example below, we save a collection of entities using SaveContext
:
EntitySet saveUsingContext(List<Customer> entities) {
SaveContext saveContext = new SaveContext();
for (Customer entity : entities) {
saveContext.saving(entity);
}
return dataManager.save(saveContext);
}
If you save a large collection of entities and don’t need saved instances immediately, use SaveContext.setDiscardSaved(true)
method. It will improve performance because DataManager
will not fetch saved entities back from the database. For example:
void saveAndReturnNothing(List<Customer> entities) {
SaveContext saveContext = new SaveContext().setDiscardSaved(true);
for (Customer entity : entities) {
saveContext.saving(entity);
}
dataManager.save(saveContext);
}
Removing Entities
Use remove()
method to remove entities from the database.
In the simplest form, the method accepts an entity instance to remove:
void removeCustomer(Customer entity) {
dataManager.remove(entity);
}
The remove()
method can accept multiple instances, arrays and collections:
void removeCustomers(List<Customer> entities) {
dataManager.remove(entities);
}
If you remove linked entities, the order of parameters can be important. First pass the entities that depend on the others, for example:
void removeOrderWithCustomer(Order order) {
dataManager.remove(order, order.getCustomer());
}
If you don’t have an entity instance but only its identifier, construct the Id
object from the identifier and pass it to the remove()
method:
void removeCustomer(UUID customerId) {
dataManager.remove(Id.of(customerId, Customer.class));
}
If you need to specify additional parameters of the remove operation, for example to turn off soft deletion and completely remove from the database an entity with the soft delete trait, use the save()
method with SaveContext
and pass the removed entities to its removing()
method:
void hardDelete(Product product) {
dataManager.save(
new SaveContext()
.removing(product)
.setHint(PersistenceHints.SOFT_DELETION, false)
);
}