OrientDB supports multi-threads access to the database. ODatabase
instances are not thread-safe, so you've to get an instance per thread and each database instance can be used only in one thread per time*.
Multiple database instances points to the same storage by using the same URL. In this case Storage is thread-save and orchestrates requests from different ODatabase*
instances.
ODatabaseDocument1------+
+----> OStorageLocal (url=local:/temp/db)
ODatabaseDocument2------+
Database instances share the following objects:
These objects are synchronized for concurrent contexts by storing the current database in the ThreadLocal variable. Every time you create, open or acquire a database connection, the database instance is automatically set into the current ThreadLocal space, so in normal use this is hidden from the developer.
The current database is always reset for all common operations like load, save, etc.
Example of using two database in the same thread:
ODocument rec1 = database1.newInstance();
ODocument rec2 = database2.newInstance();
rec1.field("name", "Luca");
database1.save(rec1); // force saving in database1 no matter where the record came from
rec2.field("name", "Luke");
database2.save(rec2); // force saving in database2 no matter where the record came from
To get the current database from the ThreadLocal use:
ODatabaseDocument database = (ODatabaseDocument) ODatabaseRecordThreadLocal.INSTANCE.get();
Beware when you reuse database instances from different threads or then a thread handle multiple databases. In this case you can override the current database by calling this manually:
ODatabaseRecordThreadLocal.INSTANCE.set( database );
Where database is the current database instance. Example:
ODocument rec1 = database1.newInstance();
ODocument rec2 = database2.newInstance();
ODatabaseRecordThreadLocal.INSTANCE.set( database1 );
rec1.field("name", "Luca");
rec1.save();
ODatabaseRecordThreadLocal.INSTANCE.set( database2 );
rec2.field("name", "Luke");
rec2.save();
Since v1.2 Orient provides an interface to manage custom database management in MultiThreading cases:
public interface ODatabaseThreadLocalFactory {
public ODatabaseRecord getThreadDatabase();
}
Examples:
public class MyCustomRecordFactory implements ODatabaseThreadLocalFactory {
public ODatabaseRecord getDb(){
return ODatabaseDocumentPool.global().acquire(url, "admin", "admin");
}
}
public class MyCustomObjectFactory implements ODatabaseThreadLocalFactory {
public ODatabaseRecord getThreadDatabase(){
return OObjectDatabasePool.global().acquire(url, "admin", "admin").getUnderlying().getUnderlying();
}
}
Registering the factory:
ODatabaseThreadLocalFactory customFactory = new MyCustomRecordFactory();
Orient.instance().registerThreadDatabaseFactory(customFactory);
When a database is not found in current thread it will be called the factory getDb() to retrieve the database instance.
What happens if you are working with two databases and close one? The Thread Local isn't a stack, so you loose the previous database in use. Example:
ODatabaseDocumentTx db1 = new ODatabaseDocumentTx("local:/temo/db1").create();
ODatabaseDocumentTx db2 = new ODatabaseDocumentTx("local:/temo/db2").create();
...
db2.close();
// NOW NO DATABASE IS SET IN THREAD LOCAL. TO WORK WITH DB1 SET IT IN THE THREAD LOCAL
ODatabaseRecordThreadLocal.INSTANCE.set( db1 );
...
If two threads update the same record, then the last one receive the following exception: "OConcurrentModificationException: Cannot update record #X:Y in storage 'Z' because the version is not the latest. Probably you are updating an old record or it has been modified by another user (db=vA your=vB)"
This is because every time you update a record, the version is incremented by 1. So the second update fails checking the current record version in database is higher than the version contained in the record to update.
To fix this problem you can:
java -Ddb.mvcc=false
If you want to leave the MVCC and write code concurrency proof:
for( int retry = 0; retry < maxRetries; ++retry ) {
try{
// APPLY CHANGES
document.field( name, "Luca" );
document.save();
break;
} catch( ONeedRetryException e ) {
// RELOAD IT TO GET LAST VERSION
document.reload();
}
}
The same in transactions:
for( int retry = 0; retry < maxRetries; ++retry ) {
db.begin();
try{
// CREATE A NEW ITEM
ODocument invoiceItem = new ODocument("InvoiceItem");
invoiceItem.field( price, 213231 );
invoiceItem.save();
// ADD IT TO THE INVOICE
Collection<ODocument> items = invoice.field( items );
items.add( invoiceItem );
invoice.save();
db.commit();
break;
} catch( OTransactionException e ) {
// RELOAD IT TO GET LAST VERSION
invoice.reload();
}
}
Where maxRetries
is the maximum number of attempt of reloading.
Transactions are bound to a database, so if you change the current database while a tx is running, the deleted and saved objects remain attached to the original database transaction. When it commits, the objects are committed.
Example:
ODatabaseDocumentTx db1 = new ODatabaseDocumentTx("local:/temo/db1").create();
db1.begin();
ODocument doc1 = new ODocument("Customer");
doc1.field("name", "Luca");
doc1.save(); // NOW IT'S BOUND TO DB1'S TX
ODatabaseDocumentTx db2 = new ODatabaseDocumentTx("local:/temo/db2").create(); // THE CURRENT DB NOW IS DB2
ODocument doc2 = new ODocument("Provider");
doc2.field("name", "Chuck");
doc2.save(); // THIS IS BOUND TO DB2 BECAUSE IT'S THE CURRENT ONE
db1.commit(); // WILL COMMIT DOC1 ONLY