To use the Graph API include the following jars in your classpath:
orient-commons-*.jar
orientdb-core-*.jar
blueprints-core-*.jar
orientdb-graphdb-*.jar
(blueprints-orient-graph-*.jar only for OrientDB < v1.7)
If you're connected to a remote server (not local/plocal/memory modes) include also:
orientdb-client-*.jar
orientdb-enterprise-*.jar
To also use the TinkerPop Pipes tool include also:
pipes-*.jar
To also use the TinkerPop Gremlin language include also:
gremlin-java-*.jar
gremlin-groovy-*.jar
groovy-*.jar
NOTE: Starting from v2.0, Lightweight Edges are disabled by default when new database are created.
Tinkerpop is a complete stack of projects to handle Graphs:
OrientDB supports different kind of storages and depends by the Database URL used:
Before working with a graph you need an instance of OrientGraph class. The constructor gets a URL that is the location of the database. If the database already exists, it will be opened, otherwise it will be created. In multi-threaded applications use one OrientGraph instance per thread.
Remember to always close the graph once done using the .shutdown()
method.
Example:
OrientGraph graph = new OrientGraph("plocal:C:/temp/graph/db");
try {
...
} finally {
graph.shutdown();
}
Starting from v1.7 the best way to get a Graph instance is through the OrientGraphFactory. To know more: Use the Graph Factory. Example:
// AT THE BEGINNING
OrientGraphFactory factory = new OrientGraphFactory("plocal:C:/temp/graph/db").setupPool(1,10);
// EVERY TIME YOU NEED A GRAPH INSTANCE
OrientGraph graph = factory.getTx();
try {
...
} finally {
graph.shutdown();
}
Every time the graph is modified an implicit transaction is started automatically if no previous transaction was running. Transactions are committed automatically when the graph is closed by calling the shutdown()
method or by explicit commit()
. To rollback changes call the rollback()
method.
Changes inside a transaction will be temporary until the commit or the close of the graph instance. Concurrent threads or external clients can see the changes only when the transaction has been fully committed.
Full example:
try{
Vertex luca = graph.addVertex(null); // 1st OPERATION: IMPLICITLY BEGIN A TRANSACTION
luca.setProperty( "name", "Luca" );
Vertex marko = graph.addVertex(null);
marko.setProperty( "name", "Marko" );
Edge lucaKnowsMarko = graph.addEdge(null, luca, marko, "knows");
graph.commit();
} catch( Exception e ) {
graph.rollback();
}
Surrounding the transaction between a try/catch assures that any errors will rollback the transaction to the previous status for all the involved elements.
NOTE: To work against a graph always use transactional OrientGraph instances and never non-transactional ones to avoid graph corruption from multi-threaded changes.
To create a new Vertex in the current Graph call the Vertex OrientGraph.addVertex(Object id)) method. Note that the id parameter is ignored since OrientDB implementation assigns a unique-id once the vertex is created. To return it use Vertex.getId()). Example:
Vertex v = graph.addVertex(null);
System.out.println("Created vertex: " + v.getId());
An Edge links two vertices previously created. To create a new Edge in the current Graph call the Edge OrientGraph.addEdge(Object id, Vertex outVertex, Vertex inVertex, String label )) method. Note that the id parameter is ignored since OrientDB implementation assigns a unique-id once the Edge is created. To return it use Edge.getId()). outVertex
is the Vertex instance where the Edge starts and inVertex
is the Vertex instance where the Edge ends. label
is the Edge's label. Specify null to not assign it.
Example:
Vertex luca = graph.addVertex(null);
luca.setProperty("name", "Luca");
Vertex marko = graph.addVertex(null);
marko.setProperty("name", "Marko");
Edge lucaKnowsMarko = graph.addEdge(null, luca, marko, "knows");
System.out.println("Created edge: " + lucaKnowsMarko.getId());
To retrieve all the vertices use the getVertices()
method:
for (Vertex v : graph.getVertices()) {
System.out.println(v.getProperty("name"));
}
To retrieve all the vertices use the getEdges()) method:
for (Edge e : graph.getEdges()) {
System.out.println(e.getProperty("age"));
}
NOTE: Starting from OrientDB v1.4.x (until 2.0, where the opposite is true) edges by default are stored as links not as records (i.e. useLightweightEdges=true by default). This is to improve performance. As a consequence, getEdges will only retrieve records of class E. With useLightweightEdges=true, records of class E are only created under certain circumstances (e.g. if the Edge has properties) otherwise they will be links on the in and out vertices. If you really want getEdges()
to return all edges, disable the Lightweight-Edge feature by executing this command once: alter database custom useLightweightEdges=false
. This will only take effect for new edges so you'll have to convert the links to actual edges before getEdges will return all edges. For more information look at: https://github.com/orientechnologies/orientdb/wiki/Troubleshooting#why-i-cant-see-all-the-edges.
To remove a vertex from the current Graph call the OrientGraph.removeVertex(Vertex vertex)) method. The vertex will be disconnected from the graph and then removed. Disconnection means that all the vertex's edges will be deleted as well. Example:
graph.removeVertex(luca);
To remove an edge from the current Graph call the OrientGraph.removeEdge(Edge edge)) method. The edge will be removed and the two vertices will not be connected anymore. Example:
graph.removeEdge(lucaKnowsMarko);
Vertices and Edges can have multiple properties where the key is a String and the value can be any supported OrientDB types.
Example:
vertex2.setProperty("x", 30.0f);
vertex2.setProperty("y", ((float) vertex1.getProperty( "y" )) / 2);
for (String property : vertex2.getPropertyKeys()) {
System.out.println("Property: " + property + "=" + vertex2.getProperty(property));
}
vertex1.removeProperty("y");
Blueprints Extension OrientDB Blueprints implementation supports setting of multiple properties in one shot against Vertices and Edges. This improves performance avoiding to save the graph element at every property set: setProperties(Object ...)). Example:
vertex.setProperties( "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX" );
You can also pass a Map of values as first argument. In this case all the map entries will be set as element properties:
Map<String,Object> props = new HashMap<String,Object>();
props.put("name", "Jill");
props.put("age", 33);
props.put("city", "Rome");
props.put("born", "Victoria, TX");
vertex.setProperties(props);
If you want to create a vertex or an edge while setting the initial properties, the OrientDB Blueprints implementation offers new methods to do it:
graph.addVertex("class:Customer", "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX");
This creates a new Vertex of class Customer
with the properties: name
, age
, city
, and born
. The same is for Edges:
person1.addEdge("class:Friend", person2, null, null, "since", "2013-07-30");
This creates a new Edge of class Friend
between vertices person1
and person2
with the property since
.
Both methods accept a Map<String, Object>
as a parameter to set one property per map entry (see above for the example).
These methods are especially useful if you've declared constraints in the schema. For example, a property cannot be null, and only using these methods will the validation checks succeed.
OrientDB allows execution queries against any field of vertices and edges, indexed and not-indexed. The first rule to speed up queries is to setup indices on the key properties you use in the query. For example, if you have a query that is looking for all the vertices with the name 'OrientDB' you do this:
graph.getVertices("name", "OrientDB");
Without an index against the property "name" this execution could take a lot of time. So let's create a new index against the "name" property:
graph.createKeyIndex("name", Vertex.class);
If the name MUST be unique you can enforce this constraint by setting the index as "UNIQUE" (this is an OrientDB only feature):
graph.createKeyIndex("name", Vertex.class, new Parameter("type", "UNIQUE"));
This constraint will be applied to all the Vertex and sub-type instances. To specify an index against a custom type like the "Customer" vertices use the additional parameter "class":
graph.createKeyIndex("name", Vertex.class, new Parameter("class", "Customer"));
You can also have both UNIQUE index against custom types:
graph.createKeyIndex("name", Vertex.class, new Parameter("type", "UNIQUE"), new Parameter("class", "Customer"));
To get a vertex or an edge by key prefix use the class name before the property. For the example above use Customer.name
in place of only name
to use the index created against the field name
of class Customer
:
for (Vertex v : graph.getVertices("Customer.name", "Jay")) {
System.out.println("Found vertex: " + v);
}
If the class name is not passed, then "V" is taken for vertices and "E" for edges:
graph.getVertices("name", "Jay");
graph.getEdges("age", 20);
For more information about indices look at Index guide.
To speed up operations like on massive insertions you can avoid transactions by using a different class than OrientGraph: OrientGraphNoTx. In this case each operation is atomic and data is updated at each operation. When the method returns, the underlying storage is updated. Use this for bulk inserts and massive operations.
NOTE: Using non-transactional graphs could create corruption in the graph if changes are made in multiple threads at the same time. So use non-transactional graph instances only for non multi-threaded operations.
Starting from v1.6 OrientDB supports configuration of the graph by setting all the properties during construction:
Name | Description | Default value |
---|---|---|
blueprints.orientdb.url | Database URL | - |
blueprints.orientdb.username | User name | admin |
blueprints.orientdb.password | User password | admin |
blueprints.orientdb.saveOriginalIds | Saves the original element IDs by using the property id. This could be useful on import of a graph to preserve original ids. | false |
blueprints.orientdb.keepInMemoryReferences | Avoids keeping records in memory by using only RIDs | false |
blueprints.orientdb.useCustomClassesForEdges | Uses the Edge's label as OrientDB class. If it doesn't exist create it under the hood. | true |
blueprints.orientdb.useCustomClassesForVertex | Uses Vertex's label as OrientDB class. If it doesn't exist create it under the hood. | true |
blueprints.orientdb.useVertexFieldsForEdgeLabels | Stores the Edge's relationships in the Vertex by using the Edge's class. This allows using multiple fields and makes faster traversal by edge's label (class). | true |
blueprints.orientdb.lightweightEdges | Uses lightweight edges. This avoids creating a physical document per edge. Documents are created only when the Edges have properties. | true |
blueprints.orientdb.autoStartTx | Auto starts a transaction as soon as the graph is changed by adding/remote vertices and edges and properties. | true |
If you use GREMLIN language with OrientDB remember to initialize it with:
OGremlinHelper.global().create()
Look at these pages about GREMLIN usage:
Multi-threaded applications must use one OrientGraph instance per thread. For more information about multi-threading look at Java Multi Threading.
OrientDB is a Graph Database on steroids because it merges the graph, document, and object-oriented worlds together. Below are some of the features exclusive to OrientDB.
OrientDB supports custom types for vertices and edges in an Object Oriented manner. Even if this isn't supported directly by Blueprints there are some tricks to use them. Look at the Graph Schema page to know how to create a schema and work against types.
OrientDB added a few variants to the Blueprints methods to work with types.
By default each class has one cluster with the same name. You can add multiple clusters to the class to allow OrientDB to write vertices and edges on multiple files. Furthermore working in Distributed Mode each cluster can be configured to be managed by a different server.
Example:
// SAVE THE VERTEX INTO THE CLUSTER 'PERSON_USA' ASSIGNED TO THE NODE 'USA'
graph.addVertex("class:Person,cluster:Person_usa");
To retrieve all the vertices of Person
class use the special getVerticesOfClass(String className)
method:
for (Vertex v : graph.getVerticesOfClass("Person")) {
System.out.println(v.getProperty("name"));
}
All the vertices of class Person and all subclasses will be retrieved. This is because by default polymorphism is used. If you're interested ONLY into Person
vertices (excluding any sub-types) use the getVerticesOfClass(String className, boolean polymorphic)
method specifying false
in the second argument polymorphic
:
for (Vertex v : graph.getVerticesOfClass("Person", false)) {
System.out.println(v.getProperty("name"));
}
The same variants also apply to the getEdges()
method as:
getEdgesOfClass(String className)
andgetEdgesOfClass(String className, boolean polymorphic)
OrientDB, by default, uses a set to handle the edge collection. Sometimes it's better having an ordered list to access the edge by an offset. Example:
person.createEdgeProperty(Direction.OUT, "Photos").setOrdered(true);
Every time you access the edge collection the edges are ordered. Below is an example to print all the photos in an ordered way.
for (Edge e : loadedPerson.getEdges(Direction.OUT, "Photos")) {
System.out.println( "Photo name: " + e.getVertex(Direction.IN).getProperty("name") );
}
To access the underlying edge list you have to use the Document Database API. Here's an example to swap the 10th photo with the last.
// REPLACE EDGE Photos
List<ODocument> photos = loadedPerson.getRecord().field("out_Photos");
photos.add(photos.remove(9));
When you work with web applications, it’s very common to query elements and render them to the user to let him apply some changes. Once the user updates some fields and presses the “save” button, what happens?
Before now the developer had to track the changes in a separate structure, load the vertex/edge from the database, and apply the changes to the element.
Starting with OrientDB v1.7 we added two new methods to the Graph API on the OrientElement and OrientBaseGraph classes:
OrientElement.detach()
OrientElement.attach()
OrientBaseGraph.detach(OrientElement)
OrientBaseGraph.attach(OrientElement)
Detach methods fetch all the record content in RAM and reset the connection to the Graph instance. This allows you to modify the element off-line and to re-attach it once finished.
Once the detached element has been modified, to save it back to the database you need to call the attach()
method. It restores the connection between the Graph Element and the Graph Instance.
The first step is load a vertex and detach it.
OrientGraph g = OrientGraph("plocal:/temp/db");
try {
Iterable<OrientVertex> results = g.query().has("name", EQUALS, "fast");
for (OrientVertex v : results)
v.detach();
} finally {
g.shutdown();
}
After a while the element is updated (from GUI or by application)
v.setProperty("name", "super fast!");
On “save” re-attach the element and save it to the database.
OrientGraph g = OrientGraph("plocal:/temp/db");
try {
v.attach(g);
v.save();
} finally {
g.shutdown();
}
Does detach go recursively to detach all connected elements? No, it works only at the current element level.
Can I add an edge against detached elements? No, you can only get/set/remove a property while is detached. Any other operation that requires the database will throw an IllegalStateException.
OrientDB supports optimistic transactions, so no lock is kept when a transaction is running, but at commit time each graph element version is checked to see if there has been an update by another client. This is the reason why you should write your code to be concurrency-proof by handling the concurrent updating case:
for (int retry = 0; retry < maxRetries; ++retry) {
try {
// LOOKUP FOR THE INVOICE VERTEX
Vertex invoice = graph.getVertices("invoiceId", 2323);
// CREATE A NEW ITEM
Vertex invoiceItem = graph.addVertex("class:InvoiceItem");
invoiceItem.field("price", 1000);
// ADD IT TO THE INVOICE
invoice.addEdge(invoiceItem);
graph.commit();
break;
} catch( OTransactionException e ) {
// SOMEONE HAVE UPDATE THE INVOICE VERTEX AT THE SAME TIME, RETRY IT
}
}
Starting with v.1.5, transactions are automaticaly retried if a timeout exception occurs. This happens in case of deadlocks or network latency. By default the AutoRetry setting is 10, but you can change it or disable it by setting it to 0, by calling:
((OTransactionOptimistic) graph.getRawGraph().getTransaction()).setAutoRetries( 0 );
The OrientDB Blueprints implementation allows you to execute commands using SQL, Javascript, and all the other supported languages.
for (Vertex v : (Iterable<Vertex>) graph.command(
new OCommandSQL("select expand( out('bough') ) from Customer where name = 'Jay'")).execute()) {
System.out.println("- Bought: " + v);
}
To execute an asynchronous query:
graph.command(
new OSQLAsynchQuery<Vertex>("select from Member",
new OCommandResultListener() {
int resultCount =0;
@Override
public boolean result(Object iRecord) {
resultCount++;
Vertex doc = graph.getVertex( iRecord );
return resultCount < 100;
}
} ).execute();
Along with queries, you can execute any SQL command like CREATE VERTEX
, UPDATE
, or DELETE VERTEX
. In the example below it sets a new property called "local" to true on all the Customers that live in Rome:
int modified = graph.command(
new OCommandSQL("UPDATE Customer SET local = true WHERE 'Rome' IN out('lives').name")).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 by calling reload()
:
graph.getRawGraph().getMetadata().getSchema().reload();
For more information look at the available SQL commands.
To execute multiple SQL commands in a batch, use the OCommandScript and SQL as the language. This is recommended when creating edges on the server side, to minimize the network roundtrip:
String cmd = "begin\n";
cmd += "let a = create vertex set script = true\n";
cmd += "let b = select from v limit 1\n";
cmd += "let e = create edge from $a to $b retry 100\n";
cmd += "commit\n";
cmd += "return $e";
OIdentifiable edge = graph.command(new OCommandScript("sql", cmd)).execute();
For more information look at SQL Batch.
To execute a database function it must be written in Javascript or any other supported languages. In the example below we imagine having written the function updateAllTheCustomersInCity(cityName)
that executes the same update like above. Note the 'Rome' attribute passed in the execute()
method:
graph.command(
new OCommandFunction("updateAllTheCustomersInCity")).execute("Rome"));
To execute code on the server side you can select between the supported language (by default Javascript):
graph.command(
new OCommandScript("javascript", "for(var i=0;i<10;++i){ print('\nHello World!'); }")).execute());
This prints the line "Hello World!" ten times in the server console or in the local console if the database has been opened in "plocal" mode.
Since the TinkerPop Blueprints API is quite raw and doesn't provide ad-hoc methods for very common use cases, you might need to access the underlying ODatabaseGraphTx object to better use the graph-engine under the hood. Commons operations are:
The OrientGraph class provides the method .getRawGraph()
to return the underlying database: [Document Database].
Example:
final OrientGraph graph = new OrientGraph("plocal:C:/temp/graph/db");
try {
List<ODocument> result = graph.getRawGraph().query(
new OSQLSynchQuery("select from V where color = 'red'"));
} finally {
graph.shutdown();
}
If you want to use OrientDB security, use the constructor that retrieves the URL, user and password. To know more about OrientDB security visit Security. By default the "admin" user is used.
Look at the Performance Tuning Blueprints page.