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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user