前两章《Docker下HBase学习,三部曲之一:极速体验》和《Docker下HBase学习,三部曲之二:集群HBase搭建》我们学习了HBase的单机和集群环境搭建,本章我们继续实战,学习在java应用中操作HBase;
完整的Demo源码
本次实战的完整的源码地址是:git@github.com:zq2599/blog_demos.git,里面有多个工程,本次Demo所在目录如下图红框所示:
网络规划
本次实战,会启动两个docker容器:hbase单机版和tomcat,java web应用部署在tomcat上,对hbase进行操作;
启动hbase容器
执行以下命令启动hbase单机版容器:
docker run --name=hbasestandalone -idt -p 60010:60010 bolingcavalry/centos7-hbase126-standalone:0.0.1
容器启动后要执行如下命令进入容器:
docker exec -it hbasestandalone /bin/bash
进入容器后,执行start-hbase.sh即可启动hbase服务,如下图:
启动tomcat容器
执行以下命令启动一个定制版的tomcat容器,该容器启动后,可以在线部署war包:
docker run --name=tomcat001 --link=hbasestandalone:hbaseserver -p 8080:8080 -idt bolingcavalry/online_deploy_tomcat:0.0.1
link参数表明了可以通过hbaseserver确定hbasestandalone的ip(/etc/hosts文件);
关于tomcat支持在线部署war包的更多信息,请参考文章《实战docker,编写Dockerfile定制tomcat镜像,实现web应用在线部署》;
开发web应用:pom.xml
本次开发的java web应用是基于maven构建的,所以在pom中出了spring,sl4j等常用的库,还要加入hbase的依赖,如下:
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.2.6</version>
</dependency>
关于Demo
本次实战的demo是围绕着学生类展开的,这个学生类只有三个字段:id、name、age,源码如下:
public class Student {
/**
* 学号
*/
private long id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private int age;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
服务接口
本次我们只是简单的实战,只涉及到新增、删除、查找这些最基本的功能,服务接口定义如下:
public interface StudentService {
/**
* 新增
* @param student
* @return
*/
String insert(Student student);
/**
* 根据学号删除
* @param id
* @return
*/
String delete(long id);
/**
* 根据学号查找
* @param id
* @return
*/
StudentDTO find(long id);
}
初始化、创建表
通过spring的@PostConstruct注解,我们可以定义一个实例在构造方法完成后被spring主动调用执行的方法,这里我们用这个方法来检查有没有student方法,如果没有就在此创建,以保证后续的操作时表是存在的,源码如下:
private Configuration configuration;
private HBaseAdmin hBaseAdmin;
@PostConstruct
public void init(){
LOGGER.info("start init");
configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum", "hbaseserver");
try {
hBaseAdmin = new HBaseAdmin(configuration);
//检查表是否存在,如果不存在就创建
if (!hBaseAdmin.tableExists(TABLE_NAME)) {
LOGGER.info("table is not exist");
HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf(TABLE_NAME));
for (String columnName : COLUMNS) {
HColumnDescriptor column = new HColumnDescriptor(columnName);
hTableDescriptor.addFamily(column);
}
hBaseAdmin.createTable(hTableDescriptor);
}
}catch (Exception e){
LOGGER.error("init error", e);
e.printStackTrace();
}
LOGGER.info("finish init");
}
如上所示,通过@PostConstruct注释,在业务调用StudentService提供的服务之前,init方法就已经执行过了,里面对configuration、hBaseAdmin等变量都做了实例化,还会检查student表是否存在,如果不存在就主动去创建student表;
注意这段代码configuration.set(“hbase.zookeeper.quorum”, “hbaseserver”),"hbaseserver"是启动docker容器时候的link参数,这里可以用来代表hbase服务器的ip(被docker服务写入了tomcat001容器的/etc/hosts文件);
新增一条记录
student表有两个family:id和info,id是学号,info包含了两个qualifier:name和age,并且每一条记录的rowkey也用学号表示,所以新增一条记录的关键代码如下:
//主键
byte[] rowIdBytes = Bytes.toBytes(rowId);
String errorStr = null;
HTable hTable = htable();
if(null==hTable){
return ERROR_CREATE_HTABLE_FAIL;
}
Put p1 = new Put(rowIdBytes);
Map<String, String> map = new HashMap<String, String>();
map.put("id", id);
map.put("info:name", student.getName());
map.put("info:age", String.valueOf(student.getAge()));
for (String columnName : map.keySet()) {
byte[] value = Bytes.toBytes(map.get(columnName));
String[] str = columnName.split(":");
byte[] family = Bytes.toBytes(str[0]);
byte[] qualifier = null;
if (str.length > 1) {
qualifier = Bytes.toBytes(str[1]);
}
p1.addColumn(family, qualifier, value);
}
try {
hTable.put(p1);
}catch(Exception e){
LOGGER.error("insert error : " + e);
e.printStackTrace();
}
删除一条记录
根据rowkey删除记录的代码如下所示:
List<Delete> list = new ArrayList<Delete>();
Delete delete = new Delete(Bytes.toBytes(String.valueOf(id)));
list.add(delete);
String errorStr = null;
try {
hTable.delete(list);
}catch(Exception e){
errorStr = "delete error : " + e;
LOGGER.error("delete error");
}
根据rowkey查询记录
根据rowkey查询记录的代码如下所示:
String errorStr = null;
Result result = null;
Get get = new Get(Bytes.toBytes(String.valueOf(id)));
try {
result = hTable.get(get);
}catch(Exception e){
errorStr = "find error : " + e;
}
StudentDTO studentDTO = new StudentDTO();
studentDTO.setErrorStr(errorStr);
if(StringUtils.isBlank(errorStr) && null!=result){
studentDTO.setStudent(buildStudent(result.rawCells()));
LOGGER.info("after build : " + JSON.toJSONString(studentDTO));
}
return studentDTO;
查找到的结果被封装在result对象中,result.rawCells()方法返回的Cell数组中有所有查询结果数据,这里专门做了个buildStudent方法来处理Cell数组,转成Student对象:
Student student = null;
for(Cell cell : cellArray){
String row = new String(CellUtil.cloneRow(cell));
String family = new String(CellUtil.cloneFamily(cell));
String qualifier = new String(CellUtil.cloneQualifier(cell));
String value = new String(CellUtil.cloneValue(cell));
LOGGER.info("row [{}], family [{}], qualifier [{}], value [{}]", row, family, qualifier, value);
if(!StringUtils.isNumeric(row)){
LOGGER.error("invalid row for build student");
return null;
}
if(null==student){
student = new Student();
student.setId(Long.valueOf(row));
}
if("info".equals(family)){
if("age".equals(qualifier)){
student.setAge(Integer.valueOf(value));
} else if("name".equals(qualifier)){
student.setName(value);
}
}
}
如上所示,从Cell中可以取出row、family、qualifier、value这些信息,综合起来就能凑够student所需的字段了;
Controller
做好了StudentService及其对应的实现,我们再实现一个Controller,以便从浏览器输入url来体验StudentService的服务:
@RequestMapping("/insert")
public String insert(HttpServletRequest request, Model model) {
String id = get(request, "id");
String name = get(request, "name");
String age = get(request, "age");
Student student = new Student();
student.setId(Long.valueOf(id));
student.setName(name);
student.setAge(Integer.valueOf(age));
String errorStr = studentService.insert(student);
LOGGER.info("student service response [{}]", errorStr);
return StringUtils.isEmpty(errorStr) ? "success" : buileFilePageInfo(errorStr, model);
}
@RequestMapping("/delete")
public String delete(HttpServletRequest request, Model model) {
String id = get(request, "id");
String errorStr = studentService.delete(Long.valueOf(id));
LOGGER.info("student service response [{}]", errorStr);
return StringUtils.isEmpty(errorStr) ? "success" : buileFilePageInfo(errorStr, model);
}
@RequestMapping("/find")
@ResponseBody
public String find(HttpServletRequest request, Model model) {
String id = get(request, "id");
StudentDTO studentDTO = studentService.find(Long.valueOf(id));
LOGGER.info("find result : {}", JSON.toJSONString(studentDTO));
return StringUtils.isEmpty(studentDTO.getErrorStr())
? JSON.toJSONString(studentDTO.getStudent())
: studentDTO.getErrorStr();
}
部署&验证
将应用打包成war,在线部署到tomcat001容器上,开始验证:
- 在浏览器输入“http://localhost:8080/hbasedemo/insert?id=9009&name=Tom&age=16”,验证新增一条记录,页面提示“操作成功”后,去hbase容器上执行hbase shell命令进入控制台,再执行scan 'student’查看student表的内容,如下图,已经新增了一条记录:
- 在浏览器输入“http://localhost:8080/hbasedemo/find?id=9009”,根据学号查找记录,服务端直接返回json格式数据,如下图:
- 在浏览器输入“http://localhost:8080/hbasedemo/delete?id=9009”,根据学号删除记录,操作成功后,不论是执行查询请求还是去hbase控制台执行scan 'student’查看student表的内容,都看不到9009这条记录了;
至此,在java应用中操作HBase的实战就结束了,完整的源码地址是:git@github.com:zq2599/blog_demos.git,里面有多个工程,本次Demo所在目录如下图红框所示: