akishin999の日記

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

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

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

の続きです。

尚、この記事の動作確認環境は以下の通りです。

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

今回は org.apache.cassandra.thrift.Cassandra.Client#multiget()、multiget_slice() メソッドを使用し、複数の行(key) に関連付けられたカラムの値を取得してみます。


前回までに使用してみた Cassandra の API では、指定したカラムの値のみ取得する get() と、指定した key に関連付けされた全てのカラムを取得可能な get_slice() がありました。
今回使ってみる multiget() と multiget_slice() は、それぞれの複数の key 対応版、といったイメージのものになります。

メソッド名 説明
get 指定した単一の key の指定した Column の値を取得。
multiget 指定した複数の key の指定した Column の値を取得。
get_slice 指定した単一の key の複数の Column の値を取得。
multiget_slice 指定した複数の key の複数の Column の値を取得。

準備

まずは準備として、cassandra-cli より適当な値を追加しておきます。
ここでは、README.txt のサンプルとデータ構造を同じくした、以下のような値を追加してみました。

cassandra> set Keyspace1.Standard1['foo']['first'] = 'bar'
Value inserted.
cassandra> set Keyspace1.Standard1['foo']['last'] = 'baz'
Value inserted.
cassandra> set Keyspace1.Standard1['foo']['age'] = '99'
Value inserted.


※ 注意
どうも現在のバージョン(0.6.1)では、コマンドプロンプトから cassandra-cli で追加したデータのタイムスタンプ値がおかしくなってしまうようです。
org.apache.cassandra.thrift.Cassandra.Client#insert() によるデータ登録では正常な値が入っているようなので、気になる場合は Java から値を入れた方が良いかもしれません。
今回のサンプルコードでは関係ありませんが、データの更新などが正常に行われなくなってしまう可能性があります。

multiget()

それでは、まずは multiget() を使用した以下のようなサンプルを動かしてみます。

package example.cassandra;

import java.util.ArrayList;
import java.util.Date;
import java.util.Map;

import org.apache.cassandra.thrift.Cassandra;
import org.apache.cassandra.thrift.Column;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnPath;
import org.apache.cassandra.thrift.ConsistencyLevel;
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 CassandraMultigetExample {

    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";
            String columnFamily = "Standard1";
            
            // 取得したいカラム名
            String columnName = "first";
            
            // ColumnPath の作成
            ColumnPath columnPath = new ColumnPath(columnFamily);
            columnPath.setColumn(columnName.getBytes("UTF8"));
            
            Map<String, ColumnOrSuperColumn> results = client.multiget(keySpace, 
                                                                       new ArrayList() {{add("jsmith"); add("foo");}}, 
                                                                       columnPath, 
                                                                       ConsistencyLevel.ONE);
        
            for (Map.Entry<String, ColumnOrSuperColumn> entry : results.entrySet()) {
                String key = entry.getKey();
                ColumnOrSuperColumn result = entry.getValue();
                Column col = result.column;
                System.out.printf("key:[%s] カラム名:[%s] 値:[%s] タイムスタンプ:[%s]\n",
                        key,
                        new String(col.name, "UTF8"),
                        new String(col.value, "UTF8"),
                        new Date(col.timestamp));

                System.out.println("--------------------");
            }
            port.close();
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}


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

key:[foo] カラム名:[first] 値:[bar] タイムスタンプ:[Fri May 23 17:50:30 JST 42302]
--------------------
key:[jsmith] カラム名:[first] 値:[John] タイムスタンプ:[Sat May 01 00:43:44 JST 2010]
--------------------


指定した key である「jsmith」、「foo」の「first」カラムの値が取得できている事が分かります。

multiget_slice()


次に、multiget_slice() の動作を確認してみます。
以下のようなコードを実行しました。

package example.cassandra;

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

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 CassandraMultigetSliceExample {

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

            // 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);
            
            Map<String, List<ColumnOrSuperColumn>> results = client.multiget_slice(keySpace, 
                                                                                   new ArrayList() {{add("jsmith"); add("foo");}}, 
                                                                                   columnParent, 
                                                                                   slicePredicate, 
                                                                                   ConsistencyLevel.ONE);
        
            for (Map.Entry<String, List<ColumnOrSuperColumn>> entry : results.entrySet()) {
                String key = entry.getKey();
                List<ColumnOrSuperColumn> list = entry.getValue();
                for (int i = 0; i < list.size(); i++) {
                    ColumnOrSuperColumn result = list.get(i); 
                    Column col = result.column;
                    System.out.printf("key:[%s] [%d] カラム名:[%s] 値:[%s] タイムスタンプ:[%s]\n",
                            key,
                            i + 1,
                            new String(col.name, "UTF8"),
                            new String(col.value, "UTF8"),
                            new Date(col.timestamp));
                }
                System.out.println("--------------------");
            }
            port.close();
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}


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

key:[foo] [1] カラム名:[age] 値:[99] タイムスタンプ:[Sat May 24 09:41:35 JST 42302]
key:[foo] [2] カラム名:[first] 値:[bar] タイムスタンプ:[Fri May 23 17:50:30 JST 42302]
key:[foo] [3] カラム名:[last] 値:[baz] タイムスタンプ:[Fri May 23 23:42:37 JST 42302]
--------------------
key:[jsmith] [1] カラム名:[age] 値:[42] タイムスタンプ:[Sun May 02 02:37:33 JST 2010]
key:[jsmith] [2] カラム名:[first] 値:[John] タイムスタンプ:[Sat May 01 00:43:44 JST 2010]
key:[jsmith] [3] カラム名:[last] 値:[Smith] タイムスタンプ:[Sun May 02 02:37:05 JST 2010]
--------------------


指定した key 以下の全ての Column の値が取得できている事がわかります。
multiget_slice() では、get_slice() 同様、SliceRange や SlicePredicate#setColumn_names() により特定の Column のみを取得してくる事も可能です。


ここまでで CRUD 操作が一通りできるようになりました。
次回は conf/storage-conf.xml に独自のデータ構造を定義してみたいと思います。