# hbase-sdk
**Repository Path**: linuxstar/hbase-sdk
## Basic Information
- **Project Name**: hbase-sdk
- **Description**: 专注于HBase的ORM框架
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 67
- **Created**: 2021-11-05
- **Last Updated**: 2021-11-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
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。
🐾 快速开始 |
🎬 视频教程 |
🌚 官方文档 |
💰 捐赠我们 |
🌾 English
***
## hbase-sdk
`hbase-sdk` 是一款轻量级的ORM框架,封装了对HBase数据表的读写操作和对集群的运维管理,并针对HBase1.x的API和2.xAPI的差异,做了统一的定义,
提供更加方便的调用API。同时,HQL的功能也已上线,提供了类SQL读写数据的能力,这将大大降低HBase Client API的使用门槛。
API文档地址: [https://weixiaotome.gitee.io/hbase-sdk/](https://weixiaotome.gitee.io/hbase-sdk/)
如果觉得这个项目不错可以 [star](https://github.com/CCweixiao/hbase-sdk/stargazers) 支持或者 [捐赠](https://www.jielongping.com) 它 :blush:
## 功能特性
* [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/dockerhbasetest](https://www.jielongping.com/archives/dockerhbasetest)
`hbase-sdk` 开发所用的工具为IDEA,所以也极力推荐导入项目到idea中。
### 1. 普通项目
`Maven` 配置:
创建一个基础的 `Maven` 工程,HBase SDK API开发时基于的HBase版本主要是1.4.3和2.1.0。
所以,如果你的HBase版本是1.x,可以使用如下依赖。
```xml
com.github.CCweixiao
hbase-sdk-core_1.4
2.0.6
```
如果你的HBase版本是2.x,则可以使用如下依赖。
```xml
com.github.CCweixiao
hbase-sdk-core_2.1
1.0.5
```
`hbase-sdk`目前最新的版本是`2.0.6`。你可以在maven仓库中搜索CCweixiao来获取hbase-sdk相关jar包的最新版本。
[https://mvnrepository.com/artifact/com.github.CCweixiao](https://mvnrepository.com/artifact/com.github.CCweixiao)
当然,如果你想重新编译,以适配你自己的HBase版本,也可以选择下载源码,修改项目根pom.xml文件中的`hbase.version`,之后运行如下编译命令:
```shell script
git clone https://github.com/CCweixiao/hbase-sdk.git or
git clone https://gitee.com/weixiaotome/hbase-sdk.git
cd hbase-sdk
mvn clean install -Dmaven.test.skip=true
```
### 2. 项目结构

API核心类继承结构示意图:

### 3. 在SpringBoot项目中使用
`Maven` 配置:
创建一个基于`Maven`的spring boot工程。
```xml
com.github.CCweixiao
spring-boot-starter-hbase_1.4
2.0.6
```
or
```xml
com.github.CCweixiao
spring-boot-starter-hbase_2.1
2.0.6
```
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`会持续完善该依赖的版本兼容。
```xml
org.apache.hbase
hbase-client
1.4.3
```
or
```xml
org.apache.hbase
hbase-client
2.1.0
```
### 5. 配置HBase数据库连接
**普通项目**
```java
// 数据读写API
HBaseTemplate hBaseTemplate = new HBaseTemplate("node1", "2181");
// 管理员API
HBaseAdminTemplate hBaseAdminTemplate = new HBaseAdminTemplate("node1", "2181");
// HQL操作API
HBaseSqlTemplate hBaseSqlTemplate = new HBaseSqlTemplate("localhost", "2181");
```
**spring boot项目**
application.yaml
```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 依赖注入
```java
@Service
public class UserService {
@Autowired
private HBaseTemplate hBaseTemplate;
@Autowired
private HBaseAdminTemplate hBaseAdminTemplate;
}
```
## Contents
- [**`集群管理`**](#集群管理)
- [**`创建namespace`**](#创建namespace)
- [**`创建表`**](#创建表)
- [**`更多操作`**](#更多操作)
- [**`数据读写`**](#数据读写)
- [**`创建数据模型类`**](#创建数据模型类)
- [**`保存数据`**](#保存数据)
- [**`批量保存数据`**](#批量保存数据)
- [**`根据RowKey查询`**](#根据RowKey查询)
- [**`scan查询`**](#scan查询)
- [**`删除数据`**](#删除数据)
- [**`HQL`**](#HQL)
- [**`insert`**](#insert)
- [**`select`**](#select)
- [**`delete`**](#delete)
- [**`HBaseThriftAPI`**](#HBaseThriftAPI)
- [**`创建HBaseThriftService连接池`**](#创建HBaseThriftService连接池)
## 集群管理
HBaseAdminTemplate封装了HBaseAdmin的常用操作,比如namespace的管理、表的管理、以及快照管理等等,后续这些API将会更加完善。

### 创建namespace
```java
@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);
}
```
### 创建表
```java
@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的数据读写操作。

### 创建数据模型类
```java
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;
}
}
```
```java
@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;
}
@Override
public 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,不做操作。
### 保存数据
```java
@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数据模型。
```java
@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("用户数据保存成功!");
}
```
### 批量保存数据
```java
@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查询
```java
@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);
}
```
```java
@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查询
```java
@Test
public void testFind() {
final List userEntities = hBaseTemplate.findAll(10, UserEntity.class);
System.out.println(userEntities);
System.out.println("用户数据批量查询");
}
@Test
public void testFindByPrefix() {
final List userEntities = hBaseTemplate.findByPrefix("11", 10, UserEntity.class);
System.out.println("用户数据批量查询");
}
```
### 删除数据
```java
@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("数据删除完成");
}
```
```java
@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`的对象实例。

构造HBaseSqlTemplate的示例。
```java
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的两个成员变量,
```java
protected HBaseTableSchema hBaseTableSchema;
protected List hBaseColumnSchemaList;
```
分别用来表的Schema信息和HBase表对应列的元数据信息。
针对HBase表列的数据类型转换,目前内置的实现有:
Boolean、Byte、Char、Date、Double、Float、Hex、Int、Long、Short、String
通过实现`LiteralInterpreter`接口,你可以定义自己的列数据类型转换实现。
```json
{
"tableName":"TEST:USER",
"defaultFamily":"INFO",
"columnSchema":[
{
"family":"INFO",
"qualifier":"name",
"typeName":"string"
},
{
"family":"INFO2",
"qualifier":"age",
"typeName":"int"
}
]
}
```
通过实现相应的接口,你可以选择加载HBase表、列元数据信息的方式。如:类型myBatis在XML文件中加载。
HBaseSqlTemplate的实例准备好之后,就可以使用HQL来进行数据读写。
### insert
```sql
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' )
```
```java
@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
```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 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
```
```java
@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
```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'
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'
```
```java
@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);
}
```
## HBaseThriftAPI
HBase常用的客户端API会直接连接zookeeper,如果api使用不当,产生BUG,会造成zookeeper的连接耗尽;HBaseThriftApi不仅有跨平台特性,
同时也会在底层避免我们直接连接zk。
如果直接使用hbase thrift的api,你可能会遇到以下几种情况:
1. 频繁创建TSocket连接,不必要的开销增加
2. 某一时间段内可能频繁创建过多的TSocket,造成本地短连接过多
3. 创建完一个TSocket,间隔时间过长不使用,会被服务端主动断开
### 创建HBaseThriftService连接池
为了解决上述问题,所以采取连接池的实现方式。HBase Thrift API 连接池的实现基于commons-pool2,类似jedis。
连接池的使用也非常简单
```java
HBaseThriftService hBaseThriftService = HBaseThriftServiceHolder.getInstance("localhost", 9090);
HBaseThriftService hBaseThriftService = HBaseThriftServiceHolder.getInstance("localhost", 9090, 10);
List allTableNames = hBaseThriftService.getTableNames();
```
更多API的使用可以参考源码中测试用例以及相关的API文档。
## 特别鸣谢
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.7 2020-12-30
- HBase Thrift API上线,以及提供Thrift 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特性
- 模块拆分
- ......