Skip to content

Unit of Work API

The heart of the Backendless Transactions API is a class called UnitOfWork. It used by the client applications to compose a transaction. Composing a transaction means adding various database operations to a unit of work. These operations may be "linked" between each other by using the output/result on one operation as the input for another. When a transaction is composed, the client application uses the execute() method on the UnitOfWork instance to dispatch the transaction so that the server can run all the operations. The execute() method can be invoked either in the blocking or the non-blocking manner. The blocking execute() will block the execution of the client  program until the entire transaction is complete or errors out and then returns the result of the transaction. The non-blocking execute() does not block the client program and the transaction result or its error is delivered to a callback.

When the client application calls the execute() method, the information about all operations you added to UnitOfWork is sent to the Backendless server. The server executes all operations one-by-one in the context of a single database transaction. If any of the operations fails, Backendless rolls back all the changes and returns the information about the result of each operation to the client. However, if all operations succeeded, the entire database transaction is committed. This means the data will be finalized in the database.

Running a Transaction

Use the following methods to initiate the transaction on the server:

// blocking method - current thread is blocked while waiting
// for the server's response. IMPORTANT: blocking methods
// cannot be invoked on the main UI thread in Android
UnitOfWorkResult result = unitOfWorkInstance.execute();

// non-blocking method - current thread does not wait.
// Result is delivered to the callback object
unitOfWork.execute( new AsyncCallback<UnitOfWorkResult>()
{
  @Override
  public void handleResponse( UnitOfWorkResult response )
  {
    // Transaction completed. Check the response object if it is a success.
  }

  @Override
  public void handleFault( BackendlessFault fault )
  {
    // Server reported an error.
  }
} );

Codeless Reference

You can run transactions using the following Codeless block:

data_service_is_transaction_successful_4

where:

Argument                Description
transaction Expects an object containing transaction details, this includes: operation type and data.
return result When this box is checked, the operation is set to return an object containing the operation result.
throw error Check this box to return an error in case if the operation cannot be executed.

Find the example in the OpResult Id section below to learn more about how this block is used in the Codeless logic.

Operations

UnitOfWork supports the following database operations. Each operation is described in detail in the sections of this guide that follow.

OpResult Id

Unit of Work assigns a unique Id to every operation added to a transaction. These Id's are referred to as OpResult Id's and are important for several reasons: when a transaction is complete and you get the result, you can see the intermediate results for each operation. This is particularly helpful when a transaction fails, in this case OpResult Id's then can be used to see which operation resulted in the overall failure. Additionally, OpResult Id's are used internally when you pass the output of one operation as the input for another one. You can obtain the OpResult Id value assigned to an operation by using the getOpResultId() method on the OpResult object returned from every operation. By default Backendless uses the following scheme to generate operation Id values:

operation name + table where operation is performed + sequential number

For example, suppose your code adds the following operation to a transaction:

// create a UnitOfWork instance - it represents a transaction and
// will accumulate all operations in it.
UnitOfWork uow = new UnitOfWork();

// Create a person object
HashMap<String, Object> person = new HashMap<>();
person.put( "name", "Joe" );
person.put( "age", 20 );

// add a "create" operation to the transaction
// notice the result object will be reused later on
OpResult createPersonResult = uow.create( "Person", person );
String opResultID = createPersonResult.getOpResultId();

// 
// skipped for brevity
//

the opResultId variable in the code above will have a value of createPerson1. For additional details about OpResult and how to assign a custom OpResult Id, see the Operation Result chapter of this guide.

Codeless Reference

The example below saves a new object to the Person data table using the "Create" Operation Codeless block. After the Codeless logic runs, the operation assigns the operation reference to the opResultId variable. Refer to the Saving a single object topic to learn more about the "Create" Operation Codeless block.

data_service_is_transaction_successful_3

Transaction Result

As you can see from the API shown above, the execute method returns an instance of the UnitOfWorkResult object. It can be used to determine if the transaction has completed successfully, or resulted in an error. It also provides access to the information about the status of each operation the transaction consists of. See the API below. All listed methods are available in the UnitOfWorkResult class - your code receives that object as a result of your transaction:

// returns true if the transaction has completed successfully
public boolean isSuccess();

// returns an object describing the error. Information available 
// through the TransactionOperationError object includes the
// error message as well as a reference to the operation which 
// caused the error
public TransactionOperationError getError();

// returns a map between operation IDs and the result object containing
// the intermediary results of the corresp?nding operation
public Map<String, OperationResult> getResults()

When your code receives UnitOfWorkResult from the transaction execution, it is important to check if the transaction has completed successfully. You can do this by checking the return value of the isSuccess() method. If the value is true, the transaction has succeeded - all operations completed without any errors.

You can inspect the results of each individual operation by using the getResults() method. The method returns a map between OpResult ID (see the section above) and the result from the corresponding operation using the OperationResult class. The OperationResult class has the following API:

// return the type of the of the operation using the OperationType
// enumeration
public OperationType getType();

// returns the result of the operation. The object type is determined
// by what the operation returned.
public Object getResult();

The OperationType is an enumeration defined as shown below. Every value in the enumeration corresponds to an operation.

public enum OperationType {
  CREATE("create"),
  CREATE_BULK("createBulk"),
  UPDATE("update"),
  UPDATE_BULK("updateBulk"),
  DELETE("delete"),
  DELETE_BULK("deleteBulk"),
  FIND("find"),
  ADD_RELATION("addToRelation"),
  SET_RELATION("setRelation"),
  DELETE_RELATION("deleteRelation");

  public String getOperationName();
}

The getResult() method in the OperationResult class returns the actual object which the corresponding database operation returned. For example, if the database operation creates an object in the database, the getResult() method for that operation will return the created object.

Codeless Reference

data_service_is_transaction_successful_1

Returns a boolean value indicating the transaction result.

Consider the structure of the data table called Person:

data_service_transactions_bulk_save_4

The following example creates and runs a transaction containing a "Find" Operation and returns true, indicating that the transaction is successful. Note that this block must be used after the Run Transaction Codeless block as shown below:

data_service_is_transaction_successful_2