1
16 package org.springframework.data.jpa.repository.support;
17
18 import static org.springframework.data.querydsl.QuerydslUtils.*;
19
20 import lombok.extern.slf4j.Slf4j;
21
22 import java.io.Serializable;
23 import java.lang.reflect.Method;
24 import java.util.Optional;
25 import java.util.stream.Stream;
26
27 import javax.persistence.EntityManager;
28 import javax.persistence.Tuple;
29
30 import org.springframework.beans.factory.BeanFactory;
31 import org.springframework.dao.InvalidDataAccessApiUsageException;
32 import org.springframework.data.jpa.projection.CollectionAwareProjectionFactory;
33 import org.springframework.data.jpa.provider.PersistenceProvider;
34 import org.springframework.data.jpa.provider.QueryExtractor;
35 import org.springframework.data.jpa.repository.JpaRepository;
36 import org.springframework.data.jpa.repository.query.AbstractJpaQuery;
37 import org.springframework.data.jpa.repository.query.DefaultJpaQueryMethodFactory;
38 import org.springframework.data.jpa.repository.query.EscapeCharacter;
39 import org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy;
40 import org.springframework.data.jpa.repository.query.JpaQueryMethod;
41 import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory;
42 import org.springframework.data.jpa.util.JpaMetamodel;
43 import org.springframework.data.projection.ProjectionFactory;
44 import org.springframework.data.querydsl.EntityPathResolver;
45 import org.springframework.data.querydsl.QuerydslPredicateExecutor;
46 import org.springframework.data.querydsl.SimpleEntityPathResolver;
47 import org.springframework.data.repository.core.RepositoryInformation;
48 import org.springframework.data.repository.core.RepositoryMetadata;
49 import org.springframework.data.repository.core.support.QueryCreationListener;
50 import org.springframework.data.repository.core.support.RepositoryComposition;
51 import org.springframework.data.repository.core.support.RepositoryFactorySupport;
52 import org.springframework.data.repository.core.support.RepositoryFragment;
53 import org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor;
54 import org.springframework.data.repository.query.QueryLookupStrategy;
55 import org.springframework.data.repository.query.QueryLookupStrategy.Key;
56 import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
57 import org.springframework.data.repository.query.ReturnedType;
58 import org.springframework.lang.Nullable;
59 import org.springframework.util.Assert;
60 import org.springframework.util.ReflectionUtils;
61
62
72 public class JpaRepositoryFactory extends RepositoryFactorySupport {
73
74 private final EntityManager entityManager;
75 private final QueryExtractor extractor;
76 private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor;
77
78 private EntityPathResolver entityPathResolver;
79 private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT;
80 private JpaQueryMethodFactory queryMethodFactory;
81
82
87 public JpaRepositoryFactory(EntityManager entityManager) {
88
89 Assert.notNull(entityManager, "EntityManager must not be null!");
90
91 this.entityManager = entityManager;
92 this.extractor = PersistenceProvider.fromEntityManager(entityManager);
93 this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
94 this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
95 this.queryMethodFactory = new DefaultJpaQueryMethodFactory(extractor);
96
97 addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);
98 addRepositoryProxyPostProcessor((factory, repositoryInformation) -> {
99
100 if (hasMethodReturningStream(repositoryInformation.getRepositoryInterface())) {
101 factory.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
102 }
103 });
104
105 if (extractor.equals(PersistenceProvider.ECLIPSELINK)) {
106 addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager));
107 }
108 }
109
110
114 @Override
115 public void setBeanClassLoader(ClassLoader classLoader) {
116
117 super.setBeanClassLoader(classLoader);
118 this.crudMethodMetadataPostProcessor.setBeanClassLoader(classLoader);
119 }
120
121
126 public void setEntityPathResolver(EntityPathResolver entityPathResolver) {
127
128 Assert.notNull(entityPathResolver, "EntityPathResolver must not be null!");
129
130 this.entityPathResolver = entityPathResolver;
131 }
132
133
138 public void setEscapeCharacter(EscapeCharacter escapeCharacter) {
139 this.escapeCharacter = escapeCharacter;
140 }
141
142
148 public void setQueryMethodFactory(JpaQueryMethodFactory queryMethodFactory) {
149
150 Assert.notNull(queryMethodFactory, "QueryMethodFactory must not be null!");
151
152 this.queryMethodFactory = queryMethodFactory;
153 }
154
155
159 @Override
160 protected final JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information) {
161
162 JpaRepositoryImplementation<?, ?> repository = getTargetRepository(information, entityManager);
163 repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata());
164 repository.setEscapeCharacter(escapeCharacter);
165
166 return repository;
167 }
168
169
176 protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information,
177 EntityManager entityManager) {
178
179 JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType());
180 Object repository = getTargetRepositoryViaReflection(information, entityInformation, entityManager);
181
182 Assert.isInstanceOf(JpaRepositoryImplementation.class, repository);
183
184 return (JpaRepositoryImplementation<?, ?>) repository;
185 }
186
187
191 @Override
192 protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
193 return SimpleJpaRepository.class;
194 }
195
196
200 @Override
201 protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) {
202
203 CollectionAwareProjectionFactory factory = new CollectionAwareProjectionFactory();
204 factory.setBeanClassLoader(classLoader);
205 factory.setBeanFactory(beanFactory);
206
207 return factory;
208 }
209
210
214 @Override
215 protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
216 QueryMethodEvaluationContextProvider evaluationContextProvider) {
217 return Optional.of(JpaQueryLookupStrategy.create(entityManager, queryMethodFactory, key, evaluationContextProvider,
218 escapeCharacter));
219 }
220
221
225 @Override
226 @SuppressWarnings("unchecked")
227 public <T, ID> JpaEntityInformation<T, ID> getEntityInformation(Class<T> domainClass) {
228
229 return (JpaEntityInformation<T, ID>) JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager);
230 }
231
232
236 @Override
237 protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
238
239 RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty();
240
241 boolean isQueryDslRepository = QUERY_DSL_PRESENT
242 && QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
243
244 if (isQueryDslRepository) {
245
246 if (metadata.isReactiveRepository()) {
247 throw new InvalidDataAccessApiUsageException(
248 "Cannot combine Querydsl and reactive repository support in a single interface");
249 }
250
251 JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
252
253 Object querydslFragment = getTargetRepositoryViaReflection(QuerydslJpaPredicateExecutor.class, entityInformation,
254 entityManager, entityPathResolver, crudMethodMetadataPostProcessor.getCrudMethodMetadata());
255
256 fragments = fragments.append(RepositoryFragment.implemented(querydslFragment));
257 }
258
259 return fragments;
260 }
261
262 private static boolean hasMethodReturningStream(Class<?> repositoryClass) {
263
264 Method[] methods = ReflectionUtils.getAllDeclaredMethods(repositoryClass);
265
266 for (Method method : methods) {
267 if (Stream.class.isAssignableFrom(method.getReturnType())) {
268 return true;
269 }
270 }
271
272 return false;
273 }
274
275
285 @Slf4j
286 private static class EclipseLinkProjectionQueryCreationListener implements QueryCreationListener<AbstractJpaQuery> {
287
288 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:";
289
290 private final JpaMetamodel metamodel;
291
292 private boolean warningLogged = false;
293
294
299 public EclipseLinkProjectionQueryCreationListener(EntityManager em) {
300
301 Assert.notNull(em, "EntityManager must not be null!");
302
303 this.metamodel = JpaMetamodel.of(em.getMetamodel());
304 }
305
306
310 @Override
311 public void onCreation(AbstractJpaQuery query) {
312
313 JpaQueryMethod queryMethod = query.getQueryMethod();
314 ReturnedType type = queryMethod.getResultProcessor().getReturnedType();
315
316 if (type.isProjecting() && !metamodel.isJpaManaged(type.getReturnedType())) {
317
318 if (!warningLogged) {
319 log.info(ECLIPSELINK_PROJECTIONS);
320 this.warningLogged = true;
321 }
322
323 log.info(" - {}", queryMethod);
324 }
325 }
326 }
327 }
328