column-store-orm是一个列式存储orm框架。通过注解的方式实现了对象—实体映射,屏蔽了底层存储系统的差异。目前支持tablestore,hbase,mongo,dynamo四种存储系统。
当项目需要进行多云部署或者私有化部署时,如果相应的环境使用的存储系统跟当前系统有差异,则不可避免的需要进行代码层面的修改。如果使用了云中立SDK,则只需要修改配置文件即可支持多环境的部署。
| 功能项 | 方法签名 | TableStore | HBase | Mongo |
|---|---|---|---|---|
| 根据主键获取单条记录 | T get(Object... primaryKeyColumns) | 支持 | 支持 | 支持 |
| 根据主键获取单条记录 | T get(PrimaryKey primaryKey) | 支持 | 支持 | 支持 |
| 根据主键获取单条记录的指定列 | T get(PrimaryKey primaryKey, Set |
支持 | 支持 | 支持 |
| 写入一条记录(如果记录不存在则插入;如果记录存在则覆盖所有列) | void put(T t) | 支持 | 支持 | 支持 |
| 更新一条记录(如果记录不存在则插入;如果记录存在,则根据请求的内容新增、修改或者删除指定列的值) | void update(T t) | 支持 | 支持 | 支持 |
| 更新一条记录,并根据条件删除动态列 | void update(T t, boolean deleteDynamicColumns) | 支持 | 支持 | 支持 |
| 根据主键删除单条记录 | void delete(Object... primaryKeyColumns) | 支持 | 支持 | 支持 |
| 根据主键删除单条记录 | void delete(PrimaryKey primaryKey) | 支持 | 支持 | 支持 |
| 根据主键批量获取记录 | List |
支持 | 支持 | 支持 |
| 根据主键批量获取记录的指定列 | List |
支持 | 支持 | 支持 |
| 批量写入记录 | void batchPut(List |
支持 | 支持 | 支持 |
| 批量更新记录 | void batchUpdate(List |
支持 | 支持 | 支持 |
| 批量更新记录,并根据条件删除动态列 | void batchUpdate(List |
支持 | 支持 | 支持 |
| 根据主键批量删除记录 | void batchDelete(List |
支持 | 支持 | 支持 |
| 根据主键进行范围查找 | List |
支持 | 支持 | 支持 |
| 根据主键进行返回查找,返回指定列 | List |
支持 | 支持 | 支持 |
| 根据主键进行返回查找,返回指定列,同时限制返回记录数 | List |
支持 | 支持 | 支持 |
| 根据主键进行范围查找,并对返回的每条记录执行consumer 方法 | void rangeConsume(RangePrimaryKey rangePrimaryKey, Consumer |
支持 | 支持 | 支持 |
| 根据指定列获取记录 | List |
支持 | 支持 | 支持 |
| 根据指定列获取记录,并进行分页,排序 | List |
支持 | 支持 | 支持 |
由于column-store-orm-mongo使用到了spring-data-mongodb,因此与spring存在版本兼容性问题。
| column-store-orm-mongo版本 | spring-data-mongodb | spring版本 | spring-boot-dependencies版本 |
|---|---|---|---|
| 1.0.7.SB1_5 | 1.10.6.RELEASE | 4.3.10.RELEASE | 1.5.6.RELEASE |
| 1.0.7.SB2_3 | 3.0.1.RELEASE | 5.2.7.RELEASE | 2.3.1.RELEASE |
<!-- 云中立虚拟仓库,关联了云中立实仓 -->
<repository>
<id>maven-neutralcloud</id>
<name>maven-neutralcloud</name>
<url>http://packages.hsifue.cn/artifactory/maven-neutralcloud-public/</url>
</repository>
<dependencies>
<dependency>
<groupId>com.glodon.paas.foundation</groupId>
<artifactId>column-store-orm-mongo</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.glodon.paas.foundation</groupId>
<artifactId>column-store-orm</artifactId>
<!-- 根据实际情况修改版本号 -->
<version>${version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
初始化仅需创建一个TableStoreDao对象,传入tableStore的config和一个实体类。
TableStoreMongoConfig config = TableStoreMongoConfig.builder()
.hosts("localhost:27017")
.database("mongo-test")
.build();
TableStoreDAO<BookEntity> dao = new TableStoreDAOMongoImpl<>(config, BookEntity.class);
实体类到TableStore的PrimaryKeyName和ColumnName的自动映射是通过注解实现的,BookEntity的代码如下:
@Data
@TSTable(name = "ut_book")
public class BookEntity {
@TSPKColumn
private String floor = "";
@TSAttrColumn
private int ibsn;
@TSAttrColumn
private String author = "";
@TSAttrColumn(dynamicColumnNames = true)
private List<String> readerIds = new ArrayList<>(10);
public BookEntity() {
}
public BookEntity(String floor, int ibsn, String author) {
this.floor = floor;
this.ibsn = ibsn;
this.author = author;
}
}
主要用到了三个注解,TSTable和TSPKColumn、TSAttrColumn。
TSTable用于实体类到TableStore中表的映射:
TSPKColumn用于实体类中属性到TableStore中PrimaryKeyName的映射:
TSAttrColumn用于实体类中属性到TableStore中PrimaryKeyName的映射:
单条记录有put、get、update、delete四种操作;
get和delete有两种调用方式,第一种为按照顺序依次传入primaryKeyValue,第二种先用buildPrimaryKey方法构造出PrimaryKey,再传入get、delete方法;
// put
BookEntity entity = new BookEntity("f1", 12345, "Jon");
entity.setReaderIds(new ArrayList<>(Arrays.asList("100", "101")));
dao.put(entity);
// get
PrimaryKey primaryKey = PrimaryKey.builder()
.addColumn("floor", "f1")
.build();
BookEntity bookEntityGet = dao.get(primaryKey);
Assert.assertEquals("Jon", bookEntityGet.getAuthor());
Assert.assertEquals(2, bookEntityGet.getReaderIds().size());
// update
entity.setAuthor("Json_updated");
entity.getReaderIds().add("102");
dao.update(entity);
BookEntity bookEntityUpdate = dao.get(primaryKey);
Assert.assertEquals("Json_updated", bookEntityUpdate.getAuthor());
Assert.assertEquals(3, bookEntityUpdate.getReaderIds().size());
// delete
dao.delete(primaryKey);
BookEntity bookEntityDelete = dao.get(primaryKey);
Assert.assertNull(bookEntityDelete);
batch有put、get、update、delete四种操作;
List<Integer> ids = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// putRow
dao.batchPut(ids.stream()
.map(id -> {
BookEntity bookEntity = new BookEntity("f" + id + "_batch", id, "author" + id + "_batch");
for (int i = 1; i <= id; i++) {
bookEntity.getReaderIds().add(String.valueOf(10000 + i));
}
return bookEntity;
})
.collect(Collectors.toList()));
// get
List<PrimaryKey> pks = ids.stream()
.map(id -> PrimaryKey.builder()
.addColumn("floor", "f" + id + "_batch")
.build())
.collect(Collectors.toList());
dao.batchGet(pks).forEach(bookEntity -> {
String floor = bookEntity.getFloor();
int start = "f".length();
int end = floor.indexOf("_batch");
int index = Integer.valueOf(floor.substring(start, end));
Assert.assertEquals("author" + index + "_batch", bookEntity.getAuthor());
Assert.assertEquals(index, bookEntity.getReaderIds().size());
});
// update
dao.batchUpdate(ids.stream()
.map(id -> {
BookEntity bookEntity = new BookEntity("f" + id + "_batch", id + 9000, "author" + id + "_batch_updated");
bookEntity.getReaderIds().add("999");
return bookEntity;
})
.collect(Collectors.toList()));
dao.batchGet(pks).forEach(bookEntity -> {
String floor = bookEntity.getFloor();
int start = "f".length();
int end = floor.indexOf("_batch");
int index = Integer.valueOf(floor.substring(start, end));
Assert.assertEquals("author" + index + "_batch_updated", bookEntity.getAuthor());
Assert.assertEquals(index + 1, bookEntity.getReaderIds().size());
});
// update delete dynamic columnValues
dao.batchUpdate(ids.stream()
.map(id -> {
BookEntity bookEntity = new BookEntity("f" + id + "_batch", id + 8000, "author" + id + "_batch_updated_2");
bookEntity.getReaderIds().add("999");
return bookEntity;
})
.collect(Collectors.toList()), true);
dao.batchGet(pks).forEach(bookEntity -> {
String floor = bookEntity.getFloor();
int start = "f".length();
int end = floor.indexOf("_batch");
int index = Integer.valueOf(floor.substring(start, end));
Assert.assertEquals("author" + index + "_batch_updated_2", bookEntity.getAuthor());
Assert.assertEquals(index, bookEntity.getReaderIds().size());
});
// delete
dao.batchDelete(pks);
List<BookEntity> ret = dao.batchGet(pks);
Assert.assertTrue(ret == null || ret.isEmpty());
range操作有range get和range consume类接口,前者返回结果,后者直接指定对结果的操作;
RangePrimaryKey有四种指定方式:
// put first
List<Integer> ids = new ArrayList<>(100);
for (int i = 1; i <= 100; i++) {
ids.add(i);
}
// putRow first
dao.batchPut(ids.stream()
.map(id -> {
BookEntity bookEntity = new BookEntity("f" + id + "_range", id, "author" + id + "_range");
for (int i = 1; i <= id; i++) {
bookEntity.getReaderIds().add(String.valueOf(20000 + i));
}
return bookEntity;
})
.collect(Collectors.toList()));
RangePrimaryKey rangePrimaryKey = RangePrimaryKey.builder()
.addSubRangeStringValue("floor", "f20", "f50")
.build();
Assert.assertEquals(dao.rangeGet(rangePrimaryKey).size(), 33);
dao.rangeConsume(RangePrimaryKey.builder().addFullRangePrimaryKeyColumn("floor").build(), bookEntity -> {
System.out.println(bookEntity.getAuthor());
dao.delete(PrimaryKey.builder().addColumn("floor", bookEntity.getFloor()).build());
});
column get为按照一个或多个列值进行匹配,操作有两个接口 columnGet(SearchColumn searchColumn)
与columnGet(SearchColumn searchColumn, int offset, int count, Boolean asc),前者默认升序列出满足条件的前十条,后者可以自定义。
// 查询ibsn为12345,author为Jon的数据
List<BookEntity> bookEntities = dao.columnGet(SearchColumn.builder()
.addColumn("ibsn", "12345")
.addColumn("author", "Jon").build());
List<BookEntity> bookEntities = dao.columnGet(SearchColumn.builder()
.addColumn("ibsn", "12345")
.addColumn("author", "Jon").build(), 0, 10, true);
@TSAttrColumn注解,每次启动都会重新建立索引,建立多元索引需要30秒左右时间。@Data
@TSTable(name = "ut_book")
public class BookEntity {
@TSPKColumn
private String floor = "";
@TSAttrColumn
@TSIndexColumn
private int ibsn;
@TSAttrColumn
@TSIndexColumn
private String author = "";
@TSAttrColumn(dynamicColumnNames = true)
private List<String> readerIds = new ArrayList<>(10);
}

表格存储整体架构并不复杂:
框架层
Annotation:框架提供的注解。
@TSTable:实体类到表的映射。
@TSIndexColumn:实体类属性到索引列的映射。
@TSPKColumn:实体类属性到主键的映射。
@TSAttrColumn:实体类属性到列的映射。
Model:框架中定义的模型。
private String tableName; // 表名
private List<PKColumnMeta> pkColumnMeta; // 主键
private List<AttrColumnMeta> attrColumnMeta; // 属性
private List<IndexMeta> indexMeta; //索引
TableStoreDAO:这是一个接口,定义了表格存储支持的功能。
AbstractTableStoreDAO:这是一个抽象类,它有两个职责:
继承了TableStoreDAO,并定义了相关的模板方法,为具体实现类提供了一个基类。
作为注解处理类,实现了对实体类注解的解析。
实现层
实现层以框架层为基础,提供了对具体存储组件的实现。
应用层
应用层以实现层为基础,基于springboot提供了自动装配功能。