HBase v2.xへJavaクライアントからアクセスする

# はじめに

JavaクライアントからHBaseに対して、CRUDを実行する簡単なサンプルプログラムの紹介です。
使用するJavaAPIはv1.0で大きく変更されているのですが、検索するとv0.9の情報が多いです。
そのため、v1.0のJavaAPIを使用した例を参考までに残しておきます。

# 環境

使用する環境は以下のとおりです。

- CentOS 7.5(サーバ)
- Windows 10（クライアント）
- JDK8
- HBase 2.2.0
- HBase Client 2.2.0

サーバはCentOS 7.5でHBase 2.2.0の疑似分散環境を構築済みです。
HBaseは以下で構築した環境を用いています。

- [HBase スタンドアローン・疑似分散環境の構築](https://qiita.com/mkyz08/items/b2eee09dd9fc9473dbb4)


# 事前準備

今回はクライアントがWindowsであるため「winutils.exe」が必要です。
「winutils.exe」がないと以下のようなエラーが発生します。

```
DEBUG | main | Failed to find winutils.exe
java.io.FileNotFoundException: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset. -see https://wiki.apache.org/hadoop/WindowsProblems
	at org.apache.hadoop.util.Shell.fileNotFoundException(Shell.java:528)
	at org.apache.hadoop.util.Shell.getHadoopHomeDir(Shell.java:549)
	at org.apache.hadoop.util.Shell.getQualifiedBin(Shell.java:572)
	at org.apache.hadoop.util.Shell.<clinit>(Shell.java:669)
	at org.apache.hadoop.util.StringUtils.<clinit>(StringUtils.java:79)
	at org.apache.hadoop.conf.Configuration.getBoolean(Configuration.java:1555)
	at org.apache.hadoop.hbase.HBaseConfiguration.checkDefaultsVersion(HBaseConfiguration.java:66)
	at org.apache.hadoop.hbase.HBaseConfiguration.addHbaseResources(HBaseConfiguration.java:80)
	at org.apache.hadoop.hbase.HBaseConfiguration.create(HBaseConfiguration.java:94)
	at sample.TestMain.test(TestMain.java:43)
	at sample.TestMain.main(TestMain.java:20)
Caused by: java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.
	at org.apache.hadoop.util.Shell.checkHadoopHomeInner(Shell.java:448)
	at org.apache.hadoop.util.Shell.checkHadoopHome(Shell.java:419)
	at org.apache.hadoop.util.Shell.<clinit>(Shell.java:496)
```

以下のURLから「winutils.exe」をダウンロードします。

https://github.com/steveloughran/winutils/tree/master/hadoop-3.0.0/bin

ダウンロードしたファイルは、binディレクトリの下に「{HADOOP_HOME}/bin/winutils.exe」のように格納します。

次に環境変数HADOOP_HOMEを設定します。
私の環境では「C:\\pleiades\\workspace\\hbase_test\\hadoop-3.2.0」を設定しています。


# Javaでクライアントプログラム作成

今回はMavenを使用しているので、pom.xmlにhbase-clientを以下のように追加します。

```xml
	<dependencies>
		<dependency>
		    <groupId>org.apache.hbase</groupId>
		    <artifactId>hbase-client</artifactId>
		    <version>2.2.0</version>
		</dependency>
	</dependencies>
```


HBaseに対して、Get/Put/Scan/Deleteを実行するプログラム例は以下のようになります。

```java
package sample;

import java.io.IOException;

import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;

public class TestClient {

    public static void main(String[] args) {
        TestClient app = new TestClient();
        app.test();
    }

    public void test() {
        org.apache.hadoop.conf.Configuration conf = HBaseConfiguration.create();
        conf.set("hbase.zookeeper.quorum", "hbaseserver1.example.com");
        conf.set("hbase.zookeeper.property.clientPort","2181");

		try (Connection conn = ConnectionFactory.createConnection(conf)) {
	        Table table = conn.getTable(TableName.valueOf("test"));

	        // PUT
	        System.out.println("put: ");
	        Put p = new Put(Bytes.toBytes("key01"));
	        p.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col1"), Bytes.toBytes("test03"));
	        table.put(p);

	        // GET
	        Get g = new Get(Bytes.toBytes("key01"));
	        Result r = table.get(g);
	        byte[] val = r.getValue(Bytes.toBytes("cf"), Bytes.toBytes("col1"));
	        System.out.printf("get: %s%n", Bytes.toString(val));

	    	// DELETE
	        System.out.println("delete: ");
	    	Delete d = new Delete(Bytes.toBytes("key01"));
	    	d.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("col1"));
	    	table.delete(d);

	    	// SCAN
	    	System.out.println("scan: ");
	        Scan scan = new Scan();
	    	ResultScanner rs = table.getScanner(scan);
	    	for (Result rv : rs) {
	    		System.out.println(Bytes.toString(rv.getRow()));
	    	}

	        table.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
```

以下のコードでは接続先のzookeeperのホスト名とポートを指定しています。
複数のzookeeperのサーバがある場合は、"hbaseserver1.example.com,hbaseserver2.example.com"のように指定するようです。

```java
        conf.set("hbase.zookeeper.quorum", "hbaseserver1.example.com");
        conf.set("hbase.zookeeper.property.clientPort","2181");
```

上述はJavaコード中に接続先のホスト名を直接指定しましたが、通常は以下のようなhbase-site.xmlを作成します。

```xml
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>hbaseserver1.example.com</value>
  </property>
  <property>
    <name>hbase.zookeeper.property.clientPort</name>
    <value>2181</value>
  </property>
</configuration>
```

作成したhbase-site.xmlを読み込む場合は以下のように記述します。

```java
Configuration conf = HBaseConfiguration.create();
conf.addResource(new Path("/hbase-site.xml"));
```

最後に/etc/hostsに以下のように自ホストのIPアドレスがないと、リモートからHBaseにアクセスすることができませんでした。
クライアントとHBaseが同一サーバ上で動いていれば以下の記述がなくても動きます。

```
192.168.10.177  hbaseserver1.example.com  hbaseserver1
```
※IPは私の環境