akishin999の日記

調べた事などを書いて行きます。

Java から Cassandra を使ってみる その3

Windows で Cassandra を動かしてみる
Java から Cassandra を使ってみる その1
Java から Cassandra を使ってみる その2

の続きです。

複数の Column の値を取得してみる

今回は org.apache.cassandra.thrift.Cassandra.Client#get_slice() メソッドを使用し、複数の Column の値を取得してみます。


Cassandra では通常の KVS と異なり、一つの key の下に複数の Column を設定する事ができます。
一つの key に関連付けされた複数の Column の値を一括で取得したい場合には、以下のように get_slice() が利用できます。

package example.cassandra;

import java.util.Date;
import java.util.List;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class CassandraSliceExample {

    public static void main(String[] args) {
        try {

            // Thrift を使用して Cassandra に接続
            TTransport port = new TSocket("localhost", 9160);
            TProtocol protocol = new TBinaryProtocol(port);
            Cassandra.Client client = new Cassandra.Client(protocol);
            port.open();

            // デフォルトで用意されている Keyspace を使用する
            String keySpace = "Keyspace1";

            // README.txt で使用されているサンプルの key を使用する
            String key = "jsmith";

            // ColumnParent には ColumnFamily 名または ColumnFamily/SuperColumn 名を指定
            ColumnParent columnParent = new ColumnParent("Standard1");

            SliceRange sliceRange = new SliceRange();
            // 取得カラムの範囲を指定。全部取得する場合は空の byte 配列を指定
            sliceRange.setStart(new byte[0]);
            sliceRange.setFinish(new byte[0]);

            SlicePredicate slicePredicate = new SlicePredicate();
            slicePredicate.setSlice_range(sliceRange);
            
            // get_slice を呼ぶ
            List<ColumnOrSuperColumn> results = client.get_slice(keySpace, 
                                                                 key, 
                                                                 columnParent, 
                                                                 slicePredicate, 
                                                                 ConsistencyLevel.ONE);
            
            for (int i = 0; i < results.size(); i++) {
                ColumnOrSuperColumn result = results.get(i); 
                Column col = result.column;
                System.out.printf("[%d] カラム名:[%s] 値:[%s] タイムスタンプ:[%s]\n", 
                        i + 1,
                        new String(col.name, "UTF8"),
                        new String(col.value, "UTF8"),
                        new Date(col.timestamp));
            }

            port.close();
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}


上記を実行すると、以下のように値が出力されました。

[1] カラム名:[age] 値:[42] タイムスタンプ:[Sun May 02 02:37:33 JST 2010]
[2] カラム名:[first] 値:[John] タイムスタンプ:[Sat May 01 00:43:44 JST 2010]
[3] カラム名:[last] 値:[Smith] タイムスタンプ:[Sun May 02 02:37:05 JST 2010]


指定した key である「jsmith」に関連付けられた全ての Column の値が取得できている事が分かります。

get_slice() では、引数の SliceRange クラスや SlicePredicate クラスの設定により、取得する Column の範囲を指定する事が可能です。
上記サンプルコードのうち、SliceRange#setStart() を呼び出している辺りを以下のように変更してみます。

// "age" から "first" までを取得
sliceRange.setStart("age".getBytes("UTF8"));
sliceRange.setFinish("first".getBytes("UTF8"));


実行すると、今度は以下のように "age" カラムと "first" カラムの値のみ取得され、"last" カラムの値は取得されないようになりました。

[1] カラム名:[age] 値:[42] タイムスタンプ:[Sun May 02 02:37:33 JST 2010]
[2] カラム名:[first] 値:[John] タイムスタンプ:[Sat May 01 00:43:44 JST 2010]


次に、先ほど変更した setStart()、setFinish() 呼び出しの下の辺りに以下のコードを追加してみます。

// "age" から "first" までを取得
sliceRange.setStart("age".getBytes("UTF8"));
sliceRange.setFinish("first".getBytes("UTF8"));

// count を設定すると指定した数のカラムのみ取得する // <= 追加
sliceRange.setCount(1);                             // <= 追加


今度は、以下のように "age" のみが取得できました。

[1] カラム名:[age] 値:[42] タイムスタンプ:[Sun May 02 02:37:33 JST 2010]


setCount() を指定すると、setStart()、setFinish で指定した範囲のうち、指定した数の Column の値のみ取得するようになるようです。


最後に、SliceRange クラスを使わず、指定したカラム名の値のみ取得してみます。
その為には、SlicePredicate#setColumn_names() を使用します。

package example.cassandra;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;

public class CassandraSliceExample {

    public static void main(String[] args) {
        try {

            // Thrift を使用して Cassandra に接続
            TTransport port = new TSocket("localhost", 9160);
            TProtocol protocol = new TBinaryProtocol(port);
            Cassandra.Client client = new Cassandra.Client(protocol);
            port.open();

            // デフォルトで用意されている Keyspace を使用する
            String keySpace = "Keyspace1";

            // README.txt で使用されているサンプルの key を使用する
            String key = "jsmith";

            // ColumnParent には ColumnFamily 名または ColumnFamily/SuperColumn 名を指定
            ColumnParent columnParent = new ColumnParent("Standard1");

            SlicePredicate slicePredicate = new SlicePredicate();

            // 指定したカラムのみ取得する場合は setColumn_names にカラム名の byte 配列の List を設定。
            // 但し、カラム名の List と SliceRange は両方が設定されているとエラーになる。
            slicePredicate.setColumn_names(new ArrayList<byte[]>() {{ add("first".getBytes("UTF8")); }});
            
            
            // get_slice を呼ぶ
            List<ColumnOrSuperColumn> results = client.get_slice(keySpace, 
                                                                 key, 
                                                                 columnParent, 
                                                                 slicePredicate, 
                                                                 ConsistencyLevel.ONE);
            
            for (int i = 0; i < results.size(); i++) {
                ColumnOrSuperColumn result = results.get(i); 
                Column col = result.column;
                System.out.printf("[%d] カラム名:[%s] 値:[%s] タイムスタンプ:[%s]\n", 
                        i + 1,
                        new String(col.name, "UTF8"),
                        new String(col.value, "UTF8"),
                        new Date(col.timestamp));
            }

            port.close();
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}


実行結果は以下のようになります。

[1] カラム名:[first] 値:[John] タイムスタンプ:[Sat May 01 00:43:44 JST 2010]


"first" カラムの値のみが取得できた事がわかります。
コード中のコメントにも書きましたが、setColumn_names() を使用する際は、SliceRange は指定できない事に注意して下さい。
SlicePredicate に両方を設定してしまっていると、 get_slice() 呼び出し時に以下のようなエラーが発生してしまいます。

InvalidRequestException(why:predicate column_names and slice_range may not both be present)
    at org.apache.cassandra.thrift.Cassandra$get_slice_result.read(Cassandra.java:4873)
    at org.apache.cassandra.thrift.Cassandra$Client.recv_get_slice(Cassandra.java:393)
    at org.apache.cassandra.thrift.Cassandra$Client.get_slice(Cassandra.java:367)
    at example.cassandra.CassandraSliceExample.main(CassandraSliceExample.java:59)


次回は multiget_slice() について調べてみたいと思います。

参考

ThriftExamples - Cassandra Wiki
http://wiki.apache.org/cassandra/ThriftExamples#Java