S-JIS[2010-02-20/2012-04-28] 変更履歴

HBaseにJavaからアクセス

HBaseにJavaからアクセスする例。


HTable

テーブルのデータにアクセスする為には、HTableのインスタンスを取得し、そのメソッドを呼び出す。[2010-07-11]

また、HTableは(書き込みに関して)スレッドセーフではないので、マルチスレッドで使用する場合は別々のインスタンスを使う必要がある。
この目的の為にHTablePoolというクラスが提供されている。

HBase0.89では、直接HTableクラスを扱うのではなく、HTableInterfaceを介して操作するようになっている。


データアクセスのコーディング

HBaseのデータを操作する為のメソッド。

  コーディング例 備考
データの取得
get
Result r = table.get(new Get(Bytes.toBytes("KEY00001")));
byte[] v = r.getValue(Bytes.toBytes("ColumnFamilyA",
                       HConstants.EMPTY_BYTE_ARRAY));
特定のROWの列ファミリー名のみ指定する例。
qualifierの無いセルのデータが返る。
値が無い場合はnullが返る。
Result r = table.get(new Get(Bytes.toBytes("KEY00001")));
byte[] v = r.getValue(Bytes.toBytes("ColumnFamilyA",
                       Bytes.toBytes("Column1")));
familyとqualifierを指定する例。
指定されたセルのデータが返る。
値が無い場合はnullが返る。
byte[] v = r.getValue(Bytes.toBytes("ColumnFamilyA:Column1")));
HBase0.89でgetValue(byte[] column)は廃止された。[2010-07-11]
0.20でも、getValue(byte[])よりgetValue(byte[], byte[])の方がよい
Mapの取得
Result r = table.get(new Get(Bytes.toBytes("KEY00001")));
NavigableMap<byte[], byte[]> map = r.getFamilyMap(Bytes.toBytes("ColumnFamilyA"));
for (Entry<byte[], byte[]> entry : map.entrySet()) {
	String key = Bytes.toString(entry.getKey());
	String val = Bytes.toString(entry.getValue());
〜
}
列ファミリー内の全データをMapの形で取得する例。
無い場合は空のマップが返る。
NavigableMapは、JDK1.6の標準クラス(インターフェース)。
Mapの実体
Result r = table.get(new Get(Bytes.toBytes("KEY00001")));
NavigableMap<byte[], NavigableMap<byte[], byte[]>> map = r.getNoVersionMap();
for (Entry<byte[], NavigableMap<byte[], byte[]>> entry : map.entrySet()) {
	String key = Bytes.toString(entry.getKey());
	NavigableMap<byte[], byte[]> m = entry.getValue();
〜
}
ROW内の全データをMapの形で取得する例。
このMapの値はgetFamilyMap()で返るマップと同じ。
Mapの実体
ROW内の
全データ取得
Result r = table.get(new Get(Bytes.toBytes("KEY00001")));
KeyValue[] sorted = r.sorted();
for (KeyValue kv : sorted) {
	String k = Bytes.toString(kv.getRow());
	String f = Bytes.toString(kv.getFamily());
	String c = Bytes.toString(kv.getQualifier());
	String v = Bytes.toString(kv.getValue());
	System.out.println(k + "/" + f + ":" + c + "=" + v);
}
ROW内の全データをKeyValueの配列の形で取得する例。
最も高速な取得方法
Result r = table.get(new Get(Bytes.toBytes("KEY00001")));
List<KeyValue> list = r.list();
for (KeyValue kv : list) {
	String k = Bytes.toString(kv.getRow());
	String f = Bytes.toString(kv.getFamily());
	String c = Bytes.toString(kv.getQualifier());
	String v = Bytes.toString(kv.getValue());
	System.out.println(k + "/" + f + ":" + c + "=" + v);
}
ROW内の全データをKeyValueのリストの形で取得する例。
最も高速な取得方法
KeyValue[] raw = r.raw();
ROW内の全データをKeyValueの配列の形で取得する。[2010-02-28]
ソートされていないが、最も高速
int size = r.size();
ROW内の全データの個数を取得する例。
存在チェック
Get g = new Get(Bytes.toBytes("KEY00001"));
boolean b = table.exists(g);
ROW(キー)が存在していたらtrue。
データ設定
put
Put p = new Put(Bytes.toBytes("KEY00001"));
p.add(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column1"), 
	Bytes.toBytes("data"));
table.put(p);
Putインスタンスに複数回のadd()を行って
HTableに1回だけput()することも出来る。
Put p = new Put(Bytes.toBytes("KEY00001"));
p.add(Bytes.toBytes("ColumnFamilyA"), HConstants.EMPTY_BYTE_ARRAY, 
	Bytes.toBytes("data"));
table.put(p);
qualifierの無いデータを指定する例。
Put p = new Put(Bytes.toBytes("KEY00001"));
KeyValue kv = new KeyValue(p.getRow(), 
	Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column1"),
	Bytes.toBytes("data"));
p.add(kv);
table.put(p);
KeyValueを使う場合、
キー(PutのrowとKeyValueのrow)が
一致している必要がある。
ロック
RowLock lock = table.lockRow(Bytes.toBytes("KEY00001"));
try {
	Put p = new Put(lock.getRow(), lock);
	p.add(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column1"),
		Bytes.toBytes("data"));
	table.put(p);
} finally {
	table.unlockRow(lock);
}
ロックをかけて更新する例。

存在しないキーであってもロックをかけることが出来る。[2010-07-15]
チェック&更新
Get g = new Get(Bytes.toBytes("KEY00001"));
Result r = table.get(g);

byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
byte[] v = r.getValue(f, q);

Put p = new Put(g.getRow());
p.add(f, q, Bytes.toBytes("data"));

boolean b = table.checkAndPut(g.getRow(), f, q, v, p);
Get g = new Get(Bytes.toBytes("KEY00001"));
Result r = table.get(g);
byte[] gf = Bytes.toBytes("ColumnFamilyA");
byte[] gq = Bytes.toBytes("Column1");
byte[] gv = r.getValue(gf, gq);

Put p = new Put(Bytes.toBytes("KEY00002"));
byte[] pf = Bytes.toBytes("ColumnFamilyB");
byte[] pq = Bytes.toBytes("Column2");
byte[] pv = Bytes.toBytes("data");
p.add(pf, pq, pv);

boolean b = table.checkAndPut(g.getRow(), gf, gq, gv, p);
指定した値と更新時点のDBの値が
等しい時は実際に更新、
そうでない時は何もしない。
checkAndPut()は、更新したらtrue
更新しなかったらfalseを返す。
ロックをかけなくても更新できる、という仕組み。

存在しないキーであってもチェック(比較)することが出来る。[2010-07-13]
応用例(一意制約の実現)
インクリメント
incr
byte[] row       = Bytes.toBytes("cnt1");
byte[] family    = Bytes.toBytes("data");
byte[] qualifier = Bytes.toBytes("cnt");
long    amount   = 1; //加算する値
boolean writeWAL = true;

long ret = table.incrementColumnValue(row, family, qualifier, amount, writeWAL);
データ(セル)をlong型(8バイト)とみなして値を加算する。[2010-07-12]
この更新はアトミック(原子的)に行われる。
戻り値は加算された値。
データが8バイトより小さい場合は例外が発生する。
8バイトより大きい場合は先頭8バイトに対して加算される。
データが無かった場合は「0に対して加算したデータ」が作られる。
writeWALがtrueの場合、WAL(Write Ahead Log:更新ログ(HLog))まで書き込む。
trueだと信頼性が高いがちょっと遅くなる。
削除
delete
Delete d = new Delete(Bytes.toBytes("KEY00001"));
d.deleteColumn(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column1"));
//d.deleteColumn(Bytes.toBytes("ColumnFamilyA:Column1"));
table.delete(d);
指定ROW(キー)の列の最新バージョンのデータ(セル)を削除する。
Putと同じような使い方をする。
ロックに関しても同様。
HBase Shellのdeleteに相当)
Delete d = new Delete(Bytes.toBytes("KEY00001"));
d.deleteColumns(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column1"));
table.delete(d);
指定ROW(キー)のセルの全バージョン(全履歴)を削除する。[2010-03-06]
HBase Shellのdeleteallに相当)
Delete d = new Delete(Bytes.toBytes("KEY00001"));
d.deleteFamily(Bytes.toBytes("ColumnFamilyA"));
table.delete(d);
指定ROW(キー)の列ファミリーのデータ(全履歴)を消す。
Delete d = new Delete(Bytes.toBytes("KEY00001"));
table.delete(d);
指定ROW(キー)の全データを消す。
チェック&削除
Get g = new Get(Bytes.toBytes("KEY00001"));
Result r = table.get(g);

byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
byte[] v = r.getValue(f, q);

Delete d = new Delete(g.getRow());
d.deleteColumns(f, q);
boolean b = table.checkAndDelete(g.getRow(), f, q, v, d);
HBase0.89で追加されたメソッド。[2010-07-11]
checkAndPut()と同様に、
指定した値と削除時点のDBの値が
等しい時は実際に削除、
そうでない時は何もしない。
checkAndDelete()は、削除したらtrue
削除しなかったらfalseを返す。
コミット
table.setAutoFlush(false);
table.flushCommits();
コミット(フラッシュ)。[2010-02-24]
setAutoFlush(true)(デフォルト)の場合、
自動的にコミットされる。
スキャン
scan
ResultScanner rs = table.getScanner(Bytes.toBytes("ColumnFamilyA"));
try {
	for (Result r : rs) {
		System.out.println(Bytes.toString(r.getRow()));
	}
} finally {
	rs.close();
}
	for (Iterator<Result> i = rs.iterator(); i.hasNext();) {
		Result r = i.next();
		System.out.println(Bytes.toString(r.getRow()));
	}
特定の列ファミリーの全ROWを取得する例。
Resultgetと同様に使うが、
指定した列ファミリーのデータしか取得できない。
	Result[] ra = rs.next(100);
レコード数(上限)を指定して、配列の形で受け取る例。
ResultScanner rs = table.getScanner(
	Bytes.toBytes("ColumnFamilyA"),
	Bytes.toBytes("Column1")
);
特定の列(family:qualifier)の全ROWを取得する例。
byte[] start = Bytes.toBytes("KEY00001");
byte[] stop  = Bytes.toBytes("KEY00004");
Scan scan = new Scan(start, stop);
ResultScanner s = table.getScanner(scan);
Scanクラスを使う例。
(ROWの範囲を指定する例)
byte[] start = Bytes.toBytes("");

Filter filter = new ValueFilter(
	ValueFilter.CompareOp.EQUAL,
	new BinaryComparator(Bytes.toBytes("k1_cfA_c1"))
);

Scan scan = new Scan(start, filter);
ResultScanner s = table.getScanner(scan);
try {
	for (Result r : s) {
		List<KeyValue> list = r.list();
		for (KeyValue kv : list) {
			System.out.println(kv + "/value="
				+ Bytes.toString(kv.getValue()));
		}
	}
} finally {
	s.close();
}
フィルターを使う例。
Filterクラス

一意制約付き登録

通常のput()は「データが既に存在していたら上書き、無かったら登録」(データが既存でもエラーにならない)という動作だが、
checkAndPut()を応用して、「データ(行・レコード)が存在していない場合だけ登録」あるいは「データが存在している場合だけ更新」という処理が出来る。[2010-07-13]
(既存データと同じキーで登録しようとするとエラーになるという意味で、RDBの一意制約(ユニークキー制約)のような動作)

	/** 一意制約の状態を保持するfamily */
	private static final byte[] UNIQUE_CONSTRAINT_FAMILY    = Bytes.toBytes("framework");
	/** 一意制約の状態を保持するqualifier */
	private static final byte[] UNIQUE_CONSTRAINT_QUALIFIER = Bytes.toBytes("unq");

	/** データ未登録を表す値 */
	private static final byte[] ABSENCE_MARKER   = HConstants.EMPTY_BYTE_ARRAY;
	/** データ登録済を表す値 */
	private static final byte[] EXISTENCE_MARKER = new byte[1];
	/**
	 * 存在チェック付きデータ登録
	 *
	 * @param table	登録対象テーブル
	 * @param put	登録データ
	 * @return	登録に成功した場合はtrue、既にROWが存在していた場合はfalse
	 * @throws IOException {@link HTableInterface#checkAndPut(byte[], byte[], byte[], byte[], Put)}
	 */
	public static boolean insert(HTableInterface table, Put put) throws IOException {
		put.add(UNIQUE_CONSTRAINT_FAMILY, UNIQUE_CONSTRAINT_QUALIFIER, EXISTENCE_MARKER);
		return table.checkAndPut(put.getRow(), UNIQUE_CONSTRAINT_FAMILY, UNIQUE_CONSTRAINT_QUALIFIER, ABSENCE_MARKER, put);
	}
	/**
	 * 存在チェック付きデータ更新
	 *
	 * @param table	更新対象テーブル
	 * @param put	更新データ
	 * @return	更新に成功した場合はtrue、ROWが存在していなかった場合はfalse
	 * @throws IOException {@link HTableInterface#checkAndPut(byte[], byte[], byte[], byte[], Put)}
	 */
	public static boolean update(HTableInterface table, Put put) throws IOException {
		return table.checkAndPut(put.getRow(), UNIQUE_CONSTRAINT_FAMILY, UNIQUE_CONSTRAINT_QUALIFIER, EXISTENCE_MARKER, put);
	}

※これを使用する場合、登録/更新対象となるテーブルに「framework」という列ファミリーを定義しておく必要がある。
※このソースはHBase0.89用。HBase0.20では、HTableInterfaceをHTableに置き換える。

insert()では、登録前に「framework:unq」項目が空のバイト列(ABSENCE_MARKER)であることをチェックし、そうであればデータを登録すると共に、「framework:unq」項目にEXISTENCE_MARKERを登録している。
(データが登録されていない場合、checkAndPut()では“空のバイト列と等しい”という認識になるようだ)
2回目以降の登録では「framework:unq」項目がEXISTENCE_MARKERになっているので、ABSENCE_MARKERと不一致だから登録できない。

update()では、更新前に「framework:unq」項目がEXISTENCE_MARKERである、すなわちデータが既に登録されていることをチェックしている。
(「framework:unq」項目を自分でdeleteしたり別の値にしたりしちゃダメね)

参考: 河野 達也さんのRe: Unique row ID constraint

呼び出す例:

	// テーブル作成→create 'test','data','framework'

	HTableFactory factory = new HTableFactory();
	HTableInterface table = factory.createHTableInterface(conf, Bytes.toBytes("test"));
	try {
		byte[] row1      = Bytes.toBytes("key1");
		byte[] row2      = Bytes.toBytes("key2");
		byte[] family    = Bytes.toBytes("data");
		byte[] qualifier = Bytes.toBytes("c1");

		// データ準備
		Delete del = new Delete(row1);
		table.delete(del);

		{ // 初回の登録(成功するはず)
			Put put = new Put(row1);
			put.add(family, qualifier, Bytes.toBytes("insert1"));
			boolean succeeded = insert(table, put);
			System.out.println(succeeded);
		}
		{ // 2回目の登録(失敗するはず)
			Put put = new Put(row1);
			put.add(family, qualifier, Bytes.toBytes("insert2"));
			boolean succeeded = insert(table, put);
			System.out.println(succeeded);
		}

		{ // いきなり更新(失敗するはず)
			Put put = new Put(row2);
			put.add(family, qualifier, Bytes.toBytes("update1"));
			boolean succeeded = update(table, put);
			System.out.println(succeeded);
		}
		{ // 既存行の更新(成功するはず)
			Put put = new Put(row1);
			put.add(family, qualifier, Bytes.toBytes("update2"));
			boolean succeeded = update(table, put);
			System.out.println(succeeded);
		}
	} finally {
		factory.releaseHTableInterface(table);
	}

HBaseへ戻る / HBase変更点へ / Cassandraへ行く / Java目次へ行く / 技術メモへ戻る
メールの送信先:ひしだま