From 73ff9cc2e37a87ee700429c370ef2fc9d95fb1c8 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 15 Jan 2026 15:00:57 +0100 Subject: [PATCH] Polishing. Fix nullability issues. See #2217 (cherry picked from commit 08c86dc94eaec428c83d4fe6805ac760d706ff9f) --- .../IterableOfEntryToMapConverter.java | 7 +-- .../core/convert/MappingJdbcConverter.java | 7 ++- .../data/r2dbc/convert/R2dbcConverters.java | 41 ++++++++--------- .../data/r2dbc/dialect/MySqlDialect.java | 5 ++- .../MappingRelationalConverter.java | 45 +++++++++---------- .../core/sql/render/TypedSubtreeVisitor.java | 11 ++--- 6 files changed, 55 insertions(+), 61 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IterableOfEntryToMapConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IterableOfEntryToMapConverter.java index 71b34e85f..2521b29ee 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IterableOfEntryToMapConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IterableOfEntryToMapConverter.java @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; -import org.jspecify.annotations.Nullable; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalConverter; import org.springframework.core.convert.converter.Converter; @@ -32,16 +31,14 @@ import org.springframework.util.Assert; */ class IterableOfEntryToMapConverter implements ConditionalConverter, Converter, Map> { - @SuppressWarnings("unchecked") - @Nullable @Override public Map convert(Iterable source) { - Map result = new HashMap(); + Map result = new HashMap<>(); source.forEach(element -> { - if (element instanceof Entry entry) { + if (element instanceof Entry entry) { result.put(entry.getKey(), entry.getValue()); return; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java index 2e7f22e35..6697befd8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java @@ -200,8 +200,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements * @return */ @Override - @Nullable - public Object readValue(@Nullable Object value, TypeInformation targetType) { + public @Nullable Object readValue(@Nullable Object value, TypeInformation targetType) { if (null == value) { return null; @@ -573,12 +572,12 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements Identifier identifier) implements ConversionContext { @Override - public S convert(Object source, TypeInformation typeHint) { + public @Nullable S convert(Object source, TypeInformation typeHint) { return delegate.convert(source, typeHint); } @Override - public S convert(Object source, TypeInformation typeHint, ConversionContext context) { + public @Nullable S convert(Object source, TypeInformation typeHint, ConversionContext context) { return delegate.convert(source, typeHint, context); } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverters.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverters.java index 76c6f7714..741726880 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverters.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/R2dbcConverters.java @@ -70,12 +70,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToBooleanConverter implements Converter { + public enum RowToBooleanConverter implements Converter { INSTANCE; @Override - public @Nullable Boolean convert(Row row) { + public Boolean convert(Row row) { return row.get(0, Boolean.class); } } @@ -85,12 +85,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToLocalDateConverter implements Converter { + public enum RowToLocalDateConverter implements Converter { INSTANCE; @Override - public @Nullable LocalDate convert(Row row) { + public LocalDate convert(Row row) { return row.get(0, LocalDate.class); } } @@ -100,12 +100,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToLocalDateTimeConverter implements Converter { + public enum RowToLocalDateTimeConverter implements Converter { INSTANCE; @Override - public @Nullable LocalDateTime convert(Row row) { + public LocalDateTime convert(Row row) { return row.get(0, LocalDateTime.class); } } @@ -115,12 +115,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToLocalTimeConverter implements Converter { + public enum RowToLocalTimeConverter implements Converter { INSTANCE; @Override - public @Nullable LocalTime convert(Row row) { + public LocalTime convert(Row row) { return row.get(0, LocalTime.class); } } @@ -141,20 +141,21 @@ abstract class R2dbcConverters { * @see java.math.BigDecimal * @author Hebert Coelho */ - public enum RowToNumberConverterFactory implements ConverterFactory { + public enum RowToNumberConverterFactory implements ConverterFactory { INSTANCE; @Override - public Converter getConverter(Class targetType) { + public Converter getConverter(Class targetType) { Assert.notNull(targetType, "Target type must not be null"); return new RowToNumber<>(targetType); } - record RowToNumber(Class targetType) implements Converter { + @SuppressWarnings("NullAway") + record RowToNumber(Class targetType) implements Converter { @Override - public @Nullable T convert(Row source) { + public T convert(Row source) { Object object = source.get(0); @@ -168,12 +169,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToOffsetDateTimeConverter implements Converter { + public enum RowToOffsetDateTimeConverter implements Converter { INSTANCE; @Override - public @Nullable OffsetDateTime convert(Row row) { + public OffsetDateTime convert(Row row) { return row.get(0, OffsetDateTime.class); } } @@ -183,12 +184,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToStringConverter implements Converter { + public enum RowToStringConverter implements Converter { INSTANCE; @Override - public @Nullable String convert(Row row) { + public String convert(Row row) { return row.get(0, String.class); } } @@ -198,12 +199,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToUuidConverter implements Converter { + public enum RowToUuidConverter implements Converter { INSTANCE; @Override - public @Nullable UUID convert(Row row) { + public UUID convert(Row row) { return row.get(0, UUID.class); } } @@ -213,12 +214,12 @@ abstract class R2dbcConverters { * * @author Hebert Coelho */ - public enum RowToZonedDateTimeConverter implements Converter { + public enum RowToZonedDateTimeConverter implements Converter { INSTANCE; @Override - public @Nullable ZonedDateTime convert(Row row) { + public ZonedDateTime convert(Row row) { return row.get(0, ZonedDateTime.class); } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java index ebd2b4c3b..7f9e8a277 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/MySqlDialect.java @@ -80,12 +80,13 @@ public class MySqlDialect extends org.springframework.data.relational.core.diale * @author Michael Berry */ @ReadingConverter - public enum ByteToBooleanConverter implements Converter { + @SuppressWarnings("NullAway") + public enum ByteToBooleanConverter implements Converter { INSTANCE; @Override - public @Nullable Boolean convert(@Nullable Byte s) { + public Boolean convert(@Nullable Byte s) { if (s == null) { return null; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java index c65d4f59b..2483038a0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MappingRelationalConverter.java @@ -352,9 +352,7 @@ public class MappingRelationalConverter extends AbstractRelationalConverter if (getConversions().hasCustomReadTarget(RowDocument.class, rawType)) { S converted = doConvert(documentAccessor.getDocument(), rawType, typeHint.getType()); - Assert.state(converted != null, "Converted must not be null"); - return converted; } @@ -363,7 +361,10 @@ public class MappingRelationalConverter extends AbstractRelationalConverter } if (typeHint.isMap()) { - return context.convert(documentAccessor, typeHint); + + S converted = context.convert(documentAccessor, typeHint); + Assert.state(converted != null, "Converted must not be null"); + return converted; } RelationalPersistentEntity entity = getMappingContext().getPersistentEntity(typeHint); @@ -425,7 +426,7 @@ public class MappingRelationalConverter extends AbstractRelationalConverter * @param targetType the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link RowDocument}. * @return the converted {@link Collection} or array, will never be {@literal null}. */ - protected @Nullable Object readCollectionOrArray(ConversionContext context, Collection source, + protected Object readCollectionOrArray(ConversionContext context, Collection source, TypeInformation targetType) { Assert.notNull(targetType, "Target type must not be null"); @@ -444,14 +445,14 @@ public class MappingRelationalConverter extends AbstractRelationalConverter : CollectionFactory.createCollection(collectionType, rawComponentType, source.size()); if (source.isEmpty()) { - return getPotentiallyConvertedSimpleRead(items, targetType); + return getRequiredPotentiallyConvertedSimpleRead(items, targetType); } for (Object element : source) { items.add(element != null ? context.convert(element, componentType) : element); } - return getPotentiallyConvertedSimpleRead(items, targetType); + return getRequiredPotentiallyConvertedSimpleRead(items, targetType); } private @Nullable T doConvert(Object value, Class target) { @@ -637,17 +638,17 @@ public class MappingRelationalConverter extends AbstractRelationalConverter * * @param value a value as it is returned by the driver accessing the persistence store. May be {@literal null}. * @param targetType {@link TypeInformation} into which the value is to be converted. Must not be {@literal null}. - * @return The converted value. May be {@literal null}. + * @return the converted value, can be {@literal null}. */ @Override - @Nullable - public Object readValue(@Nullable Object value, TypeInformation targetType) { - - if (null == value) { - return null; - } + public @Nullable Object readValue(@Nullable Object value, TypeInformation targetType) { + return null == value ? null : getPotentiallyConvertedSimpleRead(value, targetType); + } - return getPotentiallyConvertedSimpleRead(value, targetType); + private Object getRequiredPotentiallyConvertedSimpleRead(Object value, TypeInformation type) { + Object result = getPotentiallyConvertedSimpleRead(value, type); + Assert.state(result != null, "Converted must not be null"); + return result; } /** @@ -865,7 +866,7 @@ public class MappingRelationalConverter extends AbstractRelationalConverter @SuppressWarnings("unchecked") @Override - public S convert(Object source, TypeInformation typeHint, ConversionContext context) { + public @Nullable S convert(Object source, TypeInformation typeHint, ConversionContext context) { Assert.notNull(source, "Source must not be null"); Assert.notNull(typeHint, "TypeInformation must not be null"); @@ -875,7 +876,6 @@ public class MappingRelationalConverter extends AbstractRelationalConverter } if (source instanceof Collection collection) { - if (typeHint.isCollectionLike() || typeHint.getType().isAssignableFrom(Collection.class)) { return (S) collectionConverter.convert(context, collection, typeHint); } @@ -1011,9 +1011,9 @@ public class MappingRelationalConverter extends AbstractRelationalConverter * * @param source must not be {@literal null}. * @param typeHint must not be {@literal null}. - * @return the converted object. + * @return the converted object, can be {@literal null}. */ - default S convert(Object source, TypeInformation typeHint) { + default @Nullable S convert(Object source, TypeInformation typeHint) { return convert(source, typeHint, this); } @@ -1023,9 +1023,9 @@ public class MappingRelationalConverter extends AbstractRelationalConverter * @param source must not be {@literal null}. * @param typeHint must not be {@literal null}. * @param context must not be {@literal null}. - * @return the converted object. + * @return the converted object, can be {@literal null}. */ - S convert(Object source, TypeInformation typeHint, ConversionContext context); + @Nullable S convert(Object source, TypeInformation typeHint, ConversionContext context); /** * Obtain a {@link ConversionContext} for the given property {@code name}. @@ -1186,9 +1186,8 @@ public class MappingRelationalConverter extends AbstractRelationalConverter } @Override - @Nullable @SuppressWarnings("unchecked") - public T getPropertyValue(RelationalPersistentProperty property) { + public @Nullable T getPropertyValue(RelationalPersistentProperty property) { String expression = property.getSpelExpression(); Object value = expression != null ? evaluator.evaluate(expression) : accessor.get(property); @@ -1353,7 +1352,7 @@ public class MappingRelationalConverter extends AbstractRelationalConverter } @Override - protected T potentiallyConvertExpressionValue(Object object, + protected @Nullable T potentiallyConvertExpressionValue(Object object, Parameter parameter) { return context.convert(object, parameter.getType()); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java index 52a35ac8f..0b150fbce 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/render/TypedSubtreeVisitor.java @@ -15,6 +15,7 @@ */ package org.springframework.data.relational.core.sql.render; +import java.util.Objects; import java.util.function.Predicate; import org.jspecify.annotations.Nullable; @@ -50,23 +51,19 @@ abstract class TypedSubtreeVisitor extends DelegatingVisito private final ResolvableType type; private @Nullable Visitable currentSegment; - enum Assignable { - YES, NO, - } - /** * Creates a new {@link TypedSubtreeVisitor}. */ TypedSubtreeVisitor() { - this.type = refCache.computeIfAbsent(this.getClass(), - key -> ResolvableType.forClass(key).as(TypedSubtreeVisitor.class).getGeneric(0)); + this.type = Objects.requireNonNull(refCache.computeIfAbsent(this.getClass(), + key -> ResolvableType.forClass(key).as(TypedSubtreeVisitor.class).getGeneric(0))); } /** * Creates a new {@link TypedSubtreeVisitor} with an explicitly provided type. */ TypedSubtreeVisitor(Class type) { - this.type = refCache.computeIfAbsent(type, key -> ResolvableType.forClass(type)); + this.type = Objects.requireNonNull(refCache.computeIfAbsent(type, key -> ResolvableType.forClass(type))); } /**