HBase CRUD 操作指南 (二)

继  HBase CRUD 操作指南 (一)

 

3. CRUD 操作 (CRUD Operations)


数据库的初始基本操作通常称为 CRUD (create, read, update, and delete), 指的是 增、查、改、删四种操作。HBase 中有与之对应的的一组操作,由

Table interface 接口提供。

    public interface Table extends Closeable

Used to communicate with a single HBase table. Obtain an instance from a Connection and call close() afterwards.
Table can be used to get, put, delete or scan data from a table.


3.1 put 方法 (Put Method)
-----------------------------------------------------------------------------------------------------------------------------------------
put 操作可以划分为几种类型:一类操作用于单行,一类操作用于多行(lists of rows), 还有一类提供服务器端的,原子的 check-and-put 操作。



■ 单行 Put (Single Puts)
-----------------------------------------------------------------------------------------------------------------------------------------
向 HBase 中存储数据,执行如下调用:

    void put(Put put) throws IOException

由一个 Put 对象作为参数,可以通过如下构造器创建:

    Put(byte[] row)
    Put(byte[] row, long ts)
    Put(byte[] rowArray, int rowOffset, int rowLength)
    Put(ByteBuffer row, long ts)
    Put(ByteBuffer row)
    Put(byte[] rowArray, int rowOffset, int rowLength, long ts)
    Put(Put putToCopy)

需要提供一个行键 row 来创建 Put 实例。在 HBase 中每行数据都有唯一的行键(row key)作为标识,与 HBase 中的大多数数据类型类似,他是一个 Java
byte[] 数组。用户可以按自己的需求来指定每行的行键。行键的含义与实际的物理场景相关,例如,它的含义可以是一个用户名或者订单号,它的内容可以
是简单的数字,也可以是较复杂的 UUID 等。

HBase 非常友好地为我们提供了一个包含很多静态方法的辅助类 Bytes,这个类可以把许多 Java 数据类型转换为 byte[] 数组:

    static byte[] toBytes(ByteBuffer bb)
    static byte[] toBytes(String s)
    static byte[] toBytes(boolean b)
    static byte[] toBytes(long val)
    static byte[] toBytes(float f)
    static byte[] toBytes(int val)
    ...

例如,下面是将一个用户名从字符串转换为 byte[] 数组的示例:

    byte[] rowkey = Bytes.toBytes("johndoe");

除了直接的方法,还有些构造器变体利用已有的数组,并提供给定的数组偏移量和长度参数,从数组中复制所需的行键位数来创建 Put 实例:

    byte[] data = new byte[100];
    ...
    String username = "johndoe";
    byte[] username_bytes = username.getBytes(Charset.forName("UTF8"));
    ...
    System.arraycopy(username_bytes, 0, data, 45, username_    bytes.length);
    ...
    Put put = new Put(data, 45, username_bytes.length);

类似地,也可以提供一个已存在的 ByteBuffer, 或者是一个已存在的 Put 实例,它们都会从给定的已存在的对象中获取细节信息来构造 Put 实例。

创建 Put 实例之后,就可以向该实例添加数据了,添加数据的方法如下:

    Put addColumn(byte[] family, byte[] qualifier, byte[] value)
    Put addColumn(byte[] family, byte[] qualifier, long ts, byte[] value)
    Put addColumn(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value)
    Put addImmutable(byte[] family, byte[] qualifier, byte[] value)
    Put addImmutable(byte[] family, byte[] qualifier, long ts, byte[] value)
    Put addImmutable(byte[] family, ByteBuffer qualifier, long ts,    ByteBuffer value)
    Put addImmutable(byte[] family, byte[] qualifier, byte[] value,    Tag[] tag)
    Put addImmutable(byte[] family, byte[] qualifier, long ts, byte[] value, Tag[] tag)
    Put addImmutable(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value, Tag[] tag)
    Put add(Cell kv) throws IOException

每一个 addColumn 的调用指定一个列,或者指定一个可选的时间戳,一个单元格一个。注意,如果在调用 addColumn() 时没有指定时间戳,Put
实例会使用从构造器上传递过来的可选的时间戳(也称为 ts), 或者,构造器也没有设置时间戳,则由分区服务器(region server) 基于本地时钟
分配时间戳。如果客户端没有设置时间戳,在此 Put 实例上调用 getTimeStamp() 将返回 Long.MAX_VALUE。

注意,调用以上 addXYZ() 方法会在内部创建 Cell 实例。

每个 addColumn() 都有对应的 addImmutable() 方法,addImmutable() 与其对应的 addColumn() 做相同的事,区别是不从给定的数组复制数据,
它们假定不会修改给定参数的数组内容,它们更加高效地使用内存并且性能更好,但依赖于客户端正确地使用。

利用已存在 Cell 实例的变体 add(Cell kv) 是为高级用户提供的方法。要检查特定的 cell 是否存在,可使用如下方法:

    boolean has(byte[] family, byte[] qualifier)
    boolean has(byte[] family, byte[] qualifier, long ts)
    boolean has(byte[] family, byte[] qualifier, byte[] value)
    boolean has(byte[] family, byte[] qualifier, long ts, byte[] value)

Put 类还提供了很多方法,很多继承自其基础类型:

    public class Put extends Mutation
    implements org.apache.hadoop.hbase.io.HeapSize, Comparable<Row>


    //Example application inserting data into HBase
    import org.apache.hadoop.conf.Configuration;
    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.Put;
    import org.apache.hadoop.hbase.client.Table;
    import org.apache.hadoop.hbase.util.Bytes;
    import java.io.IOException;
    
    public class PutExample {
        public static void main(String[] args) throws IOException {
            //Create the required configuration
            Configuration conf = HBaseConfiguration.create();
            
            //Instantiate a new client.
            Connection connection = ConnectionFactory.createConnection(conf);
            Table table = connection.getTable(TableName.valueOf("testtable"));
            
            //Create put with specific row.
            Put put = new Put(Bytes.toBytes("row1"));
            
            //Add a column, whose name is “colfam1:qual1”, to the put.
            put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
            //Add another column, whose name is “colfam1:qual2”, to the put.
            put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val2"));
            
            //Store row with column into the HBase table.
            table.put(put);
            
            //Close table and connection instances to free resources.
            table.close();
            connection.close();
        }
    }



■ 客户端的写缓冲区 (Client-side Write Buffer)
-----------------------------------------------------------------------------------------------------------------------------------------
每一个 put 操作实际上都是一个 RPC (“remote procedure call”) 操作,它将客户端数据传送到服务器然后返回。这只适合于小数据量的操作。如果应用
程序需要每秒存储上千行的数据到 HBase 表中,这样的处理就不太合适了。

    
    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    减少 RPC 调用的关键是限制往返时间(round-trip time), 往返时间就是客户端发送一个请求到服务器,然后服务器通过网络进行响应的时间。这个时
    间不包含数据实际传输的时间,它其实就是通过线路传送网络包的开销。一般情况下,在 LAN 网络中大约要花 1 毫秒的时间,这意味着 1 秒钟的时间
    只能完成 1000 次 RPC 往返响应。
    
    另一个重要的因素就是消息的大小。如果通过网络发送的请求内容较大,那么需要请求返回的次数相应较少,这是因为时间主要花费在数据传输上。不过
    如果传送的数据量很小,比如一个计数器递增操作,那么用户把多次修改的数据批量提交给服务器并减少请求次数,性能会有相应提升。

HBase API 自带有一个内置的客户端写缓冲区(write buffer) 用于收集 put 和 delete 操作,以使它们通过一次 RPC 调用发送到服务器。这个功能的入口点
是 BufferedMutator 类。通过 Connection 类的如下方法获得:

    BufferedMutator getBufferedMutator(TableName tableName) throws IOException
    BufferedMutator getBufferedMutator(BufferedMutatorParams params) throws IOException

返回的 BufferedMutator 是线程安全的,可用于批量的 put 和 delete 操作,称为 mutations, 或 operations。使用这个类有几件事要记住:

    1. 必须在生命周期结束时调用 close() 方法。这会将未完成的操作同步刷写出去并释放资源。
    2. 当提交了特定的 mutation 需要立刻发送到服务器上时,调用 flush() 方法。
    3. 如果不调用 flush(), 那么依赖于内部的、异步的更新机制:达到某个特定的阈值,或者调用了 close() 方法。
    4. 如果应用程序失败,在那一时刻仍然缓存的任何本地 mutation 会丢失。
    
第一个方法需要表名(table name) 为参数,很明显是发送批操作的表名称。后面的那个方法有点复杂,它需要一个 BufferedMutatorParams 的实例作为参数,
其中持有不仅仅是必要的表名称,还有其它更高级的参数,有如下的构造器和方法:

    BufferedMutatorParams(TableName tableName)
    TableName getTableName()
    long getWriteBufferSize()
    BufferedMutatorParams writeBufferSize(long writeBufferSize)
    int getMaxKeyValueSize()
    BufferedMutatorParams maxKeyValueSize(int maxKeyValueSize)
    ExecutorService getPool()
    BufferedMutatorParams pool(ExecutorService pool)
    BufferedMutator.ExceptionListener getListener()
    BufferedMutatorParams listener(BufferedMutator.ExceptionListener listener)

列表中第一个是带有表名参数的构造器,下面是可以进一步获取或设置的一些参数:

    WriteBufferSize    :如果计数值超出了 WriteBufferSize 设置的值,所有缓存的 mutation 异步地发送到服务器。由 hbase-default.xml 文件中
                    hbase.client.write.buffer 属性默认设置为 2097152 bytes, 即 2MB. 默认的 2MB 值表现在 RPC    包大小和客户端进程数据保持间
                    达到很好的平衡。
                    
    MaxKeyValueSize    :操作允许客户端 API 发送到服务器之前,cell 中包含的数据的大小通过 MaxKeyValueSize 设置检查。如果 cell 超过这个设置的
                    限制,会被拒绝,并向客户端抛出 IllegalArgumentException 异常。这确保在有意义的边界内使用 HBase。
                    由 hbase-default.xml 文件中  hbase.client.keyvalue.maxsize 属性默认设置为 10485760 bytes (or 10MB)
    
    Pool            :由于所有的异步操作都是由客户端类库在后台执行的,这需要提交一个标准的 Java ExecutorService 实例。如果用户没有设置 pool
                    系统会创建一个默认的 pool, 由 hbase.htable.threads.max 控制,默认设置为 Integer.MAX_VALUE (意为无限数量), 以及
                    hbase.htable.threads.keepalivetime 属性, 设置为 60 秒。

    Listener        :最后,可以使用一个侦听器钩子(a listener hook), 在服务器上执行 mutation 期间发生错误时接收通知。要实现这一个功能,需要
                    实现 BufferedMutator.ExceptionListener 接口,它提供 onException() callback 方法。默认实现是抛出收到的异常。
                    


    // Shows the use of the client side write buffer
    private static final int POOL_SIZE = 10;
    private static final int TASK_COUNT = 100;
    private static final TableName TABLE = TableName.valueOf("testtable");
    private static final byte[] FAMILY = Bytes.toBytes("colfam1");
    
    public static void main(String[] args) throws Exception {
        Configuration configuration = HBaseConfiguration.create();
        BufferedMutator.ExceptionListener listener = new BufferedMutator.ExceptionListener() {
            
            // Create a custom listener instance
            @Override
            public void onException(RetriesExhaustedWithDetailsException e,
            BufferedMutator mutator) {
                // Handle callback in case of an exception
                for (int i = 0; i < e.getNumExceptions(); i++) {
                    LOG.info("Failed to sent put: " + e.getRow(i));
                }
            }
        };
        
        // Create a parameter instance, set the table name and custom listener reference.
        BufferedMutatorParams params = new BufferedMutatorParams(TABLE).listener(listener);
        
        //Allocate the shared resources using the Java 7 try-with-resource pattern.
        try (
            Connection conn = ConnectionFactory.createConnection(configuration);
            BufferedMutator mutator = conn.getBufferedMutator(params)
            )
        {
            // Create a worker pool to update the shared mutator in parallel.
            ExecutorService workerPool = Executors.newFixedThread‐Pool(POOL_SIZE);
            List<Future<Void>> futures = new ArrayList<>(TASK_COUNT);
            
            // Start all the workers up.
            for (int i = 0; i < TASK_COUNT; i++) {
                futures.add(workerPool.submit(new Callable<Void>() {
                
                    @Override
                    public Void call() throws Exception {
                        Put p = new Put(Bytes.toBytes("row1"));
                        p.addColumn(FAMILY, Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
                        
                        // Each worker uses the shared mutator instance, sharing the same backing buffer, callback listener, and RPC execuor pool.
                        mutator.mutate(p);
                        // [...]
                        
                        // Do work... Maybe call mutator.flush() after many edits
                        to ensure
                        // any of this worker's edits are sent before exiting the
                        Callable
                        return null;
                    }
                }));
            }
        
        
            for (Future<Void> f : futures) {
            
                // Wait for workers and shut down the pool.
                f.get(5, TimeUnit.MINUTES);
            }
            workerPool.shutdown();
        } catch (IOException e) {
        
            // The try-with-resource construct ensures that first the mutator, and then the connection are closed. This could trigger exceptions
            //  and call the custom listener.
            LOG.info("Exception while creating or freeing resources", e);
        }
        }
    }


客户端写缓冲区主要的应用场景是小的 mutation 操作,也就是 put 和 delete 请求。特别是后者更小,delete 操作并不携带值数据:delete 只有 cell 的 key
信息,设置一个删除标记。

另一个适用的场景是 MapReduce 作业操作 HBase, 因为它们都是有关尽可能快地发送 mutation 操作的。

隐式地刷写或显式调用 flush() method 会将所有修改应用到远程服务器。缓存的 Put 和 Delete 实例可以跨多个不同的行。客户端足以智能可以分别批次更新并将
它们发送到各自的分区服务器上去。仅仅需要简单的 put() 或 delete() 调用,不必担忧数据存在什么地方,因为这个处理对 HBase 客户端是透明的。

一个主意点是考虑上面提到的 executor pool. 其由 hbase.htable.threads.max 属性控制,并且默认设置为 Integer.MAX_VALUE, 意为无限数量。这不是指每个客
户端向服务器发送缓冲的写操作要创建无限多的工作线程。它真正的含义是每个分区服务器创建一个线程。这是由服务器数量决定的。但如果集群扩展到几千个节点,
就要考虑设置这个属性为某个最大值,以显式界定需要的数量。

下面是另一个客户端 API 使用 write buffer 的例子:

    // Example using the client-side write buffer
    TableName name = TableName.valueOf("testtable");
    Connection connection = ConnectionFactory.createConnection(conf);
    Table table = connection.getTable(name);
    // Get a mutator instance for the table.
    BufferedMutator mutator = connection.getBufferedMutator(name);
    
    Put put1 = new Put(Bytes.toBytes("row1"));
    put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
    
    // Store some rows with columns into HBase.
    mutator.mutate(put1);
    
    Put put2 = new Put(Bytes.toBytes("row2"));
    put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val2"));
    mutator.mutate(put2);
    Put put3 = new Put(Bytes.toBytes("row3"));
    put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
    Bytes.toBytes("val3"));
    mutator.mutate(put3);
    
    Get get = new Get(Bytes.toBytes("row1"));
    Result res1 = table.get(get);
    // Try to load previously stored row, this will print “Result:keyvalues=NONE”.
    System.out.println("Result: " + res1);
    // Force a flush, this causes an RPC to occur.
    mutator.flush();
    
    Result res2 = table.get(get);
    // Now the row is persisted and can be loaded.
    System.out.println("Result: " + res2);
    mutator.close();
    table.close();
    connection.close();



■ Put 列表 (List of Puts)
-----------------------------------------------------------------------------------------------------------------------------------------
客户端 API 可以插入单个 Put 实例,同时也有批处理操作的高级特性,调用形式如下:

    void put(List<Put> puts) throws IOException

需要创建一个 Put 实例的列表,并把它提交给上面的 put() 方法调用。

示例:

    // Example inserting data into HBase using a list
    // Create a list that holds the Put instances.
    List<Put> puts = new ArrayList<Put>();
    
    Put put1 = new Put(Bytes.toBytes("row1"));
    put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
    Bytes.toBytes("val1"));
    
    // Add put to list.
    puts.add(put1);
    Put put2 = new Put(Bytes.toBytes("row2"));
    put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
    Bytes.toBytes("val2"));
    // Add another put to list.
    puts.add(put2);
    Put put3 = new Put(Bytes.toBytes("row2"));
    put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
    Bytes.toBytes("val3"));
    // Add third put to list.
    puts.add(put3);
    
    // Store multiple rows with columns into HBase.
    table.put(puts);

由于用户提交的 mutation 的 row 列表可能涉及多个行,有可能不会全部成功。这可能会是多种原因导致的,如一个远程 region server 出现了问题,
导致客户端重试的次数超过了配置的最大值,因此不得不放弃当前操作。如果远程服务器的 put 调用出现问题,错误会通过随后的一个 IOException
异常反馈给客户端。

示例:使用不存在的列族 BOGUS 插入一个列

    // Example inserting a faulty column family into HBase

    Put put1 = new Put(Bytes.toBytes("row1"));
    put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
    puts.add(put1);

    Put put2 = new Put(Bytes.toBytes("row2"));
    // Add put with non existent family to list.
    put2.addColumn(Bytes.toBytes("BOGUS"), Bytes.toBytes("qual1"),
    Bytes.toBytes("val2"));
    puts.add(put2);

    Put put3 = new Put(Bytes.toBytes("row2"));
    put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
    Bytes.toBytes("val3"));
    puts.add(put3);

    table.put(puts);

调用 put() 会失败并产生如下错误消息:

    WARNING: #3, table=testtable, attempt=1/35 failed=1ops, last exception:
    null \
    on server-1.internal.foobar.com,65191,1426248239595, tracking \
    started Sun Mar 15 20:35:52 CET 2015; not retrying 1 - final
    failure
    Exception in thread "main" \
    org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException: \
    Failed 1 action: \
    org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException:
    \
    Column family BOGUS does not exist in region \
    testtable,,1426448152586.deecb9559bde733aa2a9fb1e6b42aa93. in
    table \
    'testtable', {NAME => 'colfam1', DATA_BLOCK_ENCODING => 'NONE', \
    BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', COMPRESSION =>
    'NONE', \
    VERSIONS => '1', TTL => 'FOREVER', MIN_VERSIONS => '0', \
    KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536', \
    IN_MEMORY => 'false', BLOCKCACHE => 'true'}
    : 1 time,



服务器遍历所有操作并尝试执行它们,失败的返回,然后客户端可以使用 RetriesExhaustedWithDetailsException 报告远程错误,这样用户就可以查询
有多少个操作失败、出错的原因以及重试的次数。要注意的是,对于错误列族,服务器端的重试次数会自动设为 1 (查看 NoSuchColumnFamilyException:
1 time), 因为 HBase 认为这是个不可恢复的错误。

另外,可以使用异常实例来获得失败操作更详细的信息,甚至错误的 mutation 本身。下面示例扩展自上面错误示例,加入 catch 块来访问错误的详细
信息:

    // Special error handling with lists of puts
    try {
        table.put(puts);
    } catch (RetriesExhaustedWithDetailsException e) {
        int numErrors = e.getNumExceptions();
        System.out.println("Number of exceptions: " + numErrors);
        for (int n = 0; n < numErrors; n++) {
        System.out.println("Cause[" + n + "]: " + e.getCause(n));
        System.out.println("Hostname[" + n + "]: " + e.getHostnamePort(n));
        System.out.println("Row[" + n + "]: " + e.getRow(n));
    }
    System.out.println("Cluster issues: " + e.mayHaveClusterIssues());
    System.out.println("Description: " + e.getExhaustiveDescription());
    }

之前提到过为 BufferedMutator 提供的 MaxKeyValueSize 参数,API 必须确保提交的操作遵守这个限制。对单行 put 和列表 put 也执行相同的检查。

使用基于列表的 put 调用还要注意另外一个特殊性:无法控制应用到服务器端的 put 操作的次序,也就意味着服务器端调用的次序也不在用户的控制之下。
如果必须保证按一定的次序执行调用,最坏的情况是分别创建更小的批处理,并在客户端显式刷写缓冲区强制发送到远程服务器。



■ 原子性操作 (Check-and-Put (Atomic Check-and-Put)
-----------------------------------------------------------------------------------------------------------------------------------------
有一种特殊的 put 调用,它能保证自身操作的原子性:检查写(check and put)。方法签名如下:

    boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException
    boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, Put put)
        throws IOException

这两个调用使用户可以发布原子性的、服务器端的带有检查保护的 mutation 操作。如果通过检查,put 操作被执行,否则完全放弃该操作。这种方法可用于
基于当前的,可能相关联的值的更新。

这种带有检查保护的操作经常用于系统处理,例如,账户余额,状态转换,或数据处理。这种处理的基本原则是,读取数据的同时处理这些数据。一旦准备好
将数据结果写回 HBase, 要确保没有其它的客户端对同样的数据做过处理。使用原子检查,比较数据的值没有被修改过,然后应用新的值。

第一个调用的含义是给定的值 value 与存储的值必须相等 (to be equal to), 第二个调用指定了一个实际的比较器(comparison operator) 进行比较,提供
了更灵活的检查测试。


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    有一种特殊类型的检查可以通过 checkAndPut() 执行:只有在另外一个值不存在时才执行更新操作。这可以通过设置 value 的值为 null 来实现。这
    种情况下,当某个特地的列不存在时操作才会成功执行。

这两个调用返回一个 boolean 类型结果值,指明 Put 是否应用成功,分别返回 true 或 false。

示例:

    // Example application using the atomic compare-and-set operations
    Put put1 = new Put(Bytes.toBytes("row1"));
    
    // Create a new Put instance.
    put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
    // Check if column does not exist and perform optional put operation.
    boolean res1 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), null, put1);
    // Print out the result, should be “Put 1a applied: true”.
    System.out.println("Put 1a applied: " + res1);
    
    // Attempt to store same cell again.
    boolean res2 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), null, put1);
    // Print out the result, should be “Put 1b applied: false” as the column now already exists.
    System.out.println("Put 1b applied: " + res2);
    
    
    Put put2 = new Put(Bytes.toBytes("row1"));
    
    // Create another Put instance, but using a different column qualifier.
    put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val2"));
    
    // Store new data only if the previous data has been saved.
    boolean res3 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"), put2);
    // Print out the result, should be “Put 2 applied: true” as the checked column exists.
    System.out.println("Put 2 applied: " + res3);
    
    Put put3 = new Put(Bytes.toBytes("row2"));
    // Create yet another Put instance, but using a different row
    put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val3"));
    // Store new data while checking a different row.
    boolean res4 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"), put3);
    
    // We will not get here as an exception is thrown beforehand!
    System.out.println("Put 3 applied: " + res4);


示例中最后的调用会抛出 DoNotRetryIOException 错误,因为 checkAndPut() 强制检查的行 (row) 必须与 Put 实例中的行匹配。可以检查一个列(column),
而更新另外的一个列,但不能跨出行边界检查。


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    HBase 提供的 compare-and-set 操作依赖于对同一行进行检查和修改。尝试检查和修改两个不同的行将返回异常。


Compare-and-set (CAS) 操作十分强大,尤其是在分布式系统中,且有多个独立的客户端同时操作数据时。通过这个方法,HBase 与其它复杂的设计结构区分
开来,提供了使不同客户端可以并发修改数据的功能。




3.2 get 方法 (Get Method)
-----------------------------------------------------------------------------------------------------------------------------------------
客户端 API 中的下一个步是从 HBase 中获取已保存的数据。为此,Table 接口提供了 get() 方法调用,以及相关的类。这类操作被划分为在单行上操作,
和在一次调用过程中在多个行上获取数据。


■ 单行 get 操作 (Single Gets)
-----------------------------------------------------------------------------------------------------------------------------------------
从一个 HBase 表中获取特定值的方法:

    Result get(Get get) throws IOException

get() 操作界定为一个特定的行,但可以获取包含在该行上的任意数量的列或 cell. 创建 Get 实例时要提供一个行键 (row key), 使用下列构造器:

    Get(byte[] row)
    Get(Get get)

Get 的主构造器通过 row 参数指定要访问的那一行,第二个构造器接受一个已经存在的 Get 实例并复制该对象的整个细节信息,效果上等同于 clone 实例。
并且,类似于 put 操作,有很多方法来指定相当广泛依据来查找目标数据,也可以指定精确的坐标获取某个单元格 (cell) 数据:

    Get addFamily(byte[] family)
    Get addColumn(byte[] family, byte[] qualifier)
    Get setTimeRange(long minStamp, long maxStamp) throws IOException
    Get setTimeStamp(long timestamp)
    Get setMaxVersions()
    Get setMaxVersions(int maxVersions) throws IOException

addFamily() 调用将请求限定到给定的列族。可以多次调用以添加更多的列族。addColumn() 调用也类似,通过该调用可以更进一步限定访问的地址空间:
特定的列。其它方法可以使用户设定要查找数据的准确的时间戳,或者匹配一个时间范围的 cell.

最后,是指定要获取多少个版本的方法(如果没有设定准确的时间戳)。默认值为 1,意思为 get 调用只返回当前匹配的数据。没有参数的 setMaxVersions()
设定返回的版本数量为 Integer.MAX_VALUE, 也就是在列族描述符中(column family descriptor) 配置的最大版本数量,因此告诉 API 返回所有匹配 cell
的所有可用的版本。

之前提到过, HBase 为我们提供了一个辅助类 Bytes,其中有很多静态方法将 Java 类型转换为 byte[] 数组,反过来一样,Bytes 类提供了将数组转换为
Java 类型的方法。从 HBase 中获取数据,例如,以前保存的行,可以利用 Bytes 的这些辅助方法,将 byte[] 数据转换会 Java 类型。

    static String toString(byte[] b)
    static boolean toBoolean(byte[] b)
    static long toLong(byte[] bytes)
    static float toFloat(byte[] bytes)
    static int toInt(byte[] bytes)
    ...

示例:
    // Example application retrieving data from HBase
    
    // Create the configuration.
    Configuration conf = HBaseConfiguration.create();
    
    // Instantiate a new table reference.
    Connection connection = ConnectionFactory.createConnection(conf);
    Table table = connection.getTable(TableName.valueOf("testtable"));
    
    // Create get with specific row.
    Get get = new Get(Bytes.toBytes("row1"));
    // Add a column to the get.
    get.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    
    // Retrieve row with selected columns from HBase.
    Result result = table.get(get);
    
    // Get a specific value for the given column.
    byte[] val = result.getValue(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    
    // Print out the value while converting it back.
    System.out.println("Value: " + Bytes.toString(val));
    
    // Close the table and connection instances to free resources
    table.close();
    connection.close();

    
Get 类提供了许多方法调用,查阅 org.apache.hadoop.hbase.client.Get javadoc 获取详细信息。其中:

    Get         setCacheBlocks(boolean cacheBlocks)
    boolean     getCacheBlocks()

控制读操作如何在服务器端处理。每一个 HBase region server 有一个块缓存,用于保留最近访问的数据,以提升后续的读取效率。在某些情况下,最好
不要使用缓存以避免在完全随机的 get 访问时搅乱缓存中的信息。不要用不相关联的数据污染块缓存中的数据,最好跳过这些缓存的块使其保持不受破坏
以使其它客户端执行读取相关联的数据。

    Get         setCheckExistenceOnly(boolean checkExistenceOnly)
    boolean     isCheckExistenceOnly()

联合使用允许客户端检查某个特定的列集,或列族是否存在。示例如下:

    // Checks for the existence of specific data
    List<Put> puts = new ArrayList<Put>();
    
    Put put1 = new Put(Bytes.toBytes("row1"));
    put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
    puts.add(put1);
    
    Put put2 = new Put(Bytes.toBytes("row2"));
    put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val2"));
    puts.add(put2);
    
    Put put3 = new Put(Bytes.toBytes("row2"));
    put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val3"));
    puts.add(put3);
    
    // Insert two rows into the table.
    table.put(puts);
    
    Get get1 = new Get(Bytes.toBytes("row2"));
    get1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    get1.setCheckExistenceOnly(true);
    // Check first with existing data.
    Result result1 = table.get(get1);
    
    byte[] val = result1.getValue(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    // Exists is “true”, while no cel was actually returned.
    System.out.println("Get 1 Exists: " + result1.getExists());

    System.out.println("Get 1 Size: " + result1.size());
    System.out.println("Get 1 Value: " + Bytes.toString(val));
    
    
    Get get2 = new Get(Bytes.toBytes("row2"));
    get2.addFamily(Bytes.toBytes("colfam1"));
    // Check for an entire family to exist.
    get2.setCheckExistenceOnly(true);
    Result result2 = table.get(get2);
    
    System.out.println("Get 2 Exists: " + result2.getExists());
    System.out.println("Get 2 Size: " + result2.size());
    
    Get get3 = new Get(Bytes.toBytes("row2"));
    get3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual9999"));
    // Check for a non-existent column.
    get3.setCheckExistenceOnly(true);
    
    Result result3 = table.get(get3);
    System.out.println("Get 3 Exists: " + result3.getExists());
    System.out.println("Get 3 Size: " + result3.size());
    
    Get get4 = new Get(Bytes.toBytes("row2"));
    get4.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual9999"));
    get4.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    // Check for an existent, and non-existent column
    get4.setCheckExistenceOnly(true);
    Result result4 = table.get(get4);
    
    // Exists is “true” because some data exists.
    System.out.println("Get 4 Exists: " + result4.getExists());
    System.out.println("Get 4 Size: " + result4.size());


    ● Alternative checks for existence
    -------------------------------------------------------------------------------------------------------------------------------------
    Table 类有另外一种方法检查数据在表中是否存在,提供如下方法:

        boolean exists(Get get) throws IOException
        boolean[] existsAll(List<Get> gets) throws IOException;
    
    需要设定一个 Get 实例,就像使用 Table 的 get() 方法调用那样。只是验证某些数据是否存在,而不是从远程服务器上获取 cell 数据, 它们只返回
    一个 boolean 类型的标志值表示数据是否存在。实际上,这些调用是在 Get 实例上使用 Get.setCheckExistenceOnly(true) 调用的快捷方式。

    使用 Table.exists(), Table.existsAll(), 或者 Get.setCheckExistenceOnly() 在 region server 上的查找具有相同的语义,包括载入文件块来检查
    一个行或列是否真的存在。只是避免在网络上携带数据,但对于检查非常多的列时很有用,或者进行非常频繁的检查。
    
    
    Get         setClosestRowBefore(boolean closestRowBefore)
    boolean     isClosestRowBefore()

提供某些模糊的行匹配。假设有一个复杂的行键设计,利用多个字段组成的复合数据。可以仅仅匹配行键中从左到右的数据,再假设有几个前导字段,但没有
更多具体的数据。可以使用 get() 方法请求某个特定的行。可以用一个 scan 操作开始。使用 setClosestRowBefore() 方法设置 closestRowBefore 值为 true

示例:
    // Retrieves a row close to the requested, if necessary
    Get get1 = new Get(Bytes.toBytes("row3"));
    
    // Attempt to read a row that does not exist.
    get1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    Result result1 = table.get(get1);

    System.out.println("Get 1 isEmpty: " + result1.isEmpty());
    CellScanner scanner1 = result1.cellScanner();
    while (scanner1.advance()) {
    System.out.println("Get 1 Cell: " + scanner1.current());
    }

    Get get2 = new Get(Bytes.toBytes("row3"));
    get2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    get2.setClosestRowBefore(true);
    
    // Instruct the get() call to fall back to the previous row, if necessary.
    Result result2 = table.get(get2);
    
    System.out.println("Get 2 isEmpty: " + result2.isEmpty());
    CellScanner scanner2 = result2.cellScanner();
    while (scanner2.advance()) {
    System.out.println("Get 2 Cell: " + scanner2.current());
    }
    // Attempt to read a row that exists.
    Get get3 = new Get(Bytes.toBytes("row2"));
    get3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    get3.setClosestRowBefore(true);
    Result result3 = table.get(get3);
    
    System.out.println("Get 3 isEmpty: " + result3.isEmpty());
    CellScanner scanner3 = result3.cellScanner();
    while (scanner3.advance()) {
    System.out.println("Get 3 Cell: " + scanner3.current());
    }
    
    Get get4 = new Get(Bytes.toBytes("row2"));
    // Read exactly a row that exists.
    get4.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
    Result result4 = table.get(get4);
    
    System.out.println("Get 4 isEmpty: " + result4.isEmpty());
    CellScanner scanner4 = result4.cellScanner();
    while (scanner4.advance()) {
    System.out.println("Get 4 Cell: " + scanner4.current());
    }

    
    int     getMaxResultsPerColumnFamily()
    Get     setMaxResultsPerColumnFamily(int limit)
    
    int     getRowOffsetPerColumnFamily()
    Get     setRowOffsetPerColumnFamily(int offset)

前面的一对方法用户处理 get 请求返回的 cell 的最大数量。后面的一对方法用于在行上设置一个可选的偏移量。

    // Retrieves parts of a row with offset and limit
    Put put = new Put(Bytes.toBytes("row1"));
    for (int n = 1; n <= 1000; n++) {
        String num = String.format("%04d", n);
        put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual" +    num), Bytes.toBytes("val" + num));
    }
    table.put(put);
    
    Get get1 = new Get(Bytes.toBytes("row1"));
    // Ask for ten cells to be returned at most.
    get1.setMaxResultsPerColumnFamily(10);
    
    Result result1 = table.get(get1);
    CellScanner scanner1 = result1.cellScanner();
    while (scanner1.advance()) {
        System.out.println("Get 1 Cell: " + scanner1.current());
    }
    
    Get get2 = new Get(Bytes.toBytes("row1"));
    get2.setMaxResultsPerColumnFamily(10);
    // In addition, also skip the first 100 cells.
    get2.setRowOffsetPerColumnFamily(100);
    Result result2 = table.get(get2);
    
    CellScanner scanner2 = result2.cellScanner();
    while (scanner2.advance()) {
        System.out.println("Get 2 Cell: " + scanner2.current());
    }


■ Result 类 (The Result class)
-----------------------------------------------------------------------------------------------------------------------------------------
使用 get() 方法调用获取数据时,会接收一个 Result 类的实例,其中包含所有匹配的单元格(cell)。 利用 Result 提供的方法,可以访问从服务器端返
回的给定行和匹配特定查询的所有数据的详细信息,例如列族,列限定符,时间戳,等等。

Result 有很多工具方法可以用于获取特定结果,第一类方法如下:

    byte[] getRow()
    byte[] getValue(byte[] family, byte[] qualifier)
    byte[] value()
    ByteBuffer getValueAsByteBuffer(byte[] family, byte[] qualifier)
    ByteBuffer getValueAsByteBuffer(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
    boolean loadValue(byte[] family, byte[] qualifier, ByteBuffer dst) throws BufferOverflowException
    boolean loadValue(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength, ByteBuffer dst)
        throws BufferOverflowException
    CellScanner cellScanner()
    Cell[] rawCells()
    List<Cell> listCells()
    boolean isEmpty()
    int size()

getRow() 方法返回行键(row key), 调用 get() 方法时创建 Get 类实例时指定的行键值。size() 方法返回当前 Result 实例所包含 Cell 实例的数量。
可以使用这个调用,或者 isEmpty() 方法调用来检查自己的客户端代码,获取调用是否返回了一些匹配的数据。

getValue() 调用可以获取返回数据中某一特定的 cell 中的数据。由于不能在此调用中指定哪个时间戳,或者说,想要获取版本,因此得到的是最新版本
的数据。value() 调用返回找到的第一列中最新版本的数据。由于列在服务器上是按词汇顺序排序的,因此返回的列值首先是按列名(包括列族和限定符)
排序的。

还有其它一些单元格值的访问器,getValueAsByteBuffer() 和 loadValue(), 它们或者创建一个 Java ByteBuffer 对象,将数据的值封装到对象内的字节
数组中,或者将数据拷贝到提供的数据容器中。

访问原始的,低级别的 Cell 实例,通过 rawCells() 方法,返回当前 Result 实例内的 Cell 实例数组。listCells() 调用简单地将 rawCells() 调用
返回的数组转换到一个 List 实例中,给用户一个通过迭代器访问的便捷方法。Result 类也实现了 CellScannable 接口,因此可以直接地迭代访问其中
含有的 cell.


    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    rawCells() 返回的数组是已经按词汇顺序排过序的,顾及到了 Cell 实例的完全坐标(full coordinates). 因此首先按列族排序,然后在每个列族内按
    限定符排序,然后按时间戳,最后按类型 (type) 排序。


另一类访问器提供更加面向列的(column-oriented) 访问:

    List<Cell> getColumnCells(byte[] family, byte[] qualifier)
    Cell getColumnLatestCell(byte[] family, byte[] qualifier)
    Cell getColumnLatestCell(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
    boolean containsColumn(byte[] family, byte[] qualifier)
    boolean containsColumn(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
    boolean containsEmptyColumn(byte[] family, byte[] qualifier)
    boolean containsEmptyColumn(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
    boolean containsNonEmptyColumn(byte[] family, byte[] qualifier)
    boolean containsNonEmptyColumn(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)

通过 getColumnCells() 方法可以请求某个特定列的多个值,返回值版本的最大数量有之前调用 get() 方法时配置的 Get 实例界定,默认为 1.换句话说,
返回的列表可能包含 0 (给定 row 上的列没有值)或 1 个项目,意为值的最新版本。如果指定了返回大于默认的 1 个版本的值,列表可能包含任意数量的项目,
最多为指定的最大值。

getColumnLatestCell() 方法返回指定列的最新版本 cell, 但与 getValue() 相反,它不返回值的原始字节数组,而是完全的 Cell 实例。

containsColumn() 是检查某个特定的列内是否有 cell 返回的的便捷方法。

第三类访问返回数据的方法,是面向 map 的方法(map-oriented):

    NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap()
    NavigableMap<byte[], NavigableMap<byte[], byte[]>> getNoVersionMap()
    NavigableMap<byte[], byte[]> getFamilyMap(byte[] family)

最通用的方法时 getMap(), 在一个 Java Map 类实例中返回整个结果集,可以迭代访问所有的值。 getNoVersionMap() 做的是同样的事,只是仅仅包含每个列
最新版本的 cell. 最后,getFamilyMap() 方法可以只选择某个给定列族的数据,但包含所有版本。

不论使用 Result 的哪种访问方法来适应用户的需求,数据已经从服务器上通过网络传输到了客户端进程,因此不会导致性能或资源的负面影响。

最后,还有一些无法归为以上类别中的方法,参考 API javadoc 了解详情。



■ Get 列表 (List of Gets)
-----------------------------------------------------------------------------------------------------------------------------------------
另一个与 put() 调用相似的 get() 方法调用是用户可以用一次请求获取多行数据,它允许用户快速高效地从远程服务器获取相关联的,或者完全随机的多
行数据。

    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    实际上,请求有可能被发往多个不同的服务器,但这部分逻辑已经被封装起来。因此对于客户端代码来说,还是表现为一次请求。

方法签名:

    Result[] get(List<Get> gets) throws IOException
    
用户需要创建一个列表,并把准备好的 Get 实例添加到列表中,然后将列表传给 get(), 返回一个与 Get 列表数量相同的 Result 数组。

示例:

    // Example of retrieving data from HBase using lists of Get instances
    // Prepare commonly used byte arrays.
    byte[] cf1 = Bytes.toBytes("colfam1");
    byte[] qf1 = Bytes.toBytes("qual1");
    byte[] qf2 = Bytes.toBytes("qual2");
    byte[] row1 = Bytes.toBytes("row1");
    byte[] row2 = Bytes.toBytes("row2");
    
    // Create a list that holds the Get instances.
    List<Get> gets = new ArrayList<Get>();
    
    // Add the Get instances to the list.
    Get get1 = new Get(row1);
    get1.addColumn(cf1, qf1);
    gets.add(get1);
    
    Get get2 = new Get(row2);
    get2.addColumn(cf1, qf1);
    gets.add(get2);
    
    Get get3 = new Get(row2);
    get3.addColumn(cf1, qf2);
    gets.add(get3);
    
    // Retrieve rows with selected columns from HBase.
    Result[] results = table.get(gets);
    
    System.out.println("First iteration...");
    // Iterate over results and check what values are available.
    for (Result result : results) {
        String row = Bytes.toString(result.getRow());
        System.out.print("Row: " + row + " ");
        byte[] val = null;
        if (result.containsColumn(cf1, qf1)) {
            val = result.getValue(cf1, qf1);
            System.out.println("Value: " + Bytes.toString(val));
        }
        if (result.containsColumn(cf1, qf2)) {
            val = result.getValue(cf1, qf2);
            System.out.println("Value: " + Bytes.toString(val));
        }
    }
    System.out.println("Second iteration...");
    // Iterate over results again, printing out all values.
    for (Result result : results) {
    
        // Two different ways to access the cell data.
        for (Cell cell : result.listCells()) {
            System.out.println(
            "Row: " + Bytes.toString(
            cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())
            +
            " Value: " + Bytes.toString(CellUtil.cloneValue(cell)));
        }
    }
    System.out.println("Third iteration...");

    for (Result result : results) {
        System.out.println(result);
    }

运行代码,将看到显示类似如下的输出:

    First iteration...
    Row: row1 Value: val1
    Row: row2 Value: val2
    Row: row2 Value: val3
    Second iteration...
    Row: row1 Value: val1
    Row: row2 Value: val2
    Row: row2 Value: val3
    Third iteration...
    keyvalues={row1/colfam1:qual1/1426678215864/Put/vlen=4/seqid=0}
    keyvalues={row2/colfam1:qual1/1426678215864/Put/vlen=4/seqid=0}
    keyvalues={row2/colfam1:qual2/1426678215864/Put/vlen=4/seqid=0}

两次遍历返回了同样的值,说明从服务器端得到结果之后,用户可以有多种方式访问结果。

Get 列表 get() 调用返回异常的方法与 Put 列表 put() 调用返回异常不同,get() 方法要么返回与给定的 Get 列表大小相同的 Result 数组,要么抛出
异常。下面示例展示了这种行为:

    // Example trying to read an erroneous column family
    List<Get> gets = new ArrayList<Get>();
    
    // Add the Get instances to the list.
    Get get1 = new Get(row1);
    get1.addColumn(cf1, qf1);
    gets.add(get1);
    
    Get get2 = new Get(row2);
    get2.addColumn(cf1, qf1);
    gets.add(get2);
    
    Get get3 = new Get(row2);
    get3.addColumn(cf1, qf2);
    gets.add(get3);
    
    Get get4 = new Get(row2);
    // Add the bogus column family get.
    get4.addColumn(Bytes.toBytes("BOGUS"), qf2);
    gets.add(get4);
    
    // An exception is thrown and the process is aborted.
    Result[] results = table.get(gets);
    
    // This line will never reached!
    System.out.println("Result count: " + results.length);

执行这个例子会导致整个 get() 操作终止,程序会抛出类似下面这样的异常,并且没有返回值:

    org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException:
    Failed 1 action: NoSuchColumnFamilyException: 1 time,
    servers with issues: 10.0.0.57:51640,
    Exception in thread "main" \
    org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException:
    \
    Failed 1 action: \
    org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException:
    \
    Column family BOGUS does not exist in region \
    testtable,,1426678215640.de657eebc8e3422376e918ed77fc33ba. \
    in table 'testtable', {NAME => 'colfam1', ...}
    at org.apache.hadoop.hbase.regionserver.HRegion.checkFamily(...)
    at org.apache.hadoop.hbase.regionserver.HRegion.get(...)
    ...

对于批量操作中的局部错误,有一种更精细的处理方法。即使用 batch() 方法。



3.3 删除方法 (Delete Method)
-----------------------------------------------------------------------------------------------------------------------------------------
delete() 方法由几个变体,一个用于单行删除,一个接受 Delete 列表参数删除,另外一个提供原子性的,服务端 check-and-delete.


■ 单行删除 (Single Deletes)
-----------------------------------------------------------------------------------------------------------------------------------------
单行 delete() 方法调用接受一个 Delete 实例:

    void delete(Delete delete) throws IOException

与 get() 和 put() 方法调用类似,需要创建一个 Delete 实例,然后向要删除的数据添加详细信息,构造器如下:

    Delete(byte[] row)
    Delete(byte[] row, long timestamp)
    Delete(final byte[] rowArray, final int rowOffset, final int rowLength)
    Delete(final byte[] rowArray, final int rowOffset, final int rowLength, long ts)
    Delete(final Delete d)

需要为 Delete 提供要修改的行,以及可选地指定要操作的版本/时间戳。

此外,应进一步限定要删除数据的详细信息,使用如下方法:

    Delete addFamily(final byte[] family)
    Delete addFamily(final byte[] family, final long timestamp)
    Delete addFamilyVersion(final byte[] family, final long timestamp)
    Delete addColumns(final byte[] family, final byte[] qualifier)
    Delete addColumns(final byte[] family, final byte[] qualifier, final long timestamp)
    Delete addColumn(final byte[] family, final byte[] qualifier)
    Delete addColumn(byte[] family, byte[] qualifier, long timestamp)
    void setTimestamp(long timestamp)

有四类方法可用于限定删除数据所涉及的范围。首先可以通过 addFamily() 方法删除整个列族,包括其中所含有的所有列。一种类型是通过 addColumns(),
将操作限定在某一个准确的列上。地三种类型类似,使用 addColumn(), 操作限定在一个特定的列上,但删除最新或者指定版本的数据。

最后一种是 setTimestamp(), 它允许设定一个时间戳,用于后续的 addXYZ() 调用。实际上,利用一个 Delete 构造器接受一个显式的时间戳参数,只是在
创建了一个 Delete 实例之后调用了 setTimestamp(). 一旦实例范围的时间戳设定好,所有进一步的操作都会利用此时间戳。

对于高级用户,还有另外的方法可用:

    Delete addDeleteMarker(Cell kv) throws IOException
    
这个调用检查提供的 Cell 实例为删除类型,行键匹配当前的一个 Delete 实例。

示例:使用单行 delete() 调用
    // Example application deleting data from HBase
    // Create delete with specific row.
    Delete delete = new Delete(Bytes.toBytes("row1"));
    // Set timestamp for row deletes.
    delete.setTimestamp(1);
    // Delete the latest version only in one column
    delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));

    // Delete specific version in one column
    delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"), 3);

    // Delete all versions in one column.
    delete.addColumns(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));

    //Delete the given and all older versions in one column.
    delete.addColumns(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"), 2);

    // Delete entire family, all columns and versions.
    delete.addFamily(Bytes.toBytes("colfam1"));

    // Delete the given and all older versions in the entire column family, i.e., from all columns therein.
    delete.addFamily(Bytes.toBytes("colfam1"), 3);

    // Delete the data from the HBase table.
    table.delete(delete);

示例中列举了用户通过设定不同的参数操作 delete() 方法。实际应用中没什么意义,可以随意注释掉一些调用,观察控制台显示结果变化。
删除操作所设定的时间戳只对匹配的单元格有影响,即匹配给定时间戳的列和值。另一方面,如果不设定时间戳,服务器会强制检索服务器端最新的时间戳,
这比执行一个具有明确时间戳的删除要慢。

如果尝试删除一个不存在的时间戳的 cell, 什么也不会发生。例如,某一列有两个版本,版本 10 和版本 20,删除版本 15 将不会影响现存的任何版本。


■ Delete 列表 (List of Deletes)
-----------------------------------------------------------------------------------------------------------------------------------------
基于列表的 delete() 调用与基于列表的 put 操作类似,需要创建一个 Delete 实例的列表,配置这些实例,然后调用如下方法:

    void delete(List<Delete> deletes) throws IOException

下面示例展示了三个不同行的删除操作,删除了它们所包含的各种细节。运行这个示例,会看到打印出删除之前和之后的状态。

    NOTE:
    -------------------------------------------------------------------------------------------------------------------------------------
    正如其它基于列表的操作,用户不能对删除操作在远程服务器上的执行顺序做任何假设。 API 会重新安排它们,并将同一个 region server 的操作集中
    到一个 RPC 请求中以提升性能。如果需要确保执行的顺序,用户需要把这些调用分成更小的组,并控制组之间的执行顺序。最坏的情况下,需要发送单独
    的 Delete 调用以保证执行顺序。
    
    
    // Example application deleting lists of data from HBase

    // Create a list that holds the Delete instances.
    List<Delete> deletes = new ArrayList<Delete>();

    Delete delete1 = new Delete(Bytes.toBytes("row1"));
    // Set timestamp for row deletes
    delete1.setTimestamp(4);
    deletes.add(delete1);

    Delete delete2 = new Delete(Bytes.toBytes("row2"));
    // Delete the latest version only in one column
    delete2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));

    // Delete the given and all older versions in another column.
    delete2.addColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), 5);
    deletes.add(delete2);

    Delete delete3 = new Delete(Bytes.toBytes("row3"));

    // Delete entire family, all columns and versions
    delete3.addFamily(Bytes.toBytes("colfam1"));

    // Delete the given and all older versions in the entire column family, i.e., from all columns therein.
    delete3.addFamily(Bytes.toBytes("colfam2"), 3);
    deletes.add(delete3);

    // Delete the data from multiple rows the HBase table.
    table.delete(deletes);

输出类似如下:

    Before delete call...
    Cell: row1/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row1/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row1/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row1/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row1/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row1/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row1/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row1/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row2/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row2/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row2/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row2/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row2/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row2/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row2/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row2/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row2/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row2/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row2/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row2/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row3/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row3/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row3/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row3/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row3/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row3/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row3/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row3/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row3/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row3/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
    After delete call...
    Cell: row1/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row1/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row1/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row1/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row2/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row2/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row2/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row2/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row2/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Cell: row2/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row2/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row2/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row2/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row2/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row3/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
    Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5

示例代码的执行过程中,使用以下方法可以看到 Cell 的详细情况:

    System.out.println("Cell: " + cell + ", Value: " +
    Bytes.toString(cell.getValueArray(), cell.getValueOffset(),
    cell.getValueLength()));


最后要介绍的是基于列表的 delete () 操作的异常处理。提交的 deletes 参数,就是 Delete 实例列表,在调用返回时,被修改为值包含失败的 Delete 实例。
换句话说,如果每个操作都成功完成了,这个列表是空的。如果有从远程服务器上报告的异常,delete() 调用会抛出该异常。应使用 try/catch 块保护delete
调用。

示例:修改自上一个示例

    // Example deleting faulty data from HBase
    Delete delete4 = new Delete(Bytes.toBytes("row2"));
    
    // Add bogus column family to trigger an error.
    delete4.addColumn(Bytes.toBytes("BOGUS"), Bytes.toBytes("qual1"));
    deletes.add(delete4);
    
    try {
        // Delete the data from multiple rows the HBase table.
        table.delete(deletes);
    } catch (Exception e) {
        // Guard against remote exceptions
        System.err.println("Error: " + e);
    }
    table.close();
    
    // Check the length of the list after the call.
    System.out.println("Deletes length: " + deletes.size());
    for (Delete delete : deletes) {
        
        // Print out failed delete for debugging purposes.
        System.out.println(delete);
    }

运行示例,输出类似如下的结果:

    Before delete call...
    Cell: row1/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    ...
    Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
    Deletes length: 1
    Error: org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException:
    \
    Failed 1 action: \
    org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException:
    \
    Column family BOGUS does not exist ...
    ...
    : 1 time,
    {"ts":9223372036854775807,"totalColumns":1,"families":{"BOGUS":[{ \
    "timestamp":9223372036854775807,"tag":[],"qualifier":"
    qual1","vlen":0}]}, \
    "row":"row2"}
    After delete call...
    Cell: row1/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row1/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
    ...
    Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
    Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5

正如所期望的,列表中含有一个剩下的 Delete 实例:bogus 列族的那个 Delete.



■ 原子性操作 Check-and-Delete (Atomic Check-and-Delete)
-----------------------------------------------------------------------------------------------------------------------------------------
之前讨论过 “Atomic Check-and-Put”, 对于 delete() 操作也有相应服务器端的,read-modify-write 功能:

    boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException
    boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, Delete delete)
        throws IOException

在实际的删除操作执行之前,要给定 row key, column family, qualifier, 以及要比较的 value. 第一个调用意为使用给定的 value 与存储的值进行相等
比较(equal), 第二个调用指定一个实际的比较器,可以进行更复杂灵活的检查测试,例如,给定的 value 相等或小于存储的值。

如果测试失败,什么也不会删除,并且调用返回 false. 如果检查成功,删除会应用并返回 true.

示例:

    // Example application using the atomic Check-and-Delete operations
    
    // Create a new Delete instance
    Delete delete1 = new Delete(Bytes.toBytes("row1"));
    delete1.addColumns(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"));
    
    // Check if column does not exist and perform optional delete operation.
    boolean res1 = table.checkAndDelete(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), null, delete1);
    // Print out the result, should be “Delete successful: false”.
    System.out.println("Delete 1 successful: " + res1);
    
    // Delete checked column manually
    Delete delete2 = new Delete(Bytes.toBytes("row1"));
    delete2.addColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"));
    table.delete(delete2);
    
    // Attempt to delete same cell again.
    boolean res2 = table.checkAndDelete(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), null, delete1);
    // Print out the result, should be “Delete successful: true” since the checked column now is gone.
    System.out.println("Delete 2 successful: " + res2);
    
    // Create yet another Delete instance, but using a different row
    Delete delete3 = new Delete(Bytes.toBytes("row2"));
    delete3.addFamily(Bytes.toBytes("colfam1"));
    try{
        // Try to delete while checking a different row.
        boolean res4 = table.checkAndDelete(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"), delete3);
        
        // We will not get here as an exception is thrown beforehand
        System.out.println("Delete 3 successful: " + res4);
    } catch (Exception e) {
        System.err.println("Error: " + e.getMessage());
    }

运行示例,会看到类似如下的输出:
    Before delete call...
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam2:qual3/3/Put/vlen=4/seqid=0, Value: val3
    Delete 1 successful: false
    Delete 2 successful: true
    Error: org.apache.hadoop.hbase.DoNotRetryIOException: \
    Action's getRow must match the passed row
    ...
    After delete call...
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2

使用 null 作为 value 参数的值会进行不存在(nonexistence)测试,如果指定的列不存在,测试成功。

与此前关于 CAS 调用一样,只能对同一行数据进行检查并删除。示例中尝试检查一个行键,而提供的 Delete 实例却指向另一个行键,因此会抛出异常。但
检查允许跨多个列族,例如,可以使用一组列族控制筛选其它的列。



3.4 Append 方法 (Append Method)
-----------------------------------------------------------------------------------------------------------------------------------------
与一般的 CRUD 功能类似,有另一种数据操作的方法调用。不是创建或更新某个列的值, append() method 执行原子性的 read-modify-write 操作,将数
据添加到某个列上:

    Result append(final Append append) throws IOException

这个调用接受一个 Append 类实例,可以通过如下构造器创建:

    Append(byte[] row)
    Append(final byte[] rowArray, final int rowOffset, final int rowLength)
    Append(Append a)

或者提供一个明显的行键,或者一个已存在的,更大的字节数组持有行键的子集,传入必要的偏移量(rowOffset) 和长度值。第三个构造器传入一个已存在的
Append 实例,并从其中拷贝所有参数。

创建了 Append 实例之后,可以利用如下方法添加细节信息:

    Append add(byte[] family, byte[] qualifier, byte[] value)
    Append add(final Cell cell)

类似于 Put 实例,必须调用其中一个方法,否则之后调用 append() 方法调用会抛出异常。第一个方法接受列族和限定符名称,以及要追加的的值。第二个方
法从已存在的 cell 实例拷贝这些参数。

示例:
    //Example application appending data to a column in HBase
    Append append = new Append(Bytes.toBytes("row1"));
    append.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
    Bytes.toBytes("newvalue"));
    append.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
    Bytes.toBytes("anothervalue"));
    table.append(append);

输出类似如下:
    Before append call...
    Cell: row1/colfam1:qual1/1/Put/vlen=8/seqid=0, Value: oldvalue
    After append call...
    Cell: row1/colfam1:qual1/1426778944272/Put/vlen=16/seqid=0,
    Value: oldvaluenewvalue
    Cell: row1/colfam1:qual1/1/Put/vlen=8/seqid=0, Value: oldvalue
    Cell: row1/colfam1:qual2/1426778944272/Put/vlen=12/seqid=0,
    Value: anothervalue

append() 方法有一个特殊选项可以设置不从服务器返回数据:

    Append setReturnResults(boolean returnResults)
    boolean isReturnResults()

通常,新更新的 cell 会返回给调用者。但如果想将 append() 发送到服务器,并不关心其返回的结果,可以调用 setReturnResults( false) 来忽略携带的结果,
此时会返回 null.


3.5 Mutate 方法 (Mutate Method)
-----------------------------------------------------------------------------------------------------------------------------------------


■ 单行 Mutations (Single Mutations)
-----------------------------------------------------------------------------------------------------------------------------------------
到目前为止,所有的操作在 Table 中都有它们特定的方法,以及数据相关的类型。但如果想要跨操作并自动执行某一个行的更新,就需要 mutateRow()调用

    void mutateRow(final RowMutations rm) throws IOException
    
RowMutations 参数是一个容器,它接受 Put 或 Delete 实例参数,然后在一次调用中在服务器端数据上应用这两种操作。

RowMutations : Performs multiple mutations atomically on a single row

    RowMutations(byte[] row)
    add(Delete)
    add(Put)
    getMutations()
    getRow()

使用一个特定的行键创建 RowMutations 实例,然后向其添加任意的 delete 或 put 实例。用于创建 RowMutations 实例的行键必须与添加的任何 mutation
行键相匹配,否则在尝试添加时会收到异常。

示例:

    // Modifies a row with multiple operations
    Put put = new Put(Bytes.toBytes("row1"));
    put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), 4, Bytes.toBytes("val99"));
    put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual4"), 4, Bytes.toBytes("val100"));
    
    Delete delete = new Delete(Bytes.toBytes("row1"));
    delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"));
    
    RowMutations mutations = new RowMutations(Bytes.toBytes("row1"));
    mutations.add(put);
    mutations.add(delete);
    
    table.mutateRow(mutations);

输出类似如下:
    Before delete call...
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
    After mutate call...
    Cell: row1/colfam1:qual1/4/Put/vlen=5/seqid=0, Value: val99
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row1/colfam1:qual4/4/Put/vlen=6/seqid=0, Value: val100


■ 原子性操作 Check-and-Mutate (Atomic Check-and-Mutate)
-----------------------------------------------------------------------------------------------------------------------------------------
对于 mutation 操作也有访问服务器端的,read-modify-write 功能设计:

    public boolean checkAndMutate(final byte[] row, final byte[] family, final byte[] qualifier, final CompareOp compareOp,
        final byte[] value, final RowMutations rm) throws IOException

在实际的 mutation 操作应用之前,需要制定 row key, column family, qualifier, 以及要检查的 value。该调用让用户指定实际的比较器,提供更灵活的
检查测试,例如使给定的 value 等于(equal) 或小于存储的值。

如果测试失败,什么也不会应用并且调用返回 false 值。如果检查成功,mutation 操作会应用并返回 true.

示例:

    // Example using the atomic check-and-mutate operations
    Put put = new Put(Bytes.toBytes("row1"));
    put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), 4, Bytes.toBytes("val99"));
    put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual4"), 4, Bytes.toBytes("val100"));
    
    Delete delete = new Delete(Bytes.toBytes("row1"));
    delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"));
    RowMutations mutations = new RowMutations(Bytes.toBytes("row1"));
    mutations.add(put);
    mutations.add(delete);
    
    // Check if the column contains a value that is less than “val1”. Here we receive “false” as the value is equal, but not lesser.
    boolean res1 = table.checkAndMutate(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual1"),
        CompareFilter.CompareOp.LESS, Bytes.toBytes("val1"), mutations);
    System.out.println("Mutate 1 successful: " + res1);
    
    // Update the checked column to have a value greater than what we check for.
    Put put2 = new Put(Bytes.toBytes("row1"));
    put2.addColumn(Bytes.toBytes("colfam2"), Bytes.toBytes("qual1"), 4, Bytes.toBytes("val2"));
    table.put(put2);
    
    // Now “val1” is less than “val2” (binary comparison) and we expect “true” to be printed on the console.
    boolean res2 = table.checkAndMutate(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual1"),
        CompareFilter.CompareOp.LESS, Bytes.toBytes("val1"), mutations);
    System.out.println("Mutate 2 successful: " + res2);
    
输出:

    Before check and mutate calls...
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam2:qual3/3/Put/vlen=4/seqid=0, Value: val3
    Mutate 1 successful: false
    Mutate 2 successful: true
    After check and mutate calls...
    Cell: row1/colfam1:qual1/4/Put/vlen=5/seqid=0, Value: val99
    Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
    Cell: row1/colfam1:qual4/4/Put/vlen=6/seqid=0, Value: val100
    Cell: row1/colfam2:qual1/4/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
    Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
    Cell: row1/colfam2:qual3/3/Put/vlen=4/seqid=0, Value: val3

与之前的操作类似,将 value 参数设定为 null 测试不存在,如果指定的列不存在,检查成功。
与 put 或 delete 的 CAS 调用类似,只能在同一行上执行 check-and-modify 操作。

 

系列目录:

HBase CRUD 操作指南 (一)

HBase CRUD 操作指南 (二)

HBase CRUD 操作指南 (三)

 

参考:

    《HBase - The Definitive Guide - 2nd Edition》Early release —— 2015.7 Lars George

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值