1010import java .util .Map ;
1111import java .util .Map .Entry ;
1212import java .util .Optional ;
13+ import java .util .concurrent .atomic .AtomicReference ;
1314import java .util .stream .Collectors ;
1415import java .util .stream .Stream ;
1516
3031
3132public class CommonPanacheQueryImpl <Entity > {
3233
34+ /*
35+ * We use this complex caching mechanism to avoid recalculating projection queries
36+ * for recurring classes. In theory this gets stored in the Class object itself so
37+ * it is GCed when the class is disposed, so it auto-cleans itself. The extra
38+ * AtomicReference is as per Franz's advice, for a reason I did not understand.
39+ * We did verify that this improves allocation and cpu a lot, as it avoids
40+ * repeated usage of reflection and string building.
41+ */
42+ private final static ClassValue <AtomicReference <String >> ProjectionQueryCache = new ClassValue <>() {
43+ @ Override
44+ protected AtomicReference <String > computeValue (Class <?> type ) {
45+ return new AtomicReference <>();
46+ }
47+ };
48+
3349 private interface NonThrowingCloseable extends AutoCloseable {
3450 @ Override
3551 void close ();
@@ -120,12 +136,16 @@ public <T> CommonPanacheQueryImpl<T> project(Class<T> type) {
120136 // FIXME: this assumes the query starts with "FROM " probably?
121137
122138 // build select clause with a constructor expression
123- String selectClause = "SELECT " + getParametersFromClass (type , null );
139+ AtomicReference <String > cachedProjection = ProjectionQueryCache .get (type );
140+ if (cachedProjection .get () == null ) {
141+ cachedProjection .set ("SELECT " + getParametersFromClass (type , null ));
142+ }
143+ String selectClause = cachedProjection .get ();
124144 // I think projections do not change the result count, so we can keep the custom count query
125145 return new CommonPanacheQueryImpl <>(this , selectClause + selectQuery , customCountQueryForSpring , null );
126146 }
127147
128- private StringBuilder getParametersFromClass (Class <?> type , String parentParameter ) {
148+ private static StringBuilder getParametersFromClass (Class <?> type , String parentParameter ) {
129149 StringBuilder selectClause = new StringBuilder ();
130150 Constructor <?> constructor = getConstructor (type );
131151
@@ -138,7 +158,7 @@ private StringBuilder getParametersFromClass(Class<?> type, String parentParamet
138158 return selectClause ;
139159 }
140160
141- private Constructor <?> getConstructor (Class <?> type ) {
161+ private static Constructor <?> getConstructor (Class <?> type ) {
142162 Constructor <?>[] typeConstructors = type .getDeclaredConstructors ();
143163
144164 //We start to look for constructors with @ProjectedConstructor
@@ -172,7 +192,7 @@ private Constructor<?> getConstructor(Class<?> type) {
172192 return typeConstructors [0 ];
173193 }
174194
175- private String getParameterName (Class <?> parentType , String parentParameter , Parameter parameter ) {
195+ private static String getParameterName (Class <?> parentType , String parentParameter , Parameter parameter ) {
176196 String parameterName ;
177197 // Check if constructor param is annotated with ProjectedFieldName
178198 if (hasProjectedFieldName (parameter )) {
@@ -201,11 +221,11 @@ private String getParameterName(Class<?> parentType, String parentParameter, Par
201221 }
202222 }
203223
204- private boolean hasProjectedFieldName (AnnotatedElement annotatedElement ) {
224+ private static boolean hasProjectedFieldName (AnnotatedElement annotatedElement ) {
205225 return annotatedElement .isAnnotationPresent (ProjectedFieldName .class );
206226 }
207227
208- private String getNameFromProjectedFieldName (AnnotatedElement annotatedElement ) {
228+ private static String getNameFromProjectedFieldName (AnnotatedElement annotatedElement ) {
209229 final String name = annotatedElement .getAnnotation (ProjectedFieldName .class ).value ();
210230 if (name .isEmpty ()) {
211231 throw new PanacheQueryException ("The annotation ProjectedFieldName must have a non-empty value." );
0 commit comments