【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理

本文主要是介绍【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 Spring Data JPA系列

1、SpringBoot集成JPA及基本使用

2、Spring Data JPA Criteria查询、部分字段查询

3、Spring Data JPA数据批量插入、批量更新真的用对了吗

4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作

5、Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用

6、【源码】Spring Data JPA原理解析之Repository的自动注入(一)

7、【源码】Spring Data JPA原理解析之Repository的自动注入(二)

8、【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码

9、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)

10、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)

11、【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理

前言

在第一篇博文

SpringBoot集成JPA及基本使用-CSDN博客

中介绍了JPA的基本使用,在Repository接口中有三大类方法,分别为:

1)继承于JpaRepositoryImplementation接口,自动实现了CRUD等方法。

2)自定义接口方法,通过方法命名规则,无需写SQL或HQL,实现数据库表的操作。

3)自定义接口方法,通过@Query注解,添加SQL或HQL,实现数据库表的操作。

【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码这篇博文从源码分析了继承于JpaRepositoryImplementation接口,自动实现了CRUD等方法的实现原理。

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)系列的两篇博文,从源码分析了自定义接口方法,通过方法命名规则,无需写SQL或HQL,实现数据库表的操作的实现原理。

这一篇博文,继续从源码的角度,分享一下通过@Query注解,添加SQL或HQL,实现数据库表的操作的实现原理。

QueryExecutorMethodInterceptor回顾

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)在这篇博文中介绍了QueryExecutorMethodInterceptor,该拦截器用于处理所有的自定义Repository的自定义方法,包括上面介绍的第二类和第三类方法的拦截处理。即通过@Query注解,添加SQL或HQL,实现数据库表的操作的实现也是在QueryExecutorMethodInterceptor中实现。

在QueryExecutorMethodInterceptor构造方法中,查询查找策略queryLookupStrategy是一个JpaQueryLookupStrategy.CreateIfNotFoundQueryLookupStrategy对象。

在loopupQuery()方法中,执行QueryLookupStrategy.resolveQuery(),即CreateIfNotFoundQueryLookupStrategy.resolveQuery(),解析方法,获得RepositoryQuery对象。

DeclaredQueryLookupStrategy回顾

CreateIfNotFoundQueryLookupStrategy的构造方法需要传入DeclaredQueryLookupStrategy和CreateQueryLookupStrategy对象。

在resolveQuery()方法中,先访问DeclaredQueryLookupStrategy.resolveQuery()获得一个RepositoryQuery,如果没有匹配的查询,则访问CreateQueryLookupStrategy.resolveQuery()。其中CreateQueryLookupStrategy是针对方法命名规则。DeclaredQueryLookupStrategy是针对添加@Query注解的查询。

以上的源码在前篇博客中都已贴出,此处重点分享DeclaredQueryLookupStrategy,代码如下:

/*** 根据@Query中是否带nativeQuery属性值,返回NativeJpaQuery,或SimpleJpaQuery。如果没有配置value和name,则返回NamedQuery*/
private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {// ExtensionAwareQueryMethodEvaluationContextProvider对象private final QueryMethodEvaluationContextProvider evaluationContextProvider;public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,QueryMethodEvaluationContextProvider evaluationContextProvider, QueryRewriterProvider queryRewriterProvider) {super(em, queryMethodFactory, queryRewriterProvider);this.evaluationContextProvider = evaluationContextProvider;}@Overrideprotected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter queryRewriter, EntityManager em,NamedQueries namedQueries) {if (method.isProcedureQuery()) {return JpaQueryFactory.INSTANCE.fromProcedureAnnotation(method, em);}// 如果@Query设置了value属性值,即有数据库执行语句if (StringUtils.hasText(method.getAnnotatedQuery())) {if (method.hasAnnotatedQueryName()) {LOG.warn(String.format("Query method %s is annotated with both, a query and a query name; Using the declared query", method));}// 如果方法的语句为native原生SQL,则创建NativeJpaQuery,否则创建SimpleJpaQuery// method.getRequiredAnnotatedQuery():获取@Query注解的value属性值,即查询语句return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, method.getRequiredAnnotatedQuery(),getCountQuery(method, namedQueries, em), queryRewriter, evaluationContextProvider);}// 获取@Query设置的name属性值String name = method.getNamedQueryName();// 包含在nameQueries中,如果方法的语句为native原生SQL,则创建NativeJpaQuery,否则创建SimpleJpaQueryif (namedQueries.hasQuery(name)) {return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),getCountQuery(method, namedQueries, em), queryRewriter, evaluationContextProvider);}RepositoryQuery query = NamedQuery.lookupFrom(method, em);return query != null //? query //: NO_QUERY;}@Nullableprivate String getCountQuery(JpaQueryMethod method, NamedQueries namedQueries, EntityManager em) {// 如果方法的@Query添加了countQuery属性值,返回属性值if (StringUtils.hasText(method.getCountQuery())) {return method.getCountQuery();}// 获取queryName。因为没有添加countQuery属性,所以返回如:UserEntity.searchByName.count。searchByName为方法名称String queryName = method.getNamedCountQueryName();if (!StringUtils.hasText(queryName)) {return method.getCountQuery();}if (namedQueries.hasQuery(queryName)) {return namedQueries.getQuery(queryName);}// 是否通过@NamedQuires,尝试通过EntityManager.createNamedQuery()执行,不存在返回nullboolean namedQuery = NamedQuery.hasNamedQuery(em, queryName);if (namedQuery) {return method.getQueryExtractor().extractQueryString(em.createNamedQuery(queryName));}return null;}
}

在resolveQuery()方法中,判断方法是否添加了@Query,如果有的话,执行如下:

1)执行method.getRequiredAnnotatedQuery(),获取@Query注解的value属性值,即查询语句;

2)执行getCountQuery(method, namedQueries, em),返回count查询语句信息;

3)执行JpaQueryFactory.INSTANCE.fromMethodWithQueryString(),源码如下:

    /*** 如果方法的语句为native原生SQL,则创建NativeJpaQuery,否则创建SimpleJpaQuery*/AbstractJpaQuery fromMethodWithQueryString(JpaQueryMethod method, EntityManager em, String queryString,@Nullable String countQueryString, QueryRewriter queryRewriter,QueryMethodEvaluationContextProvider evaluationContextProvider) {if (method.isScrollQuery()) {throw QueryCreationException.create(method, "Scroll queries are not supported using String-based queries");}return method.isNativeQuery()? new NativeJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,PARSER): new SimpleJpaQuery(method, em, queryString, countQueryString, queryRewriter, evaluationContextProvider,PARSER);}

如果是原生的sql语句,则返回NativeJpaQuery对象,否则返回SimpleJpaQuery对象。

SimpleJpaQuery和NativeJpaQuery都是继承AbstractStringBasedJpaQuery抽象类,核心逻辑都在父抽象AbstractStringBasedJpaQuery中。

package org.springframework.data.jpa.repository.query;abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {private final DeclaredQuery query;private final Lazy<DeclaredQuery> countQuery;// 查询方法评估上下文提供程序。ExtensionAwareQueryMethodEvaluationContextProvider对象private final QueryMethodEvaluationContextProvider evaluationContextProvider;// Spel表达式解析器private final SpelExpressionParser parser;private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();// 查询语句重新,通过实现接口中的方法,修改sql语句private final QueryRewriter queryRewriter;private final QuerySortRewriter querySortRewriter;private final Lazy<ParameterBinder> countParameterBinder;/**** @param method 对应方法* @param em* @param queryString 方法中的@Query注解的vaue或name属性值,即查询语句*/public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,@Nullable String countQueryString, QueryRewriter queryRewriter,QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {super(method, em);Assert.hasText(queryString, "Query string must not be null or empty");Assert.notNull(evaluationContextProvider, "ExpressionEvaluationContextProvider must not be null");Assert.notNull(parser, "Parser must not be null");Assert.notNull(queryRewriter, "QueryRewriter must not be null");this.evaluationContextProvider = evaluationContextProvider;// 获取查询的DeclaredQuery对象this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,method.isNativeQuery());this.countQuery = Lazy.of(() -> {if (StringUtils.hasText(countQueryString)) {return new ExpressionBasedStringQuery(countQueryString, method.getEntityInformation(), parser,method.isNativeQuery());}return query.deriveCountQuery(method.getCountQueryProjection());});this.countParameterBinder = Lazy.of(() -> {return this.createBinder(this.countQuery.get());});this.parser = parser;this.queryRewriter = queryRewriter;// 获取方法的参数,判断是否有排序或分页,有的话,添加对应的重写器JpaParameters parameters = method.getParameters();if (parameters.hasPageableParameter() || parameters.hasSortParameter()) {this.querySortRewriter = new CachingQuerySortRewriter();} else {this.querySortRewriter = NoOpQuerySortRewriter.INSTANCE;}Assert.isTrue(method.isNativeQuery() || !query.usesJdbcStyleParameters(),"JDBC style parameters (?) are not supported for JPA queries");}// 省略其他
}

在AbstractStringBasedJpaQuery构造方法中,解析对应的queryString和countQueryString,生成DeclaredQuery对象,实际对象为ExpressionBasedStringQuery。

ExpressionBasedStringQuery

ExpressionBasedStringQuery的代码如下:

package org.springframework.data.jpa.repository.query;class ExpressionBasedStringQuery extends StringQuery {private static final String EXPRESSION_PARAMETER = "$1#{";private static final String QUOTED_EXPRESSION_PARAMETER = "$1__HASH__{";// select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}// select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%// select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}private static final Pattern EXPRESSION_PARAMETER_QUOTING = Pattern.compile("([:?])#\\{");private static final Pattern EXPRESSION_PARAMETER_UNQUOTING = Pattern.compile("([:?])__HASH__\\{");private static final String ENTITY_NAME = "entityName";private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME;// #{#entityName}private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE;/**** @param query* @param metadata DefaultJpaEntityMetadata对象,即Repository<T, ID>中的T的元信息*/public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,boolean nativeQuery) {super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));}/*** 如果表达式或返回查询则呈现查询* @param query 查询语句* @param metadata 实体类元数据* @param parser spel表达式解析器* @return*/private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata<?> metadata,SpelExpressionParser parser) {Assert.notNull(query, "query must not be null");Assert.notNull(metadata, "metadata must not be null");Assert.notNull(parser, "parser must not be null");// 判断是否包含entityName的表达式,没有直接返回。格式为#{#entityName}if (!containsExpression(query)) {return query;}// 如果有entityName的表达式,则需要替换为对应的实体类名称StandardEvaluationContext evalContext = new StandardEvaluationContext();evalContext.setVariable(ENTITY_NAME, metadata.getEntityName());// 引用表达式参数,将?、:、[等替换为"$1__HASH__{"query = potentiallyQuoteExpressionsParameter(query);Expression expr = parser.parseExpression(query, ParserContext.TEMPLATE_EXPRESSION);// #{#entityName}替换为对应的实体类名String result = expr.getValue(evalContext, String.class);if (result == null) {return query;}// 取消引用参数表达式,将"$1__HASH__{"统一替换为$1#{return potentiallyUnquoteParameterExpressions(result);}/*** 取消引用参数表达式。将"$1__HASH__{"统一替换为$1#{*/private static String potentiallyUnquoteParameterExpressions(String result) {return EXPRESSION_PARAMETER_UNQUOTING.matcher(result).replaceAll(EXPRESSION_PARAMETER);}/*** 引用表达式参数替换。将?、:、[等替换为"$1__HASH__{"*/private static String potentiallyQuoteExpressionsParameter(String query) {return EXPRESSION_PARAMETER_QUOTING.matcher(query).replaceAll(QUOTED_EXPRESSION_PARAMETER);}/*** 判断是否包含#{#entityName}字符串*/private static boolean containsExpression(String query) {return query.contains(ENTITY_NAME_VARIABLE_EXPRESSION);}
}

在构造方法中,调用renderQueryIfExpressionOrReturnQuery()方法中,如果存在使用entityName代替具体的实体类,则将entityName替换为具体的实体类。然后执行父类StringQuery的构造方法。

StringQuery

StringQuery是JPA查询字符串的封装。提供对作为绑定的参数的访问。代码如下:

package org.springframework.data.jpa.repository.query;/*** JPA查询字符串的封装。提供对作为绑定的参数的访问。* 在ParameterBinding.prepare(Object)方法中,负责对语句中的装饰参数(如%:lastname%)进行清除。* 请注意,这个类还处理用合成绑定参数替换SpEL表达式。*/
class StringQuery implements DeclaredQuery {private final String query;private final List<ParameterBinding> bindings;private final @Nullable String alias;private final boolean hasConstructorExpression;private final boolean containsPageableInSpel;private final boolean usesJdbcStyleParameters;private final boolean isNative;// 查询增强器private final QueryEnhancer queryEnhancer;/*** @param query 查询语句* @param isNative 是否是原生sql*/StringQuery(String query, boolean isNative) {Assert.hasText(query, "Query must not be null or empty");this.isNative = isNative;this.bindings = new ArrayList<>();this.containsPageableInSpel = query.contains("#pageable");Metadata queryMeta = new Metadata();this.query = ParameterBindingParser.INSTANCE.parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(query,this.bindings, queryMeta);this.usesJdbcStyleParameters = queryMeta.usesJdbcStyleParameters;this.queryEnhancer = QueryEnhancerFactory.forQuery(this);this.alias = this.queryEnhancer.detectAlias();this.hasConstructorExpression = this.queryEnhancer.hasConstructorExpression();}boolean hasParameterBindings() {return !bindings.isEmpty();}String getProjection() {return this.queryEnhancer.getProjection();}@Overridepublic List<ParameterBinding> getParameterBindings() {return bindings;}/*** 派生计数查询*/@Overridepublic DeclaredQuery deriveCountQuery(@Nullable String countQueryProjection) {StringQuery stringQuery = new StringQuery(this.queryEnhancer.createCountQueryFor(countQueryProjection), //this.isNative);if (this.hasParameterBindings() && !this.getParameterBindings().equals(stringQuery.getParameterBindings())) {stringQuery.getParameterBindings().clear();stringQuery.getParameterBindings().addAll(this.bindings);}return stringQuery;}@Overridepublic boolean usesJdbcStyleParameters() {return usesJdbcStyleParameters;}@Overridepublic String getQueryString() {return query;}@Override@Nullablepublic String getAlias() {return alias;}@Overridepublic boolean hasConstructorExpression() {return hasConstructorExpression;}@Overridepublic boolean isDefaultProjection() {return getProjection().equalsIgnoreCase(alias);}@Overridepublic boolean hasNamedParameter() {return bindings.stream().anyMatch(b -> b.getIdentifier().hasName());}@Overridepublic boolean usesPaging() {return containsPageableInSpel;}@Overridepublic boolean isNativeQuery() {return isNative;}/*** 从给定查询字符串中提取参数绑定的解析器*/enum ParameterBindingParser {INSTANCE;private static final String EXPRESSION_PARAMETER_PREFIX = "__$synthetic$__";public static final String POSITIONAL_OR_INDEXED_PARAMETER = "\\?(\\d*+(?![#\\w]))";// .....................................................................^ not followed by a hash or a letter.// .................................................................^ zero or more digits.// .............................................................^ start with a question mark.// \?(\d*+(?![#\w]))private static final Pattern PARAMETER_BINDING_BY_INDEX = Pattern.compile(POSITIONAL_OR_INDEXED_PARAMETER);// (like |in )?(?: )?\(?(%?(\?(\d*+(?![#\w])))%?|%?((?<![:\\]):([._$[\P{Z}&&\P{Cc}&&\P{Cf}&&\P{Punct}]]+))%?)\)?private static final Pattern PARAMETER_BINDING_PATTERN;private static final Pattern JDBC_STYLE_PARAM = Pattern.compile("(?!\\\\)\\?(?!\\d)"); // no \ and [no digit]private static final Pattern NUMBERED_STYLE_PARAM = Pattern.compile("(?!\\\\)\\?\\d"); // no \ and [digit]private static final Pattern NAMED_STYLE_PARAM = Pattern.compile("(?!\\\\):\\w+"); // no \ and :[text]private static final String MESSAGE = "Already found parameter binding with same index / parameter name but differing binding type; "+ "Already have: %s, found %s; If you bind a parameter multiple times make sure they use the same binding";private static final int INDEXED_PARAMETER_GROUP = 4;private static final int NAMED_PARAMETER_GROUP = 6;private static final int COMPARISION_TYPE_GROUP = 1;static {List<String> keywords = new ArrayList<>();for (ParameterBindingType type : ParameterBindingType.values()) {if (type.getKeyword() != null) {keywords.add(type.getKeyword());}}StringBuilder builder = new StringBuilder();builder.append("(");builder.append(StringUtils.collectionToDelimitedString(keywords, "|")); // keywordsbuilder.append(")?");builder.append("(?: )?"); // some whitespacebuilder.append("\\(?"); // optional braces around parametersbuilder.append("(");builder.append("%?(" + POSITIONAL_OR_INDEXED_PARAMETER + ")%?"); // position parameter and parameter indexbuilder.append("|"); // or// named parameter and the parameter namebuilder.append("%?(" + QueryUtils.COLON_NO_DOUBLE_COLON + QueryUtils.IDENTIFIER_GROUP + ")%?");builder.append(")");builder.append("\\)?"); // optional braces around parametersPARAMETER_BINDING_PATTERN = Pattern.compile(builder.toString(), CASE_INSENSITIVE);}/*** 将查询的参数绑定解析为绑定并返回已清理的查询* @param query* @param bindings* @param queryMeta* @return*/private String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String query,List<ParameterBinding> bindings, Metadata queryMeta) {// 查找查询语句中的条件参数,如?1、?2等。返回最大的下标值,如此例中的2int greatestParameterIndex = tryFindGreatestParameterIndexIn(query);boolean parametersShouldBeAccessedByIndex = greatestParameterIndex != -1;/** Prefer indexed access over named parameters if only SpEL Expression parameters are present.*/// 如果仅存在SpEL表达式参数,则首选索引访问而非命名参数if (!parametersShouldBeAccessedByIndex && query.contains("?#{")) {parametersShouldBeAccessedByIndex = true;greatestParameterIndex = 0;}// 创建Spel提取器SpelExtractor spelExtractor = createSpelExtractor(query, parametersShouldBeAccessedByIndex,greatestParameterIndex);String resultingQuery = spelExtractor.getQueryString();Matcher matcher = PARAMETER_BINDING_PATTERN.matcher(resultingQuery);int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0;int syntheticParameterIndex = expressionParameterIndex + spelExtractor.size();ParameterBindings parameterBindings = new ParameterBindings(bindings, it -> checkAndRegister(it, bindings),syntheticParameterIndex);int currentIndex = 0;boolean usesJpaStyleParameters = false;// 查找查询关键信息,如like、in、?等while (matcher.find()) {if (spelExtractor.isQuoted(matcher.start())) {continue;}// 参数下标String parameterIndexString = matcher.group(INDEXED_PARAMETER_GROUP);String parameterName = parameterIndexString != null ? null : matcher.group(NAMED_PARAMETER_GROUP);// 下标值Integer parameterIndex = getParameterIndex(parameterIndexString);String match = matcher.group(0);if (JDBC_STYLE_PARAM.matcher(match).find()) {queryMeta.usesJdbcStyleParameters = true;}if (NUMBERED_STYLE_PARAM.matcher(match).find() || NAMED_STYLE_PARAM.matcher(match).find()) {usesJpaStyleParameters = true;}if (usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) {throw new IllegalArgumentException("Mixing of ? parameters and other forms like ?1 is not supported");}String typeSource = matcher.group(COMPARISION_TYPE_GROUP);Assert.isTrue(parameterIndexString != null || parameterName != null,() -> String.format("We need either a name or an index; Offending query string: %s", query));String expression = spelExtractor.getParameter(parameterName == null ? parameterIndexString : parameterName);String replacement = null;expressionParameterIndex++;if ("".equals(parameterIndexString)) {parameterIndex = expressionParameterIndex;}// 绑定标识符。通过名称、位置或两者来标识绑定参数。BindingIdentifier queryParameter;if (parameterIndex != null) {queryParameter = BindingIdentifier.of(parameterIndex);} else {queryParameter = BindingIdentifier.of(parameterName);}// 值类型层次结构,用于描述绑定参数的来源,方法调用或表达式ParameterOrigin origin = ObjectUtils.isEmpty(expression)? ParameterOrigin.ofParameter(parameterName, parameterIndex): ParameterOrigin.ofExpression(expression);BindingIdentifier targetBinding = queryParameter;Function<BindingIdentifier, ParameterBinding> bindingFactory;// 解析参数绑定switch (ParameterBindingType.of(typeSource)) {case LIKE:Type likeType = LikeParameterBinding.getLikeTypeFrom(matcher.group(2));bindingFactory = (identifier) -> new LikeParameterBinding(identifier, origin, likeType);break;case IN:bindingFactory = (identifier) -> new InParameterBinding(identifier, origin);break;case AS_IS: // fall-through we don't need a special parameter queryParameter for the given parameter.default:bindingFactory = (identifier) -> new ParameterBinding(identifier, origin);}// 添加到parameterBindingsif (origin.isExpression()) {// 如果是表达式的参数,则直接添加parameterBindings.register(bindingFactory.apply(queryParameter));} else {// 如果是方法参数,使用MultiValueMap做一次缓存再加入parameterBindingstargetBinding = parameterBindings.register(queryParameter, origin, bindingFactory);}replacement = targetBinding.hasName() ? ":" + targetBinding.getName(): ((!usesJpaStyleParameters && queryMeta.usesJdbcStyleParameters) ? "?": "?" + targetBinding.getPosition());String result;String substring = matcher.group(2);int index = resultingQuery.indexOf(substring, currentIndex);if (index < 0) {result = resultingQuery;} else {currentIndex = index + replacement.length();result = resultingQuery.substring(0, index) + replacement+ resultingQuery.substring(index + substring.length());}resultingQuery = result;}return resultingQuery;}private static SpelExtractor createSpelExtractor(String queryWithSpel, boolean parametersShouldBeAccessedByIndex,int greatestParameterIndex) {// 如果参数需要由索引绑定,从发现的最大索引参数的位置开始绑定合成表达式参数,以免与实际参数索引混淆。int expressionParameterIndex = parametersShouldBeAccessedByIndex ? greatestParameterIndex : 0;BiFunction<Integer, String, String> indexToParameterName = parametersShouldBeAccessedByIndex? (index, expression) -> String.valueOf(index + expressionParameterIndex + 1): (index, expression) -> EXPRESSION_PARAMETER_PREFIX + (index + 1);// 获取参数的前缀。有两种参数格式:name = ?1或name = :nameString fixedPrefix = parametersShouldBeAccessedByIndex ? "?" : ":";BiFunction<String, String, String> parameterNameToReplacement = (prefix, name) -> fixedPrefix + name;return SpelQueryContext.of(indexToParameterName, parameterNameToReplacement).parse(queryWithSpel);}@Nullableprivate static Integer getParameterIndex(@Nullable String parameterIndexString) {if (parameterIndexString == null || parameterIndexString.isEmpty()) {return null;}return Integer.valueOf(parameterIndexString);}/*** 查找查询语句中的条件参数,如?1、?2等。返回最大的下标值,如此例中的2* @param query* @return*/private static int tryFindGreatestParameterIndexIn(String query) {// 匹配\?(\d*+(?![#\w]))Matcher parameterIndexMatcher = PARAMETER_BINDING_BY_INDEX.matcher(query);int greatestParameterIndex = -1;while (parameterIndexMatcher.find()) {// 找到对应的下标,如?1,则概值为1String parameterIndexString = parameterIndexMatcher.group(1);// 转整数Integer parameterIndex = getParameterIndex(parameterIndexString);if (parameterIndex != null) {greatestParameterIndex = Math.max(greatestParameterIndex, parameterIndex);}}return greatestParameterIndex;}/*** 先检查名称或position是否一至,是才能加入* @param binding* @param bindings*/private static void checkAndRegister(ParameterBinding binding, List<ParameterBinding> bindings) {// 有效性检查,确保bindings中的名称或位置和binding是一样的。相同的才能加在一起bindings.stream() //.filter(it -> it.bindsTo(binding)) //.forEach(it -> Assert.isTrue(it.equals(binding), String.format(MESSAGE, it, binding)));if (!bindings.contains(binding)) {bindings.add(binding);}}/*** 不同类型绑定的枚举。分为Like、In、其他*/private enum ParameterBindingType {LIKE("like "), IN("in "), AS_IS(null);private final @Nullable String keyword;ParameterBindingType(@Nullable String keyword) {this.keyword = keyword;}@Nullablepublic String getKeyword() {return keyword;}static ParameterBindingType of(String typeSource) {if (!StringUtils.hasText(typeSource)) {return AS_IS;}for (ParameterBindingType type : values()) {if (type.name().equalsIgnoreCase(typeSource.trim())) {return type;}}throw new IllegalArgumentException(String.format("Unsupported parameter binding type %s", typeSource));}}}private static class Metadata {private boolean usesJdbcStyleParameters = false;}static class ParameterBindings {private final MultiValueMap<BindingIdentifier, ParameterBinding> methodArgumentToLikeBindings = new LinkedMultiValueMap<>();private final Consumer<ParameterBinding> registration;private int syntheticParameterIndex;public ParameterBindings(List<ParameterBinding> bindings, Consumer<ParameterBinding> registration,int syntheticParameterIndex) {for (ParameterBinding binding : bindings) {this.methodArgumentToLikeBindings.put(binding.getIdentifier(), new ArrayList<>(List.of(binding)));}this.registration = registration;this.syntheticParameterIndex = syntheticParameterIndex;}public boolean isBound(BindingIdentifier identifier) {return !getBindings(identifier).isEmpty();}BindingIdentifier register(BindingIdentifier identifier, ParameterOrigin origin,Function<BindingIdentifier, ParameterBinding> bindingFactory) {Assert.isInstanceOf(MethodInvocationArgument.class, origin);// 获取方法回调参数信息中的参数信息。绑定标识符。通过名称、位置或两者来标识绑定参数BindingIdentifier methodArgument = ((MethodInvocationArgument) origin).identifier();List<ParameterBinding> bindingsForOrigin = getBindings(methodArgument);// 如果为空,则解析,并进行绑定缓存if (!isBound(identifier)) {// 获取一个ParameterBinding对象ParameterBinding binding = bindingFactory.apply(identifier);// 执行ParameterBindingParser.checkAndRegister()方法,检测并注册registration.accept(binding);// 添加到缓存bindingsForOrigin.add(binding);return binding.getIdentifier();}// 获取一个ParameterBinding对象ParameterBinding binding = bindingFactory.apply(identifier);// 判断是否已经存在for (ParameterBinding existing : bindingsForOrigin) {if (existing.isCompatibleWith(binding)) {return existing.getIdentifier();}}// 拷贝一个syntheticIdentifierBindingIdentifier syntheticIdentifier;if (identifier.hasName() && methodArgument.hasName()) {int index = 0;String newName = methodArgument.getName();while (existsBoundParameter(newName)) {index++;newName = methodArgument.getName() + "_" + index;}syntheticIdentifier = BindingIdentifier.of(newName);} else {syntheticIdentifier = BindingIdentifier.of(++syntheticParameterIndex);}ParameterBinding newBinding = bindingFactory.apply(syntheticIdentifier);// 执行ParameterBindingParser.checkAndRegister()方法,检测并注册registration.accept(newBinding);// 添加到缓存bindingsForOrigin.add(newBinding);return newBinding.getIdentifier();}private boolean existsBoundParameter(String key) {return methodArgumentToLikeBindings.values().stream().flatMap(Collection::stream).anyMatch(it -> key.equals(it.getName()));}/*** 从缓存中获取当前BindingIdentifier对应的List<ParameterBinding>,首次为空* @param identifier* @return*/private List<ParameterBinding> getBindings(BindingIdentifier identifier) {return methodArgumentToLikeBindings.computeIfAbsent(identifier, s -> new ArrayList<>());}public void register(ParameterBinding parameterBinding) {registration.accept(parameterBinding);}}
}

StringQuery的主要功能是解析Sql语句,找出其中的查询条件,封装成ParameterBinding对象。

方法调用拦截回顾

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)中分享了Repository自定义方法拦截的过程。当方法调用的时候,最终会调用方法对应的RepositoryQuery.execute()方法。对于添加@Query注解的方法,对应的是SimpleJpaQuery或NativeJpaQuery。它们的execute()方法都在父类AbstractJpaQuery中实现,最后会调用AbstractJpaQuery.createQuery()方法,而后调用doCreateQuery()方法。该方法为抽象方法,对于添加@Query注解的方法,执行SimpleJpaQuery或NativeJpaQuery的父类AbstractStringBasedJpaQuery.doCreateQuery()方法。代码如下:

abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {@Overridepublic Query doCreateQuery(JpaParametersParameterAccessor accessor) {Sort sort = accessor.getSort();// 获取加了排序后的查询字符串String sortedQueryString = querySortRewriter.getSorted(query, sort);ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);// 根据查询语句,使用EntityManager.createQuery(queryString)创建QueryQuery query = createJpaQuery(sortedQueryString, sort, accessor.getPageable(), processor.getReturnedType());// 创建一个QueryParameterSetter.QueryMetadata,加入缓存QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(sortedQueryString, query);// it is ok to reuse the binding contained in the ParameterBinder although we create a new query String because the// parameters in the query do not change.// 创建一个新的查询字符串,但可以重用ParameterBinder中包含的绑定,因为查询中的参数不会更改return parameterBinder.get().bindAndPrepare(query, metadata, accessor);}@Overrideprotected Query doCreateCountQuery(JpaParametersParameterAccessor accessor) {String queryString = countQuery.get().getQueryString();EntityManager em = getEntityManager();Query query = getQueryMethod().isNativeQuery() //? em.createNativeQuery(queryString) //: em.createQuery(queryString, Long.class);QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata(queryString, query);countParameterBinder.get().bind(metadata.withQuery(query), accessor, QueryParameterSetter.ErrorHandling.LENIENT);return query;}public DeclaredQuery getQuery() {return query;}public DeclaredQuery getCountQuery() {return countQuery.get();}protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable pageable,ReturnedType returnedType) {EntityManager em = getEntityManager();if (this.query.hasConstructorExpression() || this.query.isDefaultProjection()) {return em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable));}// 判断是否投影查询。确认是否要返回的类型为TupleClass<?> typeToRead = getTypeToRead(returnedType);return typeToRead == null //? em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable)) //: em.createQuery(potentiallyRewriteQuery(queryString, sort, pageable), typeToRead);}/*** 可能重写查询。根据queryRewriter重写查询*/protected String potentiallyRewriteQuery(String originalQuery, Sort sort, @Nullable Pageable pageable) {return pageable != null && pageable.isPaged() //? queryRewriter.rewrite(originalQuery, pageable) //: queryRewriter.rewrite(originalQuery, sort);}String applySorting(CachableQuery cachableQuery) {return QueryEnhancerFactory.forQuery(cachableQuery.getDeclaredQuery()).applySorting(cachableQuery.getSort(),cachableQuery.getAlias());}/*** Query Sort Rewriter interface.*/interface QuerySortRewriter {String getSorted(DeclaredQuery query, Sort sort);}
}

在doCreateQuery()方法中,执行如下:

1)如果有添加Sort信息,则添加排序信息;

2)调用createJpaQuery(),根据查询语句,使用EntityManager.createQuery(queryString)创建Query;

在执行createQuery()之前,会调用potentiallyRewriteQuery(),执行sql重写。

对于NativeJpaQuery,重写了createJpaQuery()方法,执行EntityManager.createNativeQuery()创建Query。

3)调用ParameterBinder.bindAndPrepare(),绑定查询的参数。

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)参数绑定在这篇博文中已介绍。

小结

限于篇幅,本篇先分享到这里。该博文可以同Repository自定义方法命名规则的执行原理两篇博文一起看。以下做一个小结:

1)Repository的代理类中,会添加QueryExecutorMethodInterceptor方法拦截器;

2)QueryExecutorMethodInterceptor方法拦截器的构造方法中,会根据查询查找策略CreateIfNotFoundQueryLookupStrategy,获得RepositoryQuery对象,解析方法。对于添加@Query注解的方法,使用的RepositoryQuery对象为SimpleJpaQuery和NativeJpaQuery;

3)SimpleJpaQuery和NativeJpaQuery都是继承AbstractStringBasedJpaQuery,在AbstractStringBasedJpaQuery构造方法中,解析对应的queryString和countQueryString,生成DeclaredQuery对象,实际对象为ExpressionBasedStringQuery。核心的解析过程在父类StringQuery中;

4)解析完方法信息,保存在父类AbstractStringBasedJpaQuery后,保存到QueryExecutorMethodInterceptor的Map<Method, RepositoryQuery> queries中;

5)当Repository的接口被调用的时候,在ReflectiveMethodInvocation.proceed()中,先执行QueryExecutorMethodInterceptor.invoke()方法;

5.1)调用doInvoke()方法,获取数据库执行后的数据;

5.1.1)调用RepositoryQueryMethodInvoker.invoke() -> RepositoryQuery.execute() -> AbstractJpaQuery.execute() -> AbstractJpaQuery.doExecute() -> JpaQueryExecution.execute() -> JpaQueryExecution.doExecute();

5.1.2)doExecute()是一个抽象方法,针对不同的数据库查询返回值信息,使用不同的实现类。所有的实现类都会先调用AbstractJpaQuery.createQuery(),获取一个Query对象;

5.1.3)在AbstractJpaQuery.createQuery()中,调用抽象方法doCreateQuery()。对于添加@Query注解Repository接口,实现类为SimpleJpaQuery或NativeJpaQuery,方法实现在父类AbstractStringBasedJpaQuery.doCreateQuery();

6.1.4)在AbstractStringBasedJpaQuery.doCreateQuery()方法中,通过EntityManager.createQuery(queryString)返回Query【如果是NativeJpaQuery,使用EntityManager.createNativeQuery(queryString)返回Query】,然后执行invokeBinding(),在Query对象中,调用query.setParameter()绑定查询条件的参数值,如果有分页,设置分页信息;

6.1.5)参数完参数,在6.1.3中设置hint等。然后执行6.1.2中的具体实现类,执行数据库查询。如SingleEntityExecution实现类,执行TypeQuery.getSingleResult(),然后单个结果;

6.2)调用resultHandler.postProcessInvocationResult(),对数据库查询后的值进行返回值类型转换;

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

这篇关于【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1019046

相关文章

Python中反转字符串的常见方法小结

《Python中反转字符串的常见方法小结》在Python中,字符串对象没有内置的反转方法,然而,在实际开发中,我们经常会遇到需要反转字符串的场景,比如处理回文字符串、文本加密等,因此,掌握如何在Pyt... 目录python中反转字符串的方法技术背景实现步骤1. 使用切片2. 使用 reversed() 函

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注

Python中将嵌套列表扁平化的多种实现方法

《Python中将嵌套列表扁平化的多种实现方法》在Python编程中,我们常常会遇到需要将嵌套列表(即列表中包含列表)转换为一个一维的扁平列表的需求,本文将给大家介绍了多种实现这一目标的方法,需要的朋... 目录python中将嵌套列表扁平化的方法技术背景实现步骤1. 使用嵌套列表推导式2. 使用itert

Java操作Word文档的全面指南

《Java操作Word文档的全面指南》在Java开发中,操作Word文档是常见的业务需求,广泛应用于合同生成、报表输出、通知发布、法律文书生成、病历模板填写等场景,本文将全面介绍Java操作Word文... 目录简介段落页头与页脚页码表格图片批注文本框目录图表简介Word编程最重要的类是org.apach

Python使用pip工具实现包自动更新的多种方法

《Python使用pip工具实现包自动更新的多种方法》本文深入探讨了使用Python的pip工具实现包自动更新的各种方法和技术,我们将从基础概念开始,逐步介绍手动更新方法、自动化脚本编写、结合CI/C... 目录1. 背景介绍1.1 目的和范围1.2 预期读者1.3 文档结构概述1.4 术语表1.4.1 核

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu

Conda与Python venv虚拟环境的区别与使用方法详解

《Conda与Pythonvenv虚拟环境的区别与使用方法详解》随着Python社区的成长,虚拟环境的概念和技术也在不断发展,:本文主要介绍Conda与Pythonvenv虚拟环境的区别与使用... 目录前言一、Conda 与 python venv 的核心区别1. Conda 的特点2. Python v

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

SpringBoot+Docker+Graylog 如何让错误自动报警

《SpringBoot+Docker+Graylog如何让错误自动报警》SpringBoot默认使用SLF4J与Logback,支持多日志级别和配置方式,可输出到控制台、文件及远程服务器,集成ELK... 目录01 Spring Boot 默认日志框架解析02 Spring Boot 日志级别详解03 Sp

java中反射Reflection的4个作用详解

《java中反射Reflection的4个作用详解》反射Reflection是Java等编程语言中的一个重要特性,它允许程序在运行时进行自我检查和对内部成员(如字段、方法、类等)的操作,本文将详细介绍... 目录作用1、在运行时判断任意一个对象所属的类作用2、在运行时构造任意一个类的对象作用3、在运行时判断