Let's take a look at a simple Hello World application code for Neo4j and understand what goes on under the hood when you try to perform some simple operations on Neo4j through the Java API. Here is the code for a sample app:
import org.neo4j.graphdb.*; import org.neo4j.kernel.EmbeddedGraphDatabase; /** * Example class that constructs a simple graph with * message attributes and then prints them. */ public class NeoOneMinute { public enum MyRelationshipTypes implements RelationshipType { KNOWS } public static void main(String[] args) { GraphDatabaseService graphDb = new EmbeddedGraphDatabase("var/base"); Transaction tx = graphDb.beginTx(); try { Node node1 = graphDb.createNode(); Node node2 = graphDb.createNode(); Relationship someRel = node1.createRelationshipTo(node2, MyRelationshipTypes.KNOWS); node1.setProperty("message", "Hello, "); node2.setProperty("message", "world!"); someRel.setProperty("message", "brave Neo4j "); tx.success(); System.out.print(node1.getProperty("message")); System.out.print(someRel.getProperty("message")); System.out.print(node2.getProperty("message")); } finally { tx.finish(); graphDb.shutdown(); } } }
The initiating point in the Neo4j program is the database service object defined from org.neo4j.graphdb.GraphDatabaseService
and referred to as GraphDatabaseService
. All the core functionalities for nodes, IDs, transactions and so on are exposed through this interface. This object is a wrapper over a few other classes and interfaces, GraphDbInstance
being one of them, which starts with the config map that the user provides (empty in the preceding case). The org.neo4j.kernel.AutoConfigurator
object then receives this, and the memory to be used for memory-mapped buffers is computed from JVM statistics. You can change this behavior by setting a false
value for the use_memory_mapped_buffers
flag, causing the config map to be passed to an object of the org.neo4j.kernel.Config
class.
GraphDbModule
, the TxModule
for transactions, the manager objects for cache, persistence, and locking (CacheManager
, PersistenceModule
, LockManager
, respectively) are then created and sustained until the application's execution ends. If no errors are thrown, then the embedded database has been initiated.
NodeManager
(defined in the org.neo4j.kernel.impl.core.NodeManager
class) is one of the most crucial and large classes in the Neo4j source that provides an abstraction for the caching and the underlying persistent storage-exposing methods to operate on nodes and relationships. The configuration that is passed is then parsed and the caching for the nodes and relationships is initialized by figuring out their sizes. AdaptiveCacheManager
abstracts the sizing issue of the caches. NodeManager
handles the locking operations with the help of lock stripping. In order to maintain a balance between the level of concurrency and the performance of memory, an array stores ReentrantLocks
and, depending upon the integral ID values of the node or the relationship, locking is performed by hashing over it. When you invoke the getNodeById()
or getRelationshipById()
method, it roughly follows these steps:
NotFoundException
is thrown.This is the gist of the main work of NodeManager
. However it is intended to be used to perform many other operations that are beyond the scope of this book, but you can check them out in the source.
The
Node
and Relationship
interfaces defined in org.neo4j.graphdb
provide implementations for NodeProxy
and RelationshipProxy
and contain the unique IDs for the nodes or relationships that are represented by them. They are used in the propagation of the method calls of the interface to the NodeManager
object that is being used. This integral ID is what is returned from the method calls that are executed every time a node, relationship, or property is added to EmbeddedGraphDatabase
.
Nodes and relationships also use the NodeImpl
and RelationshipImpl
classes defined in the org.neo4j.kernel.impl.core
package; this package extends the core Primitive
class to provide abstract implementations of Properties
and help to delegate the loading and storing of property values to the object of NodeManager
. It holds the value of the ID and is extended by the implementation classes (NodeImpl
and RelationshipImpl
), each of which implements the abstract methods accordingly along with operation-specific functions (for example, the NodeImpl
class has a getRelationships()
method while the RelationshipImpl
class has getStartNode()
).
These are some of the types of implementations that NodeManager
handles internally.