由于工作中需要用到ElasticSearch,而现有的ES客户端五花八门,于是从前辈那里了解到了一款不算热门的客户端工具,之前没有听说过Bboss,为避免遗忘,这里总结了一篇文档来记录本次的学习开发过程。
Bboss介绍
官方说明:bboss是一款高性能elasticsearch ORM开发库, 以类似于mybatis管理和配置sql的方式,采用xml文件管理elasticsearch的dsl脚本,在dsl脚本中可以使用变量、dsl片段、foreach循环、逻辑判断、注释;支持在线修改、自动热加载dsl配置文件,开发和调试非常方便。
简单来说:Bboss就是Elasticsearch的Java客户端。
es和os区别:Elasticsearch是由Elastic公司维护和开发的,OpenSearch由亚马逊AWS公司创建和维护,它基于Elasticsearch的开源代码进行了修改和优化,主要是为了适应云环境下的场景和需求,总的来说,Elasticsearch和OpenSearch很相似,Elasticsearch拥有一个庞大的社区,有很多的插件和工具可供使用,社区支持也比较好。而OpenSearch的社区相对小一些,但随着OpenSearch的推出,也会逐渐增长
es常见的java客户端
- Bboss(国内开源社区自主开发)
- Spring Data Elasticsearch(Spring框架提供)
- REST High Level Client(Elastic官方提供)
bboss在性能表现上优于Rest High Level Client,因为它采用了连接池技术、异步IO操作和多线程等技术来提高请求响应速度和吞吐量。
另外,我最看重的一点就是兼容性很好。无需考虑es和springboot的版本,这是其他es客户端无法做到的。
| bboss | Elasticsearch | spring boot |
|---|---|---|
| all | 1.x | 1.x,2.x,3.x |
| all | 2.x | 1.x,2.x,3.x |
| all | 5.x | 1.x,2.x,3.x |
| all | 6.x | 1.x,2.x,3.x |
| all | 7.x | 1.x,2.x,3.x |
| all | 8.x | 1.x,2.x,3.x |
jdk兼容性:jdk 1.8+
如果喜欢直接使用query dsl(es的官方语言),但是又不想在代码里面编写冗长的dsl拼接串的话,可以考虑采用 bboss。
bboss整合springboot
参考 esdoc.bbossgroups.com/#/spring-bo…
导入依赖
<!-- 核心依赖 -->
<dependency>
<groupId>com.bbossgroups.plugins</groupId>
<artifactId>bboss-elasticsearch-spring-boot-starter</artifactId>
<version>7.0.2</version>
</dependency>
<!-- 其他非必须依赖 -->
<dependency>
<groupId>com.bbossgroups.plugins</groupId>
<artifactId>bboss-datatran-jdbc</artifactId>
<version>7.0.2</version>
</dependency>
<dependency>
<groupId>com.bbossgroups.plugins</groupId>
<artifactId>bboss-elasticsearch-rest-jdbc</artifactId>
<version>7.0.2</version>
</dependency>
<dependency>
<groupId>com.bbossgroups.plugins</groupId>
<artifactId>bboss-elasticsearch-rest-entity</artifactId>
<version>7.0.2</version>
</dependency>
application.properties
#es的地址
spring.elasticsearch.bboss.elasticsearch.rest.hostNames=10.21.20.168:9200
#es的用户名和密码
spring.elasticsearch.bboss.elasticUser=elastic
spring.elasticsearch.bboss.elasticPassword=changeme
#在控制台输出脚本调试开关showTemplate,false关闭,true打开,同时log4j至少是info级别(DSL脚本调试日志开关)
spring.elasticsearch.bboss.elasticsearch.showTemplate=true
创建客户端实例
@Autowired
private BBossESStarter bbossESStarter;
//获取加载读取dsl xml配置文件的api接口实例,可以在代码里面直接通过dsl配置名称引用dsl即可, single instance multithreaded security
ClientInterface configClientUtil = bbossESStarter.getConfigRestClient("esmapper/xxxxx.xml");
//获取不需要读取dsl xml配置文件的api接口实例,可以在代码里面直接编写dsl, single instance multi-thread security
ClientInterface clientUtil = bbossESStarter.getRestClient();
或者
// 通过ElasticSearchHelper工具直接创建api接口实例
ClientInterface clientUtil = ElasticSearchHelper.getConfigRestClientUtil("esmapper/xxxxx.xml");
ClientInterface clientUtil1 = ElasticSearchHelper.getRestClientUtil();
使用示例
索引相关
判断索引和类型是否存在
existIndice
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
// 只判断索引是否存在
boolean exist = clientUtil.existIndice("hotfix");
System.out.println("hotfix索引是否存在:" + exist);
// 判断索引和类型是否同时存在
boolean indiceType = clientUtil.existIndiceType("hotfix", "doc");
System.out.println("索引hotfix类型doc是否存在:" + indiceType);
删除索引
dropIndice
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
boolean existed = clientUtil.existIndice("hotfix1");
if (existed){
String dropIndice = clientUtil.dropIndice("hotfix1");
System.out.println("删除索引返回结果"+dropIndice);
}
// 控制台输出
删除索引返回结果{
"acknowledged" : true
}
创建索引
createIndiceMapping
ClientInterface clientUtil = ElasticSearchHelper.getConfigRestClientUtil("esmapper/xxx.xml");
clientUtil.createIndiceMapping("demo",// 索引名
"createDemoIndice");// 索引映射DSL脚本名称,在esmapper/xxx.xml中定义createDemoIndice
String demoIndice = clientUtil.getIndice("demo");
System.out.println("获取索引返回的结果:" + demoIndice);
// 控制台输出
获取索引返回的结果:{
"demo2" : {
"mappings" : {
"demo2" : { }
}
}
}
<properties>
<property name="createDemoIndice">
<![CDATA[
{
"mappings": {
"demo2": {
}
}
}
]]>
</property>
</properties>
文档相关
创建文档
addDocument
简单的创建文档不指定id
HashMap<String, String> docData = new HashMap<>();
docData.put("name", "andy");
docData.put("age", "18");
docData.put("address", "beijing");
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
String response = clientUtil.addDocument("demo2", // 索引名
"test", // 类型
docData, // 文档数据 可以是map也可以是对象
"refresh=true");// 强制刷新
System.out.println("增加文档返回结果:" + response);
// 控制台输出
增加文档返回结果:{"_index":"demo2","_type":"test","_id":"AYkfpje0WzjIuz7qoiNV","_version":1,"result":"created",
"forced_refresh":true,"_shards":{"total":2,"successful":1,"failed":0},"created":true}
创建文档并指定id的三中方式
通过@ESId指定文档id
@Data
public class Demo {
@ESId
private String demoId;
private String name;
private String age;
private String address;
}
Demo docData = new Demo();
docData.setDemoId("id_1001");
docData.setName("jordon");
docData.setAge("25");
docData.setAddress("shanghai");
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
String response = clientUtil.addDocument("demo2", // 索引名
"test", // 类型
docData, // 文档数据 可以是map也可以是对象
"refresh=true");// 强制刷新
System.out.println("增加文档返回结果:" + response);
// 控制台输出
增加文档返回结果:{"_index":"demo2","_type":"test","_id":"id_1001","_version":1,"result":"created",
"forced_refresh":true,"_shards":{"total":2,"successful":1,"failed":0},"created":true}
通过postman查看这两条文档
curl --location --request GET 'http://localhost:9200/demo2/_search' \
--header 'Content-Type: application/json' \
--data '{
"query": {
"match_all":{}
}
}'
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1.0,
"hits": [
{
"_index": "demo2",
"_type": "test",
"_id": "id_1001",
"_score": 1.0,
"_source": {
"demoId": "id_1001",
"name": "jordon",
"age": "25",
"address": "shanghai"
}
},
{
"_index": "demo2",
"_type": "test",
"_id": "AYkfpje0WzjIuz7qoiNV",
"_score": 1.0,
"_source": {
"address": "beijing",
"name": "andy",
"age": "18"
}
}
]
}
}
可以看到通过@ESId指定文档id后文档字段中也多了一个名为demoId的字段,值和文档id相同。假如我不想要字段中多一个demoId字段,只想单纯的指定文档id,可以下面的方式
通过addDocumentWithId指定文档id
HashMap<String, String> docData = new HashMap<>();
docData.put("name", "luce");
docData.put("age", "18");
docData.put("address", "beijing");
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
String response = clientUtil.addDocumentWithId("demo2", // 索引名
"test", // 类型
docData, // 文档数据 可以是map也可以是对象
"id_1002");// 文档id
System.out.println("增加文档返回结果: "+response);
// 控制台输出
增加文档返回结果: {"_index":"demo2","_type":"test","_id":"id_1002","_version":1,"result":"created",
"_shards":{"total":2,"successful":1,"failed":0},"created":true}
通过这种方式确实指定了文档id,而且也没有多余字段,但是没有指定刷新策略,如果我还想指定刷新策略怎么办呢?
通过addDocument同时指定文档id和刷新策略
HashMap<String, String> docData = new HashMap<>();
docData.put("name", "lisha");
docData.put("age", "18");
docData.put("address", "zhengzhou");
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
String response = clientUtil.addDocument("demo2", // 索引名
"test", // 类型
docData, // 文档数据 可以是map也可以是对象
"id_1003",// 文档id
"refresh=true");// 强制刷新
System.out.println("增加文档返回结果: "+response);
// 控制台输出
增加文档返回结果: {"_index":"demo2","_type":"test","_id":"id_1003","_version":1,"result":"created","forced_refresh":true,
"_shards":{"total":2,"successful":1,"failed":0},"created":true}
refresh=true解释:
在Elasticsearch中,文档的写入是异步的,即写入操作成功后,文档不一定会立即显示在搜索结果中。这是因为Elasticsearch使用了一种叫做“刷新(refresh)”的机制,即将写入的文档放在一个缓存中,而不是立即写入磁盘。只有当缓存中的文档数量达到一定阈值或者一定时间间隔后,Elasticsearch才会将缓存中的文档刷新到磁盘,并使其在搜索结果中可见。
当您在使用BBoss进行文档写入操作时,可以设置refresh参数来控制文档的刷新时间。当refresh=true时,表示写入操作成功后,BBoss会立即执行一次刷新操作,使得写入的文档可以立即在搜索结果中可见。而当refresh=false时,表示写入操作成功后,只有当缓存中的文档数量达到一定阈值或者一定时间间隔后,Elasticsearch才会将缓存中的文档刷新到磁盘,并使其在搜索结果中可见。因此,设置refresh=true可以使得写入操作的结果更加及时地反映在搜索结果中,但也会增加系统的负载和延迟。
批量创建文档
addDateDocuments
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
List<Demo> demos = new ArrayList<>();
Demo demo = new Demo();
demo.setAge("18");
demo.setName("name1");
demo.setAddress("tianjin");
demos.add(demo);
demo = new Demo();
demo.setAge("10");
demo.setName("name2");
demo.setAddress("chongqing");
demos.add(demo);
//批量创建文档
String response = clientUtil.addDateDocuments("demo",//索引表
"demo",//索引类型
demos);
获取文档
getDocument
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
Map document = clientUtil.getDocument("demo2", // 索引名
"test", // 类型
"AYkfpje0WzjIuz7qoiNV", // 文档id 创建文档时ES自动生成的
Map.class); // 结果类型 可以是map或对象来接收
System.out.println("获取文档:"+document);
// 控制台输出
获取文档:{address=beijing, name=andy, age=18}
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
Demo document = clientUtil.getDocument("demo2", // 索引名
"test", // 类型
"id_1001", // 文档id 创建文档时ES自动生成的
Demo.class); // 结果类型 可以是map或对象来接收
System.out.println("获取文档:"+document);
// 控制台输出
获取文档:Demo(demoId=id_1001, name=jordon, age=25, address=shanghai)
修改文档
getDocument
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
Demo document = clientUtil.getDocument("demo2", // 索引名
"test", // 类型
"id_1001", // 文档id 创建文档时ES自动生成的
Demo.class); // 结果类型 可以是map或对象来接收
System.out.println("获取文档:"+document);
document.setName("jordon_modified");
document.setAddress("shanghai_modified");
String response = clientUtil.addDocument("demo2",
"test",
document,
"refresh=true");
System.out.println("修改文档返回结果:" + response);
// 控制台输出
修改文档返回结果:{"_index":"demo2","_type":"test","_id":"id_1001","_version":2,"result":"updated","forced_refresh":true,
"_shards":{"total":2,"successful":1,"failed":0},"created":false}
删除文档
deleteDocument
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
String response = clientUtil.deleteDocument("demo2",
"test",
"AYkfumOWWzjIuz7qoiNX");
System.out.println("删除文档返回结果:" + response);
// 控制台输出
删除文档返回结果:{"found":true,"_index":"demo2","_type":"test","_id":"AYkfumOWWzjIuz7qoiNX","_version":2,"result":"deleted",
"_shards":{"total":2,"successful":1,"failed":0}}
deleteDocuments
ClientInterface clientUtil = ElasticSearchHelper.getRestClientUtil();
String response = clientUtil.deleteDocuments("demo2",
"test",
new String[]{"id_1001", "AYkfpje0WzjIuz7qoiNV"});
System.out.println("删除文档返回结果:" + response);
// 控制台输出
删除文档返回结果:{"took":14,"errors":false,
"items":[{"delete":{"found":true,"_index":"demo2","_type":"test","_id":"id_1001","_version":3,"result":"deleted",
"_shards":{"total":2,"successful":1,"failed":0},"status":200}},
{"delete":{"found":true,"_index":"demo2","_type":"test","_id":"AYkfpje0WzjIuz7qoiNV","_version":2,"result":"deleted",
"_shards":{"total":2,"successful":1,"failed":0},"status":200}}]}
ES搜索
以hotfix搜索为示例
代码
public ESDatas<HotfixEntity> searchHotfixList(HotfixQuery queryParam) {
// 调试打开dsl打印,线上关闭
HostDiscoverUtil.swithShowdsl(true);
ClientInterface clientUtil = ElasticSearchHelper.getConfigRestClientUtil("esmapper/hotfix-manage.xml");
ESDatas<HotfixEntity> searched = null;
try {
searched = clientUtil.searchList("hotfix/_search",
"queryServiceByCondition",
queryParam,
HotfixEntity.class);
} catch (ElasticSearchException e) {
log.error("es search hotfix documents error", e);
return new ESDatas();
}
return searched;
}
esmapper/hotfix-manage.xml
<properties>
<property name="queryServiceByCondition">
<![CDATA[
{
"query": {
"bool": {
"must": [
{
"match_phrase": {
"product": #[product]
}
}
#if($keyword && !$keyword.equals(""))
,
{
"multi_match": {
"query": #[keyword],
"fields": ["version", "app_package", "download_path", "fix_problem", "update_command", "release_date", "contact_person"]
}
}
#end
#if($version && !$version.equals("") && !$version.equals("all"))
,
{
"match_phrase": {
"version": #[version]
}
}
#end
#if($contactPersonList)
,
{
"bool": {
"should": [
#foreach($contactPerson in $contactPersonList)
{
"match_phrase": {
"contact_person": #[contactPersonList[$velocityCount]]
}
}
#if($foreach.hasNext),#end
#end
]
}
}
#end
]
,
"filter": [
#if($startTime && $endTime)
{"range": {
"release_timestamp": {
"gte": #[startTime],
"lte": #[endTime]
}
}
},
#end
{
"match_all": {}
}
]
}
},
"sort": [
#if($keyword && !$keyword.equals(""))
{
"_score": {
"order": "desc"
}
},
#end
{
"release_timestamp": {
"order": "desc"
}
}
],
"highlight": {
"fields": {
"fix_problem": {},
"update_command": {},
"download_path": {}
},
"pre_tags": "<span style='background-color: mediumspringgreen;filter: brightness(80%) contrast(120%)'>",
"post_tags": "</span>"
},
"size": 10000
}
]]>
</property>
</properties>
{
"query": {
"bool": {
"must": [
{
"match_phrase": {
"product": "chatbot"
}
},
{
"multi_match": {
"query": "数据看板",
"fields": [
"version",
"app_package",
"download_path",
"fix_problem",
"update_command",
"release_date",
"contact_person"
]
}
},
{
"match_phrase": {
"version": "5.6.0"
}
},
{
"bool": {
"should": [
{
"match_phrase": {
"contact_person": "届远"
}
},
{
"match_phrase": {
"contact_person": "克霖"
}
},
{
"match_phrase": {
"contact_person": "嗨伦"
}
}
]
}
}
],
"filter": [
{
"range": {
"release_timestamp": {
"gte": 1685548800000,
"lte": 1688140799000
}
}
},
{
"match_all": {}
}
]
}
},
"sort": [
{
"_score": {
"order": "desc"
}
},
{
"release_timestamp": {
"order": "desc"
}
}
],
"highlight": {
"fields": {
"fix_problem": {},
"update_command": {},
"download_path": {}
},
"pre_tags": "<span style='background-color: mediumspringgreen;filter: brightness(80%) contrast(120%)'>",
"post_tags": "</span>"
},
"size": 10000
}
作用
在XML中,可能包含一些特殊字符,例如“<”,“>”和“&”,这些特殊字符必须进行转义,否则会被解析器误认为是标记符号,导致解析错误,为了避免这种情况,可以使用CDATA块将包含特殊字符的文本数据进行包装,告诉解析器这段文本数据不需要进行解析和转义。注意:CDATA中不能再包含"]]>",如果非要包含"]]>",则可以通过拆分的方式,如下:
<![CDATA[ This is some text with ]]]]><![CDATA[>]]>
velocity语法
变量
脚本中变量定义语法有两种:#[xxx],xxx,大部分情况下用#[xxx],因为#[xxx]类型</font>**<font style="color:rgb(44, 62, 80);">变量值中包含的可能破坏dsl json语法结构的特殊字符(例如回车符号,换行符号,注释符号等),框架会自动进行转义处理(有点类似SQL注入);</font>**<font style="color:rgb(52, 73, 94);">xxx类型变量直接输出原始值(特殊字符不做转移处理)。当然,一些特殊情况需要用xxx类型变量可以用于if/else和foreach循环控制变量,而#[xxx]不可以。建议:在dsl拼接中采用#[xxx]替代xxx。变量来源:1.自己在脚本中定义;2.代码中通过参数传过来;3.有特殊作用的内置变量,如$velocityCount
“#”
使用"#"用来标识Velocity的关键字,包括#set、#if 、#else、#end、#foreach、#end、#include、#parse、#macro等;
“$”
使用"name,$msg等;
“!”
变量名前加上!,比如:!username返回字符串"!username返回空字符串""。!!name
声明变量
#set用来定义变量,用法:#set(开头,第一个字符必须是字母,可以包含字母,数字,连字符(-)下划线(_),等号右边的内容如果出现以开始的字符串时,将做变量的替换,例如:</font>#set(hello ="hello hello赋值为"hello andy"
条件判断
条件判断用#if关键字,和java里的if用法相似
#if(condition)
逻辑处理
#end
#if(condition)
逻辑处理
#else
逻辑处理
#end
#if(condition)
逻辑处理
#elseif()
逻辑处理
#else
逻辑处理
#end
判断List集合datas不为空并且datas的size大于0
#if($datas && $datas.size()> 0)
#foreach($bb in $datas)
#end
#end
变量值逻辑判断
#if($name)
#end
#if(!$name)
#end
#if($name && !$name.equals(""))
#end
#if($name > 0)
#end
#if($datas && $datas.size() > 0)
#end
逻辑判断还可以包含各种组合 && || 操作
循环
更多复杂案例参考:esdoc.bbossgroups.com/#/developme…
foreach循环内置循环变量:$velocityCount,用来获取当前循环到的元素的索引值,不需要从外部传入,可以直接使用,在循环内部可以通过
#[collection[velocityCount]]拿到指定下标的元素,如果元素是一个对象,而我想拿到对象的某个属性值,可以用#[collection[velocityCount]->fieldname],如果是复杂的对象呢?莫急莫急,后面有专门的总结【#[xxx]变量的格式】。
注意:bboss框架的velocityCount是从1开始计数的。也就是说bboss的模板引擎虽然大部分和Velocity模板引擎是一样的,但细微的一些地方上还是有不同的。
#foreach($item in $collection)
逻辑处理
#end
{
"bool": {
"should": [
#foreach($contact in $contactList)
{
"match_phrase": {
"contact": #[contactList[$velocityCount]]
}
}
#if($foreach.hasNext),#end
#end
]
}
}
等价于
{
"bool": {
"should": [
#foreach($contact in $contactList)
#if($velocityCount > 0),#end
{
"match_phrase": {
"contact": #[contactList[$velocityCount]]
}
}
#end
]
}
}
另外注意:这里面循环内的每一项元素并不是用contact来获取,而是通过#[contactList[velocityCount]]方式来获取,这有什么区别呢?
例如:假设$contactList={"张三", "李四", "王五"},
用$contact来获取,那么代码解析后的结果是
{
"bool": {
"should": [
{
"match_phrase": {
"contact": 张三
}
},
{
"match_phrase": {
"contact": 李四
}
},
{
"match_phrase": {
"contact": 王五
}
}
]
}
}
用#[contactList[$velocityCount]]来获取,那么代码解析后的结果是
{
"bool": {
"should": [
{
"match_phrase": {
"contact": "张三"
}
},
{
"match_phrase": {
"contact": "李四"
}
},
{
"match_phrase": {
"contact": "王五"
}
}
]
}
}
因此,如果是数字类型那么用哪种方式获取元素都是可以的,但是如果是字符串类型,只能用
#[contactList[$velocityCount]]来获取。
那么假如我就想用contact"的方式,因为$contact是值的替换
{
"bool": {
"should": [
#foreach($contact in $contactList)
#if($velocityCount > 0),#end
{
"match_phrase": {
"contact": "$contact"
}
}
#end
]
}
}
如果变量$contact为数字类型,值为100,那么替换后得到"100",数字100被当成String处理了,这种情况下可能会出现不可预知的问题
再次总结:建议在dsl拼接中采用#[xxx]替代xxx.
#[xxx]变量的格式
- #[aaa->bb] 如果aaa是一个bean对象,这个变量格式表示了对aaa对象的bb属性的引用,如果aaa是一个map对象,这个变量格式表示了对aaa对象的key为bb的元素值引用
- #[aaa[key]] 引用map对象aaa中key所对应的value数据,引用map元素的等价方法#[aaa->key]
- #[aaa[0]] (一维数组中的第一个元素,或者list中的第一个元素,具体取决于aaa变量是一个数组还是list对象)
- #[aaa[0][1]](二维数组中的第一维度的第二个元素,或者list中的第一个元素的第二个子元素,具体取决于aaa变量是每一维度是数组还是list对象)
- #[aaa[key][0]] 引用map对象aaa中key所对应的value的第一个元素,取决于value的类型是数组还是list,等价引用方法#[aaa->key[0]]
以上就是全部的类型,每种类型可以任意组合
#[aaa->bb[0]] aaa对象的bb属性的第一个元素,bb属性类型是数组或List
#[aaa[0]->bb[0]] 泛型为aaa的list的第一个元素的bb属性的第一个元素,bb属性类型是数组或List
#[aaa[0]->bb[0]->fff] #[aaa[0]->bb[0]->cc[keyname]] #[aaa[0]->bb->cc[keyname]] 等等
#[xxx]变量属性
更多参考 esdoc.bbossgroups.com/#/developme…
可以在#[]变量中指定escapeCount, serialJson, quoted,lpad,rpad,escape,esEncode,dateformat/locale/timezone属性,属性和变量名称用逗号分隔:
#[变量名,quoted=false,lpad=xxx,rpad=ddd]
#[变量名,quoted=false,lpad=xxx|3,rpad=ddd|4]
#[dynamicPriceTemplate->goodName,escapeCount=2]
#[dynamicPriceTemplate->rules[$velocityCount],serialJson=true]
#[testVar,serialJson=true]
这里只做少部分的属性的解释说明:
- quoted: boolean类型,控制是否为字符串变量和日期变量串两头添加"号,true添加,false不加,默认为true,一般在不需要自动加"号的情况下使用;
例如:
"asdfaf#[application,quoted=false]s"
变量application的值为testweb,解析后的效果如下:
"asdfaftestwebs"
"asdfaf#[application,quoted=true]s"
变量application的值为testweb,解析后的效果如下:
"asdfaf"testweb"s"
这里也解释了循环那块为什么用#[xxx]可以得到正确的字符串
- lpad、rpad:在通过lpad(左边追加)和rpad(右边追加)给变量值两头追加字符串,同时可以通过 | 指定一个数字,表示追加多少次
例如:
"#[application,quoted=false,lpad=#]s"
变量的值为testweb,解析后的效果如下:
"#testwebs"
"ddd#[application,quoted=false,lpad=#|2,rpad=#|3]s"
变量的值为testweb,解析后的效果如下:
"ddd##testweb###s"
- serialJson:boolean类型,通过属性serialJson指示框架直接将对象序列化为json数据;
- escapeCount: int类型,在脚本中,含有特殊字符的goodName需要转义2次;
$xxx变量
通过循环那块的介绍已经大概了解了xxx是一个对象呢?
变量如果是一个对象,可以通过以下方式引用对象中定义的属性:
$customer.Address
$purchase.Total
或者
$customer.getAddress()
$purchase.getTotal()
变量如果是List或者Array数组,size长度获取和为空判断方法:
$myarray.isEmpty()
$myarray.size()
如何修改$xxx的值呢?和声明变量时一样
变量定义
#set( $hasParam = false )
然后在dsl其他地方可以修改变量的值
#set( $hasParam = true )
总结#[xxx]和$xxx两种模式变量的区别
#[xxx]自动对变量值中的特殊字符进行转义处理,而$xxx不会进行处理直接输出原始值。
#[xxx]自动在String类型变量两边加上双引号"",如果不需要双引号,就写成这样:#[xxx,quoted=false]。
#[xxx]自动对日期类型变量值采用Utc标准时间格式(yyyy-MM-dd'T'HH\:mm\:ss.SSS'Z')进行格式化
处理,而$xxx不会进行处理直接输出原始值,因此如果$xxx模式变量参数中包含有特殊字符或者是日期类型,
请在程序中自行处理好。
$xxx可用于逻辑判断、循环处理语句,#[xxx]不能用于逻辑判断、循环处理语句。
$xxx变量参数值拼接到dsl中需要特别注意,如果变量的值不确定,变化频繁,在高并发的场景下会导
致严重的性能问题;$xxx用在foreach和if/else语法中不会存在这个问题。
@{pianduan}-片段变量使用
@{xxx}类型变量用于在query dsl中引用脚本片段。很多的dsl脚本会包含一些公共内容,比如查询条件,聚合操作脚本等等,可以把这些公共部分抽取出来定义成dsl片段;另外,一些复杂的搜索聚合查询的dsl脚本很长,由很多比较通用独立的部分组成,这样也可以将独立部分剥离形成片段,这样dsl的结构更加清晰,更加易于维护。作用相当于mybatis的xml中的 。
片段定义一定要定义在引用片段的dsl脚本前面。
<properties>
<property name="column">
<![CDATA[
"title": {
"type": "text",
"index": true
},
"version": {
"type": "text",
"index": true
},
"fix_problem": {
"type": "text",
"index": true
}
]]>
</property>
<property name="searchColumn">
<![CDATA[
{
"mappings": {
"hotfix": {
"properties": {
@{column}
}
}
}
}
]]>
</property>
</properties>
片段变量只是一个占位符,在系统第一次加载配置文件时候,直接被column对应的片段内容替换,片段定义中同样可以引用其他片段。
也可以引用其他文件中定义的dsl片段(外部片段引用bboss 5.5.6才支持),例如:
<properties>
<property name="pianduan">
<![CDATA[
"title": {
"type": "text",
"index": true
},
"version": {
"type": "text",
"index": true
},
"fix_problem": {
"type": "text",
"index": true
}
]]>
</property>
</properties>
<properties>
<property name="outPianduanRef"
templateFile="esmapper/pianduan.xml"
templateName="pianduan"/>
<property name="testoutPianduan">
<![CDATA[
{
"mappings": {
"hotfix": {
"properties": {
@{outPianduanRef}
}
}
}
}
]]>
</property>
</properties>
DSL注释
单行注释用 ##
##注释内容
多行注释用#* 和 *#包起来
#*
注释内容
注释内容
*#
注释使用的示例:
<properties>
<!-- 这里是普通注释 -->
<property>
<![CDATA[
## 这里是DSL注释
]]>
</property>
</properties>
至此,日常使用bboss开发ES已经满足基本的使用场景了,更多进阶教程见bboss官网。