3.2.6.2. DataManager

DataManager interface provides CRUD functionality on both middle and client tiers. It is a universal tool for loading entity graphs from the database and saving changed detached entity instances.

See Introduction to Working with Data guide to learn different use cases regarding programmatic data access with the DataManager API.

See DataManager vs. EntityManager for information on differences between DataManager and EntityManager.

DataManager in fact just delegates to a DataStore implementation and handles cross-database references if needed. The most implementation details described below are in effect when you work with entities stored in a relational database through the standard RdbmsStore. For another type of data store, everything except the interface method signatures can be different. For simplicity, when we write DataManager without additional clarification, we mean DataManager via RdbmsStore.

DataManager methods are listed below:

  • load(Class) - loads entities of the specified class. This method is an entry point to the fluent API:

    @Inject
    private DataManager dataManager;
    
    private Book loadBookById(UUID bookId) {
        return dataManager.load(Book.class).id(bookId).view("book.edit").one();
    }
    
    private List<BookPublication> loadBookPublications(UUID bookId) {
        return dataManager.load(BookPublication.class)
            .query("select p from library_BookPublication p where p.book.id = :bookId")
            .parameter("bookId", bookId)
            .view("bookPublication.full")
            .list();
    }
  • loadValues(String query) - loads key-value pairs by the query for scalar values. This method is an entry point to the fluent API:

    List<KeyValueEntity> list = dataManager.loadValues(
            "select o.customer, sum(o.amount) from demo_Order o " +
            "where o.date >= :date group by o.customer")
        .store("legacy_db") (1)
        .properties("customer", "sum") (2)
        .parameter("date", orderDate)
        .list();
    1 - specify data store where the entity is located. Omit this method if the entity is located in the main data store.
    2 - specify names of the resulting KeyValueEntity attributes. The order of the properties must correspond to the columns in the query result set.
  • loadValue(String query, Class valueType) - loads a single value by the query for scalar values. This method is an entry point to the fluent API:

    BigDecimal sum = dataManager.loadValue(
            "select sum(o.amount) from demo_Order o " +
            "where o.date >= :date group by o.customer", BigDecimal.class)
        .store("legacy_db") (1)
        .parameter("date", orderDate)
        .one();
    1 - specify data store where the entity is located. Omit this method if the entity is located in the main data store.
  • load(LoadContext), loadList(LoadContext) – load entities according to the parameters of the LoadContext object passed to it. LoadContext must include either a JPQL query or an entity identifier. If both are defined, the query is used, and the identifier is ignored.

    For example:

    @Inject
    private DataManager dataManager;
    
    private Book loadBookById(UUID bookId) {
        LoadContext<Book> loadContext = LoadContext.create(Book.class)
                .setId(bookId).setView("book.edit");
        return dataManager.load(loadContext);
    }
    
    private List<BookPublication> loadBookPublications(UUID bookId) {
        LoadContext<BookPublication> loadContext = LoadContext.create(BookPublication.class)
                .setQuery(LoadContext.createQuery("select p from library_BookPublication p where p.book.id = :bookId")
                    .setParameter("bookId", bookId))
                .setView("bookPublication.full");
        return dataManager.loadList(loadContext);
    }
  • loadValues(ValueLoadContext) - loads a list of key-value pairs. The method accepts ValueLoadContext which defines a query for scalar values and a list of keys. The returned list contains instances of KeyValueEntity. For example:

    ValueLoadContext context = ValueLoadContext.create()
            .setQuery(ValueLoadContext.createQuery(
                        "select o.customer, sum(o.amount) from demo_Order o " +
                        "where o.date >= :date group by o.customer")
                .setParameter("date", orderDate))
            .addProperty("customer")
            .addProperty("sum");
    List<KeyValueEntity> list = dataManager.loadValues(context);
  • getCount(LoadContext) - returns a number of records for a query passed to the method. When possible, the standard implementation in RdbmsStore executes select count() query with the same conditions as in the original query for maximum performance.

  • commit(CommitContext) – saves a set of entities passed in CommitContext to the database. Collections of entities for updating and deletion must be specified separately.

    The method returns the set of entity instances returned by EntityManager.merge(); essentially these are fresh instances just updated in DB. Further work should be performed with these returned instances to prevent data loss or optimistic locking. You can ensure that required attributes are present in the returned entities by setting a view for each saved instance using CommitContext.getViews() map.

    DataManager can perform bean validation of saved entities.

    Examples of saving a collection of entities:

    @Inject
    private DataManager dataManager;
    
    private void saveBookInstances(List<BookInstance> toSave, List<BookInstance> toDelete) {
        CommitContext commitContext = new CommitContext(toSave, toDelete);
        dataManager.commit(commitContext);
    }
    
    private Set<Entity> saveAndReturnBookInstances(List<BookInstance> toSave, View view) {
        CommitContext commitContext = new CommitContext();
        for (BookInstance bookInstance : toSave) {
            commitContext.addInstanceToCommit(bookInstance, view);
        }
        return dataManager.commit(commitContext);
    }
  • reload(Entity, View) - convenience method to reload a specified instance from the database with the required view. It delegates to the load() method.

  • remove(Entity) - removes a specified instance from the database. Delegates to commit() method.

  • create(Class) - creates an instance of the given entity in memory. This is a convenience method that just delegates to Metadata.create().

  • getReference(Class, Object) - returns an entity instance which can be used as a reference to an object which exists in the database.

    For example, if you are creating a User, you have to set a Group the user belongs to. If you know the group id, you could load it from the database and set to the user. This method saves you from unneeded database round trip:

    user.setGroup(dataManager.getReference(Group.class, groupId));
    dataManager.commit(user);

    A reference can also be used to delete an existing object by id:

    dataManager.remove(dataManager.getReference(Customer.class, customerId));
Query

When working with relational databases, use JPQL queries to load data. See the JPQL Functions, Case-Insensitive Substring Search and Macros in JPQL sections for information on how JPQL in CUBA differs from the JPA standard. Also note that DataManager can execute only "select" queries.

The query() method of the fluent 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 is e.

  • You can use positional parameters and pass their values right to the query() method as additional arguments.

For example:

// named parameter
dataManager.load(Customer.class)
        .query("e.name like :name")
        .parameter("name", value)
        .list();

// positional parameter
dataManager.load(Customer.class)
        .query("e.name like ?1", value)
        .list();

// case-insensitive positional parameter
dataManager.load(Customer.class)
        .query("e.name like ?1 or e.email like ?1", "(?i)%joe%")
        .list();

// multiple positional parameters
dataManager.load(Order.class)
        .query("e.date between ?1 and ?2", date1, date2)
        .list();

// omitting "select" and using a positional parameter
dataManager.load(Order.class)
        .query("from sales_Order o, sales_OrderLine l " +
                "where l.order = o and l.product.name = ?1", productName)
        .list();

Please note that positional parameters are supported only in the fluent interface. LoadContext.Query supports only named parameters.

Transactions

DataManager always starts a new transaction and commits it on operation completion, thus returning entities in the detached state. On the middle tier, you can use TransactionalDataManager if you need to implement complex transactional behavior.

Partial entities

Partial entity is an entity instance that can have only a subset of local attributes loaded. By default, DataManager loads partial entities according to views (in fact, RdbmsStore just sets the loadPartialEntities property of the view to true and passes it down to EntityManager).

There are some conditions, when DataManager loads all local attributes and uses views only for fetching references:

  • The loaded entity is cached.

  • In-memory "read" constraints are defined for the entity.

  • Dynamic attribute access control is set up for the entity.

  • The loadPartialEntities attribute of LoadContext is set to false.