/*
 * Copyright 2008-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.jpa.repository.support;

import static org.springframework.data.querydsl.QuerydslUtils.*;

import lombok.extern.slf4j.Slf4j;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.stream.Stream;

import javax.persistence.EntityManager;
import javax.persistence.Tuple;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
import org.springframework.data.jpa.provider.PersistenceProvider;
import org.springframework.data.jpa.provider.QueryExtractor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.query.AbstractJpaQuery;
import org.springframework.data.jpa.repository.query.EscapeCharacter;
import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
import org.springframework.data.jpa.repository.query.JpaQueryMethod;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.QueryCreationListener;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

JPA specific generic repository factory.
Author:Oliver Gierke, Mark Paluch, Christoph Strobl, Jens Schauder, Stefan Fussenegger
/** * JPA specific generic repository factory. * * @author Oliver Gierke * @author Mark Paluch * @author Christoph Strobl * @author Jens Schauder * @author Stefan Fussenegger */
public class JpaRepositoryFactory extends RepositoryFactorySupport { private final EntityManager entityManager; private final QueryExtractor extractor; private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor; private EntityPathResolver entityPathResolver; private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
Creates a new JpaRepositoryFactory.
Params:
  • entityManager – must not be null
/** * Creates a new {@link JpaRepositoryFactory}. * * @param entityManager must not be {@literal null} */
public JpaRepositoryFactory(EntityManager entityManager) { Assert.notNull(entityManager, "EntityManager must not be null!"); this.entityManager = entityManager; this.extractor = PersistenceProvider.fromEntityManager(entityManager); this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); this.entityPathResolver = SimpleEntityPathResolver.INSTANCE; addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor); addRepositoryProxyPostProcessor((factory, repositoryInformation) -> { if (hasMethodReturningStream(repositoryInformation.getRepositoryInterface())) { factory.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); } }); if (extractor.equals(PersistenceProvider.ECLIPSELINK)) { addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager)); } } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#setBeanClassLoader(java.lang.ClassLoader) */ @Override public void setBeanClassLoader(ClassLoader classLoader) { super.setBeanClassLoader(classLoader); this.crudMethodMetadataPostProcessor.setBeanClassLoader(classLoader); }
Configures the EntityPathResolver to be used. Defaults to SimpleEntityPathResolver.INSTANCE.
Params:
  • entityPathResolver – must not be null.
/** * Configures the {@link EntityPathResolver} to be used. Defaults to {@link SimpleEntityPathResolver#INSTANCE}. * * @param entityPathResolver must not be {@literal null}. */
public void setEntityPathResolver(EntityPathResolver entityPathResolver) { Assert.notNull(entityPathResolver, "EntityPathResolver must not be null!"); this.entityPathResolver = entityPathResolver; }
Configures the escape character to be used for like-expressions created for derived queries.
Params:
  • escapeCharacter – a character used for escaping in certain like expressions.
/** * Configures the escape character to be used for like-expressions created for derived queries. * * @param escapeCharacter a character used for escaping in certain like expressions. */
public void setEscapeCharacter(EscapeCharacter escapeCharacter) { this.escapeCharacter = escapeCharacter; } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata) */ @Override protected final JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information) { JpaRepositoryImplementation<?, ?> repository = getTargetRepository(information, entityManager); repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata()); repository.setEscapeCharacter(escapeCharacter); return repository; }
Callback to create a JpaRepository instance with the given EntityManager
Params:
  • information – will never be null.
  • entityManager – will never be null.
Returns:
/** * Callback to create a {@link JpaRepository} instance with the given {@link EntityManager} * * @param information will never be {@literal null}. * @param entityManager will never be {@literal null}. * @return */
protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) { JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType()); Object repository = getTargetRepositoryViaReflection(information, entityInformation, entityManager); Assert.isInstanceOf(JpaRepositoryImplementation.class, repository); return (JpaRepositoryImplementation<?, ?>) repository; } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryBaseClass(org.springframework.data.repository.core.RepositoryMetadata) */ @Override protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { return SimpleJpaRepository.class; } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getProjectionFactory(java.lang.ClassLoader, org.springframework.beans.factory.BeanFactory) */ @Override protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) { CollectionAwareProjectionFactory factory = new CollectionAwareProjectionFactory(); factory.setBeanClassLoader(classLoader); factory.setBeanFactory(beanFactory); return factory; } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getQueryLookupStrategy(org.springframework.data.repository.query.QueryLookupStrategy.Key, org.springframework.data.repository.query.EvaluationContextProvider) */ @Override protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider) { return Optional .of(JpaQueryLookupStrategy.create(entityManager, key, extractor, evaluationContextProvider, escapeCharacter)); } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class) */ @Override @SuppressWarnings("unchecked") public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) { return (JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager); } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryFragments(org.springframework.data.repository.core.RepositoryMetadata) */ @Override protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) { RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty(); boolean isQueryDslRepository = QUERY_DSL_PRESENT && QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface()); if (isQueryDslRepository) { if (metadata.isReactiveRepository()) { throw new InvalidDataAccessApiUsageException( "Cannot combine Querydsl and reactive repository support in a single interface"); } JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType()); Object querydslFragment = getTargetRepositoryViaReflection(QuerydslJpaPredicateExecutor.class, entityInformation, entityManager, entityPathResolver, crudMethodMetadataPostProcessor.getCrudMethodMetadata()); fragments = fragments.append(RepositoryFragment.implemented(querydslFragment)); } return fragments; } private static boolean hasMethodReturningStream(Class<?> repositoryClass) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(repositoryClass); for (Method method : methods) { if (Stream.class.isAssignableFrom(method.getReturnType())) { return true; } } return false; }
Query creation listener that informs EclipseLink users that they have to be extra careful when defining repository query methods using projections as we have to rely on the declaration order of the accessors in projection interfaces matching the order in columns. Alias-based mapping doesn't work with EclipseLink as it doesn't support Tuple based queries yet.
Author:Oliver Gierke
See Also:
  • https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141
Since:2.0.5
/** * Query creation listener that informs EclipseLink users that they have to be extra careful when defining repository * query methods using projections as we have to rely on the declaration order of the accessors in projection * interfaces matching the order in columns. Alias-based mapping doesn't work with EclipseLink as it doesn't support * {@link Tuple} based queries yet. * * @author Oliver Gierke * @since 2.0.5 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=289141 */
@Slf4j private static class EclipseLinkProjectionQueryCreationListener implements QueryCreationListener<AbstractJpaQuery> { private static final String ECLIPSELINK_PROJECTIONS = "Usage of Spring Data projections detected on persistence provider EclipseLink. Make sure the following query methods declare result columns in exactly the order the accessors are declared in the projecting interface or the order of parameters for DTOs:"; private final JpaMetamodel metamodel; private boolean warningLogged = false;
Params:
  • em – must not be null.
/** * Creates a new {@link EclipseLinkProjectionQueryCreationListener} for the given {@link EntityManager}. * * @param em must not be {@literal null}. */
public EclipseLinkProjectionQueryCreationListener(EntityManager em) { Assert.notNull(em, "EntityManager must not be null!"); this.metamodel = JpaMetamodel.of(em.getMetamodel()); } /* * (non-Javadoc) * @see org.springframework.data.repository.core.support.QueryCreationListener#onCreation(org.springframework.data.repository.query.RepositoryQuery) */ @Override public void onCreation(AbstractJpaQuery query) { JpaQueryMethod queryMethod = query.getQueryMethod(); ReturnedType type = queryMethod.getResultProcessor().getReturnedType(); if (type.isProjecting() && !metamodel.isJpaManaged(type.getReturnedType())) { if (!warningLogged) { log.info(ECLIPSELINK_PROJECTIONS); this.warningLogged = true; } log.info(" - {}", queryMethod); } } } }