diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java index 3e4b31440..ac69a5b39 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateTemplate.java @@ -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 Iterable triggerAfterLoad(Iterable all) { - return StreamSupport.stream(all.spliterator(), false).map(e -> { + List 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 triggerAfterLoad(Object id, T entity) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java index 785a24171..4ea475291 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGeneratorSource.java +++ b/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; 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 sqlGeneratorCache = new ConcurrentHashMap<>(); + private final Map, 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))); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java index 9ada3729f..84362f997 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcMappingContext.java @@ -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 RelationalPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + + RelationalPersistentEntity entity = super.createPersistentEntity(typeInformation); + PreferredConstructor constructor = entity.getPersistenceConstructor(); + + if (constructor == null) { + return entity; + } + + for (Parameter 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) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java index bc50a366f..6fdb9bdef 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorFixedNamingStrategyUnitTests.java +++ b/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.*; 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; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java index 47a1f94be..300862335 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java @@ -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> columnName; + private final Lazy columnName; private final Lazy> collectionIdColumnName; - private final Lazy> collectionKeyColumnName; + private final Lazy collectionKeyColumnName; private final Lazy isEmbedded; private final Lazy embeddedPrefix; private final Lazy> 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()); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java new file mode 100644 index 000000000..3ba48009b --- /dev/null +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/CachingNamingStrategy.java @@ -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 columnNames = new ConcurrentHashMap<>(); + private final Map keyColumns = new ConcurrentHashMap<>(); + private final Map, String> qualifiedTableNames = new ConcurrentReferenceHashMap<>(); + private final Map, String> tableNames = new ConcurrentReferenceHashMap<>(); + + private final Lazy 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); + } +} diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java index 0f7bce65a..85f75a7df 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyPathExtension.java @@ -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 path; private final MappingContext, RelationalPersistentProperty> context; + private final Lazy 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 newPath; - if (path == null) { - newPath = context.getPersistentPropertyPath(property.getName(), entity.getType()); - } else { - newPath = context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), entity.getType()); - } + PersistentPropertyPath 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 parentPath = path.getParentPath(); RelationalPersistentProperty parentLeaf = parentPath.getRequiredLeafProperty(); + if (!parentLeaf.isEmbedded()) { return suffix; } + String embeddedPrefix = parentLeaf.getEmbeddedPrefix(); + return getParentPath().assembleColumnName(embeddedPrefix + suffix); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java index e4fe625b6..99d7d9f2e 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalMappingContext.java @@ -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); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java index d4e1e33f3..048554246 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentProperty.java @@ -97,4 +97,11 @@ public interface RelationalPersistentProperty extends PersistentProperty