今日推荐

答应我, 不要再用 if (obj != null) 判空了20个示例!详解 Java8 Stream 用法,从此告别shi山(垃圾代码)利用Java8新特征,重构传统设计模式,你学会了吗?竟然有一半的人不知道 for 与 foreach 的区别???利用多线程批量拆分 List 导入数据库,效率杠杠的!

概述

本篇博客主要将介绍的是利用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语句能支持怎样的查询,我们都能支持对应的查询写法,但是这对服务器的解析逻辑就有了更加高的要求,尤其是当主表子表混在一起查询之后,会更加复杂

使用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

最后,再给大家推荐一个GitHub项目,该项目整理了上千本常用技术PDF,技术书籍都可以在这里找到。

GitHub地址:https://github.com/hello-go-maker/cs-books

电子书已经更新好了,拿走不谢,记得点一个star,持续更新中...

SpringBoot+Querydsl 框架,大大简化复杂查询操作相关推荐

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

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

  2. tp5 日期范围查询_tp5(thinkPHP5框架)时间查询操作实例分析

    本文实例讲述了tp5(thinkPHP5框架)时间查询操作.分享给大家供大家参考,具体如下: 在项目中 可能会遇到 跨月份进行查询 比如在 当输入201809 会获取当月的开始时间$start_mon ...

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

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

  4. tp5 日期范围查询_tp5(thinkPHP5框架)时间查询操作实例分析,tp5thinkphp5

    tp5(thinkPHP5框架)时间查询操作实例分析,tp5thinkphp5 本文实例讲述了tp5(thinkPHP5框架)时间查询操作.分享给大家供大家参考,具体如下: 在项目中 可能会遇到 跨月 ...

  5. yii mysql join_Yii框架连表查询操作示例

    本文实例讲述了Yii框架连表查询操作.分享给大家供大家参考,具体如下: Join //表连接 //查询出学生.班级.校区.记录表的所有数据 $data=Jf_record::find() ->j ...

  6. 大数据WEB阶段Spring框架(二)简化配置的操作

    Spring简化配置的操作 零.复习 IOC控制反转, 之前是通过new创建对象 , IOC是由Spring容器创建对象 , 需要用时getBean获取 . 导入约束文件 获取对象两种方式 : 通过I ...

  7. 云计算学习素材框架,msyql查询操作课件

    在本篇文章中将给大家讲解下msyql查询操作: 查询 简单查询:select * from '表名'; 避免重复:select distinct '字段' from '表名'; 条件查询:select ...

  8. php yii框架addselect,PHP Yii框架之数据库查询操作总结

    Yii容易学习和使用.你只需要知道PHP和面向对象编程,便可以很快上手,而不必事先去学习一种新的架构或者模板语言.用Yii的开发速度非常之快,除框架本身之外,需要为应用所写的编码极少.事实上这是最高效 ...

  9. 计算机毕业设计-基于神经网络APP-整合Vue+SpringBoot+TensorFlow框架-诗联AI

    诗联AI-基于神经网络APP-整合Vue+SpringBoot+TensorFlow框架 目录 诗联AI-基于神经网络APP-整合Vue+SpringBoot+TensorFlow框架 1.项目整体概 ...

  10. java计算机毕业设计springboot+vue城市轨道交通线路查询系统

    项目介绍 本系统是针对目前交通管理的实际需求,从实际工作出发,对过去的市轨道交通线路查询系统存在的问题进行分析,完善用户的使用体会.采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高, ...

最新文章

  1. php手册常用的函数
  2. mysql数据库增左右连接_快速增加MYSQL数据库连接数负载能力
  3. 数据结构(二)算法基础与复杂度
  4. shell之常用工具的使用
  5. python实现简单的百度翻译
  6. c语言中把一个数缩小十倍_C语言实例第04期,在控制台打印出著名的杨辉三角...
  7. [编写高质量代码:改善java程序的151个建议]后记
  8. js中__proto__和prototype的区别和联系
  9. 统计项目代码行数的工具——SLOCCount
  10. MyBatis 安装下载 及入门案例
  11. 腾云忆想技术干货|TSF微服务治理实战系列(一)——治理蓝图
  12. 【转载】GGB0/OB28/OKC7/GGB1/OBBH/OKC9 FICO增强
  13. java操作word宏
  14. Python|几十行代码带你简单实现模拟浏览器操作
  15. MLY -- 11.When to change dev/test sets and metrics
  16. 绝了!一个妹子 rm -rf 把公司整个数据库删没了...
  17. 简单的会员卡储值积分管理系统
  18. JPA @PersistenceContext和@Transactional Annotation
  19. 高通 OpenXR SDK 使用指南(2)
  20. 数字图像处理之用Python+GDAL实现BSQ格式转换为BIP格式,BIL格式转换为BSQ,BIP格式

热门文章

  1. VMware16下载与安装
  2. 如何连接到sqlplus
  3. 铃声截取软件android6,铃声剪辑
  4. 基于Mac制作iPhone铃声教程,iTunes定制铃声
  5. 系统备份恢复 Acronis True Image使用教程
  6. iPhone通讯录整理及vcf-CSV互转
  7. VMware下载(官网)
  8. 性能测试adb常用命令
  9. win2019微软更新服务器,微软2019 Windows 10更新十一月版正式版推送
  10. 三星s9 android p内测,三星开启国行Galaxy S9/S9+安卓9.0内测,限额一万名!