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

相关文章

线上Java OOM问题定位与解决方案超详细解析

《线上JavaOOM问题定位与解决方案超详细解析》OOM是JVM抛出的错误,表示内存分配失败,:本文主要介绍线上JavaOOM问题定位与解决方案的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一、OOM问题核心认知1.1 OOM定义与技术定位1.2 OOM常见类型及技术特征二、OOM问题定位工具

PHP轻松处理千万行数据的方法详解

《PHP轻松处理千万行数据的方法详解》说到处理大数据集,PHP通常不是第一个想到的语言,但如果你曾经需要处理数百万行数据而不让服务器崩溃或内存耗尽,你就会知道PHP用对了工具有多强大,下面小编就... 目录问题的本质php 中的数据流处理:为什么必不可少生成器:内存高效的迭代方式流量控制:避免系统过载一次性

基于 Cursor 开发 Spring Boot 项目详细攻略

《基于Cursor开发SpringBoot项目详细攻略》Cursor是集成GPT4、Claude3.5等LLM的VSCode类AI编程工具,支持SpringBoot项目开发全流程,涵盖环境配... 目录cursor是什么?基于 Cursor 开发 Spring Boot 项目完整指南1. 环境准备2. 创建

Spring Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

springboot中使用okhttp3的小结

《springboot中使用okhttp3的小结》OkHttp3是一个JavaHTTP客户端,可以处理各种请求类型,比如GET、POST、PUT等,并且支持高效的HTTP连接池、请求和响应缓存、以及异... 在 Spring Boot 项目中使用 OkHttp3 进行 HTTP 请求是一个高效且流行的方式。

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

Java实现字节字符转bcd编码

《Java实现字节字符转bcd编码》BCD是一种将十进制数字编码为二进制的表示方式,常用于数字显示和存储,本文将介绍如何在Java中实现字节字符转BCD码的过程,需要的小伙伴可以了解下... 目录前言BCD码是什么Java实现字节转bcd编码方法补充总结前言BCD码(Binary-Coded Decima

python获取指定名字的程序的文件路径的两种方法

《python获取指定名字的程序的文件路径的两种方法》本文主要介绍了python获取指定名字的程序的文件路径的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要... 最近在做项目,需要用到给定一个程序名字就可以自动获取到这个程序在Windows系统下的绝对路径,以下