Apache HBase 是 Java 语言编写的一款 Apache 开源的 NoSQL 型数据库,不支持 SQL,不支持事务,不支持 Join 操作,没有表关系。Apache HBase 构建在 Apache Hadoop 和 Apache Zookeeper 之上。

Apache HBase: https://hbase.apache.org/

HBase 的安装配置,请参考 “Springboot 系列 (24) - Springboot+HBase 大数据存储(二)| 安装配置 Apache HBase 和 Apache Zookeeper”。

本文将创建一个 SpringBoot 项目,演示通过 RestTemplate 访问 “Springboot 系列 (27) - Springboot+HBase 大数据存储(五)| HBase REST 服务” 里通过 curl 命令测试过的 HBase REST 服务。

 

1. 系统环境

    操作系统:Ubuntu 20.04
    Hadoop 版本:3.2.2
    Zookeeper 版本:3.6.3
    HBase 版本:2.4.4
    HBase 所在路径:~/apps/hbase-2.4.4/

    本文使用的 HBase 部署在伪分布式 Hadoop 架构上(主机名:hadoop-master-vm),在 HBase + Zookeeper (独立的) 模式下运行,Zookeeper 使用端口 2182,HBase REST 服务使用端口 8888。

 

2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample22
    Spring Boot 版本:2.6.6

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample22;
        (2) Spring Boot Web 配置;
        (3) 导入 Thymeleaf 依赖包;
        (4) 配置 jQuery;
        
    具体操作请参考 “Springboot 系列 (2) - 在 Spring Boot 项目里使用 Thymeleaf、JQuery+Bootstrap 和国际化” 里的项目实例 SpringbootExample02,文末包含如何使用 spring-boot-maven-plugin 插件运行打包的内容。

    SpringbootExample22 和 SpringbootExample02 相比,SpringbootExample22 不配置 Bootstrap、模版文件(templates/*.html)和国际化。

3. 配置 RestTemplate

    RestTemplate 默认配置是使用了 JDK 自带的 HttpURLConnection 作为底层 HTTP 客户端实现。可以通过 setRequestFactory 属性来切换使用不同的 HTTP 库,如 Apache HttpComponents、OkHttp、Netty 等。
    
    如何切换 RestTemplate 的底层库,可以参考 “Springboot 系列 (18) - 在 Spring Boot 项目里使用 RestTemplate 访问 REST 服务”。

    1) 使用默认配置
    
        创建 src/main/java/com/example/config/RestTemplateConfig.java 文件

package com.example.config;

            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;
            import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
            import org.springframework.web.client.RestTemplate;

            @Configuration
            public class RestTemplateConfig {

                @ConditionalOnMissingBean(RestTemplate.class)
                @Bean
                public RestTemplate restTemplate(){
                    RestTemplate restTemplate = new RestTemplate();
                    return restTemplate;
                }
            }

    2) 修改 src/main/resources/application.properties 文件,内容如下

spring.main.banner-mode=off

        # Web server
        server.display-name=SpringbootExample22
        server.address=localhost
        server.port=9090

        # HBase Rest Server
        hbase.rest.url=http://hadoop-master-vm:8888

        注:这里 hbase.rest.url 使用了 REST 服务器所在的主机名 hadoop-master-vm,可以替换成 REST  服务器所在主机的 IP 地址。

    3) 运行

        Edit Configurations

            Click "+" add new configuration -> Select "Maven"

                Command line: clean spring-boot:run
                Name: SpringbootExample22 [clean,spring-boot:run]

            -> Apply / OK

        Click Run "SpringbootExample22 [clean,spring-boot:run]"

            ...

            Spring boot example project       

        访问 http://localhost:9090/test

            Test Page

        注:打包可以将 Command line 改成 clean package spring-boot:repackage

4. 测试实例 (Web 模式)

    创建的一个 demo 表,demo 表包含 cf1,cf2 两个列族 (Column Family) ,我们将向 demo 表添加如下数据:

id

name

age

job

row1

Tom

12

Student

row2

Jerry

9

Engineer

row3

Jerry

10

Engineer

    REST 接口在操作数据时,会对 key、column、value 等值进行 Base64 编解码,可以运行如下命令编解码:

        $ echo -ne "Tom" | base64   # 编码

            VG9t

        $ echo -ne "VG9t" | base64 -d   # 解码

            Tom

        注:echo 的 -n 表示不换行输出,-e 表示处理特殊字符。

    以下是操作数据时使用到的 Base64 编码列表:

原值

编码后

cf1:name

Y2YxOm5hbWU=

cf1:age

Y2YxOmFnZQ==

cf2:job

Y2YyOmpvYg==

row1

cm93MQ==

row2

cm93Mg==

row3

cm93Mw==

Tom

VG9t

Jerry

SmVycnk=

12

MTI=

9

OQ==

10

MTA=

Student

U3R1ZGVudA==

Engineer

RW5naW5lZXI=


    1) 创建 src/main/resources/templates/client.html 文件

<html lang="en" xmlns:th="http://www.thymeleaf.org">
        <head>
            <meta charset="UTF-8">
            <title th:text="${var}">Client</title>
            <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}"></script>
        </head>
        <body>

        <h4>HBase REST API - Client</h4>
        <p> </p>

        <p>
            <label><strong>REST API:</strong></label><br>
            <select id="otype" style="width: 50%;">
                <option value=""></option>
                <option value="hbase_info">1. HBase 信息</option>
                <option value="table_operation">2. Table 操作</option>
                <option value="data_add">3. 添加数据</option>
                <option value="data_query_get">4. 查询数据 -> GET 操作</option>
                <option value="data_query_scanner">5. 查询数据 -> 无状态 Scanner</option>
                <option value="data_delete">6. 删除数据</option>
                <option value="namespace_operation">7. Namespace 操作</option>
            </select><br><br>

            <button type="button" id="btn_exec" class="btn btn-default btn-sm">Execute</button>
        </p>

        <p> </p>

        <div id="result_area" style="padding: 15px; width: 80%;  font-size: 12px; min-height: 120px;">
        </div>

        <script type="text/javascript">
            $(document).ready(function() {

                $("#otype").change(function(e) {
                    $('#result_area').html("");
                });

                $("#btn_exec").click(function(e) {

                    var otype = $("#otype").val();
                    if (otype == '') {
                        alert("Please select REST API");
                        $("#otype").focus();
                        return;
                    }

                    var url = "";
                    switch(otype) {
                        case "hbase_info":
                            url = "/hbase"
                            break;
                        case "table_operation":
                            url = "/table"
                            break;
                        case "data_add":
                            url = "/data/add"
                            break;
                        case "data_query_get":
                            url = "/data/query/get"
                            break;
                        case "data_query_scanner":
                            url = "/data/query/scanner"
                            break;
                        case "data_delete":
                            url = "/data/delete"
                            break;
                        case "namespace_operation":
                            url = "/namespace"
                            break;
                        default:
                            break;
                    }

                    $('#result_area').append("Executing ...<br><br>");
                    $('#btn_exec').prop('disabled', true);

                    $.ajax({
                        type: 'GET',
                        url: url,
                        timeout: 10000,
                        success: function(response) {

                            //console.log(response);
                            $('#result_area').append(response + "<br>");
                            //$('#result_area').append(JSON.stringify(response + "<br>"));

                            $('#btn_exec').prop('disabled', false);

                        },
                        error: function(err) {

                            console.log(err);
                            $('#result_area').append('AJAX Error: ' + err.statusText + " " + err.status);

                            $('#btn_exec').prop('disabled', false);

                        }
                    });

                });
            });
        </script>
        </body>
        </html>

    2) 修改 src/main/java/com/example/controller/IndexController.java 文件

package com.example.controller;

        import java.util.ArrayList;

        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.http.*;
        import org.springframework.stereotype.Controller;
        //import org.springframework.util.LinkedMultiValueMap;
        import org.springframework.util.MultiValueMap;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.ResponseBody;
        import org.springframework.web.client.RestTemplate;

        @Controller
        public class IndexController {
            @Autowired
            private RestTemplate restTemplate;
            @Value("${hbase.rest.url}")
            private String hbaseRestUrl;

            @ResponseBody
            @RequestMapping("/test")
            public String test() {
                return "Test Page";
            }

            @RequestMapping("/client")
            public String client() {
                return "client";
            }

            @ResponseBody
            @RequestMapping("/hbase")
            public String hbase() {

                // 显示 HBase 版本
                String url = hbaseRestUrl + "/version/cluster";
                System.out.println("url = " + url);
                ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
                String str = "显示 HBase 版本 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 显示 HBase 群集状态
                url = hbaseRestUrl + "/status/cluster";
                responseEntity = restTemplate.getForEntity(url, String.class);
                str += "显示 HBase 群集状态 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br>";

                return str;
            }

            @ResponseBody
            @RequestMapping("/table")
            public String table() {

                // 头部信息
                //MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
                //headers.add("Accept", "application/json");
                //headers.add("Content-Type", "application/json");

                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);

                ArrayList<MediaType> acceptList = new ArrayList<>();
                acceptList.add(MediaType.APPLICATION_JSON);
                httpHeaders.setAccept(acceptList);

                // 参数信息
                //MultiValueMap<String, String> mapParams = new LinkedMultiValueMap<>();
                //HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(mapParams, headers);

                // 查看所有表
                HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(null, httpHeaders);
                ResponseEntity<String> responseEntity = restTemplate.exchange(hbaseRestUrl, HttpMethod.GET, httpEntity, String.class);
                String str = "查看所有表 -> " + hbaseRestUrl + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 创建 demo 表 (1 个列族 cf1)
                String url = hbaseRestUrl + "/demo/schema";
                String jsonParams = "{\"name\":\"demo\",\"ColumnSchema\":[{\"name\":\"cf1\"}]}";

                HttpEntity<String> httpEntity2 = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity2, String.class);
                str += "创建 demo 表 (1 个列族 cf1) -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 添加 2 个列族 cf2 和 cf3
                jsonParams = "{\"name\":\"demo\",\"ColumnSchema\":[{\"name\":\"cf2\"},{\"name\":\"cf3\"}]}";

                HttpEntity<String> httpEntity3 = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity3, String.class);
                str += "添加 2 个列族 cf2 和 cf3 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 删除 cf3 列族,不是使用 POST,应该使用 PUT 替换表结构
                jsonParams = "{\"name\":\"demo\",\"ColumnSchema\":[{\"name\":\"cf1\"},{\"name\":\"cf2\"}]}";

                HttpEntity<String> httpEntity4 = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity4, String.class);
                str += "删除 cf3 列族 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 修改 cf1 列族的 VERSIONS 为 2,默认是 1 (即只保留最后一个版本)
                jsonParams = "{\"name\":\"demo\",\"ColumnSchema\":[{\"name\":\"cf1\",\"VERSIONS\":\"2\"}]}";

                HttpEntity<String> httpEntity5 = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity5, String.class);
                str += "修改 cf1 列族的 VERSIONS 为 2,默认是 1 (即只保留最后一个版本) -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 显示 demo 表结构信息
                responseEntity = restTemplate.getForEntity(url, String.class);
                str += "显示 demo 表结构信息 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 显示 demo 表分区
                String url2 = hbaseRestUrl + "/demo/regions";
                responseEntity = restTemplate.getForEntity(url2, String.class);
                str += "显示 demo 表分区 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                return str;
            }

            @ResponseBody
            @RequestMapping("/data/add")
            public String dataAdd() {

                // 查看 demo 表是否存在
                String url = hbaseRestUrl + "/demo/exists";
                String str = "查看 demo 表是否存在 -> " + url + "<br>";

                try {
                    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
                    str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                    str += "Result: " + responseEntity.getBody() + "<br><br>";
                } catch (Exception e) {
                    str += "Error: " + e.getMessage();
                    return str;
                }

                // 头部信息
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);

                ArrayList<MediaType> acceptList = new ArrayList<>();
                acceptList.add(MediaType.APPLICATION_JSON);
                httpHeaders.setAccept(acceptList);

                // 添加 row1 的 name、age 数据
                url = hbaseRestUrl + "/demo/row1";
                String jsonParams = "{\"Row\":[{\"key\":\"cm93MQ==\",\"Cell\":[{\"column\":\"Y2YxOm5hbWU=\",\"$\":\"VG9t\"},{\"column\":\"Y2YxOmFnZQ==\",\"$\":\"MTI=\"}]}]}";

                HttpEntity<String> httpEntity = new HttpEntity<>(jsonParams, httpHeaders);
                ResponseEntity<String> responseEntity2 = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
                str += "添加 row1 的 name、age 数据 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 添加 row1 的 job 数据
                jsonParams = "{\"Row\":[{\"key\":\"cm93MQ==\",\"Cell\":[{\"column\":\"Y2YyOmpvYg==\",\"$\":\"U3R1ZGVudA==\"}]}]}";

                httpEntity = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
                str += "添加 row1 的 job 数据 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 添加 row2 的 name、age 和 job 数据
                url = hbaseRestUrl + "/demo/row2";
                jsonParams = "{\"Row\":[{\"key\":\"cm93Mg==\",\"Cell\":[{\"column\":\"Y2YxOm5hbWU=\",\"$\":\"SmVycnk=\"},{\"column\":\"Y2YxOmFnZQ==\",\"$\":\"OQ==\"},{\"column\":\"Y2YyOmpvYg==\",\"$\":\"RW5naW5lZXI=\"}]}]}";

                httpEntity = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
                str += "添加 row2 的 name、age 和 job 数据 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 再次添加 row2 的 age 数据, 值为 10 (Cell 支持 2 个版本,这里不会替换原版本 9,以时间戳为区分,生成一个新版本)
                jsonParams = "{\"Row\":[{\"key\":\"cm93Mg==\",\"Cell\":[{\"column\":\"Y2YxOmFnZQ==\",\"$\":\"MTA=\"}]}]}";

                httpEntity = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
                str += "再次添加 row2 的 age 数据, 值为 10 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 添加 row3 的 name、age 和 job 数据
                url = hbaseRestUrl + "/demo/row3";
                jsonParams = "{\"Row\":[{\"key\":\"cm93Mw==\",\"Cell\":[{\"column\":\"Y2YxOm5hbWU=\",\"$\":\"SmVycnk=\"},{\"column\":\"Y2YxOmFnZQ==\",\"$\":\"MTA=\"},{\"column\":\"Y2YyOmpvYg==\",\"$\":\"RW5naW5lZXI=\"}]}]}";

                httpEntity = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
                str += "添加 row3 的 name、age 和 job 数据 -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 查看 row3 数据
                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
                str += "查看 row3 数据 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                return str;
            }

            @ResponseBody
            @RequestMapping("/data/query/get")
            public String dataQueryGet() {

                // 查看 demo 表是否存在
                String url = hbaseRestUrl + "/demo/exists";
                String str = "查看 demo 表是否存在 -> " + url + "<br>";

                try {
                    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
                    str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                    str += "Result: " + responseEntity.getBody() + "<br><br>";
                } catch (Exception e) {
                    str += "Error: " + e.getMessage();
                    return str;
                }

                // 获取 row2 的全部数据
                url = hbaseRestUrl + "/demo/row2";
                ResponseEntity<String> responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "获取 row2 的全部数据 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 获取 row2 的 cf1 列族的全部数据
                url = hbaseRestUrl + "/demo/row2/cf1";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "获取 row2 的 cf1 列族的全部数据 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 获取 row2 的 cf1 列族的 age 数据
                url = hbaseRestUrl + "/demo/row2/cf1:age";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "获取 row2 的 cf1 列族的 age 数据 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 获取 row2 的 age、job 数据 (多列)
                url = hbaseRestUrl + "/demo/row2/cf1:age,cf2:job";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "获取 row2 的 age、job 数据 (多列) -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 获取 row2 的 age 数据的最新 2 个版本
                url = hbaseRestUrl + "/demo/row2/cf1:age?v=2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "获取 row2 的 age 数据的最新 2 个版本 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                return str;
            }

            @ResponseBody
            @RequestMapping("/data/query/scanner")
            public String dataQueryScanner() {

                // 查看 demo 表是否存在
                String url = hbaseRestUrl + "/demo/exists";
                String str = "查看 demo 表是否存在 -> " + url + "<br>";

                try {
                    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
                    str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                    str += "Result: " + responseEntity.getBody() + "<br><br>";
                } catch (Exception e) {
                    str += "Error: " + e.getMessage();
                    return str;
                }

                // 扫描整个 demo 表
                url = hbaseRestUrl + "/demo/*";
                ResponseEntity<String> responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描整个 demo 表 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描 cf2 列簇
                url = hbaseRestUrl + "/demo/*/cf2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描 cf2 列簇 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描 cf1 和 cf2 列簇
                url = hbaseRestUrl + "/demo/*/cf1,cf2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描 cf1 和 cf2 列簇 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描 cf1 列簇的 age,显示最后两个版本
                url = hbaseRestUrl + "/demo/*/cf1:age?v=2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描 cf1 列簇的 age,显示最后两个版本 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描整个 demo 表,限定返回行数
                url = hbaseRestUrl + "/demo/*?limit=1";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描整个 demo 表,限定返回行数 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描整个 demo 表,从指定行(包括该行)开始向后扫描
                url = hbaseRestUrl + "/demo/*?startrow=row2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描整个 demo 表,从指定行(包括该行)开始向后扫描 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描整个 demo 表,扫描到指定行(不包括该行)
                url = hbaseRestUrl + "/demo/*?endrow=row2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描整个 demo 表,扫描到指定行(不包括该行) -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 扫描整个 demo 表,复合条件
                url = hbaseRestUrl + "/demo/*/cf1:age?v=2&limit=1&startrow=row2";
                responseEntity2 = restTemplate.getForEntity(url, String.class);
                str += "扫描整个 demo 表,复合条件 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                return str;
            }

            @ResponseBody
            @RequestMapping("/data/delete")
            public String dataDelete() {

                // 查看 demo 表是否存在
                String url = hbaseRestUrl + "/demo/exists";
                String str = "查看 demo 表是否存在 -> " + url + "<br>";

                try {
                    ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
                    str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                    str += "Result: " + responseEntity.getBody() + "<br><br>";
                } catch (Exception e) {
                    str += "Error: " + e.getMessage();
                    return str;
                }

                // 头部信息
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);

                ArrayList<MediaType> acceptList = new ArrayList<>();
                acceptList.add(MediaType.APPLICATION_JSON);
                httpHeaders.setAccept(acceptList);

                // 删除 row3 的 cf2 列族的 job 数据
                url = hbaseRestUrl + "/demo/row3/cf2:job/?check=delete";
                HttpEntity<String> httpEntity = new HttpEntity<>(null, httpHeaders);
                ResponseEntity<String> responseEntity2 = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
                str += "删除 row3 的 cf2 列族的 job 数据 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 删除 row3 整行数据
                url = hbaseRestUrl + "/demo/row3/?check=delete";
                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
                str += "删除 row3 整行数据 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                // 删除 demo 表
                url = hbaseRestUrl + "/demo/schema";
                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity2 = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
                str += "删除 demo 表  -> " + url + "<br>";
                str += "Status Code: " +  responseEntity2.getStatusCode() + "<br>";
                str += "Result: " + responseEntity2.getBody() + "<br><br>";

                return str;
            }

            @ResponseBody
            @RequestMapping("/namespace")
            public String namespace() {

                // 头部信息
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);

                ArrayList<MediaType> acceptList = new ArrayList<>();
                acceptList.add(MediaType.APPLICATION_JSON);
                httpHeaders.setAccept(acceptList);

                // 查看所有 Namespace
                String url = hbaseRestUrl + "/namespaces";

                HttpEntity<String> httpEntity = new HttpEntity<>(null, httpHeaders);
                ResponseEntity<String> responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
                String str = "查看所有 Namespace -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 创建 'ns_test' Namespace
                url = hbaseRestUrl + "/namespaces/ns_test";

                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class);
                str += "创建 'ns_test' Namespace -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 在 ns_test 下创建 tbl_01 表 (1 个列族 cf1)
                url = hbaseRestUrl + "/ns_test:tbl_01/schema";
                String jsonParams = "{\"name\":\"ns_test:tbl_01\",\"ColumnSchema\":[{\"name\":\"cf1\"}]}";

                httpEntity = new HttpEntity<>(jsonParams, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class);
                str += "在 ns_test 下创建 tbl_01 表 (1 个列族 cf1) -> " + url + "<br>";
                str += "Request Entity: " + jsonParams + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 查看 ns_test 下的表
                url = hbaseRestUrl + "/namespaces/ns_test/tables";

                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
                str += "查看 ns_test 下的表 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 删除 ns_test 下的 tbl_01 表
                url = hbaseRestUrl + "/ns_test:tbl_01/schema";

                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
                str += "删除 ns_test 下的 tbl_01 表 -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                // 删除 ns_test
                url = hbaseRestUrl + "/namespaces/ns_test";

                httpEntity = new HttpEntity<>(null, httpHeaders);
                responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class);
                str += "删除 ns_test -> " + url + "<br>";
                str += "Status Code: " +  responseEntity.getStatusCode() + "<br>";
                str += "Result: " + responseEntity.getBody() + "<br><br>";

                return str;
            }

        }

    3) 测试页面
    
        运行 SpringbootExample22,访问 http://localhost:9090/client。