【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)
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注解的执行原理
前言
上一篇限于篇幅,只分享了Repository自定义方法命名规则的方法在QueryExecutorMethodInterceptor的构造方法中,通过查询查找策略CreateIfNotFoundQueryLookupStrategy创建一个PartTreeJpaQuery对象。该对象解析方法名称的关键字、查询属性、查询关键字,封装成PartTree。而后将Method和PartTreeJpaQuery组合存放在QueryExecutorMethodInterceptor的Map<Method, RepositoryQuery> queries中。
本篇继续往下分享Repository自定义方法命名规则的方法是如何调用执行的。
方法调用拦截
【源码】Spring Data JPA原理解析之Repository的自动注入(二)-CSDN博客
上面博文分享了Repository bean的创建。Respository的bean是一个通过ProxyFactory创建的动态代理对象,该代理对象添加了QueryExecutorMethodInterceptor拦截器。
【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码-CSDN博客
博客中介绍了动态代理拦截,当Repository中的接口被调用的时候,会执行ReflectiveMethodInvocation.proceed()的方法,在该方法中,循环遍历所有的拦截器,执行拦截器的invoke(MethodInvocation invocation)方法。
所以会执行QueryExecutorMethodInterceptor.invoke()方法。QueryExecutorMethodInterceptor的相关代码如下:
package org.springframework.data.repository.core.support;/*** 此MethodInterceptor拦截对自定义实现的方法的调用,当自定义的方法被调用时,会被该类拦截。* 此外,它还解析对finder的方法调用,并触发它们的执行。如果返回true,则可以依赖于设置自定义存储库实现实例。*/
class QueryExecutorMethodInterceptor implements MethodInterceptor {// Repository信息,为DefaultRepositoryInformation对象。获取Repository信息,getRepositoryInformation()返回一个RepositoryInformation对象。// 如子类JpaRepositoryFactory,指定baseClass为SimpleJpaRepository.classprivate final RepositoryInformation repositoryInformation;// 方法缓存,key为方法,value为对应方法的查询解析信息private final Map<Method, RepositoryQuery> queries;// 方法调用缓存,key为方法,value为对应方法调用时要执行的执行器private final Map<Method, RepositoryMethodInvoker> invocationMetadataCache = new ConcurrentReferenceHashMap<>();// 查询执行结果处理器private final QueryExecutionResultHandler resultHandler;// 在实体类中添加@NamedQueries注解,配置相关查询信息,默认为空private final NamedQueries namedQueries;private final List<QueryCreationListener<?>> queryPostProcessors;private final RepositoryInvocationMulticaster invocationMulticaster;// 省略其他@Override@Nullablepublic Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();// 通过返回的返回值,获取执行适配器,默认都为nullQueryExecutionConverters.ExecutionAdapter executionAdapter = QueryExecutionConverters //.getExecutionAdapter(method.getReturnType());if (executionAdapter == null) {return resultHandler.postProcessInvocationResult(doInvoke(invocation), method);}return executionAdapter //.apply(() -> resultHandler.postProcessInvocationResult(doInvoke(invocation), method));}@Nullableprivate Object doInvoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();// 判断方法是否存在RepositoryQuery。在构造函数中,会解析Repository中的查询方法,并缓存到Mapif (hasQueryFor(method)) {RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);if (invocationMetadata == null) {// 首次执行对应方法,先创建一个RepositoryQueryMethodInvoker对象,保存方法即方法对应的RepositoryQueryinvocationMetadata = RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method));// 加入缓存invocationMetadataCache.put(method, invocationMetadata);}// 获取方法所在的Repository类名、方法的参数值【invocation.getArguments()】,执行RepositoryQueryMethodInvoker.invoke()方法return invocationMetadata.invoke(repositoryInformation.getRepositoryInterface(), invocationMulticaster,invocation.getArguments());}// 如果能够处理该查询方法,则不执行invocation.proceed(),即结束拦截器链return invocation.proceed();}/*** 判断是否为给定方法执行查询*/private boolean hasQueryFor(Method method) {return queries.containsKey(method);}}
1.1 在QueryExecutorMethodInterceptor.invoke()中,核心功能如下:
1)执行doInvoke()方法,执行数据库相关操作,获取返回信息;
2)执行resultHandler.postProcessInvocationResult(),进行返回值类型转换;
1.2 在doInvoke()方法中,执行如下:
1)调用hasQueryFor()方法,判断当前方法是否有对应的RepositoryQuery对象。在上一篇博文中以及做了详细介绍,该对象是在QueryExecutorMethodInterceptor的构造方法中解析方法信息后封装的和查询相关的信息对象;
2)如果存在RepositoryQuery对象,则执行RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method)),创建一个RepositoryQueryMethodInvoker对象,然后执行RepositoryQueryMethodInvoker.invoke()方法;
3)如果不存在RepositoryQuery对象,则执行invocation.proceed(),执行ReflectiveMethodInvocation.proceed()方法,继续执行下一个拦截器或执行target的对应方法;
RepositoryQueryMethodInvoker
RepositoryQueryMethodInvoker查询方法回调类的核心代码如下:
abstract class RepositoryMethodInvoker {private final Method method;private final Class<?> returnedType;private final Invokable invokable;private final boolean suspendedDeclaredMethod;@SuppressWarnings("ReactiveStreamsUnusedPublisher")protected RepositoryMethodInvoker(Method method, Invokable invokable) {this.method = method;if (KotlinDetector.isKotlinReflectPresent()) {// 省略其他} else {this.suspendedDeclaredMethod = false;this.returnedType = method.getReturnType();this.invokable = invokable;}}static RepositoryQueryMethodInvoker forRepositoryQuery(Method declaredMethod, RepositoryQuery query) {return new RepositoryQueryMethodInvoker(declaredMethod, query);}@Nullablepublic Object invoke(Class<?> repositoryInterface, RepositoryInvocationMulticaster multicaster, Object[] args)throws Exception {return doInvoke(repositoryInterface, multicaster, args);}@Nullableprivate Object doInvoke(Class<?> repositoryInterface, RepositoryInvocationMulticaster multicaster, Object[] args)throws Exception {// 创建一个RepositoryMethodInvocationCaptor对象RepositoryMethodInvocationCaptor invocationResultCaptor = RepositoryMethodInvocationCaptor.captureInvocationOn(repositoryInterface);try {// 执行对应方法的RepositoryQuery的execute方法Object result = invokable.invoke(args);if (result != null && ReactiveWrappers.supports(result.getClass())) {return new ReactiveInvocationListenerDecorator().decorate(repositoryInterface, multicaster, args, result);}if (result instanceof Stream) {return ((Stream<?>) result).onClose(() -> multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success())));}// 执行结果通知。回调RepositoryMethodInvocationListener.afterInvocation()multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.success()));return result;} catch (Exception e) {multicaster.notifyListeners(method, args, computeInvocationResult(invocationResultCaptor.error(e)));throw e;}}private RepositoryMethodInvocation computeInvocationResult(RepositoryMethodInvocationCaptor captured) {return new RepositoryMethodInvocation(captured.getRepositoryInterface(), method, captured.getCapturedResult(),captured.getDuration());}interface Invokable {@NullableObject invoke(Object[] args) throws Exception;}private static class RepositoryQueryMethodInvoker extends RepositoryMethodInvoker {public RepositoryQueryMethodInvoker(Method method, RepositoryQuery repositoryQuery) {// repositoryQuery::execute方法回调声明作为参数赋值给invokable,当执行invokable.invoke()时,// 执行repositoryQuery.execute()方法super(method, repositoryQuery::execute);}}// 省略其他
}
RepositoryQueryMethodInvoker是私有静态内部类,父类为RepositoryMethodInvoker。
在上面讲解的1.2的2)中,通过RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method)),创建一个RepositoryQueryMethodInvoker对象,将repositoryQuery::execute方法回调声明作为参数赋值给invokable。
当执行RepositoryQueryMethodInvoker.invoke()时,执行doInvoke()方法,该方法执行如下:
1)创建一个RepositoryMethodInvocationCaptor对象;
2)执行invokable.invoke(),即执行对应方法的RepositoryQuery的execute方法,执行数据库操作;
RepositoryQuery.execute() -> AbstractJpaQuery.execute() -> AbstractJpaQuery.doExecute() -> JpaQueryExecution.execute() -> JpaQueryExecution.doExecute()。
3)执行结果通知。回调RepositoryMethodInvocationListener.afterInvocation();
4)返回2)中的返回值;
第2)中调用的JpaQueryExecution.doExecute()是一个抽象方法,实现类如下:
针对数据库表操作后不同的返回值信息,使用不同的实现类,且实现类都是JpaQueryExecution的内部类。以下以SingleEntityExecution为例。
public abstract class JpaQueryExecution {static class SingleEntityExecution extends JpaQueryExecution {@Overrideprotected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {return query.createQuery(accessor).getSingleResult();}}
}
执行AbstractJpaQuery.createQuery(),获取一个Query对象,最后调用Query.getSingleResult(),返回一个查询执行结果。其他的实现类处理类似,只是最后调用Query的不同方法,从数据库中查询不同的结果值。
AbstractJpaQuery
AbstractJpaQuery的相关代码如下:
/*** 记录Repository中每个方法解析后的信息*/
public abstract class AbstractJpaQuery implements RepositoryQuery {private final JpaQueryMethod method;private final EntityManager em;private final JpaMetamodel metamodel;// 根据EntityManager,返回PersistenceProvider。PersistenceProvider是枚举类型,有HIBERNATE、ECLIPSELINK、GENERIC_JPA。// 不同的PersistenceProvider,extractQueryString、getIdentifierFrom等方式不一样private final PersistenceProvider provider;// 根据查询方法的返回值,使用不同的执行器private final Lazy<JpaQueryExecution> execution;// 参数绑定器final Lazy<ParameterBinder> parameterBinder = Lazy.of(this::createBinder);@Nullable@Overridepublic Object execute(Object[] parameters) {return doExecute(getExecution(), parameters);}/*** 执行查询* @param execution 执行器。主要根据方法的返回值确定的执行器* @param values 方法执行时的参数值* @return*/@Nullableprivate Object doExecute(JpaQueryExecution execution, Object[] values) {// 创建一个JpaParametersParameterAccessor对象,保存方法的参数信息及本次查询的参数值JpaParametersParameterAccessor accessor = obtainParameterAccessor(values);// 执行数据库查询,获取返回值Object result = execution.execute(this, accessor);ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType()));}private JpaParametersParameterAccessor obtainParameterAccessor(Object[] values) {if (method.isNativeQuery() && PersistenceProvider.HIBERNATE.equals(provider)) {return new HibernateJpaParametersParameterAccessor(method.getParameters(), values, em);}return new JpaParametersParameterAccessor(method.getParameters(), values);}/*** 获取方法对应的查询执行器*/protected JpaQueryExecution getExecution() {// 获取根据返回值确定的查询的执行器JpaQueryExecution execution = this.execution.getNullable();if (execution != null) {return execution;}// 如果添加了@Modify注解,则返回if (method.isModifyingQuery()) {return new ModifyingExecution(method, em);}// 否则返回单个实体类的执行器return new SingleEntityExecution();}/*** 为query添加定义的查询hint信息。方法中添加@QueryHints注解*/protected <T extends Query> T applyHints(T query, JpaQueryMethod method) {List<QueryHint> hints = method.getHints();if (!hints.isEmpty()) {for (QueryHint hint : hints) {applyQueryHint(query, hint);}}// Apply any meta-attributes that existif (method.hasQueryMetaAttributes()) {if (provider.getCommentHintKey() != null) {query.setHint( //provider.getCommentHintKey(), provider.getCommentHintValue(method.getQueryMetaAttributes().getComment()));}}return query;}protected <T extends Query> void applyQueryHint(T query, QueryHint hint) {Assert.notNull(query, "Query must not be null");Assert.notNull(hint, "QueryHint must not be null");query.setHint(hint.name(), hint.value());}/*** 为query应用锁模式*/private Query applyLockMode(Query query, JpaQueryMethod method) {LockModeType lockModeType = method.getLockModeType();return lockModeType == null ? query : query.setLockMode(lockModeType);}protected Query createQuery(JpaParametersParameterAccessor parameters) {return applyLockMode(applyEntityGraphConfiguration(applyHints(doCreateQuery(parameters), method), method), method);}/*** 如果方法添加@EntityGraph注解,在query中添加对应的Hint* @param query* @param method* @return*/private Query applyEntityGraphConfiguration(Query query, JpaQueryMethod method) {JpaEntityGraph entityGraph = method.getEntityGraph();if (entityGraph != null) {QueryHints hints = Jpa21Utils.getFetchGraphHint(em, method.getEntityGraph(),getQueryMethod().getEntityInformation().getJavaType());hints.forEach(query::setHint);}return query;}/*** 为查询创建一个Query,并调用query.setParameter()设置参数值及分页信息*/protected abstract Query doCreateQuery(JpaParametersParameterAccessor accessor);// 省略其他
}
在createQuery()方法中,执行如下:
1)调用抽象方法doCreateQuery(),获取一个Query;
对于自定义方法命名规则的方法,实现在PartTreeJpaQuery类。
2)执行applyHints(),在query中添加对应的Hint;
3)执行applyEntityGraphConfiguration(),如果方法添加@EntityGraph注解,在query中添加对应的Hint;
4)执行applyLockMode(),为query应用锁模式;
PartTreeJpaQuery
PartTreeJpaQuery的相关代码如下:
package org.springframework.data.jpa.repository.query;/*** 保存了方法信息,包括方法、方法参数、方法名称解析后的Part树、对应的查询query、查询计数countQuery等信息*/
public class PartTreeJpaQuery extends AbstractJpaQuery {private final PartTree tree;private final JpaParameters parameters;private final QueryPreparer query;private final QueryPreparer countQuery;private final EntityManager em;private final EscapeCharacter escape;private final JpaMetamodelEntityInformation<?, Object> entityInformation;/*** 为查询创建一个Query,并调用query.setParameter()设置参数值及分页信息*/@Overridepublic Query doCreateQuery(JpaParametersParameterAccessor accessor) {return query.createQuery(accessor);}@Override@SuppressWarnings("unchecked")public TypedQuery<Long> doCreateCountQuery(JpaParametersParameterAccessor accessor) {return (TypedQuery<Long>) countQuery.createQuery(accessor);}private class QueryPreparer {// 缓存创建的对象private final @Nullable CriteriaQuery<?> cachedCriteriaQuery;private final @Nullable ParameterBinder cachedParameterBinder;private final QueryParameterSetter.QueryMetadataCache metadataCache = new QueryParameterSetter.QueryMetadataCache();QueryPreparer(boolean recreateQueries) {JpaQueryCreator creator = createCreator(null);if (recreateQueries) {this.cachedCriteriaQuery = null;this.cachedParameterBinder = null;} else {// 子类CountQueryPreparer的createQuery(),执行JpaCountQueryCreator重写的complete()方法,// 执行query.select(),select为builder.count(),并加上predicate条件信息this.cachedCriteriaQuery = creator.createQuery();this.cachedParameterBinder = getBinder(creator.getParameterExpressions());}}/*** 为查询创建一个Query,并调用query.setParameter()设置参数值及分页信息*/public Query createQuery(JpaParametersParameterAccessor accessor) {CriteriaQuery<?> criteriaQuery = cachedCriteriaQuery;ParameterBinder parameterBinder = cachedParameterBinder;if (cachedCriteriaQuery == null || accessor.hasBindableNullValue()) {JpaQueryCreator creator = createCreator(accessor);criteriaQuery = creator.createQuery(getDynamicSort(accessor));List<ParameterMetadata<?>> expressions = creator.getParameterExpressions();parameterBinder = getBinder(expressions);}if (parameterBinder == null) {throw new IllegalStateException("ParameterBinder is null");}// 通过EntityManager.createQuery(criteriaQuery),返回TypedQueryTypedQuery<?> query = createQuery(criteriaQuery);ScrollPosition scrollPosition = accessor.getParameters().hasScrollPositionParameter()? accessor.getScrollPosition(): null;// 调用invokeBinding()执行query.setParameter()方法,设置查询的条件参数值,如果有分页,设置分页信息// 如果有需要,设置返回最大值信息return restrictMaxResultsIfNecessary(invokeBinding(parameterBinder, query, accessor, this.metadataCache),scrollPosition);}@SuppressWarnings("ConstantConditions")private Query restrictMaxResultsIfNecessary(Query query, @Nullable ScrollPosition scrollPosition) {if (scrollPosition instanceof OffsetScrollPosition offset && !offset.isInitial()) {query.setFirstResult(Math.toIntExact(offset.getOffset()) + 1);}if (tree.isLimiting()) {if (query.getMaxResults() != Integer.MAX_VALUE) {if (query.getMaxResults() > tree.getMaxResults() && query.getFirstResult() > 0) {query.setFirstResult(query.getFirstResult() - (query.getMaxResults() - tree.getMaxResults()));}}query.setMaxResults(tree.getMaxResults());}if (tree.isExistsProjection()) {query.setMaxResults(1);}return query;}/*** 通过EntityManager.createQuery(criteriaQuery),返回TypedQuery*/private TypedQuery<?> createQuery(CriteriaQuery<?> criteriaQuery) {if (this.cachedCriteriaQuery != null) {synchronized (this.cachedCriteriaQuery) {return getEntityManager().createQuery(criteriaQuery);}}return getEntityManager().createQuery(criteriaQuery);}protected JpaQueryCreator createCreator(@Nullable JpaParametersParameterAccessor accessor) {EntityManager entityManager = getEntityManager();CriteriaBuilder builder = entityManager.getCriteriaBuilder();ResultProcessor processor = getQueryMethod().getResultProcessor();ParameterMetadataProvider provider;ReturnedType returnedType;if (accessor != null) {provider = new ParameterMetadataProvider(builder, accessor, escape);returnedType = processor.withDynamicProjection(accessor).getReturnedType();} else {provider = new ParameterMetadataProvider(builder, parameters, escape);returnedType = processor.getReturnedType();}if (accessor != null && accessor.getScrollPosition() instanceof KeysetScrollPosition keyset) {return new JpaKeysetScrollQueryCreator(tree, returnedType, builder, provider, entityInformation, keyset);}return new JpaQueryCreator(tree, returnedType, builder, provider);}/*** 调用query.setParameter()方法,设置查询的条件参数值,如果有分页,设置分页信息*/protected Query invokeBinding(ParameterBinder binder, TypedQuery<?> query, JpaParametersParameterAccessor accessor,QueryParameterSetter.QueryMetadataCache metadataCache) {// 将query查询添加到缓存QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata("query", query);return binder.bindAndPrepare(query, metadata, accessor);}private ParameterBinder getBinder(List<ParameterMetadata<?>> expressions) {return ParameterBinderFactory.createCriteriaBinder(parameters, expressions);}private Sort getDynamicSort(JpaParametersParameterAccessor accessor) {return parameters.potentiallySortsDynamically() //? accessor.getSort() //: Sort.unsorted();}}private class CountQueryPreparer extends QueryPreparer {CountQueryPreparer(boolean recreateQueries) {super(recreateQueries);}/*** 创建一个JpaCountQueryCreator*/@Overrideprotected JpaQueryCreator createCreator(@Nullable JpaParametersParameterAccessor accessor) {EntityManager entityManager = getEntityManager();CriteriaBuilder builder = entityManager.getCriteriaBuilder();ParameterMetadataProvider provider;if (accessor != null) {provider = new ParameterMetadataProvider(builder, accessor, escape);} else {provider = new ParameterMetadataProvider(builder, parameters, escape);}return new JpaCountQueryCreator(tree, getQueryMethod().getResultProcessor().getReturnedType(), builder, provider);}@Overrideprotected Query invokeBinding(ParameterBinder binder, TypedQuery<?> query, JpaParametersParameterAccessor accessor,QueryParameterSetter.QueryMetadataCache metadataCache) {QueryParameterSetter.QueryMetadata metadata = metadataCache.getMetadata("countquery", query);return binder.bind(query, metadata, accessor);}}
}
1)在PartTreeJpaQuery.doCreateQuery()方法,执行QueryPreparer.createQuery()方法。
2)QueryPreparer.createQuery()方法先调用createQuery(),执行如下:
2.1)通过EntityManager.createQuery(criteriaQuery),返回TypedQuery;
2.2)执行invokeBinding(),在TypedQuery对象中,调用query.setParameter()绑定查询条件的参数值,如果有分页,设置分页信息;
通过执行ParameterBinder.bindAndPrepare()方法,调用query.setParameter()绑定查询条件的参数值,如果有分页,设置分页信息。
2.3)执行restrictMaxResultsIfNecessary(),如果有需要,设置返回最大值信息;
ParameterBinder
ParameterBinder的代码如下:
package org.springframework.data.jpa.repository.query;/*** ParameterBinder用于将方法参数绑定到Query。通常在执行AbstractJpaQuery时执行。*/
public class ParameterBinder {static final String PARAMETER_NEEDS_TO_BE_NAMED = "For queries with named parameters you need to provide names for method parameters; Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters";private final JpaParameters parameters;// 查询方法对应的参数设置器private final Iterable<QueryParameterSetter> parameterSetters;private final boolean useJpaForPaging;ParameterBinder(JpaParameters parameters, Iterable<QueryParameterSetter> parameterSetters) {this(parameters, parameterSetters, true);}public ParameterBinder(JpaParameters parameters, Iterable<QueryParameterSetter> parameterSetters,boolean useJpaForPaging) {Assert.notNull(parameters, "JpaParameters must not be null");Assert.notNull(parameterSetters, "Parameter setters must not be null");this.parameters = parameters;this.parameterSetters = parameterSetters;this.useJpaForPaging = useJpaForPaging;}public <T extends Query> T bind(T jpaQuery, QueryParameterSetter.QueryMetadata metadata,JpaParametersParameterAccessor accessor) {// 绑定参数值bind(metadata.withQuery(jpaQuery), accessor, ErrorHandling.STRICT);return jpaQuery;}public void bind(QueryParameterSetter.BindableQuery query, JpaParametersParameterAccessor accessor,ErrorHandling errorHandling) {// 遍历方法的参数设置器,调用QueryParameterSetter.setParameter() -> query.setParameter()为查询语句赋值for (QueryParameterSetter setter : parameterSetters) {setter.setParameter(query, accessor, errorHandling);}}Query bindAndPrepare(Query query, QueryParameterSetter.QueryMetadata metadata,JpaParametersParameterAccessor accessor) {// 绑定参数。调用query.setParameter(),为查询赋值bind(query, metadata, accessor);// 如果没有分页,直接返回if (!useJpaForPaging || !parameters.hasLimitingParameters() || accessor.getPageable().isUnpaged()) {return query;}// 设置分页信息query.setFirstResult(PageableUtils.getOffsetAsInteger(accessor.getPageable()));query.setMaxResults(accessor.getPageable().getPageSize());return query;}
}
小结
限于篇幅,本篇先分享到这里。结合上一篇【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)一起做一个小结:
1)Repository的代理类中,会添加QueryExecutorMethodInterceptor方法拦截器;
2)QueryExecutorMethodInterceptor方法拦截器的构造方法中,会根据查询查找策略CreateIfNotFoundQueryLookupStrategy,获得RepositoryQuery对象,解析方法。对于按方法命名规则实现的方法,使用的RepositoryQuery对象为PartTreeJpaQuery;
3)在PartTreeJpaQuery构造方法中,创建一个PartTree对象,解析方法名称中的起始关键字【如:findBy、readBy、deleteBy等】、条件属性【实体类中的属性】、查询关键字【Between、In、Equals等】;
4)创建对应方法的countQuery和query,将解析出的查询的基础信息封装在QueryPreparer对象中,根据解析出的查询信息,创建CriteriaQuery对象;
5)解析完方法信息,保存在PartTreeJpaQuery后,保存到QueryExecutorMethodInterceptor的Map<Method, RepositoryQuery> queries中;
6)当Repository的接口被调用的时候,在ReflectiveMethodInvocation.proceed()中,先执行QueryExecutorMethodInterceptor.invoke()方法;
6.1)调用doInvoke()方法,获取数据库执行后的数据;
6.1.1)调用RepositoryQueryMethodInvoker.invoke() -> RepositoryQuery.execute() -> AbstractJpaQuery.execute() -> AbstractJpaQuery.doExecute() -> JpaQueryExecution.execute() -> JpaQueryExecution.doExecute();
6.1.2)doExecute()是一个抽象方法,针对不同的数据库查询返回值信息,使用不同的实现类。所有的实现类都会先调用AbstractJpaQuery.createQuery(),获取一个Query对象;
6.1.3)在AbstractJpaQuery.createQuery()中,调用抽象方法doCreateQuery()。对于按方法命名规则的Repository接口,实现类为PartTreeJpaQuery;
6.1.4)在PartTreeJpaQuery.coCreateQuery()方法中,通过EntityManager.createQuery(criteriaQuery)返回TypedQuery,然后执行invokeBinding(),在TypedQuery对象中,调用query.setParameter()绑定查询条件的参数值,如果有分页,设置分页信息;
6.1.5)参数完参数,在6.1.3中设置hint等。然后执行6.1.2中的具体实现类,执行数据库查询。如SingleEntityExecution实现类,执行TypeQuery.getSingleResult(),然后单个结果;
6.2)调用resultHandler.postProcessInvocationResult(),对数据库查询后的值进行返回值类型转换;
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。
相关文章:

【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)
Spring Data JPA系列 1、SpringBoot集成JPA及基本使用 2、Spring Data JPA Criteria查询、部分字段查询 3、Spring Data JPA数据批量插入、批量更新真的用对了吗 4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作 5、Spring Data JPA自定…...
Vue前端中从后端获取图片验证码
前端发送请求 <template><el-form :model"user" :rules"rules" ref"userForm" class"login" label-width"auto" style"max-width: 600px"><el-form-item label"用户名" prop"name…...

【源码】多语言H5聊天室/thinkphp多国语言即时通讯/H5聊天室源码/在线聊天/全开源
多语言聊天室系统,可当即时通讯用,系统默认无需注册即可进入群聊天,全开源 【海外聊天室】多语言H5聊天室/thinkphp多国语言即时通讯/H5聊天室源码/在线聊天/全开源 - 吾爱资源网...

gitlab 创建 ssh 和 token
文章目录 一、创建ssh key二、将密钥内容复制到gitlab三、创建token 一、创建ssh key 打开控制台cmd,执行命令 ssh-keygen -t rsa -C xxxxx xxxxx是你自己的邮箱 C:\Users\xx\.ssh 目录下会创建一个名为id_rsa.pub的文件,用记事本打开,并…...
Docker - Kafka
博文目录 文章目录 说明命令 说明 Docker Hub - bitnami/kafka Docker Hub - apache/kafka Kafka QuickStart Kafka 目前没有 Docker 官方镜像, 目前拉取次数最多的是 bitnami/kafka, Apache 提供的是 apache/kafka (更新最及时), 本文使用 bitnami/kafka bitnami/kafka 镜像…...

一键实现文件夹批量高效重命名:轻松运用随机一个字母命名,让文件管理焕然一新!
在数字化时代,文件夹管理是我们日常生活和工作中不可或缺的一部分。然而,随着文件数量的不断增加,文件夹命名的繁琐和重复成为了一个让人头疼的问题。你是否曾因为手动一个个重命名文件夹而感到枯燥乏味?你是否曾渴望有一种方法能…...

Vue3项目练习详细步骤(第二部分:主页面搭建)
主页面搭建 页面主体结构 路由 子路由 主页面搭建 页面主体结构 在vuews目录下新建Layout.vue文件 主页面内容主体代码 <script setup> import {Management,Promotion,UserFilled,User,Crop,EditPen,SwitchButton,CaretBottom } from element-plus/icons-vue imp…...

[个人总结]-java常用方法
1.获取项目根路径 user.dir是一个系统属性,表示用户当前的工作目录,大多数情况下,用户的当前工作目录就是java项目的根目录(src文件的同级路径) System.getProperty("user.dir") 结果:D:\code…...
什么是Java泛型?它有什么作用
Java泛型(Generics)是一种允许在定义类、接口和方法时使用类型参数的机制。泛型提供了一种机制,使得代码可以对多种类型的对象进行操作,而无需进行类型转换。 Java泛型的作用 类型安全:通过在编译时进行类型检查&…...
[机缘参悟-197] - 《道家-水木然人间清醒1》读书笔记 -21-看问题从现象到本质的层次
目录 1. 现象层: 2. 关联层: 3. 原因层: 4. 本质层: 5. 解决方案层: 6. 设计实现层: 7. 泛化: 8. 创新与发现: 看问题从现象到本质的层次是一个逐步深入、由表及里的过程。这…...

AIGC商业案例实操课,发觉其创造和商业的无限可能,Ai技术在行业应用新的商机
课程下载:https://download.csdn.net/download/m0_66047725/89307523 更多资源下载:关注我。 课程内容 1 AI为什么火 。写在课程前面的寄语 。AIGC标志性事件:太空歌剧院 。AI人工智能为什么这么火 ,AI人工智能发展历程 。聊天AI会取…...
Java学习路径图
1.学习路径 JAVA架构师学习路径 2.路径拆解 2.1 Spring 2.1.1 SpringBoot原理 SpringBoot2学习视频 SpringBoot2笔记 SpringBoo2代码 2.2.2 SpringBoot项目 《谷粒商城》学习视频...
文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑动态定价的新能源汽车能源站优化运行》
本专栏栏目提供文章与程序复现思路,具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…...

【简单讲解下Fine-tuning BERT,什么是Fine-tuning BERT?】
🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…...

Docker搭建Redis主从 + Redis哨兵模式(一主一从俩哨兵)
我这里是搭建一主一从,俩哨兵,准备两台服务器,分别安装docker 我这里有两台centos服务器 主服务器IP:192.168.252.134 从服务器IP:192.168.252.135 1.两台服务器分别拉取redis镜像 docker pull redis 2.查看镜像 d…...

Three.js——tween动画、光线投射拾取、加载.obj/.mtl外部文件、使用相机控制器
个人简介 👀个人主页: 前端杂货铺 ⚡开源项目: rich-vue3 (基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL) 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 …...

内网渗透-在HTTP协议层面绕过WAF
进入正题,随着安全意思增强,各企业对自己的网站也更加注重安全性。但很多web应用因为老旧,或贪图方便想以最小代价保证应用安全,就只仅仅给服务器安装waf。 本次从协议层面绕过waf实验用sql注入演示,但不限于实际应用…...
qt QGroupBox radiobutton
QGroupBox 显示文本:属性 title 加载radiobutton if (jsonObject.contains("startEndTogether") && jsonObject["startEndTogether"].isString()) {QString selectedButton jsonObject["startEndTogether"].toString();//…...
jetson nano onnxruntime 安装
安装说明: onnxruntime 依赖cuda、cudnn版本,可onnxruntime查找对应关系。但可能会出现jetpack中的cuda和cudnn的版本无法查找到对应版本的onnxruntime的问题。 解决方法: 通过Jetson Zoo下载相应的whl包直接安装。...

图形学初识--屏幕空间变换
文章目录 前言正文为什么需要屏幕空间变换?什么是屏幕空间变换?屏幕空间变换矩阵如何推导?问题描述步骤描述 结尾:喜欢的小伙伴点点关注赞哦! 前言 前面章节主要讲解了视图变换和投影变换,此时距离在屏幕空间显示也就…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...