一:简介

hbase-client是HBase提供的一套比较底层的API,在实际使用时需要对其进行封装,提供更好用的api给用户。

操作hbase的客户端有以下几种方式:

  1. hbase-client 比较底层,需要自己进一步封装api,而且版本号和安装的hbase也要匹配,否则会报错
  2. spring-data-hadoop 2019年4月5停止维护
  3. Apache Phoenix 使用SQL的方式来操作HBase

二:API

package org.apache.hadoop.hbase.client;

public class ConnectionFactory {
	/*
	* 创建连接
	* @param conf hbase属性配置
	*/
	public static Connection createConnection(Configuration conf) throws IOException;
}
package org.apache.hadoop.hbase

public class HBaseConfiguration extends Configuration {
	public static Configuration create();
	
	/**
	* 设置hbase的属性配置
	* @param name property name.
    * @param value property value.
	*/
	public void set(String name, String value);
}
public interface Connection extends Abortable, Closeable {
	Admin getAdmin() throws IOException;
	Table getTable(TableName tableName) throws IOException;
	void close() throws IOException;
}
public final class TableName implements Comparable<TableName> {
	public static TableName valueOf(String name);
}
public final class TableName {
	public static TableName valueOf(String name);
	default Result get(Get get) throws IOException;
	default void close() throws IOException
}
public interface Table extends Closeable {
	default void delete(Delete delete) throws IOException;
	default void put(Put put) throws IOException;
	default ResultScanner getScanner(Scan scan) throws IOException;
	default Result get(Get get) throws IOException;
}
public interface ResultScanner extends Closeable, Iterable<Result> {
	void close();
}
public class TableDescriptorBuilder {
	public static TableDescriptorBuilder newBuilder(final TableName name);

	public TableDescriptor build();
	
	public TableDescriptorBuilder setColumnFamilies(Collection<ColumnFamilyDescriptor> families);
}
public class ColumnFamilyDescriptorBuilder {
	public static ColumnFamilyDescriptorBuilder newBuilder(final byte[] name);
	public ColumnFamilyDescriptor build();
}
public interface ColumnFamilyDescriptor {

}
public class Get extends Query implements Row {
	public Get(byte [] row);
	
	public Get addColumn(byte [] family, byte [] qualifier);
}
public class Put extends Mutation implements HeapSize {
	public Put(byte [] row);

	public Put addColumn(byte [] family, byte [] qualifier, byte [] value);
}
public class Delete extends Mutation {
	public Delete addFamily(final byte [] family);
	public Delete addColumn(final byte [] family, final byte [] qualifier);
}
public class Scan extends Query {
	public Scan setFilter(Filter filter);
}
public class RowFilter extends CompareFilter {
	public RowFilter(final CompareOperator op, final ByteArrayComparable rowComparator);
}
public class Result implements CellScannable, CellScanner {
	public List<Cell> listCells();
	
	byte[] getValueArray();
	int getValueOffset();
	int getValueLength();
}
public interface Admin extends Abortable, Closeable {
	boolean tableExists(TableName tableName) throws IOException;
	void createTable(TableDescriptor desc) throws IOException;
	
	void disableTable(TableName tableName) throws IOException;
	void deleteTable(TableName tableName) throws IOException;
}
public class Result implements CellScannable, CellScanner {
	public Cell[] rawCells();
	public NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap();
}
public interface Cell {
	byte[] getRowArray();
	byte[] getFamilyArray();
	byte[] getQualifierArray();
	byte[] getValueArray();
	int getValueOffset();
	int getValueLength();
}
public class Bytes implements Comparable<Bytes> {
	// This method will convert utf8 encoded bytes into a string
	public static String toString(final byte[] b, int off, int len);
}

三:简单示例

hbase-client 使用步骤可分为五步:

  1. 创建连接Connection
  2. 从连接中获取表Table
  3. 操作表
  4. 关闭表
  5. 关闭连接

1. pom.xml

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.1.3</version>
</dependency>

2. application.yml

hbase:
  config:
    hbase.zookeeper.quorum: 127.0.0.1
    hbase.zookeeper.port: 2181
    hbase.zookeeper.znode: /hbase
    hbase.client.keyvalue.maxsize: 1572864000

3. HbaseProperties

import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;

@ConfigurationProperties(prefix = "hbase")
public class HbaseProperties {
    private Map<String, String> config;
    public Map<String, String> getConfig() {
        return config;
    }
    public void setConfig(Map<String, String> config) {
        this.config = config;
    }
}

4. HbaseConfig

@Configuration
@EnableConfigurationProperties(HbaseProperties.class)
public class HbaseConfig {
    private final HbaseProperties properties;

    public HbaseConfig(HbaseProperties properties) {
        this.properties = properties;
    }

    public org.apache.hadoop.conf.Configuration configuration() {
        org.apache.hadoop.conf.Configuration configuration = HBaseConfiguration.create();
        Map<String, String> config = properties.getConfig();
        Set<String> keySet = config.keySet();
        for (String key : keySet) {
            configuration.set(key, config.get(key));
        }
        return configuration;
    }
}

5. HBaseClient

这里只是简单的demo实现,如要实际使用还需要对其完善封装。

@DependsOn("hbaseConfig")
@Component
public class HBaseClient {

    @Autowired
    private HbaseConfig config;

    private static Connection connection = null;
    private static Admin admin = null;
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @PostConstruct
    private void init() {
        if (connection != null) {
            return;
        }

        try {
            connection = ConnectionFactory.createConnection(config.configuration());
            admin = connection.getAdmin();
        } catch (IOException e) {
            logger.error("HBase create connection failed: {}", e);
        }
    }

    public void createTable(String tableName, String[] columnFamilies) throws IOException {
        TableName name = TableName.valueOf(tableName);

        boolean isExists = this.tableExists(tableName);
        if (isExists) {
            throw new TableExistsException(tableName + "is exists!");
        }

        TableDescriptorBuilder descriptorBuilder = TableDescriptorBuilder.newBuilder(name);
        List<ColumnFamilyDescriptor> columnFamilyList = new ArrayList<>();
        for (String columnFamily : columnFamilies) {
            ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(columnFamily.getBytes()).build();
            columnFamilyList.add(columnFamilyDescriptor);
        }
        descriptorBuilder.setColumnFamilies(columnFamilyList);
        TableDescriptor tableDescriptor = descriptorBuilder.build();
        admin.createTable(tableDescriptor);
    }

    public void insertOrUpdate(String tableName, String rowKey, String columnFamily, String column, String value) throws IOException {
        this.insertOrUpdate(tableName, rowKey, columnFamily, new String[]{column}, new String[]{value});
    }

    public void insertOrUpdate(String tableName, String rowKey, String columnFamily, String[] columns, String[] values) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Put put = new Put(Bytes.toBytes(rowKey));
        for (int i = 0; i < columns.length; i++) {
            put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columns[i]), Bytes.toBytes(values[i]));
            table.put(put);
        }
    }

    public void deleteRow(String tableName, String rowKey) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Delete delete = new Delete(rowKey.getBytes());
        table.delete(delete);
    }

    public void deleteColumnFamily(String tableName, String rowKey, String columnFamily) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Delete delete = new Delete(rowKey.getBytes());
        delete.addFamily(Bytes.toBytes(columnFamily));
        table.delete(delete);
    }

    public void deleteColumn(String tableName, String rowKey, String columnFamily, String column) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Delete delete = new Delete(rowKey.getBytes());
        delete.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));
        table.delete(delete);
    }

    public void deleteTable(String tableName) throws IOException {
        boolean isExists = this.tableExists(tableName);
        if (!isExists) {
            return;
        }

        TableName name = TableName.valueOf(tableName);
        admin.disableTable(name);
        admin.deleteTable(name);
    }

    public String getValue(String tableName, String rowkey, String family, String column) {
        Table table = null;

        String value = "";
        if (StringUtils.isBlank(tableName) || StringUtils.isBlank(family)
                || StringUtils.isBlank(rowkey) || StringUtils.isBlank(column)) {
            return null;
        }

        try {
            table = connection.getTable(TableName.valueOf(tableName));
            Get g = new Get(rowkey.getBytes());
            g.addColumn(family.getBytes(), column.getBytes());
            Result result = table.get(g);
            List<Cell> ceList = result.listCells();
            if (ceList != null && ceList.size() > 0) {
                for (Cell cell : ceList) {
                    value = Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength());
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                table.close();
                connection.close();
            }catch (IOException e) {
                e.printStackTrace();
            }
        }
        return value;
    }

    public String selectOneRow(String tableName, String rowKey) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Get get = new Get(rowKey.getBytes());
        Result result = table.get(get);
        NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> map = result.getMap();
        for (Cell cell : result.rawCells()) {
            String row = Bytes.toString(cell.getRowArray());
            String columnFamily = Bytes.toString(cell.getFamilyArray());
            String column = Bytes.toString(cell.getQualifierArray());
            String value = Bytes.toString(cell.getValueArray());

            // 可以通过反射封装成对象(列名和Java属性保持一致)
            System.out.println(row);
            System.out.println(columnFamily);
            System.out.println(column);
            System.out.println(value);
        }

        return null;
    }

    public String scanTable(String tableName, String rowKeyFilter) throws IOException {
        Table table = connection.getTable(TableName.valueOf(tableName));
        Scan scan = new Scan();
        if (!StringUtils.isEmpty(rowKeyFilter)) {
            RowFilter rowFilter = new RowFilter(CompareOperator.EQUAL, new SubstringComparator(rowKeyFilter));
            scan.setFilter(rowFilter);
        }
        ResultScanner scanner = table.getScanner(scan);

        try {
            for (Result result : scanner) {
                System.out.println(Bytes.toString(result.getRow()));
                for (Cell cell : result.rawCells()) {
                    System.out.println(cell);
                }
            }
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }

        return null;
    }

    
    /**
     * 判断表是否已经存在,这里使用间接的方式来实现
     *
     * admin.tableExists() 会报NoSuchColumnFamilyException, 有人说是hbase-client版本问题
     * @param tableName
     * @return
     * @throws IOException
     */
    public boolean tableExists(String tableName) throws IOException {
        TableName[] tableNames = admin.listTableNames();
        if (tableNames != null && tableNames.length > 0) {
            for (int i = 0; i < tableNames.length; i++) {
                if (tableName.equals(tableNames[i].getNameAsString())) {
                    return true;
                }
            }
        }
        return false;
    }
}

6. Test

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

@RunWith(SpringRunner.class)
@SpringBootTest
public class HbaseDemoApplicationTests {

    @Autowired
    private HBaseClient hBaseClient;

    /**
     * 测试删除、创建表
     */
    @Test
    public void testGetValue() {
        String value = hBaseClient.getValue("tbl_user", "mengday", "info", "age");
        System.out.println(value);
    }

    @Test
    public void testCreateTable() throws IOException {
        String tableName = "tbl_abc";
        hBaseClient.deleteTable(tableName);
        hBaseClient.createTable(tableName, new String[] {"cf1", "cf2"});
    }

    @Test
    public void dropTable() throws IOException {
        hBaseClient.deleteTable("tbl_abc");
    }


    @Test
    public void testInsertOrUpdate() throws IOException {
        hBaseClient.insertOrUpdate("tbl_abc", "rowKey1", "cf1", new String[]{"c1", "c2"}, new String[]{"v1", "v22"});
    }

    @Test
    public void testSelectOneRow() throws IOException {
        hBaseClient.selectOneRow("tbl_abc", "rowKey1");
    }

    @Test
    public void testScanTable() throws IOException {
        hBaseClient.scanTable("tbl_abc", "rowKey1");
    }
}