环境准备

新建项目后在 pom.xml 中添加依赖(API的版本要与HBase的版本一致HBase是2.4.11,所以maven版本也是2.4.11):

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.4.11</version>
</dependency>
</dependencies>

注意:如果报错 javax.el 包不存在,是一个测试用的依赖,不影响使用,如果介意则如此添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies> 
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.4.11</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>
</dependencies>

创建链接

根据官方 API 介绍,HBase 的客户端连接由 ConnectionFactory 类来创建,用户使用完成之后需要手动关闭连接。同时连接是一个重量级的,推荐一个进程使用一个连接,对 HBase的命令通过连接中的两个属性 Admin 和 Table 来实现

单线程创建链接

HBaseConnection1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.zhang.A_TestConnection;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;

import java.io.IOException;

/**
* @author yonruizhang
* @date 2023/3/20 15:21
* @description 尝试 单线程 连接HBase —— 注意导入的包名
*/

public class HBaseConnection1 {
public static void main(String[] args) throws IOException {

// 1. 创建连接 配置对象
Configuration conf = new Configuration();

// 2. 给配置对象添加配置参数 —— zookeeper地址,利用zookeeper管理
conf.set("hbase.zookeeper.quorum","hadoop103,hadoop104,hadoop105");

// 3. 利用工厂类 创建连接对象
// 默认使用同步连接
Connection connection = ConnectionFactory.createConnection(conf); // 其实默认会去resources下的hbase-site.xml文件的配置内容,所以可以不传参数
// 异步链接(Hbase2.3.x之后才有):
// CompletableFuture<AsyncConnection> asyncConnection = ConnectionFactory.createAsyncConnection(conf);

// 4. 测试是否链接上了
System.out.println(connection);

// 5. 关闭连接
connection.close();
}
}

多线程创建链接

使用类单例模式,确保使用一个连接,可以同时用于多个线程。即设置hbase链接为静态属性。

HBaseConnection2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.zhang.A_TestConnection;

import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;

import java.io.IOException;

/**
* @author yonruizhang
* @date 2023/3/20 15:34
* @description 尝试 多线程 连接HBase —— 注意导入的包名
*/

public class HBaseConnection2 {

// 声明一个静态属性
public static Connection connection = null;
// 在静态代码块中创建链接
static {
// 直接在resources下的hbase-site.xml写入配置即可,ConnectionFactory.createConnection()会自动去读入。
// 1. 利用工厂类 创建连接对象
// 默认使用同步连接
try {
connection = ConnectionFactory.createConnection();
} catch (IOException e) {
throw new RuntimeException(e);
}
}

public static void closeConnection() {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) throws IOException {
// 直接使用创建好的连接,不要在main线程中单独创建
System.out.println(connection);

// 在main线程最后关闭连接
closeConnection();
}
}

这里就不再创建 Configuration 对象,将描述信息给链接了,因为通过追踪ConnectionFactory.createConnection()发现它默认会去 resources 目录下找 hbase-site.xml 文件,该文件就是配置文件。我们也应该将配置写在配置文件中,以此可以使得连接更加方便和灵活。

在resources下创建hbase-site.xml文件,并写入:

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop103,hadoop104,hadoop105</value>
</property>
</configuration>

DDL

创建 HBaseDDL 类,添加静态方法即可作为工具类 。

直接利用HBaseConnection2文件中创建的静态连接即可:

1
public static Connection connection = HBaseConnection2.connection;

DDL操作使用Admin操作,都获取Admin:

1
Admin admin = connection.getAdmin();

创建命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 创建一个namespace
* @param namespace namespace的名字
*/
public static void createNamespace(String namespace) throws IOException {
// 1. 获取Admin
// admin 的连接是轻量级的 不是线程安全的 不推荐池化或者缓存这个连接
Admin admin = connection.getAdmin();

// 2. 调用方法创建命名空间
// 代码相对shell更加底层,保证shell能完成的功能代码也能完成
// 所以参数是 NamespaceDescriptor 对象,需要填写完整的命名空间描述

// 2.1 创建 NamespaceDescriptor 的 builder 对象(相当于是建造师)
NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);

// 2.2 给命名空间添加需求(描述信息)
builder.addConfiguration("user", "ZYR"); // 只是给人看这个namespace的描述,实际上和注释差不多

// 2.3 构造添加好参数的对象,完成创建
// 创建命名空间出现的问题 都属于本方法自身的问题 不应该抛出异常
try {
admin.createNamespace(builder.build()); // 建造NamespaceDescriptor对象
} catch (IOException e) {
System.out.println("命名空间已经存在"); // 正式开发中应该打印日志
throw new RuntimeException(e);
}

// 3. 关闭Admin
admin.close();
}

在类中的main函数中写测试代码即可,记得关闭HBase连接:

1
2
3
4
5
6
7
public static void main(String[] args) throws IOException {
// 测试创建命名空间
createNamespace("studyHBase"); // 创建一个名叫 studyHBase 的名称空间,具体结果去命令行验证

// 关闭HBase的连接
HBaseConnection2.closeConnection();
}

判断表格是否存在

其中重点是表格的表达,是利用TableName对象,但是不能直接new,要用:TableName.valueOf(namespace, tableName)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 判断表格是否存在
* @param namespace 命令空间名称
* @param tableName 表格名称
* @return true表示存在,false表示不存在
*/
public static boolean isTableExists(String namespace, String tableName) throws IOException {
// 1 获取admin
Admin admin = connection.getAdmin();

// 2 使用方法判断表格表格是否存在
// admin.tableExists() 的参数是 TableName 对象,不要直接创建TableName对象,因为底层是私有的
boolean exists = false;
try {
exists = admin.tableExists(TableName.valueOf(namespace, tableName));
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭admin
admin.close();

// 4 返回结果
return exists;
}
1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws IOException {

// 测试表格是否存在
// 这个是通过HBase Shell事先创建好的,本身存在的表,应该输出true
System.out.println(isTableExists("bigdata","student"));

// 关闭HBase的连接
HBaseConnection2.closeConnection();
}

创建表格

通过HBase Shell中的help "create"命令,查看帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Create a table with namespace=ns1 and table qualifier=t1
hbase> create 'ns1:t1', {NAME => 'f1', VERSIONS => 5}

Create a table with namespace=default and table qualifier=t1
hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
hbase> # The above in shorthand would be the following:
hbase> create 't1', 'f1', 'f2', 'f3'
hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true}
hbase> create 't1', {NAME => 'f1', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}}
hbase> create 't1', {NAME => 'f1', IS_MOB => true, MOB_THRESHOLD => 1000000, MOB_COMPACT_PARTITION_POLICY => 'weekly'}

Table configuration options can be put at the end.
Examples:

hbase> create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40']
hbase> create 't1', 'f1', SPLITS => ['10', '20', '30', '40']
hbase> create 't1', 'f1', SPLITS_FILE => 'splits.txt', OWNER => 'johndoe'
hbase> create 't1', {NAME => 'f1', VERSIONS => 5}, METADATA => { 'mykey' => 'myvalue' }
hbase> # Optionally pre-split the table into NUMREGIONS, using
hbase> # SPLITALGO ("HexStringSplit", "UniformSplit" or classname)
hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit', REGION_REPLICATION => 2, CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'}}
hbase> create 't1', 'f1', {SPLIT_ENABLED => false, MERGE_ENABLED => false}
hbase> create 't1', {NAME => 'f1', DFS_REPLICATION => 1}

You can also keep around a reference to the created table:

hbase> t1 = create 't1', 'f1'

Which gives you a reference to the table named 't1', on which you can then
call methods.

基本的信息是:命名空间名称,表格名称,列族的名称。其他信息是拓展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    /**
* 创建表格
* @param namespace 命名空间名称
* @param tableName 表格名称
* @param columnFamilies 列族名称,因为可能会有多个列族,所以用变成数组作为参数类型
*/
public static void createTable(String namespace, String tableName, String... columnFamilies) throws IOException {
// 1 获取Admin
Admin admin = connection.getAdmin();

// 2 使用admin的方法创建表格
// 方法参数是TableDescriptor,和创建连接时的配置对象类似,不能直接new
// 2.1 创建表格描述类,该类的参数是TableName类,这个类在上面判断表格是否存在时已经创建过了
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));
// 2.2 添加参数,描述信息
// 可以直接用tableDescriptorBuilder.setColumnFamilies()方法,参数是Collection<ColumnFamilyDescriptor>类型的对象,用起来很复杂
// tableDescriptorBuilder.setColumnFamilies();
// 推荐使用 tableDescriptorBuilder.setColumnFamily() 方法一个一个添加设置,参数是ColumnFamilyDescriptor类型的对象
for (String columnFamily : columnFamilies) {
// 2.2.1 创建构造者,列族描述的建造者
// 参数是byte[],这是字符数组。一般是不能直接加String类型的,这里使用HBase工具包中的Bytes.toBytes()方法,注意是HBase的util表,不要错误导包
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));

// 2.2.2 给对应的列族描述建造者添加描述信息
// 添加版本参数
columnFamilyDescriptorBuilder.setMaxVersions(5);// 最多保存5个版本

// 2.2.3 bulider建造描述,并设置列族
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptorBuilder.build());
}

// 3 创建表格
try {
admin.createTable(tableDescriptorBuilder.build());
} catch (IOException e) {
System.out.println("表格已经存在了");
throw new RuntimeException(e);
}

// 4 关闭admin
admin.close();
}
1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {

// 测试创建表格
createTable("studyHBase","student","info","msg"); // 在新创建的命名空间中添加表格

// 关闭HBase的连接
HBaseConnection2.closeConnection();
}

通过前后两次在HBase Shell中执行的list命令,可以验证创建表格成功:

1
2
3
4
5
6
7
8
9
10
11
12
13
hbase:006:0> list
TABLE
bigdata:student
1 row(s)
Took 0.0343 seconds
=> ["bigdata:student"]
hbase:007:0> list
TABLE
bigdata:student
studyHBase:student
2 row(s)
Took 0.0309 seconds
=> ["bigdata:student", "studyHBase:student"]

通过HBase Shell中的describe "studyHBase:student"可以查看描述信息。我们设置的两个列族,分别叫info和msg,version都设置为5:

1
2
3
4
5
6
7
8
9
10
11
12
13
hbase:008:0> describe "studyHBase:student"
Table studyHBase:student is ENABLED
studyHBase:student
COLUMN FAMILIES DESCRIPTION
{NAME => 'info', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '5', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_
ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65
536', REPLICATION_SCOPE => '0'}

{NAME => 'msg', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '5', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_E
NCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '655
36', REPLICATION_SCOPE => '0'}

2 row(s)

表格创建的问题

  • 表格已经存在会报错

    事先判断表格是否存在

    1
    2
    3
    4
    5
    // 0.2 判断 表格 是否存在
    if (isTableExists(namespace, tableName)) {
    System.out.println("表格已经存在了");
    return;
    }
  • 列族是变长数组类型,即可能没有参数。但是一个表格至少需要一个列族,所以也会报错。

    事先判断是否至少有一个列族

    1
    2
    3
    4
    5
    // 0.1 判断 columnFamilies 是否为空
    if (columnFamilies.length == 0) {
    System.out.println("创建表格至少需要一个列族");
    return;
    }

最终代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
	/**
* 创建表格
* @param namespace 命名空间名称
* @param tableName 表格名称
* @param columnFamilies 列族名称,因为可能会有多个列族,所以用变成数组作为参数类型
*/
public static void createTable(String namespace, String tableName, String... columnFamilies) throws IOException {
// 0 判断是否可以创建
// 0.1 判断 columnFamilies 是否为空
if (columnFamilies.length == 0) {
System.out.println("创建表格至少需要一个列族");
return;
}
// 0.2 判断 表格 是否存在
if (isTableExists(namespace, tableName)) {
System.out.println("表格已经存在了");
return;
}

// 1 获取Admin
Admin admin = connection.getAdmin();

// 2 使用admin的方法创建表格
// 方法参数是TableDescriptor,和创建连接时的配置对象类似,不能直接new
// 2.1 创建表格描述类,该类的参数是TableName类,这个类在上面判断表格是否存在时已经创建过了
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));
// 2.2 添加参数,描述信息
// 可以直接用tableDescriptorBuilder.setColumnFamilies()方法,参数是Collection<ColumnFamilyDescriptor>类型的对象,用起来很复杂
// tableDescriptorBuilder.setColumnFamilies();
// 推荐使用 tableDescriptorBuilder.setColumnFamily() 方法一个一个添加设置,参数是ColumnFamilyDescriptor类型的对象
for (String columnFamily : columnFamilies) {
// 2.2.1 创建构造者,列族描述的建造者
// 参数是byte[],这是字符数组。一般是不能直接加String类型的,这里使用HBase工具包中的Bytes.toBytes()方法,注意是HBase的util表,不要错误导包
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));

// 2.2.2 给对应的列族描述建造者添加描述信息
// 添加版本参数
columnFamilyDescriptorBuilder.setMaxVersions(5);// 最多保存5个版本

// 2.2.3 bulider建造描述,并设置列族
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptorBuilder.build());
}

// 3 创建表格
try {
admin.createTable(tableDescriptorBuilder.build());
} catch (IOException e) {
throw new RuntimeException(e);
}

// 4 关闭admin
admin.close();
}

修改表

重点是创建一个表格建造者是一个新的,这样修改会报错:无法找到该列族。

修改表时应该利用admin获取旧的表格描述,然后获取旧的列族描述。之后再对列族描述建造者添加对应列族描述完成修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 修改表格中一个列族的版本号
* @param namespace 命名空间名称
* @param tableName 表名
* @param columnFamily 列族名
* @param version 版本号
*/
public static void modifyTable(String namespace, String tableName, String columnFamily, int version) throws IOException {
// 0 提前判断,避免异常
// 0.1 判断表格是否存在
if (!isTableExists(namespace, tableName)) {
System.out.println("表格不存在");
return;
}

// 1 获取Admin
Admin admin = connection.getAdmin();

try {
// 2 修改表格信息
// 使用 admin.modifyTable() 方法,参数是TableDescriptor对象
// 2.0 获取旧的表格描述,即getDescriptor()
TableDescriptor descriptor = admin.getDescriptor(TableName.valueOf(namespace, tableName));

// 2.1 创建TableDescriptor的建造者
// 如果使用的方法填写了TableName.valueOf(),则相当于创建了一个新的表格创建者,没有之前的信息
// 如果想要修改之前的信息,必须调用方法填写一个旧的表格描述,即2.0的作用
// TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));
// 利用得到的旧的表格描述作为参数
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(descriptor);

// 2.2 对建造者管理的信息进行修改
// 使用 tableDescriptorBuilder.modifyColumnFamily(),参数是 ColumnFamilyDescriptor 对象
// 2.2.1 创建 ColumnFamilyDescriptor 对象
// ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));
// 需要填写旧的列族描述
// 2.2.1.1 获取旧的表格描述
ColumnFamilyDescriptor columnFamily1 = descriptor.getColumnFamily(Bytes.toBytes(columnFamily));
// 2.2.1.2 创建建造者
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(columnFamily1);

// 2.2.2 修改版本号
columnFamilyDescriptorBuilder.setMaxVersions(version);
// 2.2.3 列族建造者建造
tableDescriptorBuilder.modifyColumnFamily(columnFamilyDescriptorBuilder.build());
// 3 修改表格
admin.modifyTable(tableDescriptorBuilder.build());
} catch (IOException e) {
throw new RuntimeException(e);
}

// 4 关闭 admin
admin.close();
}
1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {

// 测试修改表格版本号
modifyTable("studyHBase","student","info", 10); // 把studyHBase:student info列族 的版本改成10(原本是5)

// 关闭HBase的连接
HBaseConnection2.closeConnection();
}

去HBase Shell中检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
hbase:008:0> describe "studyHBase:student"
Table studyHBase:student is ENABLED
studyHBase:student
COLUMN FAMILIES DESCRIPTION
{NAME => 'info', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '5', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_
ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '65
536', REPLICATION_SCOPE => '0'}

{NAME => 'msg', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '5', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_E
NCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '655
36', REPLICATION_SCOPE => '0'}

2 row(s)
Quota is disabled
Took 0.1055 seconds
hbase:009:0> describe "studyHBase:student"
Table studyHBase:student is ENABLED
studyHBase:student
COLUMN FAMILIES DESCRIPTION
{NAME => 'info', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '10', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK
_ENCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '6
5536', REPLICATION_SCOPE => '0'}

{NAME => 'msg', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '5', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_E
NCODING => 'NONE', COMPRESSION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE => '655
36', REPLICATION_SCOPE => '0'}

2 row(s)
Quota is disabled
Took 0.0701 seconds

可以通过两次查询看出来info列族的VERSIONS确实从5变成了10。

删除表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 删除表格
* @param namespace 命名空间名称
* @param tableName 表格名称
* @return 删除成功放回true,删除失败返回false
*/
public static boolean deleteTable(String namespace, String tableName) throws IOException {
// 1. 判断表格是否存在
if (!isTableExists(namespace, tableName)) {
System.out.println("表格不存在,无法删除");
return false;
}

// 2. 获取admin
Admin admin = connection.getAdmin();

// 3. 调用admin.deleteTable()删除表格
try {
// 注意 HBase 删除表格之前一定要标记表格不可用
TableName tableName1 = TableName.valueOf(namespace, tableName);
admin.disableTable(tableName1);
// 删除
admin.deleteTable(tableName1);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 4. 关闭admin
admin.close();

return true;
}
1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {

// 测试删除表格
deleteTable("studyHBase","student");

// 关闭HBase的连接
HBaseConnection2.closeConnection();
}

操作DDL的完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package com.zhang.B_TestDDL;

import com.zhang.A_TestConnection.HBaseConnection2;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

/**
* @author yonruizhang
* @date 2023/3/20 15:46
* @description 测试DDL的相关操作 (获取Admin —— Admin管理DDL;Table是用来管理DML的)
*/

public class HBaseDDL1 {
// 直接使用前面的连接类即可
public static Connection connection = HBaseConnection2.connection;

/**
* 创建一个namespace
* @param namespace namespace的名字
*/
public static void createNamespace(String namespace) throws IOException {
// 1. 获取Admin
// admin 的连接是轻量级的 不是线程安全的 不推荐池化或者缓存这个连接
Admin admin = connection.getAdmin();

// 2. 调用方法创建命名空间
// 代码相对shell更加底,保证shell能完成的功能代码也能完成,所以参数是 NamespaceDescriptor 对象,需要填写完整的命名空间描述

// 2.1 创建 NamespaceDescriptor 的 builder 对象(相当于是建造师)
NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);

// 2.2 给命名空间添加需求(描述信息)
builder.addConfiguration("user", "ZYR"); // 只是给人看这个namespace的描述,实际上没有什么描述,和注释差不多

// 2.3 构造添加好参数的对象,完成创建
// 创建命名空间出现的问题 都属于本方法自身的问题 不应该抛出异常
try {
admin.createNamespace(builder.build()); // 建造NamespaceDescriptor对象
} catch (IOException e) {
System.out.println("命名空间已经存在"); // 正式开发中应该打印日志
throw new RuntimeException(e);
}

// 3. 关闭Admin
admin.close();
}

/**
* 判断表格是否存在
* @param namespace 命令空间名称
* @param tableName 表格名称
* @return true表示存在,false表示不存在
*/
public static boolean isTableExists(String namespace, String tableName) throws IOException {
// 1 获取admin
Admin admin = connection.getAdmin();

// 2 使用方法判断表格表格是否存在
// admin.tableExists() 的参数是 TableName 对象,不要直接创建TableName对象,因为底层是私有的,去看底层文档
boolean exists = false;
try {
exists = admin.tableExists(TableName.valueOf(namespace, tableName));
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭admin
admin.close();

// 4 返回结果
return exists;
}

/**
* 创建表格
* @param namespace 命名空间名称
* @param tableName 表格名称
* @param columnFamilies 列族名称,因为可能会有多个列族,所以用变成数组作为参数类型
*/
public static void createTable(String namespace, String tableName, String... columnFamilies) throws IOException {
// 0 判断是否可以创建
// 0.1 判断 columnFamilies 是否为空
if (columnFamilies.length == 0) {
System.out.println("创建表格至少需要一个列族");
return;
}
// 0.2 判断 表格 是否存在
if (isTableExists(namespace, tableName)) {
System.out.println("表格已经存在了");
return;
}

// 1 获取Admin
Admin admin = connection.getAdmin();

// 2 使用admin的方法创建表格
// 方法参数是TableDescriptor,和创建连接时的配置对象类似,不能直接new
// 2.1 创建表格描述类,该类的参数是TableName类,这个类在上面判断表格是否存在时已经创建过了
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));
// 2.2 添加参数,描述信息
// 可以直接用tableDescriptorBuilder.setColumnFamilies()方法,参数是Collection<ColumnFamilyDescriptor>类型的对象,用起来很复杂
// tableDescriptorBuilder.setColumnFamilies();
// 推荐使用 tableDescriptorBuilder.setColumnFamily() 方法一个一个添加设置,参数是ColumnFamilyDescriptor类型的对象
for (String columnFamily : columnFamilies) {
// 2.2.1 创建构造者,列族描述的建造者
// 参数是byte[],这是字符数组。一般是不能直接加String类型的,这里使用HBase工具包中的Bytes.toBytes()方法,注意是HBase的util表,不要错误导包
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));

// 2.2.2 给对应的列族描述建造者添加描述信息
// 添加版本参数
columnFamilyDescriptorBuilder.setMaxVersions(5);// 最多保存5个版本

// 2.2.3 bulider 建造描述,并设置列族
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptorBuilder.build());
}

// 3 创建表格
try {
admin.createTable(tableDescriptorBuilder.build());
} catch (IOException e) {
throw new RuntimeException(e);
}

// 4 关闭admin
admin.close();
}

/**
* 修改表格中一个列族的版本号
* @param namespace 命名空间名称
* @param tableName 表名
* @param columnFamily 列族名
* @param version 版本号
*/
public static void modifyTable(String namespace, String tableName, String columnFamily, int version) throws IOException {
// 0 提前判断,避免异常
// 0.1 判断表格是否存在
if (!isTableExists(namespace, tableName)) {
System.out.println("表格不存在");
return;
}

// 1 获取Admin
Admin admin = connection.getAdmin();

try {
// 2 修改表格信息
// 使用 admin.modifyTable() 方法,参数是TableDescriptor对象
// 2.0 获取旧的表格描述,即getDescriptor()
TableDescriptor descriptor = admin.getDescriptor(TableName.valueOf(namespace, tableName));

// 2.1 创建TableDescriptor的建造者
// 如果使用的方法填写了TableName.valueOf(),则相当于创建了一个新的表格创建者,没有之前的信息
// 如果想要修改之前的信息,必须调用方法填写一个旧的表格描述,即2.0的作用
// TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespace, tableName));
// 利用得到的旧的表格描述作为参数
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(descriptor);

// 2.2 对建造者管理的信息进行修改
// 使用 tableDescriptorBuilder.modifyColumnFamily(),参数是 ColumnFamilyDescriptor 对象
// 2.2.1 创建 ColumnFamilyDescriptor 对象
// ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));
// 需要填写旧的列族描述
// 2.2.1.1 获取旧的表格描述
ColumnFamilyDescriptor columnFamily1 = descriptor.getColumnFamily(Bytes.toBytes(columnFamily));
// 2.2.1.2 创建建造者
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(columnFamily1);

// 2.2.2 修改版本号
columnFamilyDescriptorBuilder.setMaxVersions(version);
// 2.2.3 列族建造者建造
tableDescriptorBuilder.modifyColumnFamily(columnFamilyDescriptorBuilder.build());
// 3 修改表格
admin.modifyTable(tableDescriptorBuilder.build());
} catch (IOException e) {
throw new RuntimeException(e);
}

// 4 关闭 admin
admin.close();
}

/**
* 删除表格
* @param namespace 命名空间名称
* @param tableName 表格名称
* @return 删除成功放回true,删除失败返回false
*/
public static boolean deleteTable(String namespace, String tableName) throws IOException {
// 1. 判断表格是否存在
if (!isTableExists(namespace, tableName)) {
System.out.println("表格不存在,无法删除");
return false;
}

// 2. 获取admin
Admin admin = connection.getAdmin();

// 3. 调用admin.deleteTable()删除表格
try {
// 注意 HBase 删除表格之前一定要标记表格不可用
TableName tableName1 = TableName.valueOf(namespace, tableName);
admin.disableTable(tableName1);
// 删除
admin.deleteTable(tableName1);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 4. 关闭admin
admin.close();

return true;
}

public static void main(String[] args) throws IOException {
// 测试创建命名空间
// createNamespace("studyHBase"); // 创建一个名叫 studyHBase 的名称空间,具体结果去命令行验证

// 测试表格是否存在
// System.out.println(isTableExists("bigdata","student")); // 这个是通过HBase Shell事先创建好的,本身存在的表

// 测试创建表格
// createTable("studyHBase","student","info","msg"); // 在新创建的命名空间中添加表格

// 测试修改表格版本号
// modifyTable("studyHBase","student","info", 10); // 把studyHBase:student info列族 的版本改成10(原本是5)

// 测试删除表格
deleteTable("studyHBase","student");

// 关闭HBase的连接
HBaseConnection2.closeConnection();
}
}

DML

DML的相关操作是利用connection获取Table执行的:

1
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 插入数据
* @param namespace 命名空间名称
* @param tableName 表格名称
* @param rowKey 主键
* @param columnFamily 列族名称
* @param column 列名
* @param value 插入的值
*/
public static void putCell(String namespace, String tableName, String rowKey, String columnFamily, String column, String value) throws IOException {
// 0 判断表格是否存在
if (!HBaseDDL1.isTableExists(namespace, tableName)) {
System.out.println("表格不存在");
return;
}

// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 调用相关方法插入数据
// 使用 table.put() 方法,参数是Put对象
// 2.1 创建Put对象
Put put = new Put(Bytes.toBytes(rowKey));
// 2.2 向Put对象添加数据
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(column),Bytes.toBytes(value));
// 2.3 将对象写入方法
try {
table.put(put);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 关闭table
table.close();
}
1
2
3
4
5
6
7
public static void main(String[] args) throws IOException {
// 测试添加数据
putCell("studyHBase","student","2001","info","name","zhangsan");

// 关闭连接
HBaseConnection2.closeConnection();
}

测试结果:

1
2
3
4
5
6
7
8
9
hbase:001:0> scan "studyHBase:student"
ROW COLUMN+CELL
0 row(s)
Took 0.2841 seconds
hbase:002:0> scan "studyHBase:student"
ROW COLUMN+CELL
2001 column=info:name, timestamp=2023-03-24T08:21:22.454, value=zhangsan
1 row(s)
Took 0.0464 seconds

为了后续读取数据时的操作演示效果更加明显,可以再自行添加一些数据。

读取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 详细读取表格中的某一列数据
* @param namespace 命名空间名称
* @param tableName 表名
* @param rowKey 主键
* @param columnFamily 列族
* @param column 列名
*/
public static void getCells(String namespace, String tableName, String rowKey, String columnFamily, String column) throws IOException {
// 1 获取table对象
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 get 对象
Get get = new Get(Bytes.toBytes(rowKey)); // 如果不再做设置,直接读数据,此时读一整行数据

// 读取某一列数据,需要添加对应参数
get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));

// 还可以设置读取数据的版本,默认有读全部版本的方法
get.readAllVersions();

try {
// 3 设置完成就调用 table.get() 方法得到result对象
Result result = table.get(get);

// 4 处理数据
Cell[] cells = result.rawCells();
// 如果是实际开发就应该放回result,这里简单演示
for (Cell cell : cells) {
// cell 存储数据比较底层
String value = new String(CellUtil.cloneValue(cell));
System.out.println(value);
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 5 关闭 table
table.close();
}
1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {

// 测试读取数据
getCells("studyHBase","student","2001","info","name");

// 关闭连接
HBaseConnection2.closeConnection();
}

扫描数据

读取数据的效率太低,一般只能看一行的消息,但实际开发中可能会需要数十亿行的数据,所以不能简单读取数据。要实现同时读数十亿行数据就要使用扫描数据的API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 扫描数据
* @param namespace 命名空间
* @param tableName 表格名称
* @param startRow 开始的行
* @param stopRow 结束的行
* HBase Shell中默认:[ startRow , stopRow )
*/
public static void scanRaws(String namespace, String tableName, String startRow, String stopRow) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 Scan 对象
Scan scan = new Scan(); // 如果此时什么都不填,这是扫描整张表
// 实际开发中很少扫描整张表,所以要设置参数规定其实行和终止行
// 函数原型:this.withStartRow(startRow, true); 第一个参数是其实行,第二个参数表示默认要包含改行,可以给定参数指定
scan.withStartRow(Bytes.toBytes(startRow));
// 默认是不包含终止行,可自己给定第二个参数为 true 则可包含
scan.withStopRow(Bytes.toBytes(stopRow));


try {
// 读取多行数据获取 scanner
ResultScanner scanner = table.getScanner(scan);

// Result 是记录一行数据 本质是:Cell 数组
// ResultScanner 是记录多行数据 本质是:Result 数组
for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" +
new String(CellUtil.cloneFamily(cell))+ '-' + new String(CellUtil.cloneValue(cell)) + "\t");
}
System.out.println();
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭table
table.close();
}
1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {

// 测试扫描数据
scanRaws("studyHBase", "student", "2001", "2003");

// 关闭连接
HBaseConnection2.closeConnection();
}

输出结果:

1
2
2001-name-info-zhangsan	
2002-name-info-lisi

因为这里是默认不包含终止行,所以只有两条数据。

实际扫描全表内容:

1
2
3
4
5
6
7
hbase:003:0> scan "studyHBase:student"
ROW COLUMN+CELL
2001 column=info:name, timestamp=2023-03-24T08:21:22.454, value=zhangsan
2002 column=info:name, timestamp=2023-03-25T04:07:01.510, value=lisi
2003 column=info:name, timestamp=2023-03-25T04:07:01.521, value=wangwu
3 row(s)
Took 0.0390 seconds

带过滤器的扫描

过滤器有两种:

单列过滤、整行过滤

也就是添加条件,得到满足一定条件的结果。

但是注意,由于HBase的性质,是允许有缺失数据的,所以有些数据不一定能过滤掉。

举个例子:

假设按照info列族下的name列进行过滤匹配,但是有一行数据只有其他属性,name列没有数据,则会跳过匹配这一行的数据。

对于单列过滤来说无法察觉,但是对于整行过滤就可以看到效果。例如输出一行内容,但是这一行内容中并没有对应的过滤列的值。

带单列过滤的扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 带过滤的扫描数据
* @param namespace 命名空间
* @param tableName 表格名
* @param startRow 开始行
* @param stopRow 结束行
* @param columnFamily 列族名
* @param columnName 列名
* @param value 查询值
*/
public static void filterScanRows(String namespace, String tableName, String startRow, String stopRow, String columnFamily, String columnName, String value) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 Scan 对象
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes(startRow));
scan.withStopRow(Bytes.toBytes(stopRow));

// 添加过滤器的列表 (过滤器的集合)
FilterList filterList = new FilterList();
// 创建过滤器
// (1)结果只保留当前列的数据
ColumnValueFilter columnValueFilter = new ColumnValueFilter(
// 列族名称
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(columnName),
// 比较关系 是一个枚举类,可以根据实际情况是大于还是小于之类的关系
CompareOperator.EQUAL,
// 比较的值
Bytes.toBytes(value)
);
// 将过滤器添加到集合
filterList.addFilter(columnValueFilter);
scan.setFilter(filterList);

try {
ResultScanner scanner = table.getScanner(scan);

for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" +
new String(CellUtil.cloneFamily(cell))+ '-' + new String(CellUtil.cloneValue(cell)) + "\t");
}
System.out.println();
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭table
table.close();
}
1
2
3
4
5
6
7
public static void main(String[] args) throws IOException {
// 测试带过滤器的扫描 假设具体找到张三这一行的信息
filterScanRows("studyHBase", "student", "2001", "2004", "info", "name", "zhangsan");

// 关闭连接
HBaseConnection2.closeConnection();
}

输出:

1
2001-name-info-zhangsan

这一个列族下其实还有其他属性。

带整行过滤的扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    /**
* 带过滤的扫描数据
* @param namespace 命名空间
* @param tableName 表格名
* @param startRow 开始行
* @param stopRow 结束行
* @param columnFamily 列族名
* @param columnName 列名
* @param value 查询值
*/
public static void filterScanRows(String namespace, String tableName, String startRow, String stopRow, String columnFamily, String columnName, String value) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 Scan 对象
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes(startRow));
scan.withStopRow(Bytes.toBytes(stopRow));

// 添加过滤器的列表 (过滤器的集合)
FilterList filterList = new FilterList();
// 创建过滤器
// (1)结果只保留当前列的数据
ColumnValueFilter columnValueFilter = new ColumnValueFilter(
// 列族名称
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(columnName),
// 比较关系 是一个枚举类,可以根据实际情况是大于还是小于之类的关系
CompareOperator.EQUAL,
// 比较的值
Bytes.toBytes(value)
);

// (2)结果保留整行数据 构造器参数实际上是一样的
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
// 列族名称
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(columnName),
// 比较关系 是一个枚举类,可以根据实际情况是大于还是小于之类的关系
CompareOperator.EQUAL,
// 比较的值
Bytes.toBytes(value)
);

// 将过滤器添加到集合,本质可以添加多个过滤器,最终结果是越过滤数据越少,所以这里注释一个过滤器的添加
// filterList.addFilter(columnValueFilter);
filterList.addFilter(singleColumnValueFilter);
scan.setFilter(filterList);

try {
ResultScanner scanner = table.getScanner(scan);

for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" +
new String(CellUtil.cloneFamily(cell))+ '-' + new String(CellUtil.cloneValue(cell)) + "\t");
}
System.out.println();
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭table
table.close();
}
1
2
3
4
5
6
7
public static void main(String[] args) throws IOException {
// 测试带过滤器的扫描 假设具体找到张三这一行的信息
filterScanRows("studyHBase", "student", "2001", "2004", "info", "name", "zhangsan");

// 关闭连接
HBaseConnection2.closeConnection();
}

输出:

1
2001-age-info-11	2001-name-info-zhangsan

删除数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 删除一行数据
* @param namespace 名称空间
* @param tableName 表名
* @param rowKey 主键
* @param columnFamily 列族名
* @param columnName 列名
*/
public static void deleteColumn(String namespace, String tableName, String rowKey, String columnFamily, String columnName) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 delete 对象
Delete delete = new Delete(Bytes.toBytes(rowKey));

// 3 添加列信息
// delete.addColumn() 等价于 delete 是删除一个版本的数据
// delete.addColumns() 等价于 deleteAll 是删除所有版本的数据

// 一般是不会删除数据的,如果真的要删除数据大多数情况也是删除所有版本的数据
delete.addColumns(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName));

// 4 删除数据
try {
table.delete(delete);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 5 关闭table
table.close();
}
1
2
3
4
5
6
7
public static void main(String[] args) throws IOException {
// 测试删除zhangsan所在列数据
deleteColumn("studyHBase", "student", "2001", "info", "name");

// 关闭连接
HBaseConnection2.closeConnection();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hbase:003:0> scan "studyHBase:student"
ROW COLUMN+CELL
2001 column=info:age, timestamp=2023-03-25T04:34:00.445, value=11
2001 column=info:name, timestamp=2023-03-24T08:21:22.454, value=zhangsan
2002 column=info:name, timestamp=2023-03-25T04:07:01.510, value=lisi
2003 column=info:name, timestamp=2023-03-25T04:07:01.521, value=wangwu
3 row(s)
Took 0.0390 seconds
hbase:004:0> scan "studyHBase:student"
ROW COLUMN+CELL
2001 column=info:age, timestamp=2023-03-25T04:34:00.445, value=11
2002 column=info:name, timestamp=2023-03-25T04:07:01.510, value=lisi
2003 column=info:name, timestamp=2023-03-25T04:07:01.521, value=wangwu
3 row(s)
Took 0.0190 seconds

可以看到实际上删除的是info列族,name列中rowKey行的数据。它不会删除其他数据,不会删除rowKey行的其他列,例如info列族下的age列的信息就没有删除。

操作DML的完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package com.zhang.C_TestDML;

import com.zhang.A_TestConnection.HBaseConnection2;
import com.zhang.B_TestDDL.HBaseDDL1;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.ColumnValueFilter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

/**
* @author yonruizhang
* @date 2023/3/23 18:33
* @description
*/

public class HBaseDML1 {
// 添加连接的静态属性
public static Connection connection = HBaseConnection2.connection;

/**
* 插入数据
* @param namespace 命名空间名称
* @param tableName 表格名称
* @param rowKey 主键
* @param columnFamily 列族名称
* @param column 列名
* @param value 插入的值
*/
public static void putCell(String namespace, String tableName, String rowKey, String columnFamily, String column, String value) throws IOException {
// 0 判断表格是否存在
if (!HBaseDDL1.isTableExists(namespace, tableName)) {
System.out.println("表格不存在");
return;
}

// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 调用相关方法插入数据
// 使用 table.put() 方法,参数是Put对象
// 2.1 创建Put对象
Put put = new Put(Bytes.toBytes(rowKey));
// 2.2 向Put对象添加数据
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(column),Bytes.toBytes(value));
// 2.3 将对象写入方法
try {
table.put(put);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 关闭table
table.close();
}

/**
* 详细读取表格中的某一列数据
* @param namespace 命名空间名称
* @param tableName 表名
* @param rowKey 主键
* @param columnFamily 列族
* @param column 列名
*/
public static void getCells(String namespace, String tableName, String rowKey, String columnFamily, String column) throws IOException {
// 1 获取table对象
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 get 对象
Get get = new Get(Bytes.toBytes(rowKey)); // 如果不再做设置,直接读数据,此时读一整行数据

// 读取某一列数据,需要添加对应参数
get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));

// 还可以设置读取数据的版本,默认有读全部版本的方法
get.readAllVersions();

try {
// 3 设置完成就调用 table.get() 方法得到result对象
Result result = table.get(get);

// 4 处理数据
Cell[] cells = result.rawCells();
// 如果是实际开发就应该放回result,这里简单演示
for (Cell cell : cells) {
// cell 存储数据比较底层
String value = new String(CellUtil.cloneValue(cell));
System.out.println(value);
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 5 关闭 table
table.close();
}

/**
* 扫描数据
* @param namespace 命名空间
* @param tableName 表格名称
* @param startRow 开始的行
* @param stopRow 结束的行
* HBase Shell中默认:[ startRow , stopRow )
*/
public static void scanRows(String namespace, String tableName, String startRow, String stopRow) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 Scan 对象
Scan scan = new Scan(); // 如果此时什么都不填,这是扫描整张表
// 实际开发中很少扫描整张表,所以要设置参数规定其实行和终止行
// 函数原型:this.withStartRow(startRow, true); 第一个参数是其实行,第二个参数表示默认要包含改行,可以给定参数指定
scan.withStartRow(Bytes.toBytes(startRow));
// 默认是不包含终止行,可自己给定第二个参数为 true 则可包含
scan.withStopRow(Bytes.toBytes(stopRow));


try {
// 读取多行数据获取 scanner
ResultScanner scanner = table.getScanner(scan);

// Result 是记录一行数据 本质是:Cell 数组
// ResultScanner 是记录多行数据 本质是:Result 数组
for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" +
new String(CellUtil.cloneFamily(cell))+ '-' + new String(CellUtil.cloneValue(cell)) + "\t");
}
System.out.println();
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭table
table.close();
}

/**
* 带过滤的扫描数据
* @param namespace 命名空间
* @param tableName 表格名
* @param startRow 开始行
* @param stopRow 结束行
* @param columnFamily 列族名
* @param columnName 列名
* @param value 查询值
*/
public static void filterScanRows(String namespace, String tableName, String startRow, String stopRow, String columnFamily, String columnName, String value) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 Scan 对象
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes(startRow));
scan.withStopRow(Bytes.toBytes(stopRow));

// 添加过滤器的列表 (过滤器的集合)
FilterList filterList = new FilterList();
// 创建过滤器
// (1)结果只保留当前列的数据
ColumnValueFilter columnValueFilter = new ColumnValueFilter(
// 列族名称
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(columnName),
// 比较关系 是一个枚举类,可以根据实际情况是大于还是小于之类的关系
CompareOperator.EQUAL,
// 比较的值
Bytes.toBytes(value)
);

// (2)结果保留整行数据 构造器参数实际上是一样的
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
// 列族名称
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(columnName),
// 比较关系 是一个枚举类,可以根据实际情况是大于还是小于之类的关系
CompareOperator.EQUAL,
// 比较的值
Bytes.toBytes(value)
);

// 将过滤器添加到集合,本质可以添加多个过滤器,最终结果是越过滤数据越少,所以这里注释一个过滤器的添加
// filterList.addFilter(columnValueFilter);
filterList.addFilter(singleColumnValueFilter);
scan.setFilter(filterList);

try {
ResultScanner scanner = table.getScanner(scan);

for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" +
new String(CellUtil.cloneFamily(cell))+ '-' + new String(CellUtil.cloneValue(cell)) + "\t");
}
System.out.println();
}
} catch (IOException e) {
throw new RuntimeException(e);
}

// 3 关闭table
table.close();
}

/**
* 删除一行数据
* @param namespace 名称空间
* @param tableName 表名
* @param rowKey 主键
* @param columnFamily 列族名
* @param columnName 列名
*/
public static void deleteColumn(String namespace, String tableName, String rowKey, String columnFamily, String columnName) throws IOException {
// 1 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2 创建 delete 对象
Delete delete = new Delete(Bytes.toBytes(rowKey));

// 3 添加列信息
// delete.addColumn() 等价于 delete 是删除一个版本的数据
// delete.addColumns() 等价于 deleteAll 是删除所有版本的数据

// 一般是不会删除数据的,如果真的要删除数据大多数情况也是删除所有版本的数据
delete.addColumns(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName));

// 4 删除数据
try {
table.delete(delete);
} catch (IOException e) {
throw new RuntimeException(e);
}

// 5 关闭table
table.close();
}

public static void main(String[] args) throws IOException {
// 测试添加数据
// putCell("studyHBase","student","2001","info","age","11");
// putCell("studyHBase","student","2001","info","name","zhangsan");
// putCell("studyHBase","student","2002","info","name","lisi");
// putCell("studyHBase","student","2003","info","name","wangwu");

// 测试读取数据
// getCells("studyHBase","student","2001","info","name");

// 测试扫描数据
// scanRows("studyHBase", "student", "2001", "2003");

// 测试带过滤器的扫描 假设具体找到张三这一行的信息
// filterScanRows("studyHBase", "student", "2001", "2004", "info", "name", "zhangsan");

// 测试删除zhangsan所在列数据
deleteColumn("studyHBase", "student", "2001", "info", "name");

// 关闭连接
HBaseConnection2.closeConnection();
}
}

总结

HBase API是很底层的东西,调用起来比HBase Shell更加麻烦,但可查看源码,很容易理解。开放性也很好,HBase Shell能干的事情它也能干。

具体要写好,用好HBase API首先就需要先了解HBase的底层数据结构,也要了解HBase Shell的一些基本操作。