# ebatis
**Repository Path**: isheroleon/ebatis
## Basic Information
- **Project Name**: ebatis
- **Description**: ebatis 是一个简单方便上手的 Elasticsearch ORM 框架。
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 64
- **Created**: 2020-12-24
- **Last Updated**: 2020-12-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 设计说明
`ebatis`采用和`MyBatis`类似思想,只需要定义接口,便可访问`elasticsearch`,隔离业务对`elasticserach`底层接口的直接访问。如此以来,数据访问的时候,不需要自己手动去构建DSL语句,同时,当升级`elastisearch`版本的时候,业务可以完全不用关心底层接口的变动,平滑升级(目前暂时只支持elastisearch 7.5.1版本)。
# 快速入门
> 创建索引
```json
PUT /recent_order_index
{
"settings": {
"number_of_replicas": 0,
"number_of_shards": 1
},
"mappings": {
"properties": {
"cargoId": {
"type": "long"
},
"driverUserName": {
"type": "keyword"
},
"loadAddress": {
"type": "text"
},
"searchable": {
"type": "boolean"
},
"companyId": {
"type": "long"
}
}
}
}
```
> 增加测试数据
```json
POST /recent_order_index/_bulk
{"index":{}}
{"cargoId": 1, "driverUserName":"张三", "loadAddress": "南京市玄武区", "searchable": true,"companyId": 666}
{"index":{}}
{"cargoId": 2, "driverUserName":"李四", "loadAddress": "南京市秦淮区", "searchable": false,"companyId": 667}
{"index":{}}
{"cargoId": 3, "driverUserName":"王五", "loadAddress": "南京市六合区", "searchable": true,"companyId": 668}
{"index":{}}
{"cargoId": 4, "driverUserName":"赵六", "loadAddress": "南京市建邺区", "searchable": true,"companyId": 669}
{"index":{}}
{"cargoId": 5, "driverUserName":"钱七", "loadAddress": "南京市鼓楼区", "searchable": true,"companyId": 665}
```
> POM依赖
```xml
io.manbang
ebatis-core
7.5.1.1.RELEASE
```
> 创建集群连接
```java
Cluster cluster = Cluster.simple("127.0.0.1", 9200);
ClusterRouter router = ClusterRouter.single(cluster);
```
> 定义POJO对象
```java
@Data
public class RecentOrder {
private Long cargoId
private String driverUserName;
private String loadAddress;
private Boolean searchable;
private Integer companyId;
}
@Data
public class RecentOrderCondition {
private Boolean searchable;
private String driverUserName;
}
```
> 定义Mapper接口
```java
@Mapper(indices = "recent_order_index")
public interface RecentOrderRepository {
@Search
List search(RecentOrderCondition condition);
}
```
> 测试接口
```java
@AutoService(ClusterRouterProvider.class)
public class SampleClusterRouterProvider implements ClusterRouterProvider {
public static final String SAMPLE_CLUSTER_NAME = "sampleCluster";
@Override
public ClusterRouter getClusterRouter(String name) {
if (SAMPLE_CLUSTER_NAME.equalsIgnoreCase(name)) {
Cluster cluster = Cluster.simple("127.0.0.1", 9200, Credentials.basic("admin", "123456"));
return ClusterRouter.single(cluster);
} else {
return null;
}
}
}
@Slf4j
public class OrderRepositoryTest {
@Test
public void search() {
// 组装查询条件
RecentOrderCondition condition = new RecentOrderCondition();
condition.setSearchable(Boolean.TRUE);
condition.setDriverUserName("张三");
// 映射接口
RecentOrderRepository repository = MapperProxyFactory.getMapperProxy(RecentOrderRepository.class, SampleClusterRouterProvider.SAMPLE_CLUSTER_NAME);
// 搜索货源
List orders = repository.search(condition);
// 断言
Assert.assertEquals(3, orders.size());
// 打印输出
orders.forEach(order -> log.info("{}", order));
}
}
```
# 连接ES集群
为了保证高ES集群的高可用,同时支持对集群的负载均衡,`ebatis`没有直接使用`elasticsearch`提供的`RestClient`和`RestHighLevelClient`接口来访问集群,而是抽象出一个`Cluster`。一个`Cluster`代表一个ES集群,如果系统需要连接多个集群,则通过`ClusterRouter`和`ClusterLoadBalancer`来实现,多集群的路由和负载均衡。
## Cluster
`Cluster`代表一个ES集群实例,`ebatis`内建了两个实现:`SimpleCluster`,`FixWeightedCluster`和`SimpleFederalCluster`。
`SimpleCluster`和`FixedWeightedCluster`的区别在于,后者是带固定权值的值,在对集群做负载均衡的时候,可以通过权值来控制负载的比例。`SimpleFederalCluster`的特殊地方在于,在一批集群上做批量操作,同步一批集群,一般用于一批集群数据的增删改,不适用于查。
> 创建`Cluster`实例
```java
Cluster cluster = Cluster.simple("127.0.0.1", 9200);
int weight = 10
WeightedCluster cluster = Cluster.weighted(weight, "127.0.0.1", 9200);
Cluster cluster = FederalCluster.of(Cluster.simple("127.0.0.1", 9200),Cluster.simple("127.0.0.1", 9300));
```
如果需要自己实现集群的定义,可以通过继承`AbstractCuster`来实现,也可以你直接实现`Cluster`接口。
## ClusterRouter
`ClusterRouter`用于路由出一个可以访问`Cluster`,内部是通过负载均衡器`ClusterLoadBalancer`,来同一组集群中,选中一个集群的。根据不同的负载均衡器,`ebatis`内建了多个对应的路由器:
|序号|路由器|负载均衡器|备注|
|----|-----|---------|----|
|1|`RandomClusterRouter`|`RandomClusterLoadBalancer`|随机负载均衡|
|2|`RoundRobinClusterRouter`|`RoundRoubinClusterLoadBalancer`|轮询负载均衡|
|3|`SingleClusterRouter`|`SingleClusterLoaderBalancer`|只有一个集群|
|4|`WeightedClusterRouter`|`WeightedClusterLoadBalancer`|权重负载均衡|
> 创建 ClusterRouter实例
```java
Cluster[] clusters = {Cluster.simple("127.0.0.1", 9200), Cluster.simple("127.0.0.2", 9200)};
// 随机路由器
ClusterRouter router = ClusterRouter.random(clusters);
// 轮询路由器
ClusterRouter router = ClusterRouter.roundRobbin(cluster);
// 带权重的路由器,带权重的集群必须实现Weighted接口
int weight 10;
FixedWeightedCluster[] clusters = {Cluster.weighted(weight, "127.0.0.1", 9200)}
ClusterRouter router = ClusterRouter.weighted(clusters);
// 单集群路由
Cluster cluster = Cluster.simple("127.0.0.1", 9200);
ClusterRouter router = ClusterRouter.single(cluster);
```
# 创建Mapper对象
创建`Mapper`对象,需要先定义`Mapper`接口,所有的`Mapper`都需要加上`@Mapper`或`@EasyMapper`注解,然后通过`MapperProxyFactory`来创建接口的代理。
## @Mapper
此注解用于标记接口定义类是Mapper对象,属性说明如下:
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`indices`|必填|`ebatis`只支持多个索引操作|
|2|`types`|可选,默认为空|最新版的ES已经不支持`type`定义|
|3|`clusterRouter`|可选,默认为空|为每个Mapper对象绑定一个路由器,当不配置时默认取ebatis.properties中ebatis.clusterRouter配置的集群名|
## Mapper接口定义
```java
@EasyMapper(indices = "recent_order_index", types = "order" , clusterRouter = "sampleCluster")
public interface OrderRepository {
//通过注解定义搜索的类型,不同搜索类型支持的返回结果类型不一样
@Search
List search(OrderCondition condition);
}
```
## 创建cluster对象
集群的加载通过SPI方式。
```java
@AutoService(ClusterRouterProvider.class)
public class SampleClusterRouterProvider implements ClusterRouterProvider {
public static final String SAMPLE_CLUSTER_NAME = "sampleCluster";
@Override
public ClusterRouter getClusterRouter(String name) {
if (SAMPLE_CLUSTER_NAME.equalsIgnoreCase(name)) {
Cluster cluster = Cluster.simple("127.0.0.1", 9200, Credentials.basic("admin", "123456"));
ClusterRouter clusterRouter = ClusterRouter.single(cluster);
return clusterRouter;
} else {
return null;
}
}
}
```
## 创建Mapper对象
```java
OrderRepository repository = MapperProxyFactory.getMapperProxy((OrderRepository.class);
```
## Mapper搜索方法返回值类型
| 请求类型 | 接口声明返回值 | 备注 |
| --------------------------------- | ----------------------------------------------- | ------------------------- |
| @Search | Page\ | |
| | List\ | |
| | Entity[] | |
| | SearchResponse | |
| | Entity | |
| | Long | |
| | long | |
| | Boolean | |
| | boolean | |
| @MultiSearch | List\> | |
| | Page\\[] | |
| | List\> | |
| | Entity\[]\[] | |
| | List\ | |
| | List\[] | |
| | MultiSearchResponse | |
| | List\ | |
| | Long[] | |
| | long[] | |
| | List\ | |
| | Boolean[] | |
| | boolean[] | |
| @Index | IndexResponse | |
| | RestStatus | |
| | boolean | |
| | Boolean | |
| | String | |
| | void | |
| @Get | GetResponse | |
| | Entity | |
| | Optional\ | |
| @Delete | RestStatus | |
| | DeleteResponse | |
| | boolean | |
| | Boolean | |
| | void | |
| @Update | UpdateResponse | |
| | GetResult | |
| | RestStatus | |
| | boolean | |
| | Boolean | |
| | Result | |
| | void | |
| @Bulk | List\ | |
| | BulkResponse | |
| | BulkItemResponse\[] | |
| @MultiGet | MultiGetResponse | |
| | MultiGetItemResponse\[] | |
| | List\ | |
| | List\ | |
| | Entity[] | |
| | List> | |
| | Optional\[] | |
| @UpdateByQuery | BulkByScrollResponse | |
| | BulkByScrollTask.Status | |
|@DeleteByQuery | BulkByScrollResponse | |
| | BulkByScrollTask.Status | |
| @SearchScroll | SearchResponse | |
| | ScrollResponse | |
| @ClearScroll | ClearScrollResponse | |
| | boolean | |
| | Boolean | |
| @Agg | SearchResponse | (暂时只支持桶聚合 terms查询) |
| | Aggregations | |
| | List\ | |
| | Map | |
## 返回类型异步支持
Mapper搜索方法支持异步操作,只需要将Mapper接口返回结果定义为CompletableFuture>,这样异步的调用不会阻塞并且立刻返回,业务方可以继续处理自己的业务逻辑,在需要获取结果时,提取结果。
# 文档接口
## 单文档接口
### Index API
#### 接口定义
```java
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回true
*/
@Index
Boolean indexRecentOrderBoolean(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回true
*/
@Index
boolean indexRecentOrderBool(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回文档id
*/
@Index
String indexRecentOrderString(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
*/
@Index
void indexRecentOrderVoid(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回文档response
*/
@Index
IndexResponse indexRecentOrderIndexResponse(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回RestStatus状态码
*/
@Index
RestStatus indexRecentOrderRestStatus(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回异步RestStatus状态码
*/
@Index
CompletableFuture indexRecentOrderCompletableFuture(RecentOrderModel order);
/**
* 创建一笔订单
* @param order order 订单
* @return 创建成功,返回异步结果
*/
@Index
CompletableFuture indexRecentOrderFutureVoid(RecentOrderModel order);
```
> 执行index操作时,如果想自定义文档 _id,入参实体需实现IdProvider接口。
>
> 如果想自定义文档的路由值,入参实体需实现RoutingProvider接口。
#### @Index属性说明
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`timeout`|`1m`|数值+时间单位(ms/s/m/h/M/y)|
|2|`refreshPolicy`|`RefreshPolicy.NONE`|默认不刷新|
|4|`pipeline`|可选||
|5|`versionType`|`VersionType.INTERNAL`||
|6|`waitForActiveShards`|`-2`|ActiveShardCount.DEFAULT|
### Get API
#### 接口定义
```java
/**
* 查询订单
*
* @param id 文档id
* @return 订单 可能为null
*/
@Get
RecentOrder getRecentOrder(Long id);
/**
* 查询订单
*
* @param id 文档id
* @return 订单 可能为null
*/
@Get
RecentOrder getRecentOrder(String id);
/**
* 查询订单
*
* @param order 订单,需实现IdProvider
* @return 订单 可能为null
*/
@Get
RecentOrder getRecentOrder(RecentOrderModel order);
/**
* 查询订单
*
* @param id 订单id
* @return Option
*/
@Get
Optional getRecentOrderOptional(Long id);
/**
* 查询订单
*
* @param id 订单id
* @return GetResponse
*/
@Get
GetResponse getRecentOrderGetResponse(Long id);
/**
* 查询订单
*
* @param id 订单id
* @return 异步Optional
*/
@Get
CompletableFuture> getRecentOrderCompletableFuture(Long id);
/**
* 查询订单
*
* @param id 订单id
* @return 异步订单结果
*/
@Get
CompletableFuture getRecentOrderCompletableFuture(String id);
```
> 执行get操作时,如果入参非基本类型,入参实体需实现IdProvider接口。
>
> 如果想自定义文档的路由值,入参实体需实现RoutingProvider接口。
#### @Get属性说明
| 序号 | 属性名 | 默认值 | 说明 |
| ---- | ------------ | ------- | -------------------------------- |
| 1 | `preference` | "" | 设置查询偏好,影响查询的分片策略 |
| 2 | `refresh` | `false` | 设置是否刷新,默认不刷新 |
| 3 | `realtime` | `true` | 设置是否实时查询,默认实时 |
### Delete API
#### 接口定义
```java
/**
* 删除订单
*
* @param id 订单id
* @return RestStatus
*/
@Delete
RestStatus deleteRecentOrder(Long id);
/**
* 删除订单
*
* @param model model
* @return RestStatus
*/
@Delete
RestStatus deleteRecentOrder(RecentOrderModel model);
/**
* 删除订单
*
* @param id 订单id
* @return DeleteResponse
*/
@Delete
DeleteResponse deleteRecentOrderDeleteResponse(Long id);
/**
* 删除订单
*
* @param id 订单id
* @return 删除成功,返回true
*/
@Delete
Boolean deleteRecentOrderBoolean(Long id);
/**
* 删除订单
*
* @param id 订单id
* @return 删除成功,返回true
*/
@Delete
boolean deleteRecentOrderBool(Long id);
/**
* 删除订单
*
* @param id 订单id
*/
@Delete
void deleteRecentOrderVoid(Long id);
/**
* 异步删除订单
*
* @param id 订单id
* @return 异步结果
*/
@Delete
CompletableFuture deleteRecentOrderBooleanFuture(Long id);
/**
* 异步删除订单
*
* @param id 订单id
* @return 异步结果
*/
@Delete
CompletableFuture deleteRecentOrderVoidFuture(Long id);
```
> 执行delete操作时,如果入参非基本类型,入参实体需实现IdProvider接口。
>
> 如果想自定义文档的路由值,入参实体需实现RoutingProvider接口。
#### @Delete属性说明
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`waitForActiveShards`|活动分片数量|默认-2,不指定分片数量,-1或all指定全部分片|
|2|`timeout`|1m|数值+时间单位(ms/s/m/h/M/y)|
|3|`refreshPolicy`|`RefreshPolicy.NONE`|默认不刷新|
### Update API
#### 接口定义
```java
/**
* 使用脚本更新订单,支持部分更新
*
* @param orderScript 订单
* @return UpdateResponse
*/
@Update
UpdateResponse updateRecentOrder(RecentOrderModelScript orderScript);
/**
* 更新订单,支持部分更新,如果文档不存在,则将部分更新文档建立索引
*
* @param order 订单
* @return UpdateResponse
*/
@Update(docAsUpsert = true)
UpdateResponse updateRecentOrder(RecentOrderModel order);
/**
* 更新订单,支持部分更新
*
* @param order 订单
* @return GetResult
*/
@Update
GetResult updateRecentOrderGetResult(RecentOrderModel order);
/**
* 更新订单,支持部分更新
*
* @param order 订单
* @return RestStatus状态码
*/
@Update
RestStatus updateRecentOrderRestStatus(RecentOrderModel order);
/**
* 更新订单,支持部分更新
*
* @param order 订单
* @return 更新成功,返回true
*/
@Update
Boolean updateRecentOrderBoolean(RecentOrderModel order);
/**
* 更新订单,支持部分更新
*
* @param order 订单
* @return 更新成功,返回true
*/
@Update
boolean updateRecentOrderBool(RecentOrderModel order);
/**
* 更新订单,支持部分更新
*
* @param order 订单
* @return Result
*/
@Update
Result updateRecentOrderResult(RecentOrderModel order);
/**
* 更新订单,支持部分更新
*
* @param order 订单
*/
@Update
void updateRecentOrderVoid(RecentOrderModel order);
/**
* 异步更新订单,支持部分更新
*
* @param order 订单
* @return 异步结果
*/
@Update
CompletableFuture updateRecentOrderFuture(RecentOrderModel order);
/**
* 异步更新订单,支持部分更新
*
* @param order 订单
* @return 异步结果
*/
@Update
CompletableFuture updateRecentOrderFutureVoid(RecentOrderModel order);
```
> `@Update` 注解标明,此方法是更新方法。
>
> 设置docAsUpsert=true当文档不存在时,做插入操作。
>
> 如果想自定义文档 _id,入参实体需实现IdProvider接口。
#### @Update属性说明
| 序号 | 属性名 | 默认值 | 说明 |
| ---- | --------------------- | -------------------- | ------------------------------------------- |
| 1 | `docAsUpsert` | `false` | 设置true,当文档不存在时,做插入操作 |
| 2 | `retryOnConflict` | `0` | 设置版本冲突时,重试的次数 |
| 3 | `refreshPolicy` | `RefreshPolicy.NONE` | 默认不刷新 |
| 4 | `waitForActiveShards` | 活动分片数量 | 默认-2,不指定分片数量,-1或all指定全部分片 |
| 5 | `scriptedUpsert` | `false` | 运行脚本,无论文档是否存在 |
## 多文档接口
### Multi Get API
#### 接口定义
```java
/**
* 多订单查询
*
* @param ids 文档id
* @return MultiGetResponse
*/
@MultiGet
MultiGetResponse getRecentOrdersResponse(Long... ids);
/**
* 单订单查询
*
* @param id 文档id
* @return MultiGetResponse
*/
@MultiGet
MultiGetResponse getRecentOrdersResponse(Long id);
/**
* 单订单查询
*
* @param order 订单
* @return MultiGetResponse
*/
@MultiGet
MultiGetResponse getRecentOrdersResponse(RecentOrderModel order);
/**
* 多订单查询
*
* @param ids 订单id
* @return 订单集合
*/
@MultiGet
List getRecentOrders(Long... ids);
/**
* 多订单查询
*
* @param orders 订单
* @return 订单数组
*/
@MultiGet
RecentOrder[] getRecentOrders(List orders);
/**
* 多订单查询
*
* @param ids 订单id
* @return MultiGetItemResponse[]
*/
@MultiGet
MultiGetItemResponse[] getRecentOrdersItemResponse(Long... ids);
/**
* 多订单查询
*
* @param orders 订单id
* @return List
*/
@MultiGet
List getRecentOrdersItemResponse(List orders);
/**
* 多订单查询
*
* @param ids 订单id
* @return List>
*/
@MultiGet
List> getRecentOrdersOptional(Long... ids);
/**
* 多订单查询
*
* @param orders 订单
* @return Optional[]
*/
@MultiGet
Optional[] getRecentOrdersOptional(List orders);
/**
* 多订单异步查询
*
* @param orders 订单
* @return 异步订单查询结果
*/
@MultiGet
CompletableFuture[]> getRecentOrdersOptionalFuture(List orders);
```
> 入参实体非基本类型时,需实现IdProvider接口。
### Bulk API
Bulk接口目前只支持单类型批量操作,也即是要全全部是索引操作,要不全部是删除操作等等。
#### 接口定义
```java
/**
* 批量创建订单
*
* @param orders 订单
* @return 响应
*/
@Bulk(bulkType = BulkType.INDEX)
List bulkIndexRecentOrderList(List orders);
/**
* 批量创建订单
*
* @param orders 订单
* @return 响应
*/
@Bulk(bulkType = BulkType.INDEX)
List bulkIndexRecentOrderList(RecentOrderModel... orders);
/**
* 批量创建订单
*
* @param orders 订单
* @return 响应
*/
@Bulk(bulkType = BulkType.INDEX)
BulkItemResponse[] bulkIndexRecentOrderArray(RecentOrderModel... orders);
/**
* 批量创建订单
*
* @param orders 订单
* @return 响应
*/
@Bulk(bulkType = BulkType.INDEX)
BulkResponse bulkIndexRecentOrderBulkResponse(RecentOrderModel... orders);
/**
* 批量删除订单
*
* @param ids 订单id
* @return 响应
*/
@Bulk(bulkType = BulkType.DELETE)
List bulkDeleteRecentOrderList(Long... ids);
/**
* 批量删除订单
*
* @param orders 订单
* @return 响应
*/
@Bulk(bulkType = BulkType.DELETE)
List bulkDeleteRecentOrderList(List orders);
/**
* 批量更新订单,订单不存在时,则插入订单
*
* @param orders 订单
* @return 响应
*/
@Bulk(bulkType = BulkType.UPDATE, update = @Update(docAsUpsert = true))
List bulkUpdateRecentOrderList(RecentOrderModel... orders);
/**
* 脚本批量更新订单
*
* @param orderScripts 脚本
* @return 响应
*/
@Bulk(bulkType = BulkType.UPDATE)
List bulkUpdateRecentOrderList(List orderScripts);
/**
* 脚本更新订单
*
* @param orderScripts 脚本
* @return 异步响应
*/
@Bulk(bulkType = BulkType.UPDATE)
CompletableFuture> bulkUpdateRecentOrderListFuture(List orderScripts);
```
> `@Bulk` 注解标明,此方法是批量方法,必须要有此注解,同时必须要指定`BulkType`,标明是何种类型的批量操作。
>
> 做批量更新操作时,如果想文档不存在则做插入操作,设置@Update(docAsUpsert=true)。
#### @Bulk属性说明
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`bulkType`|必填|可选类型:`INDEX`/`DELETE`/`UPDATE`|
|2|`timeout`|1m|数值+时间单位(ms/s/m/h/M/y)|
|3|`waitForActiveShards`|-2|ActiveShardCount.DEFAULT|
|4|`refreshPolicy`|`RefreshPolicy.NONE`||
|5|`index`|可选|`bulkType` = `INDEX` 有效|
|6|`delete`|可选|`bulkType` = `DELETE` 有效|
|7|`update`|可选|`bulkType` = `UPDATE` 有效|
### Delete By Query API
#### 接口定义
```java
/**
* 根据指定条件删除订单
*
* @param order 查询条件
* @return 查询删除响应
*/
@DeleteByQuery
BulkByScrollResponse deleteByQueryRecentOrder(SampleRecentOrderCondition order);
/**
* 根据指定条件删除订单
*
* @param order 查询条件
* @return 查询删除状态
*/
@DeleteByQuery
Status deleteByQueryRecentOrderStatus(SampleRecentOrderCondition order);
/**
* 根据指定条件删除订单
*
* @param order 查询条件
* @return 异步查询删除响应
*/
@DeleteByQuery
CompletableFuture deleteByQueryRecentOrderFuture(SampleRecentOrderCondition order);
```
> `@DeleteByQuery` 注解标明,此方法是查询删除方法,必须要有此注解
#### @DeleteByQuery属性说明
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`routing`|可选|默认无路由|
|2|`timeout`|1m|数值+时间单位(ms/s/m/h/M/y)|
|3|`waitForActiveShards`|-2|ActiveShardCount.DEFAULT|
|4|`refresh`|`false`||
|5|`maxDocs`|-1|最大处理文档数,超过此文档数量,就不在处理,默认-1,是全部文档|
|6|`batchSize`|1000|批量大小|
|7|`conflicts`|`abort`|冲突策略:`abort`/`procced`|
|8|`slices`|1|分片|
|9|`maxRetries`|11|冲突后,最大重试次数|
|10|`shouldStoreResult`|`false`||
|11|`scrollKeepAlive`|0||
### Update By Query API
#### 接口定义
```java
/**
* 根据指定条件更新订单
*
* @param order 查询条件
* @return 更新响应
*/
@UpdateByQuery
BulkByScrollResponse updateByQueryRecentOrder(SampleRecentOrderCondition order);
/**
* 根据指定条件更新订单
*
* @param order 查询条件
* @return 更新响应
*/
@UpdateByQuery
Status updateByQueryRecentOrderStatus(SampleRecentOrderCondition order);
/**
* 根据指定条件更新订单
*
* @param order 查询条件
* @return 异步更新响应
*/
@UpdateByQuery
CompletableFuture updateByQueryRecentOrderFuture(SampleRecentOrderCondition order);
```
> `@UpdateByQuery` 注解标明,此方法是查询更新方法,必须要有此注解。
>
> 支持通过 脚本更新文档,入参实体需实现ScriptProvider。
#### @UpdateByQuery属性说明
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`routing`|可选|默认无路由|
|2|`timeout`|1m|数值+时间单位(ms/s/m/h/M/y)|
|3|`waitForActiveShards`|-2|ActiveShardCount.DEFAULT|
|4|`refresh`|`false`||
|5|`maxDocs`|-1|最大处理文档数,超过此文档数量,就不在处理,默认-1,是全部文档|
|6|`batchSize`|1000|批量大小|
|7|`conflicts`|`abort`|冲突策略:`abort`/`procced`|
|8|`slices`|1|分片|
|9|`maxRetries`|11|冲突后,最大重试次数|
|10|`shouldStoreResult`|`false`||
|11|`scrollKeepAlive`|0||
### Reindex API
> **暂不支持**
# 查询DSL
为了便宜统一设计,所有的查询语句最后都被封装成`BoolQueryBuilder`,查询条件最终会被封装成为一个有层级关系的对象,来处理。查询操作统一由`@Search`注解来标记,支持分页查询。
## 查询条件定义
### 基本条件定义
查询条件统一定义一个POJO对象,对象的属性名即为Mapping字段名称,如果属性名称和Mapping字段名称不一致,通过`@Field`注解来映射;属性分为基本类型和对象类型,对象类型会再次递归定义查询条件。
### 查询语句类型(Query Clause)
语句类型通过属性注解来表示,详细如下面:
|序号|注解|说明|
|---|---|---|
|1|`@Must`|必须满足的条件|
|2|`@MustNot`|必须排除的条件|
|3|`@Should`|可选条件|
|4|`@Filter`|过滤条件|
|5|`@Ignore`|忽略的条件,不参与语句拼装|
|6|`@Exists`|字段是否存在|
> 如果属性不加注解,默认就是`@Must`条件,语句支持嵌套属性
## @Query属性说明
|序号|属性名|默认值|说明|
|---|---|---|---|
|1|`queryType`|`QueryType.BOOL`|默认Bool查询,可选类型:`BOOL`/`FUNCTION_SCORE`/`CONSTANT_SCORE`|
|2|`searchType`|`SearchType.QUERY_THEN_FETCH`|默认就好|
## 路由说明
入参实体实现需RoutingProvider。
## Bool查询
### 接口定义
#### 条件定义
```java
@Data
public class SampleRecentOrderCondition {
@Must
private Long cargoId;
}
@Data
@EqualsAndHashCode(callSuper = false)
public class RecentOrderCondition extends SampleRecentOrderCondition implements ScoreFunctionProvider {
/**
* 基本类型
*/
@Must
private Integer cargoType;
/**
* 基本类型集合
*/
@Must
private List orderSource;
/**
* 基本类型集合
*/
@Must
@Field("orderType")
private Integer[] type;
/**
* 嵌套条件
*/
@Must
private Protocol protocol;
/**
* 非基本类型集合
*/
@Must
private List securityTranList;
/**
* 非基本类型集合
*/
@Must
private SecurityTran[] securityTrans;
/**
* 单范围查询
*/
@Must
private Range channel;
/**
* 脚本查询
*/
@Must
private Script script;
/**
* 多范围组合查询
*/
@Should
private List> channels;
/**
* 多脚本组合查询
*/
@Should(minimumShouldMatch = "2")
private Script[] scripts;
/**
* 动态化计算实例类型
*/
@Must
private Object shipperInfo;
@Must
private Object[] shipperInfos;
@Must(queryType = QueryType.EXISTS)
private boolean startAreaCode;
@Must(queryType = QueryType.WILDCARD)
private String unloadAddress;
@Override
public ScoreFunction getFunction() {
return ScoreFunction.fieldValueFactor("startCityId", 10, 10, FieldValueFactorFunction.Modifier.LN);
}
@Override
public ScoreFunctionMode getFunctionMode() {
return null;
}
}
```
#### 接口定义
```java
/**
* 搜索订单
* @param pageable 分页信息
* @param condition 搜索条件
* @return 订单分页
*/
@Search(queryType = QueryType.BOOL)
Page queryRecentOrderPage(Pageable pageable, RecentOrderCondition condition);
```
### 接口使用示例
```java
protected RecentOrderCondition getCondition() {
RecentOrderCondition condition = new RecentOrderCondition();
condition.setCargoId(10124512292911L);
condition.setCargoType(2);
condition.setOrderSource(Lists.newArrayList(2, 4, 6, 8));
condition.setType(new Integer[]{1, 3, 5, 7});
Protocol protocol = new Protocol();
protocol.setProtocolStatus(0);
RateMode rateMode = new RateMode();
rateMode.setRateModeFlag(0);
protocol.setRateMode(rateMode);
condition.setProtocol(protocol);
condition.setSecurityTranList(Lists.newArrayList(
SecurityTran.builder().securityTran(Lists.newArrayList(1, 2, 3)).build(),
SecurityTran.builder().securityTran(Lists.newArrayList(4, 5, 6)).build()));
condition.setSecurityTrans(new SecurityTran[]{
SecurityTran.builder().securityTran(Lists.newArrayList(7, 8, 9)).build(),
SecurityTran.builder().securityTran(Lists.newArrayList(10, 11, 12)).build()});
condition.setStartAreaCode(false);
condition.setChannel(Range.of(1, 100).closeLeft());
condition.setScript(Script.stored("666"));
condition.setChannels(Lists.newArrayList(Range.of(100, 200).closeLeft(), Range.of(300, 500).closeLeft()));
condition.setScripts(new Script[]{Script.stored("888"), Script.stored("1024")});
condition.setShipperInfo(ShipperInfo.builder().shipperTelephone(18030000725L).shipperTelephoneMask(999725L).shipperUserId(123321L).build());
condition.setShipperInfos(new Object[]{ShipperInfo.builder().shipperTelephone(18031111725L).shipperTelephoneMask(999726L).shipperUserId(456654L).build()});
condition.setUnloadAddress("**沈阳市皇姑区**");
return condition;
}
```
上面的POJO条件最终会被映射为如下DSL:
```json
{
"from": 10,
"size": 10,
"query": {
"bool": {
"must": [
{
"term": {
"cargoType": {
"value": 2,
"boost": 1.0
}
}
},
{
"term": {
"orderSource": {
"value": 2,
"boost": 1.0
}
}
},
{
"term": {
"orderSource": {
"value": 4,
"boost": 1.0
}
}
},
{
"term": {
"orderSource": {
"value": 6,
"boost": 1.0
}
}
},
{
"term": {
"orderSource": {
"value": 8,
"boost": 1.0
}
}
},
{
"term": {
"orderType": {
"value": 1,
"boost": 1.0
}
}
},
{
"term": {
"orderType": {
"value": 3,
"boost": 1.0
}
}
},
{
"term": {
"orderType": {
"value": 5,
"boost": 1.0
}
}
},
{
"term": {
"orderType": {
"value": 7,
"boost": 1.0
}
}
},
{
"bool": {
"must": [
{
"term": {
"protocolStatus": {
"value": 0,
"boost": 1.0
}
}
},
{
"bool": {
"must": [
{
"term": {
"rateModeFlag": {
"value": 0,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"bool": {
"must": [
{
"term": {
"securityTran": {
"value": 1,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 2,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 3,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"bool": {
"must": [
{
"term": {
"securityTran": {
"value": 4,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 5,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 6,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"bool": {
"must": [
{
"term": {
"securityTran": {
"value": 7,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 8,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 9,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"bool": {
"must": [
{
"term": {
"securityTran": {
"value": 10,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 11,
"boost": 1.0
}
}
},
{
"term": {
"securityTran": {
"value": 12,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"range": {
"channel": {
"from": 1,
"to": 100,
"include_lower": true,
"include_upper": false,
"boost": 1.0
}
}
},
{
"script": {
"script": {
"id": "666"
},
"boost": 1.0
}
},
{
"bool": {
"must": [
{
"term": {
"shipper.shipperUserId": {
"value": 123321,
"boost": 1.0
}
}
},
{
"term": {
"shipper.shipperTelephone": {
"value": 18030000725,
"boost": 1.0
}
}
},
{
"term": {
"shipper.shipperTelephoneMask": {
"value": 999725,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"bool": {
"must": [
{
"term": {
"shipper.shipperUserId": {
"value": 456654,
"boost": 1.0
}
}
},
{
"term": {
"shipper.shipperTelephone": {
"value": 18031111725,
"boost": 1.0
}
}
},
{
"term": {
"shipper.shipperTelephoneMask": {
"value": 999726,
"boost": 1.0
}
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"bool": {
"must_not": [
{
"exists": {
"field": "startAreaCode",
"boost": 1.0
}
}
],
"adjust_pure_negative": true,
"boost": 1.0
}
},
{
"wildcard": {
"unloadAddress": {
"wildcard": "**沈阳市皇姑区**",
"boost": 1.0
}
}
},
{
"term": {
"cargoId": {
"value": 10124512292911,
"boost": 1.0
}
}
}
],
"should": [
{
"range": {
"channels": {
"from": 100,
"to": 200,
"include_lower": true,
"include_upper": false,
"boost": 1.0
}
}
},
{
"range": {
"channels": {
"from": 300,
"to": 500,
"include_lower": true,
"include_upper": false,
"boost": 1.0
}
}
},
{
"script": {
"script": {
"id": "888"
},
"boost": 1.0
}
},
{
"script": {
"script": {
"id": "1024"
},
"boost": 1.0
}
}
],
"adjust_pure_negative": true,
"minimum_should_match": "2",
"boost": 1.0
}
},
"_source": {
"includes": [
"cargoId",
"end_province_code",
"companyId",
"driverUserName",
"loadAddress"
],
"excludes": []
}
}
```
## Constant Score查询
### 接口定义
```java
@Search(queryType = QueryType.FUNCTION_SCORE)
List queryRecentOrderList(RecentOrderCondition condition);
```
## Function Score查询
函数打分查询,允许我们修改文档的相关度分值,通过提供一个或多个函数来计算出查询出来的文档新分值。因此,Function Score查询条件必须要提供打分函数,`ebatis`要求,条件必须实现`ScoreFunctionProvider`接口。此接口,有两个接口方法,默认实现`getFunction`方法即可。
### 查询条件POJO定义
```java
@Data
@EqualsAndHashCode(callSuper = false)
public class FunctionRecentOrderCondition extends SampleRecentOrderCondition implements ScoreFunctionProvider {
/**
* 基本类型
*/
@Must
private Integer cargoType;
/**
* 基本类型集合
*/
@Must
private List orderSource;
@Override
public ScoreFunction getFunction() {
return ScoreFunction.fieldValueFactor("startCityId", 10, 10, FieldValueFactorFunction.Modifier.LN);
}
@Override
public ScoreFunctionMode getFunctionMode() {
return ScoreFunctionMode.of(FunctionScoreQuery.ScoreMode.MAX, CombineFunction.MAX, 1.0f, 1.0f);
}
}
```
> `ScoreFunctionProvider` 即为打分函数提供者接口,函数打分查询,必须继承此接口;`ScriptFieldProvider` 此接口为脚本字段提供者接口。
### 接口定义
```java
/**
* 函数搜索订单
*
* @param condition 查询条件
* @return 货源分页
*/
@Search(queryType = QueryType.FUNCTION_SCORE)
List queryRecentOrderList(FunctionRecentOrderCondition condition);
```
## Boosting查询
> **暂不支持**
## Dis Max查询
> **暂不支持**
## 分页查询
分页查询需要增加一个新的入参`Pageable`,此入参,必须是方法最后一个参数。
### Pagable创建
```java
int page = 0; // 页码,从0开始
int size = 20; // 分页大小
Pageable pageale = Pageable.of(page, size);
```
### 分页查询接口定义
```java
/**
* 搜索订单
*
* @param pageable 分页信息
* @param condition 搜索条件
* @return 订单分页
*/
@Search(queryType = QueryType.BOOL)
Page queryRecentOrderPage(Pageable pageable, RecentOrderCondition condition);
```
> 分页查询,返回值必须是`Page`类型
## 各种Provider
* `AggProvider`
* `CollapseProvider`
* `IdProvider`
* `MultiMatchFieldProvider`
* `ScoreFunctionProvider`
* `ScriptFieldProvider`
* `ScriptProvider`
* `ScrollProvider`
* `SortProvider`
* `SourceProvider`
* `VersionProvider`
## 排序
### 排序
排序需要实现`SortProvider`接口。
```java
@Data
public class SampleRecentOrderCondition implements SortProvider {
private static final Sort[] SORTS = {
//字段升序
Sort.fieldAsc("createTime"),
//脚本降序
Sort.scriptStringDesc(Script.stored("order_script"))};
@Must
private Long cargoId;
@Override
public Sort[] getSorts() {
return new Sort[0];
}
}
```
## Script Field 脚本字段
脚本字段需要实现`ScriptFieldProvider`接口。
```java
@Data
public class SampleRecentOrderCondition implements ScriptFieldProvider {
private static final ScriptField[] SCRIPT_FIELDS = new ScriptField[]{
ScriptField.of("createTime", Script.stored("order_script"))
};
@Must
private Long cargoId;
@Override
public ScriptField[] getScriptFields() {
return new ScriptField[0];
}
}
```
## 指定返回字段
自定返回字段,可以通过两种方式实现:
> 实现`SourceProvider`接口
```java
public class ProductCondition implements SourceProvider {
private static final String[] INCLUDE_FIELDS = {"id", "name"};
private static final String[] EXCLUDE_FIELDS = {"label"};
/**
* 获取包含的字段列表
*
* @return 字段列表
*/
@Override
public String[] getIncludeFields() {
return INCLUDE_FIELDS;
}
/**
* 获取排除的字段列表
*
* @return 字段列表
*/
@Override
public String[] getExcludeFields() {
return EXCLUDE_FIELDS;
}
}
```
> 返回值实体类型定义
```java
@Data
public class Product {
private Long id;
private String name;
@Ignore
private String label;
}
```
> 此返回值类型,说明返回的字段为:`["id", "name"]`
## 拦截器
ebatis中拦截器的加载通过SPI方式实现,只需要提供的目标类实现io.manbang.ebatis.core.interceptor.Interceptor接口,并且在/META-INF/services目录下提供io.manbang.ebatis.core.interceptor
.Interceptor文件,内容为提供的目标类的全限定名。也可以在目标类上加上注解@AutoService(Interceptor.class),由auto-service替我们生成。
```
@Slf4j
@AutoService(Interceptor.class)
public class TestInterceptor implements Interceptor {
@Override
public int getOrder() {
return 0;
}
@Override
public void handleException(Throwable throwable) {
log.error("Exception", throwable);
}
@Override
public void preRequest(Object[] args) {
...
//通过ContextHolder可以跨上下文获取绑定的值
String userId = ContextHolder.getString("userId");
}
@Override
public void postRequest(RequestInfo requestInfo) {
...
}
@Override
public void preResponse(PreResponseInfo preResponseInfo) {
...
}
@Override
public void postResponse(PostResponseInfo postResponseInfo) {
...
}
}
```
## spring集成
通过简单得注解方式,即可以与spring框架完成集成。
```java
@Configuration
@EnableEasyMapper(basePackages = "io.manbang.ebatis.sample.mapper")
public class EbatisConfig {
@Bean(destroyMethod = "close")
public ClusterRouter clusterRouter() {
Cluster cluster = Cluster.simple("127.0.0.1", 9200, Credentials.basic("admin", "123456"));
ClusterRouter clusterRouter = ClusterRouter.single(cluster);
return clusterRouter;
}
}
```