概述

本篇博客主要将介绍的是利用spring query dsl框架实现的服务端查询解析和实现介绍。

查询功能是在各种应用程序里面都有应用,且非常重要的功能。用户直接使用的查询功能往往是在我们做好的UI界面上进行查询,UI会将查询请求发给查询实现的服务器,或者专门负责实现查询的一个组件。市场上有专门做查询的框架,其中比较出名,应用也比较广泛的是elasticsearch。

定义查询请求

对于服务端来说,前端UI发送过来的查询请求必然是按一定规则组织起来的,这样的规则后端必须能够支持和解析。换一种说法就是服务调用者和服务发布者之间需要遵循同一个规范才可以。百度的UI查询是这样定义的:

图片

在上图中加了蓝色下划线的地方即为我们在百度当中搜索的字符串内容,可以发现,百度的实现是将搜索的内容当做了http请求的url的参数来处理的,用了一个q作为key,q后面的内容就是所查询的内容。

google的实现是类似的,如下图所示:

图片

对于google和百度这样的只有一个搜索框的查询界面,这样处理是比较合理的,也不是整个查询实现最关键的部分。更为关键的是后续服务器将这个查询内容进行了怎样的处理。对于别的一些产品来说,可能需要对某些关键字进行单独的查询,这个时候肯定就不是一个搜索框能个满足的需求了。

总的来说,我们可以有如下的方式来组织一个查询

google-like查询

这种查询典型的应用是一个查询框,什么都可以查的情形,例如google和百度。对于这样的查询需求来说,在构建查询请求时只需将查询的内容放在http请求的的参数里面即可。

这样的查询解析是非常方便的,难度和需要考虑得事情在于要讲查询的内容放到哪些地方去查询。从数据库的层面来说就是要去哪些数据库的哪些表去查询。

特定字段的类sql查询

这种查询是指定某个字段,然后采用类似于sql语句的写法进行查询,各种查询条件以一定的形式组织在一起,发给服务器进行解析。这样的查询对服务器解析查询的能力要求更高,它提供了一些更加具体的查询条件。

例如我们以冒号表示等于,则一个查询字符串的形式是:

name:bill

这个查询的意思就是查询名字name等于bill的记录。

我们也可以将多个条件拼接在一起,让他们直接用逻辑关系组合在一起,例如或者和并且的逻辑关系。例如:

name:bill AND city:LA

或者下面这种或者的关系:

name:bill OR city:LA

上面的查询语句意味着我们的前后台要定义一套自己的查询逻辑和架构,并且解析它,并将它转换为正确的查询。若我们想实现灵活的查询,则上面的查询语句在符合规则的前提下应当是可以自由组合的。怎么做取决于我们的实际需求。如果一个写死的查询关键字就能满足我们的需求,则在当前那个时期自然也是合理的。

但是从灵活性角度,技术角度,实现成灵活的可解析的,显然是我们更想要的功能。最灵活的当然就是sql语句能支持怎样的查询,我们都能支持对应的查询写法,但是这对服务器的解析逻辑就有了更加高的要求,尤其是当主表子表混在一起查询之后,会更加复杂

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!

使用Spring Data Querydsl

什么是Querydsl呢?Querydsl是一个框架,它可以通过它提供的的API帮助我们构建静态类型的SQL-like查询,也就是在上面我们提到的组织查询方式。可以通过诸如Querydsl之类的流畅API构造查询。

Querydsl是出于以类型安全的方式维护HQL查询的需要而诞生的。 HQL查询的增量构造需要String连接,这导致难以阅读的代码。通过纯字符串对域类型和属性的不安全引用是基于字符串的HQL构造的另一个问题。

随着域模型的不断变化,类型安全性在软件开发中带来了巨大的好处。域更改直接反映在查询中,而查询构造中的自动完成功能使查询构造更快,更安全。

用于Hibernate的HQL是Querydsl的第一个目标语言,如今querydsl支持JPA,JDO,JDBC,Lucene,Hibernate Search,MongoDB,Collections和RDFBean作为它的后端。

其官方网站在这里:http://querydsl.com/

Querydsl和spring有什么关系呢?几个Spring Data的模块通过QuerydslPredicateExecutor提供了与Querydsl的集成,如以下示例所示:

public interface QuerydslPredicateExecutor<T> {
//查找并返回与Predicate匹配的单个entity。Optional<T> findById(Predicate predicate);
//查找并返回与Predicate匹配的所有entityIterable<T> findAll(Predicate predicate);
//返回与Predicate匹配的数量。long count(Predicate predicate);
//返回是否存在与Predicate匹配的entity。boolean exists(Predicate predicate);        // … more functionality omitted.
}

Predicate就是我们需要传入的一个查询的抽象。

在spring当中使用Querydsl,只需要在spring的repository接口继承QuerydslPredicateExecutor,如以下示例所示:

interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

在定义了上面的这个接口之后,我们就可以使用Querydsl Predicate编写type-safe的查询,如以下示例所示:

Predicate predicate = user.firstname.equals("dave").and(user.lastname.startsWith("mathews"));userRepository.findAll(predicate);

上面的代码构建出的predicate体现在sql语句里的话就是这样的: where firstname = 'dave' and lastname ='mathews%'。这就是所谓的类sql的查询,用起来非常的直观。

因此,我们可以将我们接收到的查询请求,转化为对应的predicte,且从技术上讲,只要predict支持的查询拼接我们都能支持,难点只在于如何解析查询请求,以及如何将他们转换为对应的predicate.

速领:神作《凤凰架构:构建可靠的大型分布式系统》电子版

利用Spring Query DSL实现动态查询

下面是使用spring和Querydsl实现动态查询的一个例子.

现在假设我们有Model类如下:

public class Student {private String id;private String gender;private String firstName;private String lastName;private Date createdAt;private Boolean isGraduated;}

我们希望可以实现该类所有字段直接自由组合进行查询,且可以按照与和或的逻辑进行查询。且我们约定用冒号表示等于,例如:

firstname:li AND lastname:huafirstname:li OR lastname:huafirstname:li AND lastname:hua AND gender:male

上面的查询都比较清晰,解析不会有太大难度,下面我们来看这样一个查询:

firstname:li OR lastname:hua AND gender:male

这个查询的问题在于作为逻辑与的gender查询,到底是只和前面一个条件进行与操作,还是与前面两个条件一起进行一个与操作,显然与的条件往往是作为filter的功能出现的。

因此我们应当将其看作整个其他条件的与操作,因此我们需要先将前面的条在组合在一起,例如,我们可以使用括号表示这个逻辑,那么查询就会变成:

(firstname:li AND lastname:hua) AND gender:male

这下逻辑就变得清晰了,难题就在于怎么解析了

public class QueryAnalysis{private static final String EMPTY_STRING = "";private static final String BLANK_STRING = " ";private static final String COLON = ":";private static final String BP_CATEGORY_CODE = "categoryCode";private static final String OPEN_PARENTTHESIS = "(";private static final String CLOSE_PARENTTHESIS = ")";private static final String QUERY_REGEX = "([\\w.]+?)(:|<|>|!:)([^ ]*)";//it has to lie between two blanksprivate static final String QUERY_LOGIC_AND = " AND ";private void generateQueryBuilderWithQueryString(PredicateBuilder builder, String q,List<String> queryStringList) {StringBuilder stringBuilder = new StringBuilder();String queryTerm = q;if (q == null) {return;}if (!q.contains(" AND ") && !q.startsWith("(") && !q.endsWith(")")) {queryTerm = stringBuilder.append("(").append(q).append(")").toString();}Map<String, Matcher> matcherMap = getMatcherWithQueryStr(queryTerm);Matcher matcherOr = matcherMap.get("matcherOr");Matcher matcherAnd = matcherMap.get("matcherAnd");while (matcherOr.find()) {builder.withOr(matcherOr.group(1), matcherOr.group(2), matcherOr.group(3));}while (matcherAnd.find()) {builder.withAnd(matcherAnd.group(1), matcherAnd.group(2), matcherAnd.group(3));isSearchParameterValid = true;}}private static Map<String, Matcher> getMatcherWithQueryStr(String q) {StringBuilder stringBuilder = new StringBuilder();Pattern pattern = Pattern.compile(QUERY_REGEX);// inside the subString is "or",outside them are "and"String[] queryStringArraySplitByAnd = q.split(QUERY_LOGIC_AND);String queryStringOr = EMPTY_STRING;String queryStringAnd = EMPTY_STRING;for (String string : queryStringArraySplitByAnd) {if (string.trim().startsWith(OPEN_PARENTTHESIS) && string.trim().endsWith(CLOSE_PARENTTHESIS)) {//only support one OR sentencequeryStringOr = string.trim().substring(1,string.length()-1);} else {queryStringAnd = stringBuilder.append(string).append(BLANK_STRING).toString();}}String queryStringAndTrim = queryStringAnd.trim();if(queryStringAndTrim.startsWith(OPEN_PARENTTHESIS) && queryStringAndTrim.endsWith(CLOSE_PARENTTHESIS)){queryStringAnd = queryStringAndTrim.substring(1,queryStringAndTrim.length()-1);}Matcher matcherOr = pattern.matcher(queryStringOr);Matcher matcherAnd = pattern.matcher(queryStringAnd);Map<String, Matcher> matcherMap = new ConcurrentHashMap<>();matcherMap.put("matcherOr", matcherOr);matcherMap.put("matcherAnd", matcherAnd);return matcherMap;}
}

Predicate的逻辑如下:

import java.util.ArrayList;
import java.util.List;import com.querydsl.core.types.dsl.BooleanExpression;/*** This class is mainly used to classify all the query parameters*/
public class PredicateBuilder {private static final String BLANK_STRING = " ";private static final String TILDE_STRING = "~~";private List<SearchCriteria> paramsOr;private List<SearchCriteria> paramsAnd;private BusinessPartnerMessageProvider messageProvider;public PredicateBuilder(BusinessPartnerMessageProvider messageProvider){paramsOr = new ArrayList<>();paramsAnd = new ArrayList<>();}public PredicateBuilder withOr(String key, String operation, Object value) {String keyAfterConverted = keyConverter(key);Object valueAfterConverted = value.toString().replaceAll(TILDE_STRING,BLANK_STRING).trim();paramsOr.add(new SearchCriteria(keyAfterConverted, operation, valueAfterConverted));return this;}public PredicateBuilder withAnd(String key, String operation, Object value) {String keyAfterConverted = keyConverter(key);Object valueAfterConverted = value.toString().replaceAll(TILDE_STRING,BLANK_STRING).trim();paramsAnd.add(new SearchCriteria(keyAfterConverted, operation, valueAfterConverted));return this;}protected String keyConverter(String key){return key;}public BooleanExpression buildOr(Class classType) {return handleBPBooleanExpressionOr(classType);}public BooleanExpression buildAnd(Class classType) {return handleBPBooleanExpressionAnd(classType);}private BooleanExpression handleBPBooleanExpressionOr(Class classType) {if (paramsOr.isEmpty()) {return null;}return buildBooleanExpressionOr(paramsOr, classType);}private BooleanExpression handleBPBooleanExpressionAnd(Class classType) {if (paramsAnd.isEmpty()) {return null;}return buildBooleanExpressionAnd(paramsAnd, classType);}private BooleanExpression buildBooleanExpressionOr(List<SearchCriteria> paramsOr, Class classType){List<BooleanExpression> predicates = new ArrayList<>();BooleanExpressionBuilder predicate;for (SearchCriteria param : paramsOr) {predicate = new BooleanExpressionBuilder(param, messageProvider);BooleanExpression exp = predicate.buildPredicate(classType);if (exp != null) {predicates.add(exp);}}BooleanExpression result = null;if(!predicates.isEmpty()) {result = predicates.get(0);for (int i = 1; i < predicates.size(); i++) {result = result.or(predicates.get(i));}}return result;}private BooleanExpression buildBooleanExpressionAnd(List<SearchCriteria> paramsAnd, Class classType){List<BooleanExpression> predicates = new ArrayList<>();BooleanExpressionBuilder predicate;for (SearchCriteria param : paramsAnd) {predicate = new BooleanExpressionBuilder(param, messageProvider);BooleanExpression exp = predicate.buildPredicate(classType);if (exp != null) {predicates.add(exp);}}BooleanExpression result = null;if(!predicates.isEmpty()) {result = predicates.get(0);for (int i = 1; i < predicates.size(); i++) {result = result.and(predicates.get(i));}}return result;}}

BooleanExpressionBuilder的逻辑如下:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.TimeZone;import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.BooleanPath;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.core.types.dsl.NumberPath;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.core.types.dsl.StringPath;public class BooleanExpressionBuilder {private SearchCriteria criteria;private BusinessPartnerMessageProvider messageProvider;private static final String NO_SUCH_FILED_MESSAGE = "NO_SUCH_FIELD_FOR_QUERY_PARAMETER";public BooleanExpressionBuilder(final SearchCriteria criteria ) {this.criteria = new SearchCriteria(criteria.getKey(),criteria.getOperation(),criteria.getValue());}public BooleanExpression buildPredicate(Class classType) {// the second param for PathBuilder constructor is the binding path.PathBuilder<Class> entityPath = new PathBuilder<>(classType, classType.getSimpleName());Boolean isValueMatchEndWith = criteria.getValue().toString().endsWith("*");Boolean isValueMatchStartWith = criteria.getValue().toString().startsWith("*");Boolean isOperationColon = ":".equalsIgnoreCase(criteria.getOperation());int searchValueLength = criteria.getValue().toString().length();StringPath stringPath = entityPath.getString(criteria.getKey());DateTimePath<Date> timePath = entityPath.getDateTime(criteria.getKey(), Date.class);NumberPath<Integer> numberPath = entityPath.getNumber(criteria.getKey(), Integer.class);if ((isOperationColon) && (!isValueMatchStartWith) && (!isValueMatchEndWith)) {return getEqualBooleanExpression(classType, entityPath, stringPath, timePath, numberPath);}if (">".equalsIgnoreCase(criteria.getOperation())) {return getGreaterThanBooleanExpression(classType, timePath, numberPath);}if ("<".equalsIgnoreCase(criteria.getOperation())) {return getLessThanBooleanExpression(classType, timePath, numberPath);}// !:means !=if ("!:".equalsIgnoreCase(criteria.getOperation())) {return getNotEqualBooleanExpression(classType, entityPath,stringPath, timePath, numberPath);}//start with xxxif ((isOperationColon) && isValueMatchEndWith && (!isValueMatchStartWith)) {if (isSearchKeyValidForClass(classType))return stringPath.startsWithIgnoreCase(criteria.getValue().toString().substring(0, searchValueLength - 1).trim());}if ((isOperationColon) && (!isValueMatchEndWith) && (isValueMatchStartWith)) {if (isSearchKeyValidForClass(classType))return stringPath.endsWithIgnoreCase(criteria.getValue().toString().substring(1, searchValueLength).trim());}//contain xxxif ((isOperationColon) && isValueMatchEndWith && isValueMatchStartWith) {return getContainsBooleanExpression(classType, searchValueLength, stringPath);}return null;}private BooleanExpression getContainsBooleanExpression(Class classType,int searchValueLength, StringPath stringPath) {try {Class fieldType = classType.getDeclaredField(criteria.getKey()).getType();if (fieldType.equals(String.class) && searchValueLength>1) {return stringPath.containsIgnoreCase(criteria.getValue().toString().substring(1,searchValueLength-1).trim());}//if there are only a "*" in the seatch value, thenif(fieldType.equals(String.class) && searchValueLength==1){return stringPath.eq(criteria.getValue().toString());}} catch (NoSuchFieldException | SecurityException e) {}return null;}private boolean isSearchKeyValidForClass(Class classType) {try {Class fieldType = classType.getDeclaredField(criteria.getKey()).getType();if (fieldType.equals(String.class)) {return true;}} catch (NoSuchFieldException | SecurityException e) {throw new BadRequestValidationException(messageProvider.getMessage(NO_SUCH_FILED_MESSAGE,new Object[] { criteria.getKey() }), e);}return false;}private BooleanExpression getNotEqualBooleanExpression(Class classType, PathBuilder<Class> entityPath,StringPath stringPath, DateTimePath<Date> timePath, NumberPath<Integer> numberPath) {try {Class fieldType = classType.getDeclaredField(criteria.getKey()).getType();if (fieldType.equals(Date.class)) {dateTimeValueConverter();return timePath.ne((Date) criteria.getValue());}if (fieldType.equals(Integer.class)) {int value = Integer.parseInt(criteria.getValue().toString());return numberPath.ne(value);}if (fieldType.equals(String.class)) {return stringPath.ne(criteria.getValue().toString());}if (fieldType.equals(boolean.class)) {booleanConverter();BooleanPath booleanPath = entityPath.getBoolean(criteria.getKey());return booleanPath.ne((Boolean) criteria.getValue());}if (fieldType.equals(Boolean.class)) {booleanConverter();BooleanPath booleanPath = entityPath.getBoolean(criteria.getKey());return booleanPath.ne((Boolean) criteria.getValue());}} catch (NoSuchFieldException | SecurityException e) {throw new BadRequestValidationException();}return null;}private BooleanExpression getLessThanBooleanExpression(Class classType,DateTimePath<Date> timePath, NumberPath<Integer> numberPath) {try {Class fieldType = classType.getDeclaredField(criteria.getKey()).getType();if (fieldType.equals(Date.class)) {dateTimeValueConverter();return timePath.lt((Date) criteria.getValue());}if (fieldType.equals(Integer.class)) {integerValueConverter();return numberPath.lt((Integer) criteria.getValue());}} catch (NoSuchFieldException | SecurityException e) {throw new BadRequestValidationException(e.getCause());}return null;}private BooleanExpression getGreaterThanBooleanExpression(Class classType,DateTimePath<Date> timePath, NumberPath<Integer> numberPath) {// other data types do not make sense when use >try {Class fieldType = classType.getDeclaredField(criteria.getKey()).getType();if (fieldType.equals(Date.class)) {dateTimeValueConverter();return timePath.gt((Date) criteria.getValue());}if (fieldType.equals(Integer.class)) {integerValueConverter();return numberPath.gt((Integer) criteria.getValue());}} catch (NoSuchFieldException | SecurityException e) {throw new BadRequestValidationException(e.getCause());}return null;}private BooleanExpression getEqualBooleanExpression(Class classType, PathBuilder<Class> entityPath, StringPath stringPath,DateTimePath<Date> timePath, NumberPath<Integer> numberPath) {// :means =try {Class fieldType = classType.getDeclaredField(criteria.getKey()).getType();if (fieldType.equals(Integer.class)) {integerValueConverter();return numberPath.eq((Integer) criteria.getValue());}if (fieldType.equals(Date.class)) {dateTimeValueConverter();return timePath.eq((Date) criteria.getValue());}if (fieldType.equals(boolean.class)) {booleanConverter();BooleanPath booleanPath = entityPath.getBoolean(criteria.getKey());return booleanPath.eq((Boolean) criteria.getValue());}if (fieldType.equals(Boolean.class)) {booleanConverter();BooleanPath booleanPath = entityPath.getBoolean(criteria.getKey());return booleanPath.eq((Boolean) criteria.getValue());}if (fieldType.equals(String.class)) {return stringPath.equalsIgnoreCase(criteria.getValue().toString());}} catch (NoSuchFieldException | SecurityException e) {throw new BadRequestValidationException(e.getCause());}return null;}// convert string to datetimeprivate void dateTimeValueConverter() {criteria.setValue(convertToTimeStamp(criteria.getValue().toString()));}private void  booleanConverter() {if (criteria.getValue().toString().equalsIgnoreCase("true")) {criteria.setValue(true);} else if (criteria.getValue().toString().equalsIgnoreCase("false")) {criteria.setValue(false);} else {throw new BadRequestValidationException("Invalid Boolean");}}// convert string to Integerprivate void integerValueConverter() {criteria.setValue(Integer.parseInt(criteria.getValue().toString()));}private Date convertToTimeStamp(String time) {//convert date herereturn parsedDate;}}

查询条件的抽象类SearchCriteria定义如下:

public class SearchCriteria {private String key;private String operation;private Object value;
}

大致的实现逻辑如下图所示:

图片

比较关键的点有下面这些:

  • 对字符串的解析需要借助正则表达式的帮助,正则表达式决定了我们支持怎样的查询.

  • 由于字符串可以任意输入,存在无限种可能,对查询字符串的校验很关键也很复杂。

  • 不同逻辑的查询条件需要存放在不同的容器里面,因为他们的拼接逻辑不一样,一个是或一个是与

  • 不同的字段类型需要调用不同的生成Predicate的方法,例如String,Boolean和Date这些类型他们都有自己对应的查询实现

  • 生成子表的Predicate很复杂,与主表的查询条件一起查询时逻辑更加复杂,上面的逻辑拿掉了这一部分。但是这个功能是可以实现的。

实现过程中的难题

主表包含多个子表数据时的AND查询

距离说明,现在有数据定义如下:

{"customerNumber": "5135116903","customerType": "INDIVIDUAL","createdBy": "Android.chen@sap.com","changedBy": "Android.chen@sap.com","createdAt": "2018-06-26T10:15:17.212Z","changedAt": "2018-06-26T10:15:17.212Z","markets": [{"marketId": "A1","currency": "USD","country": "US","active": true}, {"marketId": "A2","currency": "USD","country": "US","active": false}, {"marketId": "A3","currency": "USD","country": "US","active": true}]
}

其中父节点表是customer,子节点markets信息存储在market表当中。

现在,假设我们有这样的查询:

customerNumber: 5135116903 AND markets.active:false

没有疑问,上面的数据应该被查出来。现在查询条件变成下面这样:

customerNumber: 5135116903 AND markets.active:false AND markets.marketId:A1

现在问题来了,语句的意思是此客户的marker既要是非active 的且ID要是A1,但是此客户又有多个market,从整个数组里来看,这个条件是满足的。但是从单个的market个体来看这个条件是不满足的。而我们作为用户的话希望得到的效果必然是无法查处此customer信息。

这会给实现带来问题,因为由于market是一个数组,在数据表中对应的就是几条记录,我们在解析并构建子表查询时,必须确保对于子表的查询条件是作用于单独的一个node,也就是单独的一条记录,而不是从整个数组当中去查,否则就会有问题。

来源:https://blog.csdn.net/topdeveloperr

我们创建了一个高质量的技术交流群,与优秀的人在一起,自己也会优秀起来,赶紧点击加群,享受一起成长的快乐。另外,如果你最近想跳槽的话,年前我花了2周时间收集了一波大厂面经,节后准备跳槽的可以点击这里领取!

推荐阅读

  • Spring Boot 生产中的 16 条最佳实践

  • Stream转List的新API,真是干净又卫生啊!

  • 2分钟学会IDEA中调试过程回退的2种方法

··································

你好,我是程序猿DD,10年开发老司机、阿里云MVP、腾讯云TVP、出过书创过业、国企4年互联网6年。从普通开发到架构师、再到合伙人。一路过来,给我最深的感受就是一定要不断学习并关注前沿。只要你能坚持下来,多思考、少抱怨、勤动手,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你看好一个事情,一定是坚持了才能看到希望,而不是看到希望才去坚持。相信我,只要坚持下来,你一定比现在更好!如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯,帮你积累弯道超车的资本。

点击领取2022最新10000T学习资料

SpringBoot + QueryDSL 大大简化复杂查询操作相关推荐

  1. SpringBoot整合thymeleaf之模糊查询操作模块

    SpringBoot整合thymeleaf之模糊查询操作模块 引言 1.一般情况下,Mybatis的模糊查询操作 模糊查询操作 2.在实战中的模糊查询操作 web页面提交数据 <form act ...

  2. SpringBoot+MangoDB查询操作(MongoTemplate)总结

    SpringBoot+MangoDB查询操作(MongoTemplate)总结 没有学过非关系型数据库,特别是嵌套实体类查询,寸步难行 实体类的结构是这样的 实体类T1 @Data @Document ...

  3. [深入学习C#]LINQ查询表达式详解(1)——基本语法、使用扩展方法和Lambda表达式简化LINQ查询

    此文章非原创,转载自诗人江湖老,原文地址 在Git上下载源码 在工程中我们少不了要定义类或者结构去储存数据,这些数据将被临时地储存在内存中,现在我们想要对其完成一些类似于查找.过滤等等常见的任务的时候 ...

  4. springboot实体映射到数据库_SpringBoot 操作 ElasticSearch 详解

    点击上方☝SpringForAll社区 轻松关注!及时获取有趣有料的技术文章 本文来源:http://www.mydlq.club/article/64/ 一.ElasticSearch 简介 . 1 ...

  5. 网络信息检索(五)查询处理:查询方式+查询操作

    文章目录 一.查询方式 1:基于关键词的查询 (1)布尔查询 (2)短语查询 (3)近邻查询:短语查询扩展 2:结构查询 3:容错查询(Tolerant Retrieval) (1)模式匹配 (2)拼 ...

  6. Jmeter Springboot Redisson分布式锁并发订单操作(下单、取消单、完成单、加库存)

    Jmeter+Springboot+Redisson分布式锁并发订单操作(下单.取消单.完成单.加库存) 涉及知识点: java+springboot+mybatis开发 redis分布式锁+Redi ...

  7. MongoDB文档查询操作(一)

    上篇文章我们主要介绍了MongoDB的修改操作,本文我们来看看查询操作. 本文是MongoDB系列的第五篇文章,了解前面的文章有助于更好的理解本文: 1.Linux上安装MongoDB 2.Mongo ...

  8. mysql简单查询操作

    前面说了那么多,现在终于到了我们广大程序员最关心的"查询"操作了. ##查询操作## -- 语法 select [select选项] 字段列表 [as] 字段别名 /* from ...

  9. 使用Vue 简化 用户查询/添加功能

    使用Vue简化 用户查询/添加功能 1. 查询功能 1.1 Vue核心对象: 1.2 brand.html: 1.3 selectAllServlet(无变化): 2. 添加功能 2.1 addBra ...

  10. resultset mysql_MySQL数据库学习笔记(九)----JDBC的ResultSet接口(查询操作)、PreparedStatement接口重构增删改查(含SQL注入的解释)...

    [声明] 欢迎转载,但请保留文章原始出处→_→ [正文] 一.ResultSet接口的介绍: 对数据库的查询操作,一般需要返回查询结果,在程序中,JDBC为我们提供了ResultSet接口来专门处理查 ...

最新文章

  1. oracle游标声明中使用变量赋值,Oracle游标使用方法(游标里使用变量绑定)
  2. [转]常见的动态规划问题分析与求解
  3. discuzx2.5添加自定义积分日志
  4. CSS3:FlexBox的详解
  5. iPhone Xs发布会其实是苹果计算机视觉技术发布会!
  6. 利用OLAMI在unity游戏中加入中文语音控制(一)
  7. sql左外连接和右外连接的区别
  8. Redis中文学习文档redis.cn
  9. 证件照处理工具,软件虽小,但是功能强大!(证件照工具,都用得上)
  10. 微波雷达感应模块,智能马桶传感方案,智能化生活
  11. 高度的思想境界的几个特征
  12. 公开信息查询的一些方法,查征信,查婚姻状况,查询公开个人信息
  13. 用devc++表白_【吉利表白墙】19级倪yl,风吹起如花般破碎的流年,而你的笑容摇晃摇晃,成为我命途中最美的点缀...
  14. LeetCode 714. 买卖股票的最佳时机含手续费--动态规划
  15. 似然比和p值不同的原因_二代P值:传统P值的升级 | 社论前沿
  16. S4D440Customcode adaption practice
  17. 把 14 亿人都拉到一个微信群,在技术上能实现吗?
  18. java 保存输入的数据_java从键盘输入数据并保存到数组中
  19. 一键搜索出Android Studio内所有中文
  20. Ubuntu系统从机械硬盘迁移到SSD

热门文章

  1. VMware 15.6版本下载安装
  2. 2020软件设计师考试大纲
  3. smarty入门教程
  4. 计算机视觉论文-2021-08-03
  5. APUE学习笔记-15章进程间通信
  6. python怎么输出文件_python如何输出文件内容
  7. 基于51单片机GPS的导航系统设计(3)---毕设论文
  8. 微信抢红包插件 English Version
  9. 解决:UnsatisfiedDependencyException
  10. edius隐藏快捷键_edius8常用快捷键有哪些|edius快捷键使用大全汇总 - 软件教程 - 格子啦...