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