【源码】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

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain