Transaction ExecutionBuilder
When assembling a transaction that cannot be handled using one of the TransactionController.execute methods, a transaction ExecutionBuilder instance can be used. An ExecutionBuilder supports setting a transaction-specific timeout and identifying more DatasetReader/DatasetWriterReader resources to include in the transaction than is possible using the execute methods.
Dataset instances involved in a transaction must be identified to the transaction context by providing DatasetReader and/or DatasetWriterReader instances to the context through the ExecutionBuilder.using methods. Accesses to undeclared Dataset instances which are not under the scope of the transaction can result in unexpected behavior.
ReadOnlyExecutionBuilder
ReadOnlyExecutionBuilder
readOnlyExecutionBuilder = controller1.transact() // 1
.timeout(100, TimeUnit.SECONDS) // 2
.using("empReader", employeeReader) // 3
.using("custReader", customerReader); // 4
1 | transact() in TransactionController returns a ReadOnlyExecutionBuilder, the starting point for all transaction builders, which is then used to construct a read-only transaction. |
2 | A transaction can have its own timeout defined through the ExecutionBuilder. |
3 | A DatasetReader is added as a resource to the execution builder. This resource will be used by the executing transaction. |
4 | Multiple resources can be added to an execution builder. |
Note: For a transaction in which no mutations will take place, using DatasetReader instead of DatasetWriterReader instances will result in a ReadOnlyTransaction which can provide improved performance.
ReadWriteExecutionBuilder
ReadWriteExecutionBuilder readWriteExecutionBuilder = controller2.transact()
.using("custWriterReader", customerWriterReader) // 1
.using("empReader", employeeReader); // 2
1 | If a DatasetWriterReader is added as a resource to a ReadOnlyExecutionBuilder, then it returns a ReadWriteExecutionBuilder which can be used to execute a read/write transaction. |
2 | A DatasetReader can also be added to a ReadWriteExecutionBuilder as resource that could eventually get used by the read/write transaction executed using the ReadWriteExecutionBuilder. |
Executing Using an ExecutionBuilder
The transactions API provides two ways of executing a transaction using an execution builder as described below.
Executing a TransactionalAction
One way to execute a transaction is to write a function that contains all the logic of the transaction and then execute that function using the execution builder.
Double totalSalary = transactionController.transact()
.using("empReader", employeeReader)
.execute(readers -> { // 1
DatasetReader<Integer> empTransactionalReader =
(DatasetReader<Integer>) readers.get("empReader"); // 2
double first100EmployeesSalaryCount = 0;
for (int i = 1; i < 100; i++) {
first100EmployeesSalaryCount +=
empTransactionalReader.get(i).map(rec ->
rec.get(SALARY).orElse(0D)).orElse(0D); // 3
}
return first100EmployeesSalaryCount; // 4
});
1 | ReadOnlyExecutionBuilder.execute() takes a TransactionalAction instance as parameter. TransactionalAction is a functional interface with a method perform() taking in a single argument (Map<String, DatasetReader>) and returning an object. In this example, the TransactionalAction instance is formed from a lambda expression. This TransactionalAction is executed as a single transaction satisfying all the ACID properties. |
2 | The Map (readers) provides access to the DatasetReader instances that were added as resources. |
3 | Transactional read operations can be performed using these extracted dataset readers. |
4 | The value returned by perform() of TransactionalAction is returned by the execute() method here. |
Note: For proper transactional operations, use only DatasetReader instances obtained from the Map and not from variables defined outside of the scope of the lambda and captured within the lambda.
int numberOfRecordsUpdated = transactionController.transact()
.using("empReader", employeeReader)
.using("empWriterReader", employeeWriterReader)
.execute((writerReaders, readers) -> { // 1
DatasetWriterReader<Integer> empTransactionalWriterReader =
(DatasetWriterReader<Integer>)
writerReaders.get("empWriterReader"); // 2
DatasetReader<Integer> empTransactionalReader =
(DatasetReader<Integer>) readers.get("empReader"); // 2
int numRecordsUpdated = 0;
for (int i = 1; i < 100; i++) {
numRecordsUpdated += empTransactionalWriterReader.on(i) // 3
.update(UpdateOperation.write(SALARY).doubleResultOf(
SALARY.doubleValueOr(0D).add(100D)))
.isPresent() ? 1 : 0;
}
System.out.println("Total Employee = " +
empTransactionalReader.records().count()); // 3
return numRecordsUpdated; // 4
});
1 | ReadWriteExecutionBuilder.execute() takes a TransactionalBiActioninstance as parameter. TransactionalBiAction is a functional interface with a method perform() taking two arguments (Map<String, DatasetWriterReader> and Map<String, DatasetReader>) and returning an object. In this example, the TransactionalBiAction instance is formed from a lambda expression. This TransactionalBiAction is executed as a single transaction satisfying all the ACID properties. |
2 | The maps (writerReaders, readers) provide access to the DatasetWriterReader and DatasetReader instances that were added as resources. |
3 | Transactional CRUD operations can be performed using these extracted dataset readers and writerReaders. |
4 | The value returned by perform() of TransactionalBiAction is returned by the execute() method here. |
Note: For proper transactional operations, use only DatasetWriterReader and DatasetReader instances obtained from the maps and not from variables defined outside of the scope of the lambda and captured within the lambda.
External Transaction Control
Another way of executing a transaction is by creating a Transaction instance using an execution builder. This Transaction instance is then used to extract the transactional versions of the dataset readers and writerReaders that were added as resources. These instances are then used to perform all the transactional activity and finally the transaction is committed through the Transaction instance.
Note: External transaction control is deprecated in favor of the execution using lambda forms described above.
ReadOnlyTransaction readOnlyTransaction =
transactionController.transact()
.using("empReader", employeeReader)
.begin(); // 1
boolean exceptionThrown1 = false;
try {
DatasetReader<Integer> empTransactionalReader =
readOnlyTransaction.reader("empReader"); // 2
double first100EmployeesSalarySum = 0;
for (int i = 1; i <= 100; i++) {
first100EmployeesSalarySum +=
empTransactionalReader.get(i).map(rec ->
rec.get(SALARY).orElse(0D)).orElse(0D); // 3
}
System.out.println("First 100 employee's salary sum = " +
first100EmployeesSalarySum);
} catch (Exception e) {
exceptionThrown1 = true;
e.printStackTrace();
readOnlyTransaction.rollback();
}
if (exceptionThrown1 == false) {
readOnlyTransaction.commit(); // 4
}
1 | ReadOnlyExecutionBuilder.begin() returns a ReadOnlyTransaction instance. |
2 | The ReadOnlyTransaction instance is then used to retrieve transactional dataset readers. |
3 | The readers can then be used for performing transactional read operations. |
4 | Finally, to finish the transaction commit() or rollback() is called on the ReadOnlyTransaction instance. |
TransactionController.ReadWriteTransaction
readWriteTransaction = transactionController.transact()
.using("empReader", employeeReader)
.using("empWriterReader", employeeWriterReader)
.begin(); // 1
boolean exceptionThrown = false;
int numRecordsUpdated = 0;
try {
DatasetWriterReader<Integer> empTransactionalWriterReader =
readWriteTransaction.writerReader("empWriterReader"); // 2
DatasetReader<Integer> empTransactionalReader1 =
readWriteTransaction.reader("empReader"); // 2
for (int i = 1; i < 100; i++) {
numRecordsUpdated += empTransactionalWriterReader.on(i) // 3
.update(UpdateOperation.write(SALARY).doubleResultOf(
SALARY.doubleValueOr(0D).add(100D)))
.isPresent() ? 1 : 0;
}
System.out.println("Total Employee = " +
empTransactionalReader1.records().count()); // 3
} catch (Exception e) {
exceptionThrown = true;
e.printStackTrace();
readWriteTransaction.rollback();
}
if (exceptionThrown == false) {
if (numRecordsUpdated == 100) {
readWriteTransaction.commit(); // 4
} else {
readWriteTransaction.rollback(); // 4
}
}
1 | ReadWriteExecutionBuilder.begin() returns a ReadWriteTransaction instance. |
2 | The ReadWriteTransaction instance is then used to retrieve transactional dataset readers and writer-readers. |
3 | The readers and writer-readers can then be used for performing transactional operations. |
4 | Finally, to finish the transaction, commit() or rollback() is called on the ReadWriteTransaction instance. For read/write transactions the commit() or rollback() will resolve the dataset states to their correct forms. |