目录
1 需求与数据集
某某自来水公司,需要存储大量的缴费明细数据。以下截取了缴费明细的一部分内容。
因为缴费明细的数据记录非常庞大,该公司的信息部门决定使用HBase来存储这些数据。并且,他
们希望能够通过Java程序来访问这些数据。
2.1 创建IDEA Maven项目
2.2 导入pom依赖
<repositories><!-- 代码库 -->
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.14.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
</plugins>
</build>
2.3 复制HBase和Hadoop配置文件
将以下三个配置文件复制到resource目录中
⚫ hbase-site.xml
从Linux中下载:sz /export/server/hbase-2.1.0/conf/hbase-site.xml
⚫ core-site.xml
从Linux中下载:sz /export/server/hadoop-2.7.5/etc/hadoop/core-site.xml
⚫ log4j.properties
注意:请确认配置文件中的服务器节点hostname/ip地址配置正确
2.4 创建包结构和类
- 在test目录创建 cn.itcast.hbase.admin.api_test 包结构
- 创建TableAmdinTest类
2.5 创建Hbase连接以及admin管理对象
要操作Hbase也需要建立Hbase的连接。此处我们仍然使用TestNG来编写测试。使用@BeforeTest
初始化HBase连接,创建admin对象、@AfterTest关闭连接。
实现步骤:
- 使用HbaseConfiguration.create()创建Hbase配置
- 使用ConnectionFactory.createConnection()创建Hbase连接
- 要创建表,需要基于Hbase连接获取admin管理对象
- 使用admin.close、connection.close关闭连接
参考代码:
public class TableAmdinTest {
private Configuration configuration;
private Connection connection;
private Admin admin;
@BeforeTest
public void beforeTest() throws IOException {
configuration = HBaseConfiguration.create();
connection = ConnectionFactory.createConnection(configuration);
admin = connection.getAdmin();
}
@AfterTest
public void afterTest() throws IOException {
admin.close();
connection.close();
}
}
3 需求一:使用Java代码创建表
创建一个名为WATER_BILL的表,包含一个列蔟C1。
实现步骤:
- 判断表是否存在
a) 存在,则退出 - 使用TableDescriptorBuilder.newBuilder构建表描述构建器
- 使用ColumnFamilyDescriptorBuilder.newBuilder构建列蔟描述构建器
- 构建列蔟描述,构建表描述
- 创建表
参考代码:
// 创建一个名为WATER_BILL的表,包含一个列蔟C1
@Test
public void createTableTest() throws IOException {
// 表名
String TABLE_NAME = "WATER_BILL";
// 列蔟名
String COLUMN_FAMILY = "C1";
// 1. 判断表是否存在
if(admin.tableExists(TableName.valueOf(TABLE_NAME))) {
return;
}
// 2. 构建表描述构建器
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(TABLE_NAME));
// 3. 构建列蔟描述构建器
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(COLUMN_FAMILY));
// 4. 构建列蔟描述
ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
// 5. 构建表描述
// 添加列蔟
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
// 6. 创建表
admin.createTable(tableDescriptor);
}
4 需求三:使用Java代码删除表
实现步骤:
- 判断表是否存在
- 如果存在,则禁用表
- 再删除表
参考代码:
// 删除表
@Test
public void dropTable() throws IOException {
// 表名
TableName tableName = TableName.valueOf("WATER_BILL");
// 1. 判断表是否存在
if(admin.tableExists(tableName)) {
// 2. 禁用表
admin.disableTable(tableName);
// 3. 删除表
admin.deleteTable(tableName);
}
}
5 需求二:往表中插入一条数据
5.1 创建包
- 在 test 目录中创建 cn.itcast.hbase.data.api_test 包
- 创建DataOpTest类
5.2 初始化Hbase连接
在@BeforeTest中初始化HBase连接,在@AfterTest中关闭Hbase连接。
参考代码:
public class DataOpTest {
private Configuration configuration;
private Connection connection;
@BeforeTest
public void beforeTest() throws IOException {
configuration = HBaseConfiguration.create();
connection = ConnectionFactory.createConnection(configuration);
}
@AfterTest
public void afterTest() throws IOException {
connection.close();
}
}
5.3 插入姓名列数据
在表中插入一个行,该行只包含一个列。
实现步骤:
- 使用Hbase连接获取Htable
- 构建ROWKEY、列蔟名、列名
- 构建Put对象(对应put命令)
- 添加姓名列
- 使用Htable表对象执行put操作
- 关闭Htable表对象
参考代码:
@Test
public void addTest() throws IOException {
// 1.使用Hbase连接获取Htable
TableName waterBillTableName = TableName.valueOf("WATER_BILL");
Table waterBillTable = connection.getTable(waterBillTableName);
// 2.构建ROWKEY、列蔟名、列名
String rowkey = "4944191";
String cfName = "C1";
String colName = "NAME";
// 3.构建Put对象(对应put命令)
Put put = new Put(Bytes.toBytes(rowkey));
// 4.添加姓名列
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colName)
, Bytes.toBytes("登卫红"));
// 5.使用Htable表对象执行put操作
waterBillTable.put(put);
// 6. 关闭表
waterBillTable.close();
}
5.4 查看HBase中的数据
get ‘WATER_BILL’,‘4944191’,{FORMATTER => ‘toString’}
5.5 插入其他列
参考代码:
@Test
public void addTest() throws IOException {
// 1.使用Hbase连接获取Htable
TableName waterBillTableName = TableName.valueOf("WATER_BILL");
Table waterBillTable = connection.getTable(waterBillTableName);
// 2.构建ROWKEY、列蔟名、列名
String rowkey = "4944191";
String cfName = "C1";
String colName = "NAME";
String colADDRESS = "ADDRESS";
String colSEX = "SEX";
String colPAY_DATE = "PAY_DATE";
String colNUM_CURRENT = "NUM_CURRENT";
String colNUM_PREVIOUS = "NUM_PREVIOUS";
String colNUM_USAGE = "NUM_USAGE";
String colTOTAL_MONEY = "TOTAL_MONEY";
String colRECORD_DATE = "RECORD_DATE";
String colLATEST_DATE = "LATEST_DATE";
// 3.构建Put对象(对应put命令)
Put put = new Put(Bytes.toBytes(rowkey));
// 4.添加姓名列
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colName)
, Bytes.toBytes("登卫红"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colADDRESS)
, Bytes.toBytes("贵州省铜仁市德江县7单元267室"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colSEX)
, Bytes.toBytes("男"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colPAY_DATE)
, Bytes.toBytes("2020-05-10"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colNUM_CURRENT)
, Bytes.toBytes("308.1"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colNUM_PREVIOUS)
, Bytes.toBytes("283.1"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colNUM_USAGE)
, Bytes.toBytes("25"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colTOTAL_MONEY)
, Bytes.toBytes("150"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colRECORD_DATE)
, Bytes.toBytes("2020-04-25"));
put.addColumn(Bytes.toBytes(cfName)
, Bytes.toBytes(colLATEST_DATE)
, Bytes.toBytes("2020-06-09"));
// 5.使用Htable表对象执行put操作
waterBillTable.put(put);
// 6. 关闭表
waterBillTable.close();
}
6 需求三:查看一条数据
查询rowkey为4944191的所有列的数据,并打印出来。
实现步骤:
- 获取HTable
- 使用rowkey构建Get对象
- 执行get请求
- 获取所有单元格
- 打印rowkey
- 迭代单元格列表
- 关闭表
参考代码:
@Test
public void getOneTest() throws IOException {
// 1. 获取HTable
TableName waterBillTableName = TableName.valueOf("WATER_BILL");
Table waterBilltable = connection.getTable(waterBillTableName);
// 2. 使用rowkey构建Get对象
Get get = new Get(Bytes.toBytes("4944191"));
// 3. 执行get请求
Result result = waterBilltable.get(get);
// 4. 获取所有单元格
List<Cell> cellList = result.listCells();
// 打印rowkey
System.out.println("rowkey => " + Bytes.toString(result.getRow()));
// 5. 迭代单元格列表
for (Cell cell : cellList) {
// 打印列蔟名
System.out.print(Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
System.out.println(" => " + Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()));
}
// 6. 关闭表
waterBilltable.close();
}
7 需求四:删除一条数据
删除rowkey为4944191的整条数据。
实现步骤:
- 获取HTable对象
- 根据rowkey构建delete对象
- 执行delete请求
- 关闭表
参考代码:
// 删除rowkey为4944191的整条数据
@Test
public void deleteOneTest() throws IOException {
// 1. 获取HTable对象
Table waterBillTable = connection.getTable(TableName.valueOf("WATER_BILL"));
// 2. 根据rowkey构建delete对象
Delete delete = new Delete(Bytes.toBytes("4944191"));
// 3. 执行delete请求
waterBillTable.delete(delete);
// 4. 关闭表
waterBillTable.close();
}
8 需求五:导入数据
8.1 需求
在资料中,有一份10W的抄表数据文件,我们需要将这里面的数据导入到HBase中。
8.2 Import JOB
在HBase中,有一个Import的MapReduce作业,可以专门用来将数据文件导入到HBase中。
用法
hbase org.apache.hadoop.hbase.mapreduce.Import 表名 HDFS数据文件路径
8.3 导入数据
- 将资料中数据文件上传到Linux中
- 再将文件上传到hdfs中
hadoop fs -mkdir -p /water_bill/output_ept_10W
hadoop fs -put part-m-00000_10w /water_bill/output_ept_10W - 启动YARN集群
start-yarn.sh - 使用以下方式来进行数据导入
hbase org.apache.hadoop.hbase.mapreduce.Import WATER_BILL /water_bill/output_ept_10W
8.4 导出数据
hbase org.apache.hadoop.hbase.mapreduce.Export WATER_BILL
/water_bill/output_ept_10W_export
9.1 需求分析
在Java API中,我们也是使用scan + filter来实现过滤查询。2020年6月份其实就是从2020年6月1
日到2020年6月30日的所有抄表数据。
9.2 准备工作
- 在cn.itcast.hbase.data.api_test包下创建ScanFilterTest类
- 使用@BeforeTest、@AfterTest构建HBase连接、以及关闭HBase连接
9.3 实现
实现步骤:
- 获取表
- 构建scan请求对象
- 构建两个过滤器
a) 构建两个日期范围过滤器(注意此处请使用RECORD_DATE——抄表日期比较
b) 构建过滤器列表 - 执行scan扫描请求
- 迭代打印result
- 迭代单元格列表
- 关闭ResultScanner(这玩意把转换成一个个的类似get的操作,注意要关闭释放资源)
- 关闭表
参考代码:
// 查询2020年6月份所有用户的用水量数据
@Test
public void queryTest1() throws IOException {
// 1. 获取表
Table waterBillTable = connection.getTable(TableName.valueOf("WATER_BILL"));
// 2. 构建scan请求对象
Scan scan = new Scan();
// 3. 构建两个过滤器
// 3.1 构建日期范围过滤器(注意此处请使用RECORD_DATE——抄表日期比较
SingleColumnValueFilter startDateFilter = new SingleColumnValueFilter(Bytes.toBytes("C1")
, Bytes.toBytes("RECORD_DATE")
, CompareOperator.GREATER_OR_EQUAL
, Bytes.toBytes("2020-06-01"));
SingleColumnValueFilter endDateFilter = new SingleColumnValueFilter(Bytes.toBytes("C1")
, Bytes.toBytes("RECORD_DATE")
, CompareOperator.LESS_OR_EQUAL
, Bytes.toBytes("2020-06-30"));
// 3.2 构建过滤器列表
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL
, startDateFilter
, endDateFilter);
scan.setFilter(filterList);
// 4. 执行scan扫描请求
ResultScanner resultScan = waterBillTable.getScanner(scan);
// 5. 迭代打印result
for (Result result : resultScan) {
System.out.println("rowkey -> " + Bytes.toString(result.getRow()));
System.out.println("------");
List<Cell> cellList = result.listCells();
// 6. 迭代单元格列表
for (Cell cell : cellList) {
// 打印列蔟名
System.out.print(Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
System.out.println(" => " + Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()));
}
System.out.println("------");
}
resultScanner.close();
// 7. 关闭表
waterBillTable.close();
}
9.4 解决乱码问题
因为前面我们的代码,在打印所有的列时,都是使用字符串打印的,Hbase中如果存储的是int、
double,那么有可能就会乱码了。
System.out.print(Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength()));
System.out.println(" => " + Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()));
要解决的话,我们可以根据列来判断,使用哪种方式转换字节码。如下:
- NUM_CURRENT
- NUM_PREVIOUS
- NUM_USAGE
- TOTAL_MONEY
这4列使用double类型展示,其他的使用string类型展示。
参考代码:
String colName = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(), cell.getQualifierLength());
System.out.print(colName);
if(colName.equals("NUM_CURRENT")
|| colName.equals("NUM_PREVIOUS")
|| colName.equals("NUM_USAGE")
|| colName.equals("TOTAL_MONEY")) {
System.out.println(" => " + Bytes.toDouble(cell.getValueArray(), cell.getValueOffset()));
}
else {
System.out.println(" => " + Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()));
}
10 # hbase KeyValue结构打印
10.1表内容
hbase(main):003:0> scan ‘tableCreateTest1’
ROW | COLUMN+CELL |
---|---|
row1 | column=cf1:name, timestamp=1467639285495, value=zq |
1 row(s) in 0.0210 seconds
10.2 KeyValue
1.片段代码
Get get = new Get(Bytes.toBytes("row1"));
try {
Result rs = ht.get(get);
System.out.println("result size: " + rs.size());
System.out.println("result.toString: " + rs.toString());
KeyValue kv = rs.getColumnLatest(Bytes.toBytes("cf1"), Bytes.toBytes("name"));
System.out.println("kv key: " + Bytes.toString(kv.getKey()));
System.out.println("kv key: " + kv.getKeyString());
System.out.println("kv key: " + KeyValue.keyToString(kv.getKey()));
System.out.println("kv row: " + Bytes.toString(kv.getRow()));
System.out.println("kv family: " + Bytes.toString(kv.getFamily()));
System.out.println("kv qualifier: " + Bytes.toString(kv.getQualifier()));
System.out.println("kv timestam: " + String.valueOf(kv.getTimestamp()));
System.out.println("kv value: " + Bytes.toString(kv.getValue()));
} catch (IOException e) {
e.printStackTrace();
}
2.结果
result size: 1
result.toString: keyvalues={row1/cf1:name/1467639285495/Put/vlen=2/mvcc=0}
kv key: row1cf1nameU�F�
kv key:\x00\x04row1\x03cf1name\x00\x00\x01U\xB61\x1B\xF1\x04
kv key: row1/cf1:name/1467639285495/Put
kv row: row1
kv family: cf1
kv qualifier: name
kv timestam: 1467639285495
kv value: zq