Document API

To use the Document API include the following jars in your classpath:

  1. orientdb-core-*.jar

If you’re using the Document Database interface connected to a remote server (not local/embedded mode) include also:

  1. orientdb-client-*.jar
  2. orientdb-enterprise-*.jar

Introduction

The Orient Document DB is the base of higher-level implementation like Object-Database and Graph-Database. The Document Database API has the following features:

This is an example to store 2 linked documents in the database:

  1. // OPEN THE DATABASE
  2. ODatabaseDocumentTx db = new ODatabaseDocumentTx("remote:localhost/petshop").open("admin", "admin");
  3. // CREATE A NEW DOCUMENT AND FILL IT
  4. ODocument doc = new ODocument("Person");
  5. doc.field( "name", "Luke" );
  6. doc.field( "surname", "Skywalker" );
  7. doc.field( "city", new ODocument("City").field("name","Rome").field("country", "Italy") );
  8. // SAVE THE DOCUMENT
  9. doc.save();
  10. db.close();

This is the very first example. While the code is pretty clear and easy to understand please note that we haven’t declared the type “Person” before now. When an ODocument instance is saved, the declared type “Person” will be created without constraints. To declare persistent classes look at the Schema management.

Use the database

Before to execute any operation you need an opened database instance. You can open an existent database or create a new one. Databases instances aren’t thread safe, so use one database per thread.

Before to open or create a database instance you need a valid URL. URL is where the database is available. URL says what kind of database will be used. For example memory: means in-memory only database, plocal: is for embedded ones and remote: to use a remote database hosted on an up & running DBServer OrientDB Server instance. For more information look at Database URL.

Database instances must be closed once finished to release precious resources. To assure it the most common usage is to enclose all the database operations inside a try/finally block:

  1. ODatabaseDocumentTx db = new ODatabaseDocumentTx("plocal:/temp/test");
  2. db.open("admin", "admin");
  3. try {
  4. // YOUR CODE
  5. } finally {
  6. db.close();
  7. }

If you are using a remote storage (url starts with “remote:”) assure the server is up & running and include the orientdb-client.jar file in your classpath.

Multi-threading

The ODatabaseDocumentTx class is non thread-safe. For this reason use different ODatabaseDocumentTx instances by multiple threads. They will share the same Storage instance (with the same URL) and the same level-2 cache. For more information look at Multi-Threading with Java.

Create a new database

In local filesystem

  1. ODatabaseDocumentTx db = new ODatabaseDocumentTx ("plocal:/tmp/databases/petshop").create();

On a remote server

To create a database in a remote server you need the user/password of the remote OrientDB Server instance. By default the “root” user is created on first startup of the server. Check this in the file config/orientdb-server-config.xml, where you will also find the password.

To create a new document database called dbname on dbhost using filesystem storage (as opposed to in-memory storage):

  1. new OServerAdmin("remote:dbhost")
  2. .connect("root", "kjhsdjfsdh128438ejhj")
  3. .createDatabase("dbname","document","local").close();

To create a graph database replace “document” with “graph”.

To store the database in memory replace “local” with “memory”.

Open a database

  1. ODatabaseDocumentTx db = new ODatabaseDocumentTx ("remote:localhost/petshop").open("admin", "admin");

The database instance will share the connection versus the storage. if it’s a “local” storage, then all the database instances will be synchronized on it. If it’s a “remote” storage then the network connection will be shared among all the database instances.

Use the connection Pool

One of most common use cases is to reuse the database, avoiding to create it every time. It’s also the typical scenario of the Web applications. Instead of creating a new ODatabaseDocumentTx instance all the times, get an available instance from the pool:

  1. // OPEN THE DATABASE
  2. ODatabaseDocumentTx db = ODatabaseDocumentPool.global().acquire("remote:localhost/petshop", "admin", "admin");
  3. try {
  4. // YOUR CODE
  5. ...
  6. } finally {
  7. db.close();
  8. }

Remember to always close the database instance using the close() database method like a classic non-pooled database. In this case the database will be not closed for real, but the instance will be released to the pool, ready to be reused by future requests. The best is to use a try/finally block to avoid cases where the database instance remains open, just like the example above.

Global pool

By default OrientDB provide a global pool declared with maximum 20 instances. Use it with: ODatabaseDocumentPool.global().

Use your pool

To create your own pool build it and call the setup(min, max) method to define minimum and maximum managed instances. Remember to close it when the pool is not more used. Example:

  1. // CREATE A NEW POOL WITH 1-10 INSTANCES
  2. ODatabaseDocumentPool pool = new ODatabaseDocumentPool();
  3. pool.setup(1,10);
  4. ...
  5. pool.close();

Schema

OrientDB can work in schema-full (like RDBMS), schema-less (like many NoSQL Document databases) and in schema-hybrid mode. For more information about the Schema look at the Schema page.

To use the schema with documents create the ODocument instance using the ODocument(String className) constructor passing the class name. If the class hasn’t been declared, it’s created automatically with no fields. This can’t work during transaction because schema changes can’t be applied in transactional context.

Security

Few NoSQL solutions supports security. OrientDB does it. To know more about it look at Security.

To manage the security get the Security Manager and use it to work with users and roles. Example:

  1. OSecurity sm = db.getMetadata().getSecurity();
  2. OUser user = sm.createUser("god", "god", new String[] { "admin" } );

To get the reference to the current user use:

  1. OUser user = db.getUser();

Create a new document

ODocument instances can be saved by calling the save() method against the object itself. Note that the behaviour depends on the running transaction, if any. See Transactions.

  1. ODocument animal = new ODocument("Animal");
  2. animal.field( "name", "Gaudi" );
  3. animal.field( "location", "Madrid" );
  4. animal.save();

Retrieve documents

Browse all the documents in a cluster

  1. for (ODocument doc : database.browseCluster("CityCars")) {
  2. System.out.println( doc.field("model") );

Browse all the records of a class

  1. for (ODocument animal : database.browseClass("Animal")) {
  2. System.out.println( animal.field( "name" ) );

Count records of a class

  1. long cars = database.countClass("Car");

v= Count records of a cluster ==

  1. long cityCars = database.countCluster("CityCar");

Execute a query

Although OrientDB is part of the NoSQL database community, it supports a subset of SQL that allows it to process links to documents and graphs.

To know more about the SQL syntax supported go to: SQL-Query.

Example of a SQL query:

  1. List<ODocument> result = db.query(
  2. new OSQLSynchQuery<ODocument>("select * from Animal where ID = 10 and name like 'G%'"));

Asynchronous query

OrientDB supports asynchronous queries. The result is not collected and returned like synchronous ones (see above) but a callback is called every time a record satisfy the predicates:

  1. database.command(
  2. new OSQLAsynchQuery<ODocument>("select * from animal where name = 'Gipsy'",
  3. new OCommandResultListener() {
  4. resultCount = 0;
  5. @Override
  6. public boolean result(Object iRecord) {
  7. resultCount++;
  8. ODocument doc = (ODocument) iRecord;
  9. // DO SOMETHING WITH THE DOCUMENT
  10. return resultCount > 20 ? false : true;
  11. }
  12. @Override
  13. public void end() {
  14. }
  15. })).execute();

Asynchronous queries are useful to manage big result sets because don’t allocate memory to collect results.

Non-Blocking query (since v2.1)

Both Synchronous and Asynchronous queries are blocking, that means that the first instruction you have after db.query() or db.command().execute() will be executed only after you received all the result-set or last callback was invoked. OrientDB also supports non-blocking queries. The API is very similar to asynchronous queries (you have a callback that is invoked for every record in the result-set), but the behavior is completely different: the execution of your current thread continues without blocking on the db.query() or db.command().execute(), and the callback is invoked by a different thread. That means that in the meantime you can close your db instance and keep on receiving callbacks from the query result.

  1. Future future = database.command(new OSQLNonBlockingQuery<Object>("select * from animal where name = 'Gipsy'",
  2. new OCommandResultListener() {
  3. resultCount = 0;
  4. @Override
  5. public boolean result(Object iRecord) {
  6. resultCount++;
  7. ODocument doc = (ODocument) iRecord;
  8. // DO SOMETHING WITH THE DOCUMENT
  9. System.out.println("callback "+resultCount+" invoked");
  10. return resultCount > 20 ? false : true;
  11. }
  12. @Override
  13. public void end() {
  14. }
  15. })).execute();
  16. System.out.println("query executed");
  17. future.get();

the result of this snippet of code will be something like

  1. query executed
  2. callback 0 invoked
  3. callback 1 invoked
  4. callback 2 invoked
  5. callback 3 invoked
  6. callback 4 invoked

but it could also be

  1. callback 0 invoked
  2. callback 1 invoked
  3. query executed
  4. callback 2 invoked
  5. callback 3 invoked
  6. callback 4 invoked

depending on race conditions on the two parallel threads (the one that fires query execution and then continues with “query executed”, and the other one that invokes callbacks).

future.get(); is a blocking call that returns only after last callback invocation (you can avoid this if you don’t need to know when the query terminates).

Prepared query

Prepared query are quite similar to the Prepared Statement of JDBC. Prepared queries are pre-parsed so on multiple execution of the same query are faster than classic SQL queries. Furthermore the pre-parsing doesn’t allow SQL Injection. Note: prepared queries (parameter substition) only works with select statements (but not select statements within other types of queries such as “create vertex”).

Prepared query uses two kinds of markers to substitute parameters on execution:

  1. ? is positional parameter
  2. :<par> is named parameter

Example of positional parameters:

  1. OSQLSynchQuery<ODocument> query = new OSQLSynchQuery<ODocument>("select from Profile where name = ? and surname = ?");
  2. List<ODocument> result = database.command(query).execute("Barack", "Obama");

Example of named parameters:

  1. OSQLSynchQuery<ODocument> query = new OSQLSynchQuery<ODocument>("select from Profile where name = :name and
  2. surname = :surname");
  3. Map<String,Object> params = new HashMap<String,Object>();
  4. params.put("name", "Barack");
  5. params.put("surname", "Obama");
  6. List<ODocument> result = database.command(query).execute(params);

Right usage of the graph

OrientDB is a graph database. This means that traversing is very efficient. You can use this feature to optimize queries. A common technique is the Pivoting.

SQL Commands

To execute SQL commands use the command() method passing a OCommandSQL object:

  1. int recordsUpdated = db.command(
  2. new OCommandSQL("update Animal set sold = false")).execute();

If the command modifies the schema (like create/alter/drop class and create/alter/drop property commands), remember to force updating of the schema of the database instance you’re using:

  1. db.getMetadata().getSchema().reload();

For more information look at the available SQL commands.

Traverse records

Traversing is the operation to cross documents by links (relationships). OrientDB is a graph database so this operation is much much more efficient than executing a JOIN in the relational databases. To know more about traversing look at the Java traverse API.

The example below traverses, for each movie, all the connected records up to the 5th depth level.

  1. for (OIdentifiable id : new OTraverse()
  2. .field("in").field("out")
  3. .target( database.browseClass("Movie").iterator() )
  4. .predicate(new OCommandPredicate() {
  5. public boolean evaluate(ORecord<?> iRecord, OCommandContext iContext) {
  6. return ((Integer) iContext.getVariable("depth")) <= 5;
  7. }
  8. })) {
  9. System.out.println(id);
  10. }

Update a document

Any persistent document can be updated by using the Java API and then by calling the db.save() method. Alternatively, you can call the document’s save() method to synchronize the changes to the database. The behaviour depends on the transaction begun, if any. See Transactions.

  1. animal.field( "location", "Nairobi" );
  2. animal.save();

OrientDB will update only the fields really changed.

Example of how to increase the price of all the animals by 5%:

  1. for (ODocument animal : database.browseClass("Animal")) {
  2. animal.field( "price", animal.field( "price" ) * 105 / 100 );
  3. animal.save();
  4. }

Delete a document

To delete a document call the delete() method on the document instance that’s loaded. The behaviour depends on the transaction begun, if any. See Transactions.

  1. animal.delete();

Example of deletion of all the documents of class “Animal”.

  1. for (ODocument animal : database.browseClass("Animal"))
  2. animal.delete();

Transactions

Transactions are a practical way to group a set of operations together. OrientDB supports ACID transactions so that all or none of the operations succeed. The database always remains consistent. For more information look at Transactions.

Transactions are managed at the database level. Nested transactions are currently not supported. A database instance can only have one transaction running. The database’s methods to handle transactions are:

  • begin() to start a new transaction. If a transaction was already running, it’s rolled back and a new one is begun.
  • commit() makes changes persistent. If an error occurs during commit the transaction is rolled back and an OTransactionException exception is raised.
  • rollback() aborts a transaction. All the changes will be lost.

Optimistic approach

The current release of OrientDB only supports OPTIMISTIC transactions where no lock is kept and all operations are checked at commit time. This improves concurrency but can throw an OConcurrentModificationException exception in the case where records are modified by concurrent clients or threads. In this scenario, the client code can reload the updated records and repeat the transaction.

Optimistic transactions keep all the changes in memory in the client. If you’re using remote storage no changes are sent to the server until commit() is called. All the changes will be transferred in a block. This reduces network latency, speeds-up the execution, and increases concurrency. This is a big difference compared to most Relational DBMS where, during a transaction, changes are sent immediately to the server.

Usage

Transactions are committed only when the commit() method is called and no errors occur. The most common usage of transactions is to enclose all the database operations inside a try/finally block. On closing of the database (“finally” block) if a pending transaction is running it will be rolled back automatically. Look at this example:

  1. ODatabaseDocumentTx db = new ODatabaseDocumentTx(url);
  2. db.open("admin", "admin");
  3. try {
  4. db.begin();
  5. // YOUR CODE
  6. db.commit();
  7. } finally {
  8. db.close();
  9. }

Index API

Even though you can use Indices via SQL, the best and most efficient way is to use the Java API.

The main class to use to work with indices is the IndexManager. To get the implementation of the IndexManager use:

  1. OIndexManager idxManager = database.getMetadata().getIndexManager();

The Index Manager allows you to manage the index life-cycle for creating, deleting, and retrieving an index instance. The most common usage is with a single index. You can get the reference to an index by using:

  1. OIndex<?> idx = database.getMetadata().getIndexManager().getIndex("Profile.name");

Where “Profile.name” is the index name. Note that by default OrientDB assigns the name as <class>.<property> for automatic indices created against a class’s property.

The OIndex interface is similar to a Java Map and provides methods to get, put, remove, and count items. The following are examples of retrieving records using a UNIQUE index against a name field and a NOTUNIQUE index against a gender field:

  1. OIndex<?> nameIdx = database.getMetadata().getIndexManager().getIndex("Profile.name");
  2. // THIS IS A UNIQUE INDEX, SO IT RETRIEVES A OIdentifiable
  3. OIdentifiable luke = nameIdx.get( "Luke" );
  4. if( luke != null )
  5. printRecord( (ODocument) luke.getRecord() );
  6. OIndex<?> genderIdx = database.getMetadata().getIndexManager().getIndex("Profile.gender");
  7. // THIS IS A NOTUNIQUE INDEX, SO IT RETRIEVES A Set<OIdentifiable>
  8. Set<OIdentifiable> males = genderIdx.get( "male" );
  9. for( OIdentifiable male : males )
  10. printRecord( (ODocument) male.getRecord() );

While automatic indices are managed automatically by OrientDB hooks, the manual indices can be used to store any value. To create a new entry use the put():

  1. OIndex<?> addressbook = database.getMetadata().getIndexManager().getIndex("addressbook");
  2. addressbook.put( "Luke", new ODocument("Contact").field( "name", "Luke" );

Resources