DATAJDBC-399 - Performance improvements in metadata lookups.

Avoid Stream usage in favor of a simple for loop in event triggering in JdbcAggregateTemplate. Tweaked JdbcMappingContext to verify the presence of parameter names on metadata creation. Avoid the re-resolution of the column name for a property by caching the resolved column name. This significantly improves performance as it avoids repeated parsing and concatenation of strings. Added caching to PersistentPropertyPathExtension.
This commit is contained in:
Oliver Drotbohm
2019-07-23 08:36:38 +02:00
parent 1fb404a84e
commit 5a8abe73e6
9 changed files with 196 additions and 32 deletions

View File

@@ -15,10 +15,10 @@
*/
package org.springframework.data.jdbc.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
@@ -364,13 +364,17 @@ public class JdbcAggregateTemplate implements JdbcAggregateOperations {
private <T> Iterable<T> triggerAfterLoad(Iterable<T> all) {
return StreamSupport.stream(all.spliterator(), false).map(e -> {
List<T> result = new ArrayList<>();
for (T e : all) {
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(e.getClass());
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(e);
return triggerAfterLoad(identifierAccessor.getRequiredIdentifier(), e);
}).collect(Collectors.toList());
result.add(triggerAfterLoad(identifierAccessor.getRequiredIdentifier(), e));
}
return result;
}
private <T> T triggerAfterLoad(Object id, T entity) {

View File

@@ -18,9 +18,9 @@ package org.springframework.data.jdbc.core.convert;
import lombok.RequiredArgsConstructor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Provides {@link SqlGenerator}s per domain type. Instances get cached, so when asked multiple times for the same
@@ -31,12 +31,10 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
@RequiredArgsConstructor
public class SqlGeneratorSource {
private final Map<Class, SqlGenerator> sqlGeneratorCache = new ConcurrentHashMap<>();
private final Map<Class<?>, SqlGenerator> CACHE = new ConcurrentReferenceHashMap<>();
private final RelationalMappingContext context;
SqlGenerator getSqlGenerator(Class<?> domainType) {
return sqlGeneratorCache.computeIfAbsent(domainType,
t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t)));
return CACHE.computeIfAbsent(domainType, t -> new SqlGenerator(context, context.getRequiredPersistentEntity(t)));
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.jdbc.core.mapping;
import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.PreferredConstructor.Parameter;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
@@ -23,6 +25,8 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* {@link MappingContext} implementation for JDBC.
@@ -35,6 +39,8 @@ import org.springframework.data.util.TypeInformation;
*/
public class JdbcMappingContext extends RelationalMappingContext {
private static final String MISSING_PARAMETER_NAME = "A constructor parameter name must not be null to be used with Spring Data JDBC! Offending parameter: %s";
/**
* Creates a new {@link JdbcMappingContext}.
*/
@@ -51,6 +57,27 @@ public class JdbcMappingContext extends RelationalMappingContext {
super(namingStrategy);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.RelationalMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
*/
@Override
protected <T> RelationalPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
RelationalPersistentEntity<T> entity = super.createPersistentEntity(typeInformation);
PreferredConstructor<T, RelationalPersistentProperty> constructor = entity.getPersistenceConstructor();
if (constructor == null) {
return entity;
}
for (Parameter<Object, RelationalPersistentProperty> parameter : constructor.getParameters()) {
Assert.state(StringUtils.hasText(parameter.getName()), () -> String.format(MISSING_PARAMETER_NAME, parameter));
}
return entity;
}
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)

View File

@@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.*;
import org.assertj.core.api.SoftAssertions;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.convert.SqlGenerator;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
import org.springframework.data.mapping.PersistentPropertyPath;

View File

@@ -29,6 +29,7 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
import org.springframework.data.mapping.model.Property;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.relational.core.mapping.Embedded.OnEmpty;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Optionals;
import org.springframework.lang.Nullable;
@@ -57,9 +58,9 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
}
private final RelationalMappingContext context;
private final Lazy<Optional<String>> columnName;
private final Lazy<String> columnName;
private final Lazy<Optional<String>> collectionIdColumnName;
private final Lazy<Optional<String>> collectionKeyColumnName;
private final Lazy<String> collectionKeyColumnName;
private final Lazy<Boolean> isEmbedded;
private final Lazy<String> embeddedPrefix;
private final Lazy<Class<?>> columnType = Lazy.of(this::doGetColumnType);
@@ -89,7 +90,8 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
this.columnName = Lazy.of(() -> Optional.ofNullable(findAnnotation(Column.class)) //
.map(Column::value) //
.filter(StringUtils::hasText));
.filter(StringUtils::hasText) //
.orElseGet(() -> context.getNamingStrategy().getColumnName(this)));
this.collectionIdColumnName = Lazy.of(() -> Optionals
.toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)).map(MappedCollection::idColumn),
@@ -99,7 +101,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
this.collectionKeyColumnName = Lazy.of(() -> Optionals
.toStream(Optional.ofNullable(findAnnotation(MappedCollection.class)).map(MappedCollection::keyColumn),
Optional.ofNullable(findAnnotation(Column.class)).map(Column::keyColumn)) //
.filter(StringUtils::hasText).findFirst());
.filter(StringUtils::hasText).findFirst().orElseGet(() -> context.getNamingStrategy().getKeyColumn(this)));
}
/*
@@ -127,7 +129,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
*/
@Override
public String getColumnName() {
return columnName.get().orElseGet(() -> context.getNamingStrategy().getColumnName(this));
return columnName.get();
}
/**
@@ -188,12 +190,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
@Override
public String getKeyColumn() {
if (isQualified()) {
return collectionKeyColumnName.get().orElseGet(() -> context.getNamingStrategy().getKeyColumn(this));
} else {
return null;
}
return isQualified() ? collectionKeyColumnName.get() : null;
}
@Override
@@ -229,6 +226,18 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
return isEmbedded() ? embeddedPrefix.get() : null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.RelationalPersistentProperty#shouldCreateEmptyEmbedded()
*/
@Override
public boolean shouldCreateEmptyEmbedded() {
Embedded findAnnotation = findAnnotation(Embedded.class);
return findAnnotation != null && OnEmpty.USE_EMPTY.equals(findAnnotation.onEmpty());
}
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2019 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.relational.core.mapping;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.data.util.Lazy;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* A {@link NamingStrategy} to cache the results of the target one.
*
* @author Oliver Drotbohm
* @since 1.1
*/
class CachingNamingStrategy implements NamingStrategy {
private final NamingStrategy delegate;
private final Map<RelationalPersistentProperty, String> columnNames = new ConcurrentHashMap<>();
private final Map<RelationalPersistentProperty, String> keyColumns = new ConcurrentHashMap<>();
private final Map<Class<?>, String> qualifiedTableNames = new ConcurrentReferenceHashMap<>();
private final Map<Class<?>, String> tableNames = new ConcurrentReferenceHashMap<>();
private final Lazy<String> schema;
/**
* Creates a new {@link CachingNamingStrategy} with the given delegate {@link NamingStrategy}.
*
* @param delegate must not be {@literal null}.
*/
public CachingNamingStrategy(NamingStrategy delegate) {
Assert.notNull(delegate, "Delegate must not be null!");
this.delegate = delegate;
this.schema = Lazy.of(delegate::getSchema);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getKeyColumn(org.springframework.data.relational.core.mapping.RelationalPersistentProperty)
*/
@Override
public String getKeyColumn(RelationalPersistentProperty property) {
return keyColumns.computeIfAbsent(property, delegate::getKeyColumn);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getQualifiedTableName(java.lang.Class)
*/
@Override
public String getQualifiedTableName(Class<?> type) {
return qualifiedTableNames.computeIfAbsent(type, delegate::getQualifiedTableName);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getTableName(java.lang.Class)
*/
@Override
public String getTableName(Class<?> type) {
return tableNames.computeIfAbsent(type, delegate::getTableName);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getReverseColumnName(org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension)
*/
@Override
public String getReverseColumnName(PersistentPropertyPathExtension path) {
return delegate.getReverseColumnName(path);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getReverseColumnName(org.springframework.data.relational.core.mapping.RelationalPersistentProperty)
*/
@Override
public String getReverseColumnName(RelationalPersistentProperty property) {
return delegate.getReverseColumnName(property);
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getSchema()
*/
@Override
public String getSchema() {
return schema.get();
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.mapping.NamingStrategy#getColumnName(org.springframework.data.relational.core.mapping.RelationalPersistentProperty)
*/
@Override
public String getColumnName(RelationalPersistentProperty property) {
return columnNames.computeIfAbsent(property, delegate::getColumnName);
}
}

View File

@@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.util.Lazy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -30,13 +31,15 @@ import org.springframework.util.Assert;
* @author Jens Schauder
* @since 1.1
*/
@EqualsAndHashCode
@EqualsAndHashCode(exclude = { "columnAlias", "context" })
public class PersistentPropertyPathExtension {
private final RelationalPersistentEntity<?> entity;
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
private final MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> context;
private final Lazy<String> columnAlias = Lazy.of(() -> prefixWithTableAlias(getColumnName()));
/**
* Creates the empty path referencing the root itself.
*
@@ -188,8 +191,7 @@ public class PersistentPropertyPathExtension {
* @throws IllegalStateException when called on an empty path.
*/
public String getColumnAlias() {
return prefixWithTableAlias(getColumnName());
return columnAlias.get();
}
/**
@@ -316,12 +318,9 @@ public class PersistentPropertyPathExtension {
*/
public PersistentPropertyPathExtension extendBy(RelationalPersistentProperty property) {
PersistentPropertyPath<RelationalPersistentProperty> newPath;
if (path == null) {
newPath = context.getPersistentPropertyPath(property.getName(), entity.getType());
} else {
newPath = context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getType());
}
PersistentPropertyPath<RelationalPersistentProperty> newPath = path == null //
? context.getPersistentPropertyPath(property.getName(), entity.getType()) //
: context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getType());
return new PersistentPropertyPathExtension(context, newPath);
}
@@ -409,12 +408,16 @@ public class PersistentPropertyPathExtension {
if (path.getLength() <= 1) {
return suffix;
}
PersistentPropertyPath<RelationalPersistentProperty> parentPath = path.getParentPath();
RelationalPersistentProperty parentLeaf = parentPath.getRequiredLeafProperty();
if (!parentLeaf.isEmbedded()) {
return suffix;
}
String embeddedPrefix = parentLeaf.getEmbeddedPrefix();
return getParentPath().assembleColumnName(embeddedPrefix + suffix);
}

View File

@@ -54,7 +54,7 @@ public class RelationalMappingContext
Assert.notNull(namingStrategy, "NamingStrategy must not be null!");
this.namingStrategy = namingStrategy;
this.namingStrategy = new CachingNamingStrategy(namingStrategy);
setSimpleTypeHolder(SimpleTypeHolder.DEFAULT);
}

View File

@@ -97,4 +97,11 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio
default String getEmbeddedPrefix() {
return null;
};
/**
* Returns whether an empty embedded object is supposed to be created for this property.
*
* @return
*/
boolean shouldCreateEmptyEmbedded();
}