Txn
provides a high level interface to Jena transactions. It is a
library over the core functionality - applications do not have to use Txn
to use transactions.
Features:
The basic transactions API provides operations
begin
, commit
, abort
and end
.
A write transaction looks like:
dataset.begin(ReadWrite.write) ; try { ... write operations ... dataset.commit() ; // Or abort } finally { dataset.end() ; }
Txn
simplifies writing transaction handling by wrapping application code,
contained in a Java lambda expression or a Java Runnable object, in the correct entry
and exit code for a transaction, eliminating coding errors.
The form is:
Txn.executeRead(ds, ()-> { . . . }) ;
and
Txn.executeWrite(ds, ()-> { . . . }) ;
This first example shows how to write a SPARQL query .
Dataset ds = ... ; Query query = ... ; Txn.executeRead(ds, ()-> { try(QueryExecution qExec = QueryExecutionFactory.create(query, ds)) { ResultSetFormatter.out(qExec.execSelect()) ; } }) ;
Here, a try-with-resources
ensures correct handling of the
QueryExecution
inside a read transaction.
Writing to a file is a read-action (it does not update the RDF data, the writer needs to read the dataset or model):
Dataset ds = ... ; Txn.executeRead(ds, ()-> { RDFDataMgr.write(System.out, ds, Lang.TRIG) ; }) ;
whereas reading data into an RDF dataset needs to be a write transaction (the dataset or model is changed).
Dataset ds = ... ; Txn.executeWrite(ds, ()-> { RDFDataMgr.read(ds, "data.ttl") ; }) ;
Applications are not limited to a single operation inside a transaction. It can involve multiple applications read operations, such as making several queries:
Dataset ds = ... ; Query query1 = ... ; Query query2 = ... ; Txn.executeRead(ds, ()-> { try(QueryExecution qExec1 = QueryExecutionFactory.create(query1, ds)) { ... } try(QueryExecution qExec2 = QueryExecutionFactory.create(query2, ds)) { ... } }) ;
A Txn.calculateRead
block can return a result but only with the condition
that what is returned does not touch the data again unless it uses a new
transaction.
This includes returning a result set or returning a model from a dataset.
ResultSets
by default stream - each time hasNext
or next
is
called, new data might be read from the RDF dataset. A copy of the
results needs to be take:
Dataset ds = ... ; Query query = ... ; List<String> results = Txn.calculateRead(ds, ()-> { List<String> accumulator = new ArrayList<>() ; try(QueryExecution qExec = QueryExecutionFactory.create(query, ds)) { qExec.execSelect().forEachRemaining((row)->{ String strThisRow = row.getLiteral("variable").getLexicalForm() ; accumulator.add(strThisRow) ; }) ; } return accumulator ; }) ; // use "results" Dataset ds = ... ; Query query = ... ; ResultSet List<String> resultSet = Txn.calculateRead(ds, ()-> { List<String> accumulator = new ArrayList<>() ; try(QueryExecution qExec = QueryExecutionFactory.create(query, ds)) { return ResultSetFactory.copyResults(qExec.execSelect()) ; } }) ; // use "resultSet"
The functions Txn.execute
and Txn.calculate
start READ_PROMOTE
transactions which start in "read" mode but convert to "write" mode if
needed. For details of transaction promtion see the
csection in the transaction API documentation.
The unit of transaction is the dataset. Model in datasets are just views of that dataset. Model should not be passed out of a transaction because they are still attached to the dataset.
If there is a transaction already started for the thread, the Txn.execute...
will be performed as part of
the transaction and that transaction is not terminated. If there is not transaction already started,
a transaction is wrapped around the Txn.execute...
action.
Dataset dataset = ... // Main transaction. dataset.begin(ReadWrite.WRITE) ; try { ... // Part of the transaction above. Txn.executeRead(dataset, () -> ...) ; ... // Part of the transaction above - no commit/abort Txn.executeWrite(dataset, () -> ...) ; dataset.commit() ; } finally { dataset.end() ; }
Txn
uses Java Runnable
for the application code, passed into control code
that wraps the transaction operations around the application code. This results
in application code automatically applied transaction begin/commit/end as needed.
A bare read transaction requires the following code structure (no exception handling):
txn.begin(ReadWrite.READ) ; try { ... application code ... } finally { txn.end() ; }
while a write transaction requires either a commit
or an abort
at the end of the application code as well.
Without the transaction continuation code (simplified, the Txn
code
for a read transaction takes the form:
public static <T extends Transactional> void executeRead(T txn, Runnable r) { txn.begin(ReadWrite.READ) ; try { r.run() ; } catch (Throwable th) { txn.end() ; throw th ; } txn.end() ; }
See Txn.java
for full details.