# column-store-orm **Repository Path**: glodon/column-store-ormc ## Basic Information - **Project Name**: column-store-orm - **Description**: 云中立-表格存储 - **Primary Language**: Java - **License**: MIT - **Default Branch**: open_2.3.x - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2024-03-06 - **Last Updated**: 2024-12-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # [表格存储](https://km.glodon.com/pages/viewpage.action?pageId=221645929) ## 一、项目介绍 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 columnsToGet) | 支持 | 支持 | 支持 | | 写入一条记录(如果记录不存在则插入;如果记录存在则覆盖所有列) | void put(T t) | 支持 | 支持 | 支持 | | 更新一条记录(如果记录不存在则插入;如果记录存在,则根据请求的内容新增、修改或者删除指定列的值) | void update(T t) | 支持 | 支持 | 支持 | | 更新一条记录,并根据条件删除动态列 | void update(T t, boolean deleteDynamicColumns) | 支持 | 支持 | 支持 | | 根据主键删除单条记录 | void delete(Object... primaryKeyColumns) | 支持 | 支持 | 支持 | | 根据主键删除单条记录 | void delete(PrimaryKey primaryKey) | 支持 | 支持 | 支持 | | 根据主键批量获取记录 | List batchGet(List primaryKeys) | 支持 | 支持 | 支持 | | 根据主键批量获取记录的指定列 | List batchGet(List primaryKeys, Set columnsToGet) | 支持 | 支持 | 支持 | | 批量写入记录 | void batchPut(List ts) | 支持 | 支持 | 支持 | | 批量更新记录 | void batchUpdate(List ts) | 支持 | 支持 | 支持 | | 批量更新记录,并根据条件删除动态列 | void batchUpdate(List ts, boolean deleteDynamicColumns) | 支持 | 支持 | 支持 | | 根据主键批量删除记录 | void batchDelete(List primaryKeys) | 支持 | 支持 | 支持 | | 根据主键进行范围查找 | List rangeGet(RangePrimaryKey rangePrimaryKey) | 支持 | 支持 | 支持 | | 根据主键进行返回查找,返回指定列 | List rangeGet(RangePrimaryKey rangePrimaryKey, Set columnsToGet) | 支持 | 支持 | 支持 | | 根据主键进行返回查找,返回指定列,同时限制返回记录数 | List rangeGet(RangePrimaryKey rangePrimaryKey, Set columnsToGet, int count) | 支持 | 支持 | 支持 | | 根据主键进行范围查找,并对返回的每条记录执行consumer 方法 | void rangeConsume(RangePrimaryKey rangePrimaryKey, Consumer consumer) | 支持 | 支持 | 支持 | | 根据指定列获取记录 | List columnGet(SearchColumn searchColumn) | 支持 | 支持 | 支持 | | 根据指定列获取记录,并进行分页,排序 | List columnGet(SearchColumn searchColumn, int offset, int count, Boolean asc) | 支持 | 支持 | 支持 | ## 四、快速接入 ### 1.版本说明 由于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 ### 2.maven settings.xml添加仓库地址 ```xml maven-neutralcloud maven-neutralcloud http://packages.glodon.com/artifactory/maven-neutralcloud-public/ ``` ### 3.项目中添加pom依赖 ```xml com.glodon.paas.foundation column-store-orm-mongo com.glodon.paas.foundation column-store-orm ${version} pom import ``` ### 4.初始化 初始化仅需创建一个TableStoreDao对象,传入tableStore的config和一个实体类。 ```java TableStoreMongoConfig config = TableStoreMongoConfig.builder() .hosts("localhost:27017") .database("mongo-test") .build(); TableStoreDAO dao = new TableStoreDAOMongoImpl<>(config, BookEntity.class); ``` 实体类到TableStore的PrimaryKeyName和ColumnName的自动映射是通过注解实现的,BookEntity的代码如下: ```java @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 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中表的映射: - name,指定表名; **TSPKColumn**用于实体类中属性到TableStore中PrimaryKeyName的映射: - name,主键名 - order,当有多个属性做主键时,安装order顺序拼接 **TSAttrColumn**用于实体类中属性到TableStore中PrimaryKeyName的映射: - name,PrimaryKeyName或者ColumnName,当dynamicColumnNames为true时,指ColumnName的前缀; - dynamicColumnNames,是否为动态属性列,默认false;表格存储中列数与列名是不固定的,把此选项设为true来定义变化的列名,暂时不支持列值的定义; ### 5.单条记录操作 单条记录有put、get、update、delete四种操作; get和delete有两种调用方式,第一种为按照顺序依次传入primaryKeyValue,第二种先用buildPrimaryKey方法构造出PrimaryKey,再传入get、delete方法; ```java // 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); ``` ### 6.batch操作 batch有put、get、update、delete四种操作; ```java List 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 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 ret = dao.batchGet(pks); Assert.assertTrue(ret == null || ret.isEmpty()); ``` ### 7.range get操作 range操作有range get和range consume类接口,前者返回结果,后者直接指定对结果的操作; RangePrimaryKey有四种指定方式: - Fixed,固定的值,一般位于最开始的主键列; - FullRange,全部范围,一般位于最后的主键列; - SubRangeString,String类型的指定范围的主键列,一般位于Fixed和FullRange之间; ```java // put first List 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()); }); ``` ### 8.column get操作 column get为按照一个或多个列值进行匹配,操作有两个接口` columnGet(SearchColumn searchColumn)` 与`columnGet(SearchColumn searchColumn, int offset, int count, Boolean asc)`,前者默认升序列出满足条件的前十条,后者可以自定义。 ```java // 查询ibsn为12345,author为Jon的数据 List bookEntities = dao.columnGet(SearchColumn.builder() .addColumn("ibsn", "12345") .addColumn("author", "Jon").build()); ``` ```java List bookEntities = dao.columnGet(SearchColumn.builder() .addColumn("ibsn", "12345") .addColumn("author", "Jon").build(), 0, 10, true); ``` - HBase与MongoDB可以直接使用 - 阿里云TableStore需要额外在类要查询的列上声明`@TSAttrColumn`注解,每次启动都会重新建立索引,建立多元索引需要30秒左右时间。 ```java @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 readerIds = new ArrayList<>(10); } ``` ## 五、架构设计 ![](https://km.glodon.com/download/attachments/217058125/%E8%A1%A8%E6%A0%BC%E5%AD%98%E5%82%A8%E6%9E%B6%E6%9E%84%E8%AE%BE%E8%AE%A1.png?version=1&modificationDate=1651111854000&api=v2) 表格存储整体架构并不复杂: * **框架层** * **Annotation:框架提供的注解。** * *@TSTable*:实体类到表的映射。 - name:表名。 * *@TSIndexColumn*:实体类属性到索引列的映射。 - name:索引列名。 * *@TSPKColumn*:实体类属性到主键的映射。 - name:主键名。 - order:当有多个属性做主键时,按照order顺序拼接。 * *@TSAttrColumn*:实体类属性到列的映射。 - name:属性名。 - dynamicColumnNames:是否为动态属性列,默认 false;表格存储中列数与列名是不固定的,把此选项设为 true 来定义变化的列名,暂时不支持列值的定义。 * **Model:框架中定义的模型。** * *TableMeta*:表的元数据,每个实体对应一个。注解处理类会读取实体类的相关注解,将元数据保存到*TableMeta*实例。 ```java private String tableName; // 表名 private List pkColumnMeta; // 主键 private List attrColumnMeta; // 属性 private List indexMeta; //索引 ``` * *PrimaryKey*:主键。 * *RangePrimaryKey*:范围主键,在接入文档中有详细描述。 * *Column*:列。 * *SearchColumn*:当进行搜索时,用来构造相关的搜索列。 * *Row*:用来表示一条记录,其中包含一个*PrimaryKey*和多个*Column*。 * ***TableStoreDAO*:这是一个接口,定义了表格存储支持的功能。** * 单行操作(基于主键):单条记录的CRUD操作。 * 批量操作(基于主键):批量的CRUD操作。 * 搜索(基于列):根据指定列进行搜索。 * ***AbstractTableStoreDAO*:这是一个抽象类,它有两个职责:** * 继承了*TableStoreDAO*,并定义了相关的模板方法,为具体实现类提供了一个基类。 * 作为注解处理类,实现了对实体类注解的解析。 * **实现层** 实现层以框架层为基础,提供了对具体存储组件的实现。 * *TableStore*: * *TableStoreAliyunConfig*:表格存储的配置类。 * *TableStoreDAOAliyunImpl*:基于*AbstractTableStoreDAO*的模板方法,提供了对阿里云表格存储的实现。 * *HBase*: * *TableStoreHBaseConfig*:HBase的配置类。 * *TableStoreDAOHBaseImpl*:基于*AbstractTableStoreDAO*的模板方法,提供了对HBase的实现。 * *Mongo*: * *TableStoreMongoConfig*:Mongo的配置类。 * *TableStoreDAOMongoImpl*:基于*AbstractTableStoreDAO*的模板方法,提供了对Mongo的实现。 * **应用层** 应用层以实现层为基础,基于springboot提供了自动装配功能。 * *AliyunAutoConfiguration*:阿里云表格存储的自动配置。 * *HBaseAutoConfiguration*:HBase的自动配置。 * *MongoDBAutoConfiguration*:Mongo的自动配置。 ## 六、常见问题