ドキュメント+グラフのハイブリットNoSQL -> OrientDB 〜Java API編〜


f:id:Naotsugu:20150304221949p:plain

前回ドキュメント+グラフのハイブリットNoSQL -> OrientDB 〜導入編〜 - A Memorandum の続きで、今回は OrientDB を Java から操作する。

Java API

OrientDB のコンポーネントは以下のようになっている。

f:id:Naotsugu:20150304222130p:plain

データベースを操作する際には以下のAPIを利用することになる。

今回はこれらを簡単に見ていく。

JARの分類

orientdb の jar は以下のように分かれている。

JAR 内容 説明
orientdb-core-*.jar コアライブラリ 必ず必要
orientdb-client-*.jar リモートクライアント リモートサーバ経由で処理を行う場合に必要
orientdb-enterprise-*.jar クライアントとサーバで共有のネットワークプロトコルの基本ライブラリ リモートサーバ経由で処理を行う場合に必要
orientdb-server-*.jar サーバコンポーネント 組み込みサーバとして使う場合に必要
orientdb-tools-*.jar コンソールとコンソールコマンド 必要なし
orientdb-object-*.jar Object データベースのインターフェース 必要時
orientdb-graphdb-*.jar Graph データベースのインターフェース 必要時
orientdb-distributed-*.jar 追加プラグイン サーバクラスタ利用時

Java プロジェクトの準備

依存関係は orientdb-graphdb としておけばあらかた入ってくる。

build.gradle は以下。

apply plugin: 'java'
apply plugin: 'idea'

repositories {
    mavenCentral()
}

dependencies {
    compile 'com.orientechnologies:orientdb-graphdb:2.0.4'
}

OrientDB設定ファイルの準備

最低限の設定ファイルを以下のように準備する。

名前はなんでもよいが、orientdb-server-config.xml として設定ファイルを作成。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<orient-server>
    <network>
        <protocols>
            <protocol name="binary" implementation="com.orientechnologies.orient.server.network.protocol.binary.ONetworkProtocolBinary"/>
            <protocol name="http" implementation="com.orientechnologies.orient.server.network.protocol.http.ONetworkProtocolHttpDb"/>
        </protocols>
        <listeners>
            <listener ip-address="0.0.0.0" port-range="2424-2430" protocol="binary"/>
            <listener ip-address="0.0.0.0" port-range="2480-2490" protocol="http"/>
        </listeners>
    </network>
    <users>
        <user name="root" password="root" resources="*"/>
    </users>
    <properties>
        <entry name="log.console.level" value="info"/>
        <entry name="log.file.level" value="fine"/>
        <entry name="plugin.dynamic" value="false"/>
    </properties>
</orient-server>

src/main/resources/orientdb-server-config.xml に配置しておく。

サーバの起動と停止

起動と停止は以下の流れ。

    OServer server = OServerMain.create();
    server.startup(new File("/path/to/config/orientdb-server-config.xml"));
    server.activate();
    ...
    server.shutdown();

設定ファイルは InputStream として渡すこともできる。

Document API

MongoDB や CouchDB と同じようにスキーマレスのドキュメントを扱う。

ODatabaseDocumentTx が入り口となるクラスで、基本的な流れは以下となる。

ODatabaseDocumentTx db = new ODatabaseDocumentTx("memory:foo").create();
        
ODocument foo = new ODocument();
foo.setClassName("Foo");
foo.field("code", i);
foo.save();

db.close();

ODatabaseDocumentTx に渡す接続設定は以下の形式。

connect remote:localhost:{port}/{db} {user} {password}

ここではローカルのインメモリで利用するので memory:foo で良い。 ストレージ利用ならplocal:/path/to/db のようにパスを指定。

他項目の説明は以下。

項目 説明
remote リモート接続の場合はremote。 ローカル接続の場合は plocal。 インメモリの場合は memory
port バイナリサーバのリスンポート番号を指定。設定ファイル中"2424-2430"に該当
db データベース名を指定。デフォルトは db。設定ファイルで<entry name="server.database.path" value="db"/>のように指定もできる
user DB接続するユーザ名
password DB接続するユーザのパスワード

Document APIの利用例

では組み込みでOrientDBのサーバ起動して Document APIを利用してみる。

Main.java

package example;

import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import com.orientechnologies.orient.server.OServer;
import com.orientechnologies.orient.server.OServerMain;

import java.util.stream.IntStream;

public class Main {

    private OServer server;
    
    public void exec() {

        ODatabaseDocumentTx db = new ODatabaseDocumentTx("memory:foo").create();
        OGlobalConfiguration.USE_WAL.setValue(false);// Write Ahead Log

        try {

            long start = System.currentTimeMillis();

            final ODocument foo = new ODocument();
            System.out.println("start");

            IntStream.range(0, 200000).forEach( i -> {
                foo.reset();
                foo.setClassName("Foo");
                foo.field("code", i);
                foo.save();
            });

            long end = System.currentTimeMillis();
            System.out.println(end - start + "ms" + " " + ((end - start) / 200000.) + "ms/レコード");

            db.command(new OCommandSQL("TRUNCATE CLASS Foo")).execute();

        } finally {
            db.close();
        }
    }

    public void shutdown() throws Exception {
        server.shutdown();
    }

    public void start() throws Exception {
        server = OServerMain.create()
                .startup(getClass().getClassLoader().getResourceAsStream("orientdb-server-config.xml"))
                .activate();
    }
    
    public static void main(String...args) {
        try {
            Main main = new Main();
            main.start();
            main.op();
            main.shutdown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

実行

Main クラス実行すると以下のように出力される。

OrientDB auto-config DISKCACHE=4,323MB (heap=1,820MB os=8,192MB disk=139,579MB) [orientechnologies]
INFO  Loading configuration from input stream [OServerConfigurationLoaderXml]
INFO  OrientDB Server v2.0.3 is starting up... [OServer]
INFO  Databases directory: /・・・/orientdb-example/./databases [OServer]
INFO  Listening binary connections on 0.0.0.0:2424 (protocol v.28, socket=default) [OServerNetworkListener]
INFO  Listening http connections on 0.0.0.0:2480 (protocol v.10, socket=default) [OServerNetworkListener]
INFO  OrientDB Server v2.0.3 is active. [OServer]

start
5572ms 0.02786ms/レコード

INFO  OrientDB Server is shutting down... [OServer]
INFO  Shutting down listeners: [OServer]
INFO  - ONetworkProtocolBinary /0.0.0.0:2424: [OServer]
INFO  - ONetworkProtocolHttpDb /0.0.0.0:2480: [OServer]
INFO  Shutting down protocols [OServer]
INFO  Shutting down plugins: [OServerPluginManager]
INFO  Shutting down databases: [OServer]
INFO  - closing storage: foo... [Orient]
INFO  OrientDB Engine shutdown complete [Orient]
INFO  OrientDB Server shutdown complete [OServer]

1レコード当たり、0.03 ms 程度。

gradle から実行する場合には、build.gradle に以下追加し

apply plugin: 'application'
mainClassName = 'example.Main'

コマンドラインから

gradle run

すれば良い。

Graph API

Graph APIblueprintsに従うので Neo4 と同じように扱える。

OrientGraph が入り口となるクラスで、基本的な流れは以下となる。

OrientGraph graph = new OrientGraph("memory:foo");
try {
    Vertex luca = graph.addVertex(null);
    ...
    graph.commit();
} catch( Exception e ) {
    graph.rollback();
} finally {
    graph.shutdown();
}

トランザクションの開始は上記 graph.addVertex(null) のような graph への操作で暗黙的に開始する。shutdown()トランザクションは暗黙的にコミットされる。明示的に commit() することもできる。

Vertex と Edge の生成

Vertex は OrientGraph.addVertex(Object id) で生成する。

Vertex v = graph.addVertex(null);
System.out.println("Created vertex: " + v.getId());

ID は自動取得される。

Edge は OrientGraph.addEdge(Object id, Vertex outVertex, Vertex inVertex, String label) で生成する。

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());

Vertex と Edge 取得

それぞれ getVertices() getEdges() を使う。

for (Vertex v : graph.getVertices()) {
    System.out.println(v.getProperty("name"));
}
for (Edge e : graph.getEdges()) {
    System.out.println(e.getProperty("age"));
}

Vertex と Edge の削除

removeXXX() で削除する。

graph.removeVertex(luca);

graph.removeEdge(lucaKnowsMarko);

属性の操作

属性は以下のように操作する。

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");

一括で設定する場合は以下のようにもできる。

vertex.setProperties( "name", "Jill", "age", 33, "city", "Rome", "born", "Victoria, TX" );

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);

Object API

OObjectDatabaseTx が入り口となるクラスで、基本的な流れは以下となる。

OObjectDatabaseTx db = new OObjectDatabaseTx("memory:foo").create();
db.getEntityManager().registerEntityClasses("foo.domain");

try {
  ...
} finally {
  db.close();
}

Objectデータベースの生成

インメモリの場合は以下のようになる。

OObjectDatabase db1 = new OObjectDatabaseTx("memory:petshop").create();

リモートデータベースへの接続は以下のようになる。

OObjectDatabase db2 = new OObjectDatabaseTx("remote:localhost/petshop").open("admin", "admin");

コネクションプーリングを利用する場合は以下のように OObjectDatabaseTx を取得する。

OObjectDatabaseTx db= OObjectDatabasePool.global().acquire("remote:localhost/petshop", "admin", "admin");

スキーマレスのObjectDB利用

以下のPOJOがある場合、

public class Person {
    private String name;
    private String surname;

    public Person(){
    }

    public Person(String name){
         this.name = name;
    }

    public Person(String name, String surname){
        this.name = name;
        this.surname = surname;
    }
    // getters and setters
}

以下で永続化できる。

OObjectDatabase db = new OObjectDatabaseTx("memory:petshop").create();
db.getEntityManager().registerEntityClass(Person.class);

Person p = db.newInstance(Person.class);
p.setName("Luca");
p.setSurname("Garulli");
p.setCity(new City("Rome", "Italy"));
db.save(p);

Person person = db.newInstance(Person.class, "Antoni");
animal.setSurname("Gaudi");
db.save(person);

Person person = db.newInstance(Person.class, "Antoni", "Gaudi");
db.save(person);

db.close();

newInstance() の第2引数以降にコンストラクタ引数が渡せる。

以下のフィールドは永続化対象外となる。

  • transient の付いたフィールド
  • static 指定されたフィールド
  • getter/setter が無いフィールド
  • 無名クラス型のセット

newInstance() からではなく、通常のオブジェクトの永続化もできる。

Person p = new Person();
p.setName("Gaudi");
p.setSurname("Madrid");
p = db.save(p);

この場合 save() の戻り値のオブジェクトが データベースとのプロキシとなる。

ObjectAPI によるレコードの照会

cluster から全てのレコードを取得するには以下。

for (Object o : database.browseCluster("CityCars"))
  System.out.println( ((Car) o).getModel() );

class から全てのレコードを取得するには以下。

for (Animal animal : database.browseClass(Animal.class)) {
  System.out.println( animal.getName() );

レコード件数の取得は以下。

long cityCars = database.countCluster("CityCar");
long cars = database.countClass("Car");

ObjectAPI による更新と削除

更新は保存時と同じように save を呼べば良い。更新されたフィールドが変更される。

animal.setLocation("Nairobi");
db.save(animal);

削除は以下。

db.delete(animal);

削除時のカスケードは JPAorphanRemoval = true と同じ。

public class JavaCascadeDeleteTestClass {

  @OneToOne(orphanRemoval = true)
  private JavaSimpleTestClass simpleClass;

  @ManyToMany(cascade = { CascadeType.REMOVE })
  private Map<String, Child> children = new HashMap<String, Child>();

  @OneToMany(orphanRemoval = true)
  private List<Child> list = new ArrayList<Child>();

  @OneToMany(orphanRemoval = true)
  private Set<Child> set = new HashSet<Child>();

  ...

  // getter/setter
  }

以下のように削除すれば良い。

database.delete(testClass);

for (JavaCascadeDeleteTestClass testClass : 
        database.browseClass(JavaCascadeDeleteTestClass.class))
    database.delete(testClass);

クエリとコマンド

以下のように OSQLSynchQuery でクエリ発行できる。

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

コマンドは OCommandSQL で行う。

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

Getting Started with OrientDB

Getting Started with OrientDB

NoSQLプログラミング実践活用技法 (Programmer’s SELECTION)

NoSQLプログラミング実践活用技法 (Programmer’s SELECTION)