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
This commit is contained in:
Mark Paluch
2024-01-24 14:00:45 +01:00
parent 225af9e18c
commit 783c6a87ec
6 changed files with 134 additions and 60 deletions

View File

@@ -27,12 +27,12 @@ import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -1406,8 +1406,7 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
throw new MappingException(String.format("Parameter %s does not have a name", parameter));
}
CassandraPersistentProperty property = getPersistentProperty(name, parameter.getType(),
parameter.getAnnotations());
CassandraPersistentProperty property = entity.getProperty(parameter);
if (property == null) {
@@ -1418,19 +1417,6 @@ public class MappingCassandraConverter extends AbstractCassandraConverter
return (T) getReadValue(context.forProperty(property.getName()), provider, property);
}
@Nullable
private CassandraPersistentProperty getPersistentProperty(String name, TypeInformation<?> typeInformation,
MergedAnnotations annotations) {
CassandraPersistentProperty property = entity.getPersistentProperty(name);
if (annotations.isPresent(Column.class) || annotations.isPresent(Element.class)) {
return new AnnotatedCassandraConstructorProperty(
property == null ? new CassandraConstructorProperty(name, entity, typeInformation) : property, annotations);
}
return property;
}
}
private record PropertyTranslatingPropertyAccessor<T> (PersistentPropertyAccessor<T> delegate,

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021-2024 the original author or authors.
* Copyright 2024 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.convert;
package org.springframework.data.cassandra.core.mapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
@@ -22,11 +22,9 @@ import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.data.cassandra.core.cql.Ordering;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.cassandra.core.mapping.Column;
import org.springframework.data.cassandra.core.mapping.Element;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.TypeInformation;
@@ -46,41 +44,31 @@ class AnnotatedCassandraConstructorProperty implements CassandraPersistentProper
private final CassandraPersistentProperty delegate;
private final MergedAnnotations annotations;
private final MergedAnnotation<Column> column;
private final MergedAnnotation<Element> element;
public AnnotatedCassandraConstructorProperty(CassandraPersistentProperty delegate, MergedAnnotations annotations) {
this.delegate = delegate;
this.annotations = annotations;
this.column = annotations.get(Column.class);
this.element = annotations.get(Element.class);
}
@Override
@Nullable
public CqlIdentifier getColumnName() {
if (annotations.isPresent(Column.class)) {
return CqlIdentifier.fromCql(annotations.get(Column.class).getString("value"));
}
return delegate.getColumnName();
return column.isPresent() ? CqlIdentifier.fromCql(column.getString("value")) : delegate.getColumnName();
}
@Override
public boolean hasExplicitColumnName() {
if (annotations.isPresent(Column.class)) {
return !ObjectUtils.isEmpty(annotations.get(Column.class).getString("value"));
}
return false;
return column.isPresent() && !ObjectUtils.isEmpty(column.getString("value"));
}
@Override
@Nullable
public Integer getOrdinal() {
if (annotations.isPresent(Element.class)) {
return annotations.get(Element.class).getInt("value");
}
return delegate.getOrdinal();
return element.isPresent() ? Integer.valueOf(element.getInt("value")) : delegate.getOrdinal();
}
@Override

View File

@@ -17,7 +17,9 @@ package org.springframework.data.cassandra.core.mapping;
import java.lang.annotation.Annotation;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import org.springframework.beans.BeansException;
@@ -26,9 +28,11 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryAccessor;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -60,6 +64,8 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
private @Nullable StandardEvaluationContext spelContext;
private final Map<Parameter<?, CassandraPersistentProperty>, CassandraPersistentProperty> constructorProperties = new ConcurrentHashMap<>();
/**
* Create a new {@link BasicCassandraPersistentEntity} given {@link TypeInformation}.
*
@@ -213,4 +219,25 @@ public class BasicCassandraPersistentEntity<T> extends BasicPersistentEntity<T,
public boolean isUserDefinedType() {
return false;
}
@Override
public CassandraPersistentProperty getProperty(Parameter<?, CassandraPersistentProperty> parameter) {
if (parameter.getName() == null) {
return null;
}
MergedAnnotations annotations = parameter.getAnnotations();
if (annotations.isPresent(Column.class) || annotations.isPresent(Element.class)) {
return constructorProperties.computeIfAbsent(parameter, it -> {
CassandraPersistentProperty property = getPersistentProperty(it.getName());
return new AnnotatedCassandraConstructorProperty(
property == null ? new CassandraConstructorProperty(it, this) : property, it.getAnnotations());
});
}
return getPersistentProperty(parameter.getName());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2021-2024 the original author or authors.
* Copyright 2024 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.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.cassandra.core.convert;
package org.springframework.data.cassandra.core.mapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
@@ -25,9 +25,8 @@ import java.util.Optional;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.data.cassandra.core.cql.Ordering;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -42,17 +41,20 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
*/
class CassandraConstructorProperty implements CassandraPersistentProperty {
private final Parameter<?, CassandraPersistentProperty> constructorParameter;
private final String name;
private final CassandraPersistentEntity<?> owner;
private final TypeInformation<?> typeInformation;
public CassandraConstructorProperty(String name, CassandraPersistentEntity<?> owner,
TypeInformation<?> typeInformation) {
this.name = name;
public CassandraConstructorProperty(Parameter<?, CassandraPersistentProperty> constructorParameter,
CassandraPersistentEntity<?> owner) {
this.constructorParameter = constructorParameter;
this.name = constructorParameter.getName();
this.owner = owner;
this.typeInformation = typeInformation;
this.typeInformation = constructorParameter.getType();
}
@Nullable
@@ -296,4 +298,21 @@ class CassandraConstructorProperty implements CassandraPersistentProperty {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof CassandraConstructorProperty that)) {
return false;
}
return constructorParameter.equals(that.constructorParameter);
}
@Override
public int hashCode() {
return constructorParameter.hashCode();
}
}

View File

@@ -15,7 +15,9 @@
*/
package org.springframework.data.cassandra.core.mapping;
import org.springframework.data.mapping.Parameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.datastax.oss.driver.api.core.CqlIdentifier;
@@ -29,6 +31,19 @@ import com.datastax.oss.driver.api.core.CqlIdentifier;
*/
public interface CassandraPersistentEntity<T> extends PersistentEntity<T, CassandraPersistentProperty> {
/**
* Retrieve a {@link CassandraPersistentProperty} from a {@link Parameter persistence creator (constructor/factory
* method) parameter}. Parameters are either derived by name or synthesized if their name does not map to a existing
* property.
*
* @param parameter the parameter to create a property from. Parameters without a name return no ({@literal null})
* parameter.
* @return the property, synthetic property or {@literal null}, if the parameter is unnamed.
* @since 4.1.9
*/
@Nullable
CassandraPersistentProperty getProperty(Parameter<?, CassandraPersistentProperty> parameter);
/**
* Returns whether this entity represents a composite primary key.
*/

View File

@@ -29,19 +29,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.data.cassandra.core.cql.Ordering;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PersistentPropertyPathAccessor;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.mapping.*;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
@@ -323,6 +311,13 @@ public class EmbeddedEntityOperations {
return delegate.requiresPropertyPopulation();
}
@Nullable
@Override
public CassandraPersistentProperty getProperty(Parameter<?, CassandraPersistentProperty> parameter) {
CassandraPersistentProperty property = delegate.getProperty(parameter);
return property == null ? null : wrap(property);
}
@NotNull
@Override
public Iterator<CassandraPersistentProperty> iterator() {
@@ -342,6 +337,28 @@ public class EmbeddedEntityOperations {
return delegate.spliterator();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PrefixedCassandraPersistentEntity<?> that)) {
return false;
}
if (!prefix.equals(that.prefix)) {
return false;
}
return delegate.equals(that.delegate);
}
@Override
public int hashCode() {
int result = prefix.hashCode();
result = 31 * result + delegate.hashCode();
return result;
}
private PrefixedCassandraPersistentProperty wrap(CassandraPersistentProperty source) {
return new PrefixedCassandraPersistentProperty(prefix, source);
}
@@ -653,5 +670,27 @@ public class EmbeddedEntityOperations {
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
delegate.setApplicationContext(applicationContext);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PrefixedCassandraPersistentProperty that)) {
return false;
}
if (!prefix.equals(that.prefix)) {
return false;
}
return delegate.equals(that.delegate);
}
@Override
public int hashCode() {
int result = prefix.hashCode();
result = 31 * result + delegate.hashCode();
return result;
}
}
}