2
2
3
3
import org .slf4j .Logger ;
4
4
import org .slf4j .LoggerFactory ;
5
+ import org .springframework .core .annotation .AnnotationUtils ;
5
6
import org .springframework .data .domain .Page ;
6
7
import org .springframework .data .domain .PageImpl ;
7
8
import org .springframework .data .domain .Pageable ;
8
9
import org .springframework .data .domain .Sort ;
9
10
import org .springframework .data .jpa .domain .Specification ;
10
- import org .springframework .data .jpa .repository .query .Jpa21Utils ;
11
11
import org .springframework .data .jpa .repository .query .JpaEntityGraph ;
12
+ import org .springframework .data .jpa .repository .query .QueryUtils ;
12
13
import org .springframework .data .jpa .repository .support .JpaEntityInformation ;
13
14
import org .springframework .data .jpa .repository .support .SimpleJpaRepository ;
15
+ import org .springframework .data .mapping .PropertyPath ;
14
16
import org .springframework .data .projection .ProjectionFactory ;
15
17
import org .springframework .data .projection .SpelAwareProxyProjectionFactory ;
18
+ import org .springframework .data .repository .query .MyResultProcessor ;
19
+ import org .springframework .data .repository .query .ReturnTypeWarpper ;
20
+ import org .springframework .data .repository .query .ReturnedType ;
21
+ import org .springframework .data .repository .query .TupleConverter ;
22
+ import org .springframework .data .repository .support .PageableExecutionUtils ;
23
+ import org .springframework .lang .Nullable ;
24
+ import org .springframework .util .Assert ;
16
25
import th .co .geniustree .springdata .jpa .repository .JpaSpecificationExecutorWithProjection ;
17
26
18
27
import javax .persistence .*;
28
+ import javax .persistence .criteria .*;
29
+ import javax .persistence .metamodel .Attribute ;
30
+ import javax .persistence .metamodel .Bindable ;
31
+ import javax .persistence .metamodel .ManagedType ;
32
+ import javax .persistence .metamodel .PluralAttribute ;
19
33
import java .io .Serializable ;
20
- import java .util .HashMap ;
21
- import java .util .Map ;
22
- import java .util .Optional ;
34
+ import java .lang .annotation .Annotation ;
35
+ import java .lang .reflect .AnnotatedElement ;
36
+ import java .lang .reflect .Member ;
37
+ import java .util .*;
38
+
39
+ import static javax .persistence .metamodel .Attribute .PersistentAttributeType .*;
40
+
23
41
24
42
/**
25
43
* Created by pramoth on 9/29/2016 AD.
26
44
*/
27
45
public class JpaSpecificationExecutorWithProjectionImpl <T , ID extends Serializable > extends SimpleJpaRepository <T , ID > implements JpaSpecificationExecutorWithProjection <T > {
28
46
29
47
private static final Logger log = LoggerFactory .getLogger (JpaSpecificationExecutorWithProjectionImpl .class );
48
+ private static final Map <Attribute .PersistentAttributeType , Class <? extends Annotation >> ASSOCIATION_TYPES ;
49
+ static {
50
+ Map <Attribute .PersistentAttributeType , Class <? extends Annotation >> persistentAttributeTypes = new HashMap <Attribute .PersistentAttributeType , Class <? extends Annotation >>();
51
+ persistentAttributeTypes .put (ONE_TO_ONE , OneToOne .class );
52
+ persistentAttributeTypes .put (ONE_TO_MANY , null );
53
+ persistentAttributeTypes .put (MANY_TO_ONE , ManyToOne .class );
54
+ persistentAttributeTypes .put (MANY_TO_MANY , null );
55
+ persistentAttributeTypes .put (ELEMENT_COLLECTION , null );
30
56
57
+ ASSOCIATION_TYPES = Collections .unmodifiableMap (persistentAttributeTypes );
58
+ }
31
59
private final EntityManager entityManager ;
32
60
33
61
private final ProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory ();
@@ -43,52 +71,227 @@ public JpaSpecificationExecutorWithProjectionImpl(JpaEntityInformation entityInf
43
71
44
72
@ Override
45
73
public <R > Optional <R > findOne (Specification <T > spec , Class <R > projectionType ) {
46
- TypedQuery <T > query = getQuery (spec , Sort .unsorted ());
74
+ final ReturnedType returnedType = ReturnTypeWarpper .of (projectionType , getDomainClass (), projectionFactory );
75
+ final TypedQuery <Tuple > query = getTupleQuery (spec , Sort .unsorted (), returnedType );
47
76
try {
48
- T result = query .getSingleResult ();
49
- return Optional .ofNullable (projectionFactory .createProjection (projectionType , result ));
77
+ Tuple result = query .getSingleResult ();
78
+ final MyResultProcessor resultProcessor = new MyResultProcessor (projectionFactory ,returnedType );
79
+ final R singleResult = resultProcessor .processResult (result , new TupleConverter (returnedType ));
80
+ return Optional .ofNullable (singleResult );
50
81
} catch (NoResultException e ) {
51
82
return Optional .empty ();
52
83
}
53
84
}
54
85
55
86
@ Override
56
87
public <R > Page <R > findAll (Specification <T > spec , Class <R > projectionType , Pageable pageable ) {
57
- TypedQuery <T > query = getQuery (spec , pageable );
58
- return readPageWithProjection (spec , projectionType , pageable , query );
88
+ final ReturnedType returnedType = ReturnTypeWarpper .of (projectionType , getDomainClass (), projectionFactory );
89
+ final TypedQuery <Tuple > query = getTupleQuery (spec , pageable .isPaged () ? pageable .getSort () : Sort .unsorted (), returnedType );
90
+ final MyResultProcessor resultProcessor = new MyResultProcessor (projectionFactory ,returnedType );
91
+ if (pageable .isPaged ()) {
92
+ query .setFirstResult ((int )pageable .getOffset ());
93
+ query .setMaxResults (pageable .getPageSize ());
94
+ }
95
+ final List <R > resultList = resultProcessor .processResult (query .getResultList (), new TupleConverter (returnedType ));
96
+ final Page <R > page = PageableExecutionUtils .getPage (resultList , pageable , () -> executeCountQuery (this .getCountQuery (spec , getDomainClass ())));
97
+ return pageable .isUnpaged () ? new PageImpl (resultList ) : page ;
98
+ }
99
+
100
+ static Long executeCountQuery (TypedQuery <Long > query ) {
101
+ Assert .notNull (query , "TypedQuery must not be null!" );
102
+ List <Long > totals = query .getResultList ();
103
+ Long total = 0L ;
104
+
105
+ Long element ;
106
+ for (Iterator var3 = totals .iterator (); var3 .hasNext (); total = total + (element == null ? 0L : element )) {
107
+ element = (Long )var3 .next ();
108
+ }
109
+
110
+ return total ;
59
111
}
60
112
61
113
@ Override
62
114
public <R > Page <R > findAll (Specification <T > spec , Class <R > projectionType , String namedEntityGraph , org .springframework .data .jpa .repository .EntityGraph .EntityGraphType type , Pageable pageable ) {
63
- EntityGraph <?> entityGraph = this .entityManager .getEntityGraph (namedEntityGraph );
64
- if (entityGraph == null ) {
65
- throw new IllegalArgumentException ("Not found named entity graph -> " + namedEntityGraph );
66
- }
67
- TypedQuery <T > query = getQuery (spec , pageable );
68
- query .setHint (type .getKey (), entityGraph );
69
- return readPageWithProjection (spec , projectionType , pageable , query );
115
+ return findAll (spec ,projectionType ,pageable );
70
116
}
71
117
72
118
@ Override
73
119
public <R > Page <R > findAll (Specification <T > spec , Class <R > projectionType , JpaEntityGraph dynamicEntityGraph , Pageable pageable ) {
74
- TypedQuery <T > query = getQuery (spec , pageable );
75
- Map <String , Object > entityGraphHints = new HashMap <String , Object >();
76
- entityGraphHints .putAll (Jpa21Utils .tryGetFetchGraphHints (this .entityManager , dynamicEntityGraph , getDomainClass ()));
77
- applyEntityGraphQueryHints (query , entityGraphHints );
78
- return readPageWithProjection (spec , projectionType , pageable , query );
120
+ return findAll (spec ,projectionType ,pageable );
121
+ }
122
+
123
+ protected TypedQuery <Tuple > getTupleQuery (@ Nullable Specification spec , Sort sort , ReturnedType returnedType ) {
124
+ CriteriaBuilder builder = this .entityManager .getCriteriaBuilder ();
125
+ CriteriaQuery <Tuple > query = builder .createQuery (Tuple .class );
126
+ Root <T > root = this .applySpecificationToCriteria (spec , getDomainClass (), query );
127
+ Predicate predicate = spec .toPredicate (root , query , builder );
128
+
129
+ if (predicate != null ) {
130
+ query .where (predicate );
131
+ }
132
+ if (returnedType .isProjecting ()) {
133
+ List <Selection <?>> selections = new ArrayList <>();
134
+
135
+ for (String property : returnedType .getInputProperties ()) {
136
+ PropertyPath path = PropertyPath .from (property , returnedType .getReturnedType ());
137
+ selections .add (toExpressionRecursively (root , path , true ).alias (property ));
138
+ }
139
+
140
+ query .multiselect (selections );
141
+ } else {
142
+ throw new IllegalArgumentException ("only except projection" );
143
+ }
144
+ if (sort .isSorted ()) {
145
+ query .orderBy (QueryUtils .toOrders (sort , root , builder ));
146
+ }
147
+
148
+ return this .applyRepositoryMethodMetadata (this .entityManager .createQuery (query ));
79
149
}
80
150
81
- private <R > Page <R > readPageWithProjection (Specification <T > spec , Class <R > projectionType , Pageable pageable , TypedQuery <T > query ) {
82
- if (log .isDebugEnabled ()) {
83
- query .getHints ().forEach ((key , value ) -> log .info ("apply query hints -> {} : {}" , key , value ));
151
+
152
+ private <S , U extends T > Root <U > applySpecificationToCriteria (@ Nullable Specification <U > spec , Class <U > domainClass ,
153
+ CriteriaQuery <S > query ) {
154
+
155
+ Assert .notNull (domainClass , "Domain class must not be null!" );
156
+ Assert .notNull (query , "CriteriaQuery must not be null!" );
157
+
158
+ Root <U > root = query .from (domainClass );
159
+
160
+ if (spec == null ) {
161
+ return root ;
162
+ }
163
+
164
+ CriteriaBuilder builder = entityManager .getCriteriaBuilder ();
165
+ Predicate predicate = spec .toPredicate (root , query , builder );
166
+
167
+ if (predicate != null ) {
168
+ query .where (predicate );
84
169
}
85
- Page < T > result = pageable . isUnpaged () ? new PageImpl <>( query . getResultList ()) : readPage ( query , getDomainClass (), pageable , spec );
86
- return result . map ( item -> projectionFactory . createProjection ( projectionType , item )) ;
170
+
171
+ return root ;
87
172
}
88
173
89
- private void applyEntityGraphQueryHints (Query query , Map <String , Object > hints ) {
90
- for (Map .Entry <String , Object > hint : hints .entrySet ()) {
174
+ private <S > TypedQuery <S > applyRepositoryMethodMetadata (TypedQuery <S > query ) {
175
+
176
+ if (getRepositoryMethodMetadata () == null ) {
177
+ return query ;
178
+ }
179
+
180
+ LockModeType type = getRepositoryMethodMetadata ().getLockModeType ();
181
+ TypedQuery <S > toReturn = type == null ? query : query .setLockMode (type );
182
+ applyQueryHints (toReturn );
183
+
184
+ return toReturn ;
185
+ }
186
+ private void applyQueryHints (Query query ) {
187
+ QueryHints queryHints = DefaultQueryHints .of (this .entityInformation , getRepositoryMethodMetadata ());
188
+ if (queryHints ==null ){
189
+ queryHints = QueryHints .NoHints .INSTANCE ;
190
+ }
191
+ for (Map .Entry <String , Object > hint : queryHints .withFetchGraphs (this .entityManager )) {
91
192
query .setHint (hint .getKey (), hint .getValue ());
92
193
}
93
194
}
195
+
196
+
197
+ static Expression <Object > toExpressionRecursively (Path <Object > path , PropertyPath property ) {
198
+
199
+ Path <Object > result = path .get (property .getSegment ());
200
+ return property .hasNext () ? toExpressionRecursively (result , property .next ()) : result ;
201
+ }
202
+
203
+ static <T > Expression <T > toExpressionRecursively (From <?, ?> from , PropertyPath property , boolean isForSelection ) {
204
+
205
+ Bindable <?> propertyPathModel ;
206
+ Bindable <?> model = from .getModel ();
207
+ String segment = property .getSegment ();
208
+
209
+ if (model instanceof ManagedType ) {
210
+
211
+ /*
212
+ * Required to keep support for EclipseLink 2.4.x. TODO: Remove once we drop that (probably Dijkstra M1)
213
+ * See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=413892
214
+ */
215
+ propertyPathModel = (Bindable <?>) ((ManagedType <?>) model ).getAttribute (segment );
216
+ } else {
217
+ propertyPathModel = from .get (segment ).getModel ();
218
+ }
219
+
220
+ if (requiresJoin (propertyPathModel , model instanceof PluralAttribute , !property .hasNext (), isForSelection )
221
+ && !isAlreadyFetched (from , segment )) {
222
+ Join <?, ?> join = getOrCreateJoin (from , segment );
223
+ return (Expression <T >) (property .hasNext () ? toExpressionRecursively (join , property .next (), isForSelection )
224
+ : join );
225
+ } else {
226
+ Path <Object > path = from .get (segment );
227
+ return (Expression <T >) (property .hasNext () ? toExpressionRecursively (path , property .next ()) : path );
228
+ }
229
+ }
230
+
231
+ private static boolean requiresJoin (@ Nullable Bindable <?> propertyPathModel , boolean isPluralAttribute ,
232
+ boolean isLeafProperty , boolean isForSelection ) {
233
+
234
+ if (propertyPathModel == null && isPluralAttribute ) {
235
+ return true ;
236
+ }
237
+
238
+ if (!(propertyPathModel instanceof Attribute )) {
239
+ return false ;
240
+ }
241
+
242
+ Attribute <?, ?> attribute = (Attribute <?, ?>) propertyPathModel ;
243
+
244
+ if (!ASSOCIATION_TYPES .containsKey (attribute .getPersistentAttributeType ())) {
245
+ return false ;
246
+ }
247
+
248
+ // if this path is part of the select list we need to generate an explicit outer join in order to prevent Hibernate
249
+ // to use an inner join instead.
250
+ // see https://hibernate.atlassian.net/browse/HHH-12999.
251
+ if (isLeafProperty && !isForSelection && !attribute .isCollection ()) {
252
+ return false ;
253
+ }
254
+
255
+ Class <? extends Annotation > associationAnnotation = ASSOCIATION_TYPES .get (attribute .getPersistentAttributeType ());
256
+
257
+ if (associationAnnotation == null ) {
258
+ return true ;
259
+ }
260
+
261
+ Member member = attribute .getJavaMember ();
262
+
263
+ if (!(member instanceof AnnotatedElement )) {
264
+ return true ;
265
+ }
266
+
267
+ Annotation annotation = AnnotationUtils .getAnnotation ((AnnotatedElement ) member , associationAnnotation );
268
+ return annotation == null ? true : (boolean ) AnnotationUtils .getValue (annotation , "optional" );
269
+ }
270
+
271
+ private static Join <?, ?> getOrCreateJoin (From <?, ?> from , String attribute ) {
272
+
273
+ for (Join <?, ?> join : from .getJoins ()) {
274
+
275
+ boolean sameName = join .getAttribute ().getName ().equals (attribute );
276
+
277
+ if (sameName && join .getJoinType ().equals (JoinType .LEFT )) {
278
+ return join ;
279
+ }
280
+ }
281
+
282
+ return from .join (attribute , JoinType .LEFT );
283
+ }
284
+ private static boolean isAlreadyFetched (From <?, ?> from , String attribute ) {
285
+
286
+ for (Fetch <?, ?> fetch : from .getFetches ()) {
287
+
288
+ boolean sameName = fetch .getAttribute ().getName ().equals (attribute );
289
+
290
+ if (sameName && fetch .getJoinType ().equals (JoinType .LEFT )) {
291
+ return true ;
292
+ }
293
+ }
294
+
295
+ return false ;
296
+ }
94
297
}
0 commit comments