Browse Source

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.
pull/165/head
Oliver Drotbohm 6 years ago
parent
commit
5a8abe73e6
  1. 14
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java
  2. 8
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java
  3. 27
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java
  4. 1
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java
  5. 31
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java
  6. 117
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java
  7. 21
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java
  8. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java
  9. 7
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java

14
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java

@ -15,10 +15,10 @@ @@ -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 { @@ -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) {

8
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java

@ -18,9 +18,9 @@ package org.springframework.data.jdbc.core.convert; @@ -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 @@ -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)));
}
}

27
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java

@ -15,6 +15,8 @@ @@ -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 @@ -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; @@ -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 { @@ -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)

1
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java

@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.*; @@ -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;

31
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java

@ -29,6 +29,7 @@ import org.springframework.data.mapping.PersistentEntity; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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());
}

117
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java

@ -0,0 +1,117 @@ @@ -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);
}
}

21
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java

@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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);
}

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java

@ -54,7 +54,7 @@ public class RelationalMappingContext @@ -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);
}

7
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java

@ -97,4 +97,11 @@ public interface RelationalPersistentProperty extends PersistentProperty<Relatio @@ -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();
}

Loading…
Cancel
Save