2011年4月12日火曜日

オープンソース NoSQL データベース OrientDB を使ってみる #3

前回の投稿から半年以上過ぎましたが、OrientDB の紹介3回目です
前の2回は以下の通りです

オープンソース NoSQL データベース OrientDB を使ってみる #1
オープンソース NoSQL データベース OrientDB を使ってみる #2

データ間の関連(リレーション)について紹介します
データ間の関連とは2つの異なるレコードを関連付けることで、データの透過的な参照や複雑なデータ構造の表現が可能となります

OrientDB では参照型( Referenced relationships )と組込型( Embedded relationships )という2種類の関連付けをサポートしています
参照型は、フィールドに対象レコードのレコードID(全レコードにてユニークとなるID)を持つことでデータ間の関連付けを行います
そのため、関連付ける各レコードは独立したレコードとして各々参照できる必要があります
対して組込型は、フィールドに直接レコードをそのものを設定するもので、組み込まれたレコードを独立して参照することはできません

また、参照、組込型として関連付けられるデータは 1:1 だけでなく、各々 List 、 Set 、 Map という 1:N 、または N:M の関係を作ることができます
(今回はこの部分の説明の詳細は省略させて頂きます)

それでは実際にデータの登録を行い、どのような構造のレコードが生成されるかを説明したいと思います
今回使用した環境は下記の通りです

・Mac OS X 10.6.7
・Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326)
・OrientDB 0.9.25
・NetBeans 7.0 RC2

以降は上記環境にて、サーバの起動、専用クライアント(コンソール)の接続まで完了している前提で進めます
サーバやクライアントの起動方法、クライアントを使用したレコード操作などについては、前々回のエントリを参照ください
また、文中で使われるレコードIDは環境により異なりますので、お使いの環境にあわせて読み替えてください

まず、テストデータを登録するため、下記クエリを実行して Person クラスと Group クラスを作成します
( ">" はプロンプトとなりますので、コピーして実行する際にはご注意ください)
> create class Person
> create class Group
次に Group クラス のデータを登録します
> insert into Group (name) values ('Rebel Alliance')
> insert into Group (name) values ('Galactic Empire')
下記コマンドで Group クラスに2レコード登録されたことが確認できます
> browse class Group
---+--------+--------------------
  #| REC ID |NAME                
---+--------+--------------------
  0|    25:0|Rebel Alliance      
  1|    25:1|Galactic Empire     
---+--------+--------------------
"REC ID" は先の説明のレコードIDを指し、 "Rebel Alliance" は "25:0" 、 "Galactic Empire" は "25:1" のレコードIDを持つこととなります

次に参照型の関連フィールドを持つレコードを作成します
関連付けるレコードは先ほど登録した Group クラスのレコードです
下記クエリを実行して、 レコードを登録します
> insert into Person (name, surname, group) values ('Luke', 'Skywalker', 25:0)
> insert into Person (name, surname, group) values ('Han', 'Solo', 25:0)
> insert into Person (name, group) values ('Chewbacca', 25:0)
> insert into Person (name, surname, group) values ('Leia', 'Organa', 25:0)

> insert into Person (name, group) values ('Darth Sidious', 25:1)
> insert into Person (name, group) values ('Darth Vader', 25:1)
レコードに関連付けを設定する場合は、フィールドに対象のレコードIDを直接記述します
(そのため、事前に関連づけるレコードIDを知っておく必要があります)
上記の場合、 "group" というフィールドにレコードIDを設定しており、これで Person クラスと Group クラスの各レコードが関連付けられることとなります

下記コマンドで Person クラスに6レコード登録されたことが確認できます
> browse class Person
---+--------+--------------------+--------------------+--------------------
  #| REC ID |NAME                |SURNAME             |GROUP               
---+--------+--------------------+--------------------+--------------------
  0|    24:0|Luke                |Skywalker           |25:0                
  1|    24:1|Han                 |Solo                |25:0                
  2|    24:2|Chewbacca           |null                |25:0                
  3|    24:3|Leia                |Organa              |25:0                
  4|    24:4|Darth Sidious       |null                |25:1                
  5|    24:5|Darth Vader         |null                |25:1                
---+--------+--------------------+--------------------+--------------------
関連付けられたレコードに対しては、「フィールド名.対象レコードのフィールド名」の形式で指定することで値を参照することができます
以下は select 文にて group フィールドに関連付けられた、 Group クラスの name フィールドを参照しています
(where 句にある "@rid" はレコードIDを意味するレコード属性になります)
> select name, surname, group.name from Person where @rid = 24:0
---+--------+--------------------+--------------------+--------------------
  #| REC ID |NAME                |SURNAME             |GROUP               
---+--------+--------------------+--------------------+--------------------
  0|   -1:-1|Luke                |Skywalker           |Rebel Alliance      
---+--------+--------------------+--------------------+--------------------
また、他のフィールドと同様に where 句の検索条件としても使用できます
> select from Person where group.name = 'Rebel Alliance'
---+--------+--------------------+--------------------+--------------------
  #| REC ID |NAME                |SURNAME             |GROUP               
---+--------+--------------------+--------------------+--------------------
  0|    24:0|Luke                |Skywalker           |25:0                
  1|    24:1|Han                 |Solo                |25:0                
  2|    24:2|Chewbacca           |null                |25:0                
  3|    24:3|Leia                |Organa              |25:0                
---+--------+--------------------+--------------------+--------------------
Java API を利用する場合、参照型レコードの登録は以下のようになります
(import 文、パッケージ名は省略しますのでご注意下さい)
public class Main {

    public static void main(String[] args) {
        // admin ユーザでサーバーの demo データベースに接続します
        ODatabaseDocumentTx database = new ODatabaseDocumentTx("remote:localhost/demo").open("admin", "admin");

        // 新規レコード(Group)の作成
        ODocument jediOrder = new ODocument(database, "Group");
        jediOrder.field("name", "Jedi Order");
        jediOrder.save();

        // ユニークIDを取得
        ORID jediOrderORID = jediOrder.getIdentity();
        
        // 新規レコード(Rank)の作成
        ODocument master = new ODocument(database, "Rank");
        master.field("name", "Jedi Master");
        master.save();

        // ユニークIDを取得
        ORID masterOrderORID = master.getIdentity();

        ODocument knight = new ODocument(database, "Rank");
        knight.field("name", "Jedi Knight");
        knight.save();

        // ユニークIDを取得
        ORID knightOrderORID = knight.getIdentity();

        ODocument padawan = new ODocument(database, "Rank");
        padawan.field("name", "Padawan");
        padawan.save();

        // ユニークIDを取得
        ORID padawanOrderORID = padawan.getIdentity();

        // 新規レコード(Person)の作成
        ODocument yoda = new ODocument(database, "Person");
        yoda.field("name", "Yoda");
        yoda.field("group", jediOrderORID);
        yoda.field("rank", masterOrderORID);
        yoda.save();
        
        ODocument obiwan = new ODocument(database, "Person");
        obiwan.field("name", "Obi-Wan");
        obiwan.field("surname", "Kenobi");
        obiwan.field("group", jediOrderORID);
        obiwan.field("rank", knightOrderORID);
        obiwan.save();

        ODocument ahsoka = new ODocument(database, "Person");
        ahsoka.field("name", "Ahsoka");
        ahsoka.field("surname", "Tano");
        ahsoka.field("group", jediOrderORID);
        ahsoka.field("rank", padawanOrderORID);
        ahsoka.save();
        
        // 接続の解除
        database.close();
    }
}
上記では Group クラスに1レコード登録、Rank クラスの新規作成と3レコード登録、 Person クラスに3レコード登録しています
参照型の関連付けを行う方法はクエリを使用したケースと同様にフィールドにレコードIDを設定します
Java API にてレコードID( Java API では ORID クラスにて表現されています)を取得するには、ODocument クラスの getIdentity() メソッドを使用します
なお、新規レコードの場合、事前に save() メソッドを呼び出してデータベースに登録が完了していないとレコードIDは取得できませんので、ご注意ください

レコードを取得する場合は下記のようになります
public class Main {

    public static void main(String[] args) {
        // admin ユーザでサーバーの demo データベースに接続します
        ODatabaseDocumentTx database = new ODatabaseDocumentTx("remote:localhost/demo").open("admin", "admin");

        System.out.println("-----レコードIDを指定してレコードを取得-----");
        
        // RID=24:6(Yoda) を取得
        ODocument yoda = new ODocument(database, new ORecordId(24, 6));
        
        // group フィールドを取得
        ODocument yodaGroup = yoda.field("group");
        Object yodaGroupName = yodaGroup == null ? "" : yodaGroup.field("name");
        // rank 1フィールドを取得
        ODocument yodaRank = yoda.field("rank");
        Object yodaRankName = yodaRank == null ? "" : yodaRank.field("name");
        
        // 取得レコードの出力
        System.out.println("name   : " + yoda.field("name"));
        System.out.println("surname: " + yoda.field("surname"));
        System.out.println("group  : " + yodaGroupName);
        System.out.println("rank   : " + yodaRankName);
        System.out.println();

        System.out.println("-----クエリを実行してレコードを取得-----");
        
        // SELECT 文を作成する
        String selectQuery = "select from Person where group.name = 'Jedi Order' ";

        // クエリを実行する
        List<ODocument> people = database.query(new OSQLSynchQuery<ODocument>(selectQuery));

        for (ODocument person : people) {
            // group フィールドを取得
            ODocument group = person.field("group");
            Object groupName = group == null ? "" : group.field("name");
            // rank 1フィールドを取得
            ODocument rank = person.field("rank");
            Object rankName = rank == null ? "" : rank.field("name");

            // 取得レコードの出力
            System.out.println("name   : " + person.field("name"));
            System.out.println("surname: " + person.field("surname"));
            System.out.println("group  : " + groupName);
            System.out.println("rank   : " + rankName);
            System.out.println();
        }

        // 接続の解除
        database.close();
    }
}
上記を実行すると、以下のように値が取得できます
-----レコードIDを指定してレコードを取得-----
name   : Yoda
surname: null
group  : Jedi Order
rank   : Jedi Master

-----クエリを実行してレコードを取得-----
name   : Yoda
surname: null
group  : Jedi Order
rank   : Jedi Master

name   : Obi-Wan
surname: Kenobi
group  : Jedi Order
rank   : Jedi Knight

name   : Ahsoka
surname: Tano
group  : Jedi Order
rank   : Padawan

次に組込型の関連付けですが、現在(2011年4月12日時点)のバージョンではクエリによる組込型のレコードの登録、更新(参照は可能です)は対応しておらず、 Java API でのみとなります

Java API を利用する場合の登録は以下のようになります
public class Main {

    public static void main(String[] args) {
        // admin ユーザでサーバーの demo データベースに接続します
        ODatabaseDocumentTx database = new ODatabaseDocumentTx("remote:localhost/demo").open("admin", "admin");
        
        // 新規レコードを作成
        ODocument lightsaber = new ODocument(database, "Weapon");
        lightsaber.field("name", "Lightsaber");
        lightsaber.field("type", "sword"); 
        
        // RID=24:0(Luke) を取得
        ODocument luke = new ODocument(database, new ORecordId(24, 0));
        luke.field("weapon", lightsaber, OType.EMBEDDED);
        luke.save();
        
        database.close();
    }
}
上記では Weapon クラスのレコードを1件作成し、 Person クラスのレコードの "weapon" フィールドに設定しています
参照型と大きく異なる点は作成した Weapon クラスの save()メソッドを呼び出していない点、取得した Person クラスの "weapon" フィールドに設定する際、第3引数に "OType.EMBEDDED" を設定している点です

レコードの参照方法に関しては、組込型も参照型も差異はありません
>  select from Person where @rid = 24:0                          

---+--------+--------------------+--------------------+--------------------+--------------------
  #| REC ID |NAME                |WEAPON              |SURNAME             |GROUP               
---+--------+--------------------+--------------------+--------------------+--------------------
  0|    24:0|Luke                |-1:-1               |Skywalker           |25:0                
---+--------+--------------------+--------------------+--------------------+--------------------
参照型のフィールド "group" にはレコードIDの値("25:0")が設定されているのに対し、組込型は直接レコードが設定されているため、レコードIDは設定されていません
組込まれたレコードのフィールドも同様の形式で参照可能です
> select name, surname, weapon.name, weapon.type from Person where @rid = 24:0

---+--------+--------------------+--------------------+--------------------+--------------------
  #| REC ID |NAME                |WEAPON              |SURNAME             |WEAPON2             
---+--------+--------------------+--------------------+--------------------+--------------------
  0|   -1:-1|Luke                |Lightsaber          |Skywalker           |sword               
---+--------+--------------------+--------------------+--------------------+--------------------
以上が OrientDB の関連付けの機能の紹介と利用方法になります

次回は、 OrientDB のグラフデータベースとしての機能を紹介したいと思います
(次はあまり期間をあけないようしたいと思います…)

1 件のコメント:

匿名 さんのコメント...

Event though I can understand only the code snippets and the console outputs
(I don't speak Japanese)
I consider this document as excellent orientdb introduction. Thank you.