Skip to content

Commit 783c6a8

Browse files
committed
Introduce caching and equality checks to derived persistent entities and properties.
We now provide equals/hashCode and selective caching for derived properties and entities derived from embedded properties and constructor-annotated properties to avoid memory leaks using properties and entities as cache keys. Closes #1471
1 parent 225af9e commit 783c6a8

File tree

6 files changed

+134
-60
lines changed

6 files changed

+134
-60
lines changed

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@
2727

2828
import org.apache.commons.logging.Log;
2929
import org.apache.commons.logging.LogFactory;
30+
3031
import org.springframework.beans.BeansException;
3132
import org.springframework.beans.factory.BeanClassLoaderAware;
3233
import org.springframework.context.ApplicationContext;
3334
import org.springframework.context.ApplicationContextAware;
3435
import org.springframework.core.CollectionFactory;
35-
import org.springframework.core.annotation.MergedAnnotations;
3636
import org.springframework.core.convert.ConversionService;
3737
import org.springframework.core.convert.support.DefaultConversionService;
3838
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -1406,8 +1406,7 @@ public <T> T getParameterValue(Parameter<T, CassandraPersistentProperty> paramet
14061406
throw new MappingException(String.format("Parameter %s does not have a name", parameter));
14071407
}
14081408

1409-
CassandraPersistentProperty property = getPersistentProperty(name, parameter.getType(),
1410-
parameter.getAnnotations());
1409+
CassandraPersistentProperty property = entity.getProperty(parameter);
14111410

14121411
if (property == null) {
14131412

@@ -1418,19 +1417,6 @@ public <T> T getParameterValue(Parameter<T, CassandraPersistentProperty> paramet
14181417
return (T) getReadValue(context.forProperty(property.getName()), provider, property);
14191418
}
14201419

1421-
@Nullable
1422-
private CassandraPersistentProperty getPersistentProperty(String name, TypeInformation<?> typeInformation,
1423-
MergedAnnotations annotations) {
1424-
1425-
CassandraPersistentProperty property = entity.getPersistentProperty(name);
1426-
1427-
if (annotations.isPresent(Column.class) || annotations.isPresent(Element.class)) {
1428-
return new AnnotatedCassandraConstructorProperty(
1429-
property == null ? new CassandraConstructorProperty(name, entity, typeInformation) : property, annotations);
1430-
}
1431-
1432-
return property;
1433-
}
14341420
}
14351421

14361422
private record PropertyTranslatingPropertyAccessor<T> (PersistentPropertyAccessor<T> delegate,
Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021-2024 the original author or authors.
2+
* Copyright 2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.data.cassandra.core.convert;
16+
package org.springframework.data.cassandra.core.mapping;
1717

1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.AnnotatedType;
@@ -22,11 +22,9 @@
2222

2323
import org.springframework.beans.BeansException;
2424
import org.springframework.context.ApplicationContext;
25+
import org.springframework.core.annotation.MergedAnnotation;
2526
import org.springframework.core.annotation.MergedAnnotations;
2627
import org.springframework.data.cassandra.core.cql.Ordering;
27-
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
28-
import org.springframework.data.cassandra.core.mapping.Column;
29-
import org.springframework.data.cassandra.core.mapping.Element;
3028
import org.springframework.data.mapping.Association;
3129
import org.springframework.data.mapping.PersistentEntity;
3230
import org.springframework.data.util.TypeInformation;
@@ -46,41 +44,31 @@ class AnnotatedCassandraConstructorProperty implements CassandraPersistentProper
4644

4745
private final CassandraPersistentProperty delegate;
4846

49-
private final MergedAnnotations annotations;
47+
private final MergedAnnotation<Column> column;
48+
49+
private final MergedAnnotation<Element> element;
5050

5151
public AnnotatedCassandraConstructorProperty(CassandraPersistentProperty delegate, MergedAnnotations annotations) {
5252
this.delegate = delegate;
53-
this.annotations = annotations;
53+
this.column = annotations.get(Column.class);
54+
this.element = annotations.get(Element.class);
5455
}
5556

5657
@Override
5758
@Nullable
5859
public CqlIdentifier getColumnName() {
59-
60-
if (annotations.isPresent(Column.class)) {
61-
return CqlIdentifier.fromCql(annotations.get(Column.class).getString("value"));
62-
}
63-
64-
return delegate.getColumnName();
60+
return column.isPresent() ? CqlIdentifier.fromCql(column.getString("value")) : delegate.getColumnName();
6561
}
6662

6763
@Override
6864
public boolean hasExplicitColumnName() {
69-
if (annotations.isPresent(Column.class)) {
70-
return !ObjectUtils.isEmpty(annotations.get(Column.class).getString("value"));
71-
}
72-
return false;
65+
return column.isPresent() && !ObjectUtils.isEmpty(column.getString("value"));
7366
}
7467

7568
@Override
7669
@Nullable
7770
public Integer getOrdinal() {
78-
79-
if (annotations.isPresent(Element.class)) {
80-
return annotations.get(Element.class).getInt("value");
81-
}
82-
83-
return delegate.getOrdinal();
71+
return element.isPresent() ? Integer.valueOf(element.getInt("value")) : delegate.getOrdinal();
8472
}
8573

8674
@Override

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
import java.lang.annotation.Annotation;
1919
import java.util.Comparator;
20+
import java.util.Map;
2021
import java.util.Optional;
22+
import java.util.concurrent.ConcurrentHashMap;
2123
import java.util.function.BiFunction;
2224

2325
import org.springframework.beans.BeansException;
@@ -26,9 +28,11 @@
2628
import org.springframework.context.expression.BeanFactoryAccessor;
2729
import org.springframework.context.expression.BeanFactoryResolver;
2830
import org.springframework.core.annotation.AnnotationUtils;
31+
import org.springframework.core.annotation.MergedAnnotations;
2932
import org.springframework.data.mapping.Association;
3033
import org.springframework.data.mapping.AssociationHandler;
3134
import org.springframework.data.mapping.MappingException;
35+
import org.springframework.data.mapping.Parameter;
3236
import org.springframework.data.mapping.model.BasicPersistentEntity;
3337
import org.springframework.data.util.TypeInformation;
3438
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -60,6 +64,8 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
6064

6165
private @Nullable StandardEvaluationContext spelContext;
6266

67+
private final Map<Parameter<?, CassandraPersistentProperty>, CassandraPersistentProperty> constructorProperties = new ConcurrentHashMap<>();
68+
6369
/**
6470
* Create a new {@link BasicCassandraPersistentEntity} given {@link TypeInformation}.
6571
*
@@ -213,4 +219,25 @@ public boolean isTupleType() {
213219
public boolean isUserDefinedType() {
214220
return false;
215221
}
222+
223+
@Override
224+
public CassandraPersistentProperty getProperty(Parameter<?, CassandraPersistentProperty> parameter) {
225+
226+
if (parameter.getName() == null) {
227+
return null;
228+
}
229+
230+
MergedAnnotations annotations = parameter.getAnnotations();
231+
if (annotations.isPresent(Column.class) || annotations.isPresent(Element.class)) {
232+
233+
return constructorProperties.computeIfAbsent(parameter, it -> {
234+
235+
CassandraPersistentProperty property = getPersistentProperty(it.getName());
236+
return new AnnotatedCassandraConstructorProperty(
237+
property == null ? new CassandraConstructorProperty(it, this) : property, it.getAnnotations());
238+
});
239+
}
240+
241+
return getPersistentProperty(parameter.getName());
242+
}
216243
}
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021-2024 the original author or authors.
2+
* Copyright 2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.data.cassandra.core.convert;
16+
package org.springframework.data.cassandra.core.mapping;
1717

1818
import java.lang.annotation.Annotation;
1919
import java.lang.reflect.AnnotatedType;
@@ -25,9 +25,8 @@
2525
import org.springframework.beans.BeansException;
2626
import org.springframework.context.ApplicationContext;
2727
import org.springframework.data.cassandra.core.cql.Ordering;
28-
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
29-
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
3028
import org.springframework.data.mapping.Association;
29+
import org.springframework.data.mapping.Parameter;
3130
import org.springframework.data.mapping.PersistentEntity;
3231
import org.springframework.data.util.TypeInformation;
3332
import org.springframework.lang.Nullable;
@@ -42,17 +41,20 @@
4241
*/
4342
class CassandraConstructorProperty implements CassandraPersistentProperty {
4443

44+
private final Parameter<?, CassandraPersistentProperty> constructorParameter;
45+
4546
private final String name;
4647

4748
private final CassandraPersistentEntity<?> owner;
4849

4950
private final TypeInformation<?> typeInformation;
5051

51-
public CassandraConstructorProperty(String name, CassandraPersistentEntity<?> owner,
52-
TypeInformation<?> typeInformation) {
53-
this.name = name;
52+
public CassandraConstructorProperty(Parameter<?, CassandraPersistentProperty> constructorParameter,
53+
CassandraPersistentEntity<?> owner) {
54+
this.constructorParameter = constructorParameter;
55+
this.name = constructorParameter.getName();
5456
this.owner = owner;
55-
this.typeInformation = typeInformation;
57+
this.typeInformation = constructorParameter.getType();
5658
}
5759

5860
@Nullable
@@ -296,4 +298,21 @@ public void setForceQuote(boolean forceQuote) {
296298
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
297299
throw new UnsupportedOperationException();
298300
}
301+
302+
@Override
303+
public boolean equals(Object o) {
304+
if (this == o) {
305+
return true;
306+
}
307+
if (!(o instanceof CassandraConstructorProperty that)) {
308+
return false;
309+
}
310+
311+
return constructorParameter.equals(that.constructorParameter);
312+
}
313+
314+
@Override
315+
public int hashCode() {
316+
return constructorParameter.hashCode();
317+
}
299318
}

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
*/
1616
package org.springframework.data.cassandra.core.mapping;
1717

18+
import org.springframework.data.mapping.Parameter;
1819
import org.springframework.data.mapping.PersistentEntity;
20+
import org.springframework.lang.Nullable;
1921
import org.springframework.util.Assert;
2022

2123
import com.datastax.oss.driver.api.core.CqlIdentifier;
@@ -29,6 +31,19 @@
2931
*/
3032
public interface CassandraPersistentEntity<T> extends PersistentEntity<T, CassandraPersistentProperty> {
3133

34+
/**
35+
* Retrieve a {@link CassandraPersistentProperty} from a {@link Parameter persistence creator (constructor/factory
36+
* method) parameter}. Parameters are either derived by name or synthesized if their name does not map to a existing
37+
* property.
38+
*
39+
* @param parameter the parameter to create a property from. Parameters without a name return no ({@literal null})
40+
* parameter.
41+
* @return the property, synthetic property or {@literal null}, if the parameter is unnamed.
42+
* @since 4.1.9
43+
*/
44+
@Nullable
45+
CassandraPersistentProperty getProperty(Parameter<?, CassandraPersistentProperty> parameter);
46+
3247
/**
3348
* Returns whether this entity represents a composite primary key.
3449
*/

spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EmbeddedEntityOperations.java

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,7 @@
2929
import org.springframework.beans.BeansException;
3030
import org.springframework.context.ApplicationContext;
3131
import org.springframework.data.cassandra.core.cql.Ordering;
32-
import org.springframework.data.mapping.Alias;
33-
import org.springframework.data.mapping.Association;
34-
import org.springframework.data.mapping.AssociationHandler;
35-
import org.springframework.data.mapping.IdentifierAccessor;
36-
import org.springframework.data.mapping.InstanceCreatorMetadata;
37-
import org.springframework.data.mapping.PersistentEntity;
38-
import org.springframework.data.mapping.PersistentProperty;
39-
import org.springframework.data.mapping.PersistentPropertyAccessor;
40-
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
41-
import org.springframework.data.mapping.PreferredConstructor;
42-
import org.springframework.data.mapping.PropertyHandler;
43-
import org.springframework.data.mapping.SimpleAssociationHandler;
44-
import org.springframework.data.mapping.SimplePropertyHandler;
32+
import org.springframework.data.mapping.*;
4533
import org.springframework.data.mapping.context.MappingContext;
4634
import org.springframework.data.util.TypeInformation;
4735
import org.springframework.lang.Nullable;
@@ -323,6 +311,13 @@ public boolean requiresPropertyPopulation() {
323311
return delegate.requiresPropertyPopulation();
324312
}
325313

314+
@Nullable
315+
@Override
316+
public CassandraPersistentProperty getProperty(Parameter<?, CassandraPersistentProperty> parameter) {
317+
CassandraPersistentProperty property = delegate.getProperty(parameter);
318+
return property == null ? null : wrap(property);
319+
}
320+
326321
@NotNull
327322
@Override
328323
public Iterator<CassandraPersistentProperty> iterator() {
@@ -342,6 +337,28 @@ public Spliterator<CassandraPersistentProperty> spliterator() {
342337
return delegate.spliterator();
343338
}
344339

340+
@Override
341+
public boolean equals(Object o) {
342+
if (this == o) {
343+
return true;
344+
}
345+
if (!(o instanceof PrefixedCassandraPersistentEntity<?> that)) {
346+
return false;
347+
}
348+
349+
if (!prefix.equals(that.prefix)) {
350+
return false;
351+
}
352+
return delegate.equals(that.delegate);
353+
}
354+
355+
@Override
356+
public int hashCode() {
357+
int result = prefix.hashCode();
358+
result = 31 * result + delegate.hashCode();
359+
return result;
360+
}
361+
345362
private PrefixedCassandraPersistentProperty wrap(CassandraPersistentProperty source) {
346363
return new PrefixedCassandraPersistentProperty(prefix, source);
347364
}
@@ -653,5 +670,27 @@ public <T> PersistentPropertyAccessor<T> getAccessorForOwner(T owner) {
653670
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
654671
delegate.setApplicationContext(applicationContext);
655672
}
673+
674+
@Override
675+
public boolean equals(Object o) {
676+
if (this == o) {
677+
return true;
678+
}
679+
if (!(o instanceof PrefixedCassandraPersistentProperty that)) {
680+
return false;
681+
}
682+
683+
if (!prefix.equals(that.prefix)) {
684+
return false;
685+
}
686+
return delegate.equals(that.delegate);
687+
}
688+
689+
@Override
690+
public int hashCode() {
691+
int result = prefix.hashCode();
692+
result = 31 * result + delegate.hashCode();
693+
return result;
694+
}
656695
}
657696
}

0 commit comments

Comments
 (0)