diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java index 0eb4ce5ab..636398652 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverter.java @@ -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; @@ -1424,8 +1424,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) { @@ -1436,19 +1435,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 (PersistentPropertyAccessor delegate, diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/AnnotatedCassandraConstructorProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/AnnotatedCassandraConstructorProperty.java similarity index 88% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/AnnotatedCassandraConstructorProperty.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/AnnotatedCassandraConstructorProperty.java index e44285428..6e73b320d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/AnnotatedCassandraConstructorProperty.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/AnnotatedCassandraConstructorProperty.java @@ -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; + + private final MergedAnnotation 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 diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java index f3d3da72f..deb55daa5 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/BasicCassandraPersistentEntity.java @@ -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 extends BasicPersistentEntity, CassandraPersistentProperty> constructorProperties = new ConcurrentHashMap<>(); + /** * Create a new {@link BasicCassandraPersistentEntity} given {@link TypeInformation}. * @@ -213,4 +219,25 @@ public class BasicCassandraPersistentEntity extends BasicPersistentEntity 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()); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConstructorProperty.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraConstructorProperty.java similarity index 87% rename from spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConstructorProperty.java rename to spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraConstructorProperty.java index 299c9ae99..40db8cf2d 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/convert/CassandraConstructorProperty.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraConstructorProperty.java @@ -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 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 constructorParameter, + CassandraPersistentEntity owner) { + this.constructorParameter = constructorParameter; + this.name = constructorParameter.getName(); this.owner = owner; - this.typeInformation = typeInformation; + this.typeInformation = constructorParameter.getType(); } @Nullable @@ -301,4 +303,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(); + } } diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java index 20284dd85..8d9731512 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/CassandraPersistentEntity.java @@ -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 extends PersistentEntity { + /** + * 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.2.3 + */ + @Nullable + CassandraPersistentProperty getProperty(Parameter parameter); + /** * Returns whether this entity represents a composite primary key. */ diff --git a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EmbeddedEntityOperations.java b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EmbeddedEntityOperations.java index 74cd7501d..8006602dc 100644 --- a/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EmbeddedEntityOperations.java +++ b/spring-data-cassandra/src/main/java/org/springframework/data/cassandra/core/mapping/EmbeddedEntityOperations.java @@ -311,6 +311,13 @@ public class EmbeddedEntityOperations { return delegate.requiresPropertyPopulation(); } + @Nullable + @Override + public CassandraPersistentProperty getProperty(Parameter parameter) { + CassandraPersistentProperty property = delegate.getProperty(parameter); + return property == null ? null : wrap(property); + } + @NotNull @Override public Iterator iterator() { @@ -330,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); } @@ -646,5 +675,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; + } } } diff --git a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java index 35930c8b0..9641502c0 100755 --- a/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java +++ b/spring-data-cassandra/src/test/java/org/springframework/data/cassandra/core/convert/MappingCassandraConverterUnitTests.java @@ -36,6 +36,8 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; + +import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.converter.Converter; import org.springframework.data.annotation.Id; @@ -53,6 +55,7 @@ import org.springframework.data.cassandra.domain.User; import org.springframework.data.cassandra.domain.UserToken; import org.springframework.data.cassandra.support.UserDefinedTypeBuilder; import org.springframework.data.cassandra.test.util.RowMockUtil; +import org.springframework.data.convert.SimplePropertyValueConversions; import org.springframework.data.convert.ValueConverter; import org.springframework.data.projection.EntityProjection; import org.springframework.data.projection.EntityProjectionIntrospector; @@ -456,8 +459,7 @@ public class MappingCassandraConverterUnitTests { rowMock = RowMockUtil.newRowMock(column("id", "my-id", DataTypes.ASCII), column("localDate", LocalDate.of(2010, 7, 4), DataTypes.DATE)); - TypeWithLocalDateMappedToDate result = converter.readRow(TypeWithLocalDateMappedToDate.class, - rowMock); + TypeWithLocalDateMappedToDate result = converter.readRow(TypeWithLocalDateMappedToDate.class, rowMock); assertThat(result.localDate).isNotNull(); assertThat(result.localDate.getYear()).isEqualTo(2010); @@ -581,8 +583,7 @@ public class MappingCassandraConverterUnitTests { void shouldFailWriteWhereConditionUsingEntityWithNullId() { assertThatIllegalArgumentException().isThrownBy( - () -> converter.write(new User(), new Where(), - mappingContext.getRequiredPersistentEntity(User.class))); + () -> converter.write(new User(), new Where(), mappingContext.getRequiredPersistentEntity(User.class))); } @Test // DATACASS-308 @@ -604,8 +605,7 @@ public class MappingCassandraConverterUnitTests { entity.setFirstname("Walter"); entity.setLastname("White"); - converter.write(entity, where, - mappingContext.getRequiredPersistentEntity(TypeWithCompositeKey.class)); + converter.write(entity, where, mappingContext.getRequiredPersistentEntity(TypeWithCompositeKey.class)); assertThat(where).containsEntry(CqlIdentifier.fromCql("firstname"), "Walter"); assertThat(where).containsEntry(CqlIdentifier.fromCql("lastname"), "White"); @@ -643,8 +643,7 @@ public class MappingCassandraConverterUnitTests { Where where = new Where(); - converter.write(Condition.MINT, where, - mappingContext.getRequiredPersistentEntity(EnumPrimaryKey.class)); + converter.write(Condition.MINT, where, mappingContext.getRequiredPersistentEntity(EnumPrimaryKey.class)); assertThat(where).containsEntry(CqlIdentifier.fromCql("condition"), "MINT"); } @@ -682,8 +681,8 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-308 void shouldFailWritingWhereConditionForTypeWithPkClassKeyUsingEntityWithNullId() { - assertThatIllegalArgumentException().isThrownBy(() -> converter.write(new TypeWithKeyClass(), - new Where(), mappingContext.getRequiredPersistentEntity(TypeWithKeyClass.class))); + assertThatIllegalArgumentException().isThrownBy(() -> converter.write(new TypeWithKeyClass(), new Where(), + mappingContext.getRequiredPersistentEntity(TypeWithKeyClass.class))); } @Test // DATACASS-308 @@ -721,8 +720,7 @@ public class MappingCassandraConverterUnitTests { Row row = RowMockUtil.newRowMock(column("firstname", "Walter", DataTypes.TEXT), column("lastname", "White", DataTypes.TEXT)); - TableWithCompositeKeyViaConstructor result = converter - .read(TableWithCompositeKeyViaConstructor.class, row); + TableWithCompositeKeyViaConstructor result = converter.read(TableWithCompositeKeyViaConstructor.class, row); assertThat(result.key.firstname).isEqualTo("Walter"); assertThat(result.key.lastname).isEqualTo("White"); @@ -743,8 +741,8 @@ public class MappingCassandraConverterUnitTests { @Test // DATACASS-308 void shouldFailWhereConditionForTypeWithPkClassKeyUsingMapIdHavingUnknownProperty() { - assertThatIllegalArgumentException().isThrownBy(() -> converter.write(id("unknown", "Walter"), - new Where(), mappingContext.getRequiredPersistentEntity(TypeWithMapId.class))); + assertThatIllegalArgumentException().isThrownBy(() -> converter.write(id("unknown", "Walter"), new Where(), + mappingContext.getRequiredPersistentEntity(TypeWithMapId.class))); } @Test // DATACASS-362 @@ -854,8 +852,7 @@ public class MappingCassandraConverterUnitTests { RowMockUtil.column("other", "Some other value", DataTypes.TEXT), RowMockUtil.column("tuple", tupleValue, tupleType)); - TypeWithPropertyValueConverter result = converter.read(TypeWithPropertyValueConverter.class, - rowMock); + TypeWithPropertyValueConverter result = converter.read(TypeWithPropertyValueConverter.class, rowMock); assertThat(result.name).isEqualTo("Other: Some other value, reversed: Walter"); assertThat(result.other).isEqualTo("Some other value"); @@ -999,8 +996,7 @@ public class MappingCassandraConverterUnitTests { RowMockUtil.column("firstname", "Heisenberg", DataTypes.ASCII), RowMockUtil.column("lastname", "White", DataTypes.ASCII)); - WithColumnAnnotationInConstructor converted = this.converter - .read(WithColumnAnnotationInConstructor.class, rowMock); + WithColumnAnnotationInConstructor converted = this.converter.read(WithColumnAnnotationInConstructor.class, rowMock); assertThat(converted.firstname).isEqualTo("Walter"); assertThat(converted.lastname).isEqualTo("White"); @@ -1078,6 +1074,43 @@ public class MappingCassandraConverterUnitTests { assertThat(result.tuple().one).isEqualTo("One"); } + @Test // GH-1471 + void propertyValueConversionsCacheShouldConsiderPropertyEquality() { + + rowMock = RowMockUtil.newRowMock(RowMockUtil.column("fn", "Walter", DataTypes.ASCII), + RowMockUtil.column("lastname", "White", DataTypes.ASCII), + RowMockUtil.column("n_firstname", "Heisenberg", DataTypes.ASCII), + RowMockUtil.column("c_firstname", "", DataTypes.ASCII), // required as marker to prevent Nullable embedded being + // null + RowMockUtil.column("c_fn", "Nested Walter", DataTypes.ASCII), + RowMockUtil.column("c_lastname", "Nested White", DataTypes.ASCII)); + + SimplePropertyValueConversions spvc = (SimplePropertyValueConversions) converter.getCustomConversions() + .getPropertyValueConversions(); + DirectFieldAccessor accessor = new DirectFieldAccessor(spvc.getConverterFactory()); + Map cache = (Map) new DirectFieldAccessor(accessor.getPropertyValue("cache")) + .getPropertyValue("perPropertyCache"); + + assertThat(cache).isEmpty(); + + SourceForPersistentPropertyDerivation converted = this.converter.read(SourceForPersistentPropertyDerivation.class, + rowMock); + + assertThat(converted.firstname).isEqualTo("Walter"); + assertThat(converted.lastname).isEqualTo("White"); + assertThat(converted.name.firstname()).isEqualTo("Heisenberg"); + assertThat(converted.constructor.firstname).isEqualTo("Nested Walter"); + assertThat(converted.constructor.lastname).isEqualTo("Nested White"); + + assertThat(cache).hasSize(5); + + for (int i = 0; i < 10; i++) { + this.converter.read(SourceForPersistentPropertyDerivation.class, rowMock); + } + + assertThat(cache).hasSize(5); + } + private static List getValues(Map statement) { return new ArrayList<>(statement.values()); } @@ -1362,6 +1395,19 @@ public class MappingCassandraConverterUnitTests { } } + private static class SourceForPersistentPropertyDerivation { + + String firstname; + final @Transient String lastname; + @Embedded.Nullable("n_") Name name; + @Embedded.Nullable("c_") WithColumnAnnotationInConstructor constructor; + + public SourceForPersistentPropertyDerivation(@Column("fn") String fn, @Column("lastname") String lastname) { + this.firstname = fn; + this.lastname = lastname; + } + } + private static class WithMappedTuple { String firstname; @@ -1569,8 +1615,7 @@ public class MappingCassandraConverterUnitTests { Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("prefixage", 30, DataTypes.INT), column("prefixfirstname", "fn", DataTypes.TEXT)); - WithPrefixedNullableEmbeddedType target = converter.read(WithPrefixedNullableEmbeddedType.class, - source); + WithPrefixedNullableEmbeddedType target = converter.read(WithPrefixedNullableEmbeddedType.class, source); assertThat(target.nested).isEqualTo(new EmbeddedWithSimpleTypes("fn", 30, null)); } @@ -1614,14 +1659,12 @@ public class MappingCassandraConverterUnitTests { Row source = RowMockUtil.newRowMock(column("id", "id-1", DataTypes.TEXT), column("name", "my-book", DataTypes.INT), column("author", udtValue, authorType)); - EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create( - converter.getProjectionFactory(), + EntityProjectionIntrospector introspector = EntityProjectionIntrospector.create(converter.getProjectionFactory(), EntityProjectionIntrospector.ProjectionPredicate.typeHierarchy() .and((target, underlyingType) -> !converter.getCustomConversions().isSimpleType(target)), mappingContext); - BookProjection projection = converter - .project(introspector.introspect(BookProjection.class, Book.class), source); + BookProjection projection = converter.project(introspector.introspect(BookProjection.class, Book.class), source); assertThat(projection.getName()).isEqualTo("my-book by Walter White"); }