原生的TransportClient,JedisCluster,MongoClient的API操作比较接近底层, 比较接近命令式操作,但操作较麻烦。下面分别看看spring data的玩法
- ES部分:ElasticsearchTemplate 持有TransportClient的引用,对其装饰增强,简化结果集的处理,不必显示进行model的赋值,只需要传入model的clazz对象。而以继承ElasticsearchCrudRepository接口的方式类似JPA方式操作看似easy,也自动处理结果集但个人认为有以下弊端:它通过model注解指定要操作的索引,通过方法名或@query注解指定查询条件较复杂时不太适合,操。作较麻烦易出错,不适合不熟悉JPA的coder。推荐ElasticsearchTemplate 的API,三种姿势比较如下
添加依赖,一般情况只需要spring-boot-starter-data-elasticsearch <!--要使用支持xpack的连接,不宜使用spring-boot-starter-data-elasticsearch自动配置--> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>--> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-elasticsearch</artifactId> <version>3.1.5.RELEASE</version> <exclusions> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.elasticsearch.plugin</groupId> <artifactId>x-pack-api</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>x-pack-transport</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>6.2.4</version> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.2.4</version> </dependency>
ES连接配置类 @Configuration public class EsClientConfiguration { @Value("${es.clusterName}") private String clusterName; @Value("${es.userName}") private String userName; @Value("${es.userPwd}") private String userPwd; @Value("${es.clusterNodes}") private String clusterNodes; @Value("${es.caPath}") private String caPath; @Value("${es.esKey}") private String esKey; @Value("${es.certificate}") private String certificate; @Value("${es.SSLflag:true}") private boolean SSLflag; @Bean public TransportClient transportClient() throws UnknownHostException { Settings settings = null; if (SSLflag) { settings = Settings.builder() .put("cluster.name", clusterName) .put("xpack.security.user", userName + ":" + userPwd) .put("xpack.ssl.certificate_authorities", caPath) .put("xpack.ssl.key", esKey) .put("xpack.ssl.certificate",certificate ) .put("xpack.security.transport.ssl.enabled", "true").build(); } else { settings = Settings.builder().put("cluster.name", clusterName) .put("xpack.security.user", userName + ":" + userPwd) .put("xpack.security.transport.ssl.enabled", "false").build(); } TransportClient client = new PreBuiltXPackTransportClient(settings); String[] ip_port = clusterNodes.split(","); for (String s : ip_port) { String[] split = s.split(":"); client.addTransportAddress(new TransportAddress(InetAddress.getByName(split[0]), Integer.parseInt(split[1]))); } return client; } @Bean public ElasticsearchTemplate elasticsearchTemplate() throws UnknownHostException { return new ElasticsearchTemplate(transportClient()); } }
操作ES的三种姿势对比 @Service public class SearchServiceImpl implements ISearchService { private static final Pattern isChineseRegex = Pattern.compile("[\u4E00-\u9FA5]{1}"); private static final String commodityIndex = "mallt_XXXX"; private static final String commodityIndexType = "mallt_XXXX"; @Autowired TransportClient client; @Autowired ElasticsearchTemplate elasticsearchTemplate; @Autowired CommoditySearchRepository repository; @Override public JsonResult<PageData<SearchSuggestVO>> getSearchSuggest(String searchKey, int pageNum, int pageSize) { return searchSuggestV3(searchKey, pageNum, pageSize); } /** * @Description: 原生TransportClient API * @param * @return * @throws Exception * @author majun * @date 2019/3/18 11:49 * @version 1.0 */ private JsonResult<PageData<SearchSuggestVO>> searchSuggestV1(String searchKey, int pageNum, int pageSize) { //构建postFilter查询条件 BoolQueryBuilder filter = QueryBuilders.boolQuery(); filter.must(new TermQueryBuilder("mallStatus", "7")); //构建query查询条件 BoolQueryBuilder query = QueryBuilders.boolQuery(); if (isChineseRegex.matcher(searchKey).find()) { query.should(new WildcardQueryBuilder("commodityName", "*" + searchKey + "*").boost(10)) .should(new MatchPhraseQueryBuilder("commodityName.chinese", searchKey).boost(5)); } else { searchKey = searchKey.toLowerCase(); query.should(new WildcardQueryBuilder("commodityName.pinyin", "*" + searchKey + "*").boost(15)) .should(new PrefixQueryBuilder("keyWord.pinyin", searchKey).boost(10)); } SearchRequestBuilder requestBuilder = client.prepareSearch(commodityIndex) .setTypes(commodityIndexType) .setQuery(query) .setPostFilter(filter) .setSize(10) .setFetchSource("commodityName", null); System.out.println(requestBuilder); SearchResponse response=requestBuilder.get(); RestStatus esStatus = response.status(); if (200 != esStatus.getStatus()) { return JsonResult.fail(); } List<SearchSuggestVO> list = new ArrayList<>(); SearchHits hits = response.getHits(); for (SearchHit entry : hits) { String value = entry.getSourceAsMap().get("commodityName").toString(); SearchSuggestVO vo = new SearchSuggestVO(); vo.setCommodityName(value); list.add(vo); } PageData<SearchSuggestVO> data = new PageData<>(); data.setTotalCount((int) hits.totalHits); data.setPageNum(pageNum); data.setPageSize(pageSize); data.setDataList(list); return JsonResult.success(data); } /** * @Description: 基于spring-data-elasticsearch的ElasticsearchTemplate的API * @param * @return * @throws Exception * @author majun * @date 2019/3/18 11:49 * @version 1.0 */ private JsonResult<PageData<SearchSuggestVO>> searchSuggestV2(String searchKey, int pageNum, int pageSize) { //构建postFilter查询条件 BoolQueryBuilder filter = QueryBuilders.boolQuery(); filter.must(new TermQueryBuilder("mallStatus", "7")); //构建query查询条件 BoolQueryBuilder query = QueryBuilders.boolQuery(); if (isChineseRegex.matcher(searchKey).find()) { query.should(new WildcardQueryBuilder("commodityName", "*" + searchKey + "*").boost(10)) .should(new MatchPhraseQueryBuilder("commodityName.chinese", searchKey).boost(5)); } else { searchKey = searchKey.toLowerCase(); query.should(new WildcardQueryBuilder("commodityName.pinyin", "*" + searchKey + "*").boost(15)) .should(new PrefixQueryBuilder("keyWord.pinyin", searchKey).boost(10)); } NativeSearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices(commodityIndex) .withTypes(commodityIndexType) .withFilter(filter) .withQuery(query) .withPageable(PageRequest.of(pageNum, pageSize)) .withFields("commodityName") .build(); AggregatedPage<SearchSuggestVO> result = elasticsearchTemplate.queryForPage(searchQuery, SearchSuggestVO.class); PageData<SearchSuggestVO> data = new PageData<>(); data.setTotalCount((int)result.getTotalElements()); data.setPageNum(pageNum); data.setPageSize(pageSize); data.setDataList(result.getContent()); return JsonResult.success(data); } /** * @Description: 基于spring-data-elasticsearch的ElasticsearchCrudRepository接口的API * @param * @return * @throws Exception * @author majun * @date 2019/3/18 11:49 * @version 1.0 */ private JsonResult<PageData<SearchSuggestVO>> searchSuggestV3(String searchKey, int pageNum, int pageSize) { Page<SearchSuggestVO> result = repository.getByQueryStr(searchKey,PageRequest.of(pageNum,pageSize)); PageData<SearchSuggestVO> data = new PageData<>(); data.setTotalCount((int)result.getTotalElements()); data.setPageNum(pageNum); data.setPageSize(pageSize); data.setDataList(result.getContent()); return JsonResult.success(data); } @Repository //默认入口类@EnableElasticsearchRepositories开启的,通过@query指定查询条件,通//过SearchSuggestVO注解@Document(indexName = "mallt_XXX_index",type = //"mallt_XXX")指定要查询的索引,ElasticsearchCrudRepository继承//spring-data-common的PagingAndSortingRepository接口,该接口的抽象实现类也要//依赖ElasticsearchTemplate public interface CommoditySearchRepository extends ElasticsearchCrudRepository<SearchSuggestVO, String> { @Query(" {\n" + " \"bool\": {\n" + " \"should\": [\n" + " {\n" + " \"wildcard\": {\n" + " \"commodityName.pinyin\": {\n" + " \"wildcard\": \"*?0*\",\n" + " \"boost\": 15\n" + " }\n" + " }\n" + " },\n" + " {\n" + " \"prefix\": {\n" + " \"keyWord.pinyin\": {\n" + " \"value\": \"?0\",\n" + " \"boost\": 10\n" + " }\n" + " }\n" + " }\n" + " ],\n" + " \"adjust_pure_negative\": true,\n" + " \"boost\": 1\n" + " }\n" + " },\n" + " \"post_filter\": {\n" + " \"bool\": {\n" + " \"must\": [\n" + " {\n" + " \"term\": {\n" + " \"mallStatus\": {\n" + " \"value\": \"7\",\n" + " \"boost\": 1\n" + " }\n" + " }\n" + " }\n" + " ],\n" + " \"adjust_pure_negative\": true,\n" + " \"boost\": 1\n" + " }\n" + " },\n" + " \"_source\": {\n" + " \"includes\": [\n" + " \"commodityName\"\n" + " ],\n" + " \"excludes\": []\n" + " }") Page<SearchSuggestVO> getByQueryStr(String keyword,Pageable pageable);
- Redis部分:redisTemplate与jedisCluster区别
redisTempalte不是基于jedisCluster装饰实现,它对操作Redis的所有API进行了归类,
对应通用的API,redisTempalte直接调用,对应其它数据类型及集群操作通过redisTempalte.posForXXX来获取其操作对象。
默认RedisTemplate的key和value都是使用jdk方式序列化,如下user对象必须实现Serializable 接口,如果需要系列化为json,可以自己配置RedisTemplate并设置key、value的序列化方式,如序列化为json就不用实现序列化接口。redisTemplate.setValueSerializer(RedisSerializer.json())
示例: redisTemplate.opsForValue().set("aaaa",new User("123",11), Duration.ofSeconds(10)); User aaaa = (User)redisTemplate.opsForValue().get("aaaa");
- MongoDB部分:mongoClient原始API很少使用,spring-data-mongodb的api类似spring-data-elasticsearch,也有mongoTemplate 和继承一个MongoRepository接口(该接口依然是继承了spring-data-common的PagingAndSortingRepository,依赖mongoTemplate)。后者 仍然是不够灵活,不适合操作较复杂条件的查询。推荐mongoTemplate,链式编程构建查询条件,传入clazz自动映射结果集。
Query query = new Query() .addCriteria(Criteria.where("username").regex("^[a-zA-Z0-9_]{4,15}$")) .addCriteria(Criteria.where("age").lt(18)) .skip(10) .limit(1); List<User> user = mongoTemplate.find(query, User.class, "user");