当前位置: 首页 > news >正文

【源码】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(),对数据库查询后的值进行返回值类型转换;

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

相关文章:

  • 木叶飞舞之【机器人ROS2】篇章_第三节、给turtlebot3安装realsense深度相机
  • 大语言模型应用与传统程序的不同
  • R可视化:另类的柱状图
  • 生信分析进阶3 - pysam操作bam文件统计unique reads和mapped reads高级技巧合辑
  • Windows Server安全配置
  • EXSI虚拟机新增磁盘并将空间扩充到已有分区
  • 【ANdroid】WEb服务搭建华为云
  • 贪心算法教程(个人总结版)
  • 开源模型应用落地-语音转文本-whisper模型-AIGC应用探索(二)
  • 最佳 Mac 数据恢复:恢复 Mac 上已删除的文件
  • MySQL各种锁
  • 低功耗蓝牙模块在便携式医疗设备上的应用前景
  • uniapp的tooltip功能放到表单laber
  • 2024中国军民两用智能装备与通信技术产业展览会带你走进轻元素量子材料世界
  • 【html知识】html中常用的表单元素+css格式美化
  • [iOS]Core Data浅析一 -- 启用Core Data
  • 《Java编程思想》读书笔记-对象导论
  • create-react-app做的留言板
  •  D - 粉碎叛乱F - 其他起义
  • HashMap剖析之内部结构
  • HTTP 简介
  • Netty 4.1 源代码学习:线程模型
  • NSTimer学习笔记
  • php的插入排序,通过双层for循环
  • Sequelize 中文文档 v4 - Getting started - 入门
  • Traffic-Sign Detection and Classification in the Wild 论文笔记
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • Vue 动态创建 component
  • Vue官网教程学习过程中值得记录的一些事情
  • windows下如何用phpstorm同步测试服务器
  • 给新手的新浪微博 SDK 集成教程【一】
  • 工作中总结前端开发流程--vue项目
  • 理解在java “”i=i++;”所发生的事情
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 微信开放平台全网发布【失败】的几点排查方法
  • 物联网链路协议
  • 新手搭建网站的主要流程
  • ​​​​​​​开发面试“八股文”:助力还是阻力?
  • ​学习一下,什么是预包装食品?​
  • #Datawhale AI夏令营第4期#AIGC方向 文生图 Task2
  • #鸿蒙生态创新中心#揭幕仪式在深圳湾科技生态园举行
  • (02)Hive SQL编译成MapReduce任务的过程
  • (2)从源码角度聊聊Jetpack Navigator的工作流程
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (Oracle)SQL优化基础(三):看懂执行计划顺序
  • (STM32笔记)九、RCC时钟树与时钟 第二部分
  • (STM32笔记)九、RCC时钟树与时钟 第一部分
  • (力扣题库)跳跃游戏II(c++)
  • (求助)用傲游上csdn博客时标签栏和网址栏一直显示袁萌 的头像
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (四)软件性能测试
  • (一)kafka实战——kafka源码编译启动
  • (转)setTimeout 和 setInterval 的区别
  • .gitignore