java连接hbase_HBase 工具 | hbasesdk 推出HQL功能
hbase-sdk
基于HBase Client的相關API開發而來的一款輕量級的HBase ORM框架。提供SQL查詢功能,以類SQL的方式——HQL讀寫HBase數據。?
針對HBase 1.x和2.xAPI的不同之處,在其上做了一層統一的封裝。
hbase-sdk分為spring-boot-starter-hbase和hbase-sdk-core兩部分。
SpringBoot項目中引入spring-boot-starter-hbase,在普通的Java項目中則可以使用hbase-sdk-core。
hbase-sdk 是一款輕量級的ORM框架,封裝了對HBase數據表的讀寫操作和對集群的運維管理,并針對HBase1.x的API和2.xAPI的差異,做了統一的定義, 提供更加方便的調用API。同時,HQL的功能也已上線,提供了類SQL讀寫數據的能力,這將大大降低HBase Client API的使用門檻。API文檔地址:?
https://weixiaotome.gitee.io/hbase-sdk/如果覺得這個項目不錯可以 star 支持或者打賞它。
功能特性
- [x] 消除不同版本API的差異,重新定義接口規范
- [x] 優良的ORM特性,數據查詢結果集自動映射Java實體類
- [x] HQL,以類SQL的形式讀寫HBase的表中數據
- [x] 利用spring-boot-starter-hbase無縫與SpringBoot集成
- [x] HBatis,類似于myBatis,提供配置文件管理HQL的功能(努力規劃中)
- [x] 熔斷能力,提供API級別的主備集群自動切換功能,保障服務的高可用(努力規劃中)
- [x] JDK8+
快速開始
hbase-sdk 的每個版本經過測試完成之后,都會編譯打包至各個模塊,最后發布到maven中央倉庫之中,只是最新版本的代碼有一定的延遲。如果你想在第一時間體驗該項目, 可以選擇在Gitee或Github中克隆源碼,在本地編譯并運行測試用例。
hbase-sdk 基于java8開發,如果你想自己編譯或體驗,請確保已經安裝了Java8和maven3+。此外,如果你想在本地進行開發調試,建議在本地存在一個可連通的HBase環境。如果你想快速搭建一個HBase的開發環境,請參考:
https://www.jielongping.com/archives/dockerhbasetesthbase-sdk 開發所用的工具為IDEA,所以也極力推薦導入項目到idea中。
1. 普通項目
Maven 配置:
創建一個基礎的 Maven 工程,HBase SDK API開發時基于的HBase版本主要是1.4.3和2.1.0。
所以,如果你的HBase版本是1.x,可以使用如下依賴。
<dependency>????<groupId>com.github.CCweixiaogroupId>
????<artifactId>hbase-sdk-core_1.4artifactId>
????<version>2.0.6version>
dependency>
如果你的HBase版本是2.x,則可以使用如下依賴。
<dependency>????<groupId>com.github.CCweixiaogroupId>
????<artifactId>hbase-sdk-core_2.1artifactId>
????<version>1.0.5version>
dependency>
hbase-sdk目前最新的版本是2.0.6。你可以在maven倉庫中搜索CCweixiao來獲取hbase-sdk相關jar包的最新版本。
https://mvnrepository.com/artifact/com.github.CCweixiao當然,如果你想重新編譯,以適配你自己的HBase版本,也可以選擇下載源碼,修改項目根pom.xml文件中的hbase.version,之后運行如下編譯命令:
git?clone?https://github.com/CCweixiao/hbase-sdk.git??orgit?clone?https://gitee.com/weixiaotome/hbase-sdk.git
cd?hbase-sdk
mvn?clean?install?-Dmaven.test.skip=true
2. 項目結構
projectAPI核心類繼承結構示意圖:
3. 在SpringBoot項目中使用
Maven 配置:
創建一個基于Maven的spring boot工程。
<dependency>????<groupId>com.github.CCweixiaogroupId>
????<artifactId>spring-boot-starter-hbase_1.4artifactId>
????<version>2.0.6version>
dependency>
or
<dependency>????<groupId>com.github.CCweixiaogroupId>
????<artifactId>spring-boot-starter-hbase_2.1artifactId>
????<version>2.0.6version>
dependency>
spring-boot-starter-hbase這個模塊中已經包含了hbase-sdk-core。
4. 引入hbase-client的依賴
除了引入hbase-sdk的相關依賴之外,你還需要引入hbase-client的依賴,hbase-client的版本目前建議為1.2.x、1.4.x、2.1.x。hbase-client1.x和2.x的新舊API有所差異。未來,hbase-sdk會持續完善該依賴的版本兼容。
<dependency>????<groupId>org.apache.hbasegroupId>
????<artifactId>hbase-clientartifactId>
????<version>1.4.3version>
dependency>????????
or
<dependency>????<groupId>org.apache.hbasegroupId>
????<artifactId>hbase-clientartifactId>
????<version>2.1.0version>
dependency>????????
5. 配置HBase數據庫連接
普通項目
//?數據讀寫APIHBaseTemplate?hBaseTemplate?=?new?HBaseTemplate("node1",?"2181");
//?管理員API
HBaseAdminTemplate?hBaseAdminTemplate?=?new?HBaseAdminTemplate("node1",?"2181");
//?HQL操作API
HBaseSqlTemplate?hBaseSqlTemplate?=?new?HBaseSqlTemplate("localhost",?"2181");
spring boot項目
application.yaml
spring:??data:
????hbase:
??????quorum:?node1,node2,node3
??????node-parent:?/hbase
??????zk-client-port:?2181
??????root-dir:?/hbase
??????client-properties:?hbase.client.retries.number=3
@Autowired 依賴注入
@Servicepublic?class?UserService?{
????@Autowired
????private?HBaseTemplate?hBaseTemplate;
????@Autowired
????private?HBaseAdminTemplate?hBaseAdminTemplate;
}
集群管理
HBaseAdminTemplate封裝了HBaseAdmin的常用操作,比如namespace的管理、表的管理、以及快照管理等等,后續這些API將會更加完善。
admin-api創建namespace
????@Test????public?void?testCreateNamespace()?{
????????String?namespaceName?=?"LEO_NS";
????????
????????NamespaceDesc?namespaceDesc?=?new?NamespaceDesc();
????????namespaceDesc.setNamespaceName(namespaceName);
????????//?為namespace添加屬性
????????namespaceDesc?=?namespaceDesc.addNamespaceProp("desc",?"測試命名空間")
????????????????.addNamespaceProp("createBy",?"leo").addNamespaceProp("updateBy",?"admin");
????????hBaseTemplate.createNamespace(namespaceDesc);
????}
創建表
????@Test????public?void?testCreateTable()?{
????????String?tableName?=?"LEO_NS:USER";
????????TableDesc?tableDesc?=?new?TableDesc();
????????tableDesc.setTableName(tableName);
????????tableDesc?=?tableDesc.addProp("tag",?"測試用戶表").addProp("createUser",?"leo");
????????FamilyDesc?familyDesc1?=?new?FamilyDesc.Builder()
????????????????.familyName("INFO")
????????????????.replicationScope(1)
????????????????.compressionType("NONE")
????????????????.timeToLive(2147483647)
????????????????.maxVersions(1).build();
????????FamilyDesc?familyDesc2?=?new?FamilyDesc.Builder()
????????????????.familyName("INFO2")
????????????????.replicationScope(0)
????????????????.compressionType("SNAPPY")
????????????????.timeToLive(864000)
????????????????.maxVersions(3).build();
????????tableDesc?=?tableDesc.addFamilyDesc(familyDesc1).addFamilyDesc(familyDesc2);
????????hBaseTemplate.createTable(tableDesc,?false);
????}
更多操作
可以參考相關API文檔或測試用例
數據讀寫
類似于Hibernate,你也可以使用hbase-sdk框架所提供的ORM特性,來實現對HBase的數據讀寫操作。
api-data創建數據模型類
public?class?ModelEntity?{????private?String?createBy;
????private?Long?createTime;
????public?String?getCreateBy()?{
????????return?createBy;
????}
????public?void?setCreateBy(String?createBy)?{
????????this.createBy?=?createBy;
????}
????public?Long?getCreateTime()?{
????????return?createTime;
????}
????public?void?setCreateTime(Long?createTime)?{
????????this.createTime?=?createTime;
????}
}
@HBaseTable(schema?=?"TEST",?name?=?"LEO_USER",?uniqueFamily?=?"info1")
public?class?UserEntity?extends?ModelEntity{
????@HBaseRowKey
????private?String?userId;
????private?String?username;
????private?int?age;
????private?List?addresses;private?Map?contactInfo;private?Double?pay;@HBaseColumn(name?=?"is_vip",?family?=?"INFO2",?toUpperCase?=?true)private?boolean?isVip;public?String?getUserId()?{return?userId;
????}public?void?setUserId(String?userId)?{this.userId?=?userId;
????}public?String?getUsername()?{return?username;
????}public?void?setUsername(String?username)?{this.username?=?username;
????}public?int?getAge()?{return?age;
????}public?void?setAge(int?age)?{this.age?=?age;
????}public?boolean?isVip()?{return?isVip;
????}public?void?setVip(boolean?vip)?{
????????isVip?=?vip;
????}public?List?getAddresses()?{return?addresses;
????}public?void?setAddresses(List?addresses)?{this.addresses?=?addresses;
????}public?Map?getContactInfo()?{return?contactInfo;
????}public?void?setContactInfo(Map?contactInfo)?{this.contactInfo?=?contactInfo;
????}public?Double?getPay()?{return?pay;
????}public?void?setPay(Double?pay)?{this.pay?=?pay;
????}@Overridepublic?String?toString()?{return?"UserEntity{"?+"userId='"?+?userId?+?'\''?+",?username='"?+?username?+?'\''?+",?age="?+?age?+",?addresses="?+?addresses?+",?contactInfo="?+?contactInfo?+",?pay="?+?pay?+",?isVip="?+?isVip?+'}';
????}
}
@HBaseTable(schema = "TEST", name = "LEO_USER", uniqueFamily = "info1")
HBaseTable注解用于定義HBase的表信息,schema用于定義該表的命名空間,如果不指定,默認是default, name用于定義該表的表名,如果不指定,表名則為類名的組合單詞拆分加'_'拼接,如:UserEntity對應的表名為:user_entity。uniqueFamily用于定義如果所有的字段不特配置列簇名,則使用此處配置的列簇名。
@HBaseRowKey private String userId;
該注解表示userId字段為rowKey字段。
@HBaseColumn(name = "is_vip", family = "INFO2", toUpperCase = true) private boolean isVip; 該注解用于定義一個字段信息,name用于定義字段名,如果不指定,則默認使用字段名的組合單詞拆分加'_'拼接,如:isVip,對應的字段名是:is_vip. family用于定義該字段屬于INFO2列簇,toUpperCase表示字段名是否轉大寫,默認false,不做操作。
保存數據
???@Test????public?void?testSaveUser()?{
????????UserEntity?userEntity?=?new?UserEntity();
????????userEntity.setUserId("10001");
????????userEntity.setUsername("leo");
????????userEntity.setAge(18);
????????userEntity.setVip(true);
????????userEntity.setAddresses(Arrays.asList("北京",?"上海"));
????????userEntity.setCreateBy("admin");
????????userEntity.setCreateTime(System.currentTimeMillis());
????????Map?contactInfo?=?new?HashMap<>(2);
????????contactInfo.put("email",?"2326130720@qq.com");
????????contactInfo.put("phone",?"18739577988");
????????contactInfo.put("address",?"浦東新區");
????????userEntity.setContactInfo(contactInfo);
????????userEntity.setPay(100000.0d);try?{
????????????hBaseTemplate.save(userEntity);
????????????System.out.println("用戶數據保存成功!");
????????}?catch?(Exception?e)?{
????????????e.printStackTrace();
????????}
????}
除此之外,保存數據時也可以不必構造數據模型類,而直接構造map數據模型。
????@Test????public?void?testToSave()?{
????????Map?data?=?new?HashMap<>();
????????data.put("info1:addresses",?Arrays.asList("廣州",?"深圳"));
????????data.put("info1:username",?"leo");
????????data.put("info1:age",?18);
????????data.put("INFO2:IS_VIP",?true);
????????data.put("info1:pay",?10000.1d);
????????data.put("info1:create_by",?"tom");
????????data.put("info1:create_time",?System.currentTimeMillis());
????????Map?contactInfo?=?new?HashMap<>(2);
????????contactInfo.put("email",?"2326130720@qq.com");
????????contactInfo.put("phone",?"18739577988");
????????contactInfo.put("address",?"浦東新區");
????????data.put("info1:contact_info",?contactInfo);
????????hBaseTemplate.save("TEST:LEO_USER",?"10002",?data);
????????System.out.println("用戶數據保存成功!");
????}
批量保存數據
????@Test????public?void?testToSaveBatch()?{
????????Map>?data?=?new?HashMap<>();
????????Map?data1?=?new?HashMap<>();
????????data1.put("info1:username",?"kangkang");
????????data1.put("info1:age",?18);
????????data1.put("INFO2:IS_VIP",?true);
????????Map?data2?=?new?HashMap<>();
????????data2.put("info1:username",?"jane");
????????data2.put("info1:age",?18);
????????data2.put("INFO2:IS_VIP",?false);
????????data.put("12003",?data1);
????????data.put("11004",?data2);
????????hBaseTemplate.saveBatch("TEST:LEO_USER",?data);
????????System.out.println("用戶數據批量保存成功!");
????}
根據RowKey查詢
????@Test????public?void?testGet()?{
????????UserEntity?userEntity?=?hBaseTemplate.getByRowKey("10001",?UserEntity.class);
????????final?UserEntity?userEntity1?=?hBaseTemplate.getByRowKey("10002",?UserEntity.class);
????????System.out.println("用戶數據獲取成功!");
????????System.out.println(userEntity);
????}
????@Test
????public?void?testGetToMap()?{
????????Map?userInfo?=?hBaseTemplate.getByRowKey("TEST:LEO_USER",?"10001");
????????System.out.println(Boolean.valueOf(userInfo.get("INFO2:IS_VIP").toString()));
????????System.out.println(userInfo);
????}
scan查詢
????@Test????public?void?testFind()?{
????????final?List?userEntities?=?hBaseTemplate.findAll(10,?UserEntity.class);
????????System.out.println(userEntities);
????????System.out.println("用戶數據批量查詢");
????}@Testpublic?void?testFindByPrefix()?{final?List?userEntities?=?hBaseTemplate.findByPrefix("11",?10,?UserEntity.class);
????????System.out.println("用戶數據批量查詢");
????}
刪除數據
????@Test????public?void?testDeleteData()?{
????????hBaseTemplate.delete("TEST:LEO_USER",?"12003");
????????hBaseTemplate.delete("TEST:LEO_USER",?"11004",?"INFO2");
????????hBaseTemplate.delete("TEST:LEO_USER",?"10001",?"info1",?"addresses");
????????System.out.println("數據刪除完成");
????}
????@Test
????public?void?testDeleteBatch()?{
????????hBaseTemplate.deleteBatch("TEST:LEO_USER",?Arrays.asList("10001",?"10002"));
????????hBaseTemplate.deleteBatch("TEST:LEO_USER",?Collections.singletonList("10003"),?"INFO2");
????????hBaseTemplate.deleteBatch("TEST:LEO_USER",?Collections.singletonList("10004"),
????????????????"info1",?"age",?"username");
????}
HQL
hbase-sdk 從2.0.6版本開始,開始提供HQL功能,一種以類SQL的方式讀寫HBase集群的數據,降低API的使用復雜度。HQL的操作依賴HBaseSqlTemplate來完成, 因此使用之前,必須構造好HBaseSqlTemplate的對象實例。
hql構造HBaseSqlTemplate的示例。
????private?HBaseSqlTemplate?hBaseSqlTemplate;????@Before
????public?void?testInitHBaseSqlTemplate()?{
????????hBaseSqlTemplate?=?new?HBaseSqlTemplate("localhost",?"2181");
????????List?hBaseColumnSchemas?=?createHBaseColumnSchemaList();
????????HBaseTableSchema?hBaseTableSchema?=?new?HBaseTableSchema();
????????hBaseTableSchema.setTableName("LEO_USER");
????????hBaseTableSchema.setDefaultFamily("g");//hBaseTableSchema.setRowKeyHandlerName("string");
????????HBaseTableConfig?hBaseTableConfig?=?new?DefaultHBaseTableConfig(hBaseTableSchema,?hBaseColumnSchemas);
????????hBaseSqlTemplate.setHBaseTableConfig(hBaseTableConfig);
????}public?List?createHBaseColumnSchemaList()?{
????????????List?hBaseColumnSchemas?=?new?ArrayList<>();
????????????HBaseColumnSchema?hBaseColumnSchema1?=?new?HBaseColumnSchema();
????????????hBaseColumnSchema1.setFamily("g");
????????????hBaseColumnSchema1.setQualifier("id");
????????????hBaseColumnSchema1.setTypeName("string");
????????????HBaseColumnSchema?hBaseColumnSchema2?=?new?HBaseColumnSchema();
????????????hBaseColumnSchema2.setFamily("g");
????????????hBaseColumnSchema2.setQualifier("name");
????????????hBaseColumnSchema2.setTypeName("string");
????????????HBaseColumnSchema?hBaseColumnSchema3?=?new?HBaseColumnSchema();
????????????hBaseColumnSchema3.setFamily("g");
????????????hBaseColumnSchema3.setQualifier("age");
????????????hBaseColumnSchema3.setTypeName("int");
????????????HBaseColumnSchema?hBaseColumnSchema4?=?new?HBaseColumnSchema();
????????????hBaseColumnSchema4.setFamily("g");
????????????hBaseColumnSchema4.setQualifier("address");
????????????hBaseColumnSchema4.setTypeName("string");
????????????hBaseColumnSchemas.add(hBaseColumnSchema1);
????????????hBaseColumnSchemas.add(hBaseColumnSchema2);
????????????hBaseColumnSchemas.add(hBaseColumnSchema3);
????????????hBaseColumnSchemas.add(hBaseColumnSchema4);return?hBaseColumnSchemas;
????????}
構造hBaseSqlTemplate示例需要先構造HBaseTableConfig,HBaseTableConfig的兩個成員變量,
????protected?HBaseTableSchema?hBaseTableSchema;????protected??List?hBaseColumnSchemaList;
分別用來表的Schema信息和HBase表對應列的元數據信息。
針對HBase表列的數據類型轉換,目前內置的實現有:
Boolean、Byte、Char、Date、Double、Float、Hex、Int、Long、Short、String
通過實現LiteralInterpreter接口,你可以定義自己的列數據類型轉換實現。
{?"tableName":"TEST:USER",
?"defaultFamily":"INFO",
?"columnSchema":[
??{
???"family":"INFO",
???"qualifier":"name",
???"typeName":"string"
??},
??{
???"family":"INFO2",
???"qualifier":"age",
???"typeName":"int"
??}
?]
}
通過實現相應的接口,你可以選擇加載HBase表、列元數據信息的方式。如:類型myBatis在XML文件中加載。
HBaseSqlTemplate的實例準備好之后,就可以使用HQL來進行數據讀寫。
insert
insert?into?LEO_USER?(?g:id?,?g:name?,?g:age?,?g:address?)?values?(?'10001',?'leo1'?,?'18',?'shanghai'?)?where?rowKey?is?stringkey?(?'a10002'?)?ts?is?'1604160000000'insert?into?LEO_USER?(?g:id?,?g:name?,?g:age?,?g:address?)?values?(?'10002',?'leo2'?,?'17',?'beijing'?)?where?rowKey?is?stringkey?(?'a10002'?)
????@Test
????public?void?testInsertSql()?{
????????String?sql?=?"insert?into?LEO_USER?(?g:id?,?g:name?,?g:age?,?g:address?)?values?(?'10001',?'leo'?,?'18',?'shanghai'?)?where?rowKey?is?stringkey?(?'a10002'?)?ts?is?'1604160000000'";
????????hBaseSqlTemplate.insert(sql);
????????System.out.println("insert?successfully!");
????}
select
select?(?g:id?,?g:name?,?g:age?,?g:address?)?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10002'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)??maxversion?is?2??startTS?is?'1604160000000'?,?endTS?is?'1604160000001'?limit?1,?10?select?*?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10002'?)?(?(?name?equal?'leo1'?and?age?less?'20'?)?or?(?id?greater?'10000'?)?)??maxversion?is?2??startTS?is?'1604160000000'?,?endTS?is?'1604160000001'?limit?10
????@Test
????public?void?testSelectSql()?{
????????String?sql?=?"select?(?g:id?,?g:name?,?g:age?,?g:address?)?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10002'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)??maxversion?is?2??startTS?is?'1604160000000'?,?endTS?is?'1604160000001'?limit?10?";
????????sql?=?"select?*?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10002'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)??maxversion?is?2??startTS?is?'1604160000000'?,?endTS?is?'1604160000001'?limit?10?";
????????final?List>?listList?=?hBaseSqlTemplate.select(sql);
????????listList.forEach(dataList?->?{
????????????dataList.forEach(data?->?{
????????????????System.out.println(data.getRowKey());
????????????????System.out.println(data.getFamilyStr());
????????????????System.out.println(data.getQualifierStr());
????????????????System.out.println(data.getTsDate());
????????????????System.out.println("########################################");
????????????});
????????});
????}
delete
delete?(?id?,?name?)?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10003'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)?ts?is?'1604160000000'delete?*?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10003'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)?ts?is?'1604160000000'
delete?*?from?LEO_USER?where?rowKey?is?stringkey?(?'a10002'?)?(?name?equal?'leo2'?or?age?less?'21'?)?ts?is?'1604160000000'
????@Test
????public?void?testDeleteSql(){
????????String?sql?=?"delete?(?id?,?name?)?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10003'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)?ts?is?'1604160000000'";
????????sql?=?"delete?*?from?LEO_USER?where?startKey?is?stringkey?(?'a10001'?)?,?endKey?is?stringkey?(?'a10003'?)?(?(?name?equal?'leo'?and?age?less?'12'?)?or?(?id?greater?'10000'?)?)?ts?is?'1604160000000'";
????????sql?=?"delete?*?from?LEO_USER?where?rowKey?is?stringkey?(?'a10002'?)?(?name?equal?'leo'?or?age?less?'21'?)?ts?is?'1604160000000'";
????????hBaseSqlTemplate.delete(sql);
????}
特別鳴謝
HQL的語法設計以及antlr4的語法解析,有參考alibaba的開源項目 simplehbase,在此特別感謝。simplehbase感覺是一個被遺棄的項目,針對的HBase版本是0。94, 已經有超過6年沒有維護了。
hbase-sdk 在simplehbase的基礎上進行重組和解耦,以兼容hbase-sdk原有的框架設計,并便于以后的擴展。
hbase-sdk 目前的不足
非HQL的數據讀寫API還不豐富,特別是數據過濾的查詢API。
HQL的antlr4解析功能不太完善,比如,目前HQL對中文要求不太好,同時,HQL對語法的要求比較嚴格,多一個空格少一個空格貌似都會引起語法錯誤。后續會針對這些缺點一一優化。
未來計劃
- HBatis,類似于MyBatis的ORM框架,以XML管理SQL的方式維護集群數據的讀寫操作
- 集成Hystrix熔斷框架,實現API層面的主備集群自動切換功能
- 還有更多
更新日志
v2.0.6 2020-11-29
- HQL功能上線
v2.0.5 2020-11-14
- 新增功能與代碼優化
v2.0.3 2020-10-08
- 大量重構和優化
v1.0.5 2020-09-07
- 完善基礎API的功能
- 完成ORM特性
- 模塊拆分
- ......
總結
以上是生活随笔為你收集整理的java连接hbase_HBase 工具 | hbasesdk 推出HQL功能的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python交互式终端是怎么实现的_py
- 下一篇: 汽车信号放大器工作原理是什么?