diff --git a/.gitignore b/.gitignore index d9642d2c66..37d0ff8eee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ target/ .idea/ +.vscode/ .settings/ *.iml .flattened-pom.xml diff --git a/pom.xml b/pom.xml index 98f945a6b2..296c68636a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-relational-parent - 4.0.0-SNAPSHOT + 4.0.0-1980-jspecify-SNAPSHOT pom Spring Data Relational Parent diff --git a/spring-data-jdbc-distribution/pom.xml b/spring-data-jdbc-distribution/pom.xml index b3c39e64c3..9a73f9bd9a 100644 --- a/spring-data-jdbc-distribution/pom.xml +++ b/spring-data-jdbc-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-relational-parent - 4.0.0-SNAPSHOT + 4.0.0-1980-jspecify-SNAPSHOT ../pom.xml diff --git a/spring-data-jdbc/pom.xml b/spring-data-jdbc/pom.xml index e61fd64020..1db4d455ef 100644 --- a/spring-data-jdbc/pom.xml +++ b/spring-data-jdbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-jdbc - 4.0.0-SNAPSHOT + 4.0.0-1980-jspecify-SNAPSHOT Spring Data JDBC Spring Data module for JDBC repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 4.0.0-SNAPSHOT + 4.0.0-1980-jspecify-SNAPSHOT diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/JdbcRuntimeHints.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/JdbcRuntimeHints.java index 3a5eb3a73e..a788e59f6b 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/JdbcRuntimeHints.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/aot/JdbcRuntimeHints.java @@ -17,6 +17,7 @@ import java.util.Arrays; +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -30,7 +31,6 @@ import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback; import org.springframework.data.relational.core.mapping.event.BeforeDeleteCallback; import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback; -import org.springframework.lang.Nullable; /** * {@link RuntimeHintsRegistrar} for JDBC. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java index 75579d83a4..b1f33efcaa 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutionContext.java @@ -15,20 +15,12 @@ */ package org.springframework.data.jdbc.core; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; @@ -48,7 +40,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.LockMode; import org.springframework.data.util.Pair; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -82,8 +73,8 @@ class JdbcAggregateChangeExecutionContext { void executeInsertRoot(DbAction.InsertRoot insert) { - Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), Identifier.empty(), - insert.getIdValueSource()); + Object id = accessStrategy.insert(insert.entity(), insert.getEntityType(), Identifier.empty(), + insert.idValueSource()); add(new DbActionExecutionResult(insert, id)); } @@ -91,7 +82,7 @@ void executeBatchInsertRoot(DbAction.BatchInsertRoot batchInsertRoot) { List> inserts = batchInsertRoot.getActions(); List> insertSubjects = inserts.stream() - .map(insert -> InsertSubject.describedBy(insert.getEntity(), Identifier.empty())).collect(Collectors.toList()); + .map(insert -> InsertSubject.describedBy(insert.entity(), Identifier.empty())).collect(Collectors.toList()); Object[] ids = accessStrategy.insert(insertSubjects, batchInsertRoot.getEntityType(), batchInsertRoot.getBatchValue()); @@ -104,8 +95,8 @@ void executeBatchInsertRoot(DbAction.BatchInsertRoot batchInsertRoot) { void executeInsert(DbAction.Insert insert) { Identifier parentKeys = getParentKeys(insert, converter); - Object id = accessStrategy.insert(insert.getEntity(), insert.getEntityType(), parentKeys, - insert.getIdValueSource()); + Object id = accessStrategy.insert(insert.entity(), insert.getEntityType(), parentKeys, + insert.idValueSource()); add(new DbActionExecutionResult(insert, id)); } @@ -113,7 +104,7 @@ void executeBatchInsert(DbAction.BatchInsert batchInsert) { List> inserts = batchInsert.getActions(); List> insertSubjects = inserts.stream() - .map(insert -> InsertSubject.describedBy(insert.getEntity(), getParentKeys(insert, converter))) + .map(insert -> InsertSubject.describedBy(insert.entity(), getParentKeys(insert, converter))) .collect(Collectors.toList()); Object[] ids = accessStrategy.insert(insertSubjects, batchInsert.getEntityType(), batchInsert.getBatchValue()); @@ -135,27 +126,27 @@ void executeUpdateRoot(DbAction.UpdateRoot update) { void executeDeleteRoot(DbAction.DeleteRoot delete) { - if (delete.getPreviousVersion() != null) { - accessStrategy.deleteWithVersion(delete.getId(), delete.getEntityType(), delete.getPreviousVersion()); + if (delete.previousVersion() != null) { + accessStrategy.deleteWithVersion(delete.id(), delete.getEntityType(), delete.previousVersion()); } else { - accessStrategy.delete(delete.getId(), delete.getEntityType()); + accessStrategy.delete(delete.id(), delete.getEntityType()); } } void executeBatchDeleteRoot(DbAction.BatchDeleteRoot batchDelete) { - List rootIds = batchDelete.getActions().stream().map(DbAction.DeleteRoot::getId).toList(); + List rootIds = batchDelete.getActions().stream().map(DbAction.DeleteRoot::id).toList(); accessStrategy.delete(rootIds, batchDelete.getEntityType()); } void executeDelete(DbAction.Delete delete) { - accessStrategy.delete(delete.getRootId(), delete.getPropertyPath()); + accessStrategy.delete(delete.rootId(), delete.propertyPath()); } void executeBatchDelete(DbAction.BatchDelete batchDelete) { - List rootIds = batchDelete.getActions().stream().map(DbAction.Delete::getRootId).toList(); + List rootIds = batchDelete.getActions().stream().map(DbAction.Delete::rootId).toList(); accessStrategy.delete(rootIds, batchDelete.getBatchValue()); } @@ -166,7 +157,7 @@ void executeDeleteAllRoot(DbAction.DeleteAllRoot deleteAllRoot) { void executeDeleteAll(DbAction.DeleteAll delete) { - accessStrategy.deleteAll(delete.getPropertyPath()); + accessStrategy.deleteAll(delete.propertyPath()); } void executeAcquireLock(DbAction.AcquireLockRoot acquireLock) { @@ -185,11 +176,11 @@ private Identifier getParentKeys(DbAction.WithDependingOn action, JdbcConvert Object id = getParentId(action); - AggregatePath aggregatePath = context.getAggregatePath(action.getPropertyPath()); + AggregatePath aggregatePath = context.getAggregatePath(action.propertyPath()); JdbcIdentifierBuilder identifier = JdbcIdentifierBuilder // .forBackReferences(converter, aggregatePath, getIdMapper(id, aggregatePath, converter)); - for (Map.Entry, Object> qualifier : action.getQualifiers() + for (Map.Entry, Object> qualifier : action.qualifiers() .entrySet()) { identifier = identifier.withQualifier(context.getAggregatePath(qualifier.getKey()), qualifier.getValue()); } @@ -200,21 +191,21 @@ private Identifier getParentKeys(DbAction.WithDependingOn action, JdbcConvert static Function getIdMapper(Object idValue, AggregatePath path, JdbcConverter converter) { RelationalPersistentProperty idProperty = path.getIdDefiningParentPath().getRequiredIdProperty(); - RelationalPersistentEntity entity = converter.getMappingContext() - .getPersistentEntity(idProperty); + RelationalPersistentEntity entity = converter.getMappingContext().getPersistentEntity(idProperty); if (entity == null) { return aggregatePath -> idValue; } PersistentPropertyPathAccessor propertyPathAccessor = entity.getPropertyPathAccessor(idValue); - return aggregatePath -> propertyPathAccessor.getProperty(aggregatePath.getSubPathBasedOn(idProperty.getActualType()).getRequiredPersistentPropertyPath()); + return aggregatePath -> propertyPathAccessor + .getProperty(aggregatePath.getSubPathBasedOn(idProperty.getActualType()).getRequiredPersistentPropertyPath()); } private Object getParentId(DbAction.WithDependingOn action) { DbAction.WithEntity idOwningAction = getIdOwningAction(action, - context.getAggregatePath(action.getPropertyPath()).getIdDefiningParentPath()); + context.getAggregatePath(action.propertyPath()).getIdDefiningParentPath()); return getPotentialGeneratedIdFrom(idOwningAction); } @@ -229,16 +220,16 @@ private DbAction.WithEntity getIdOwningAction(DbAction.WithEntity action, return action; } - if (idPath.equals(context.getAggregatePath(withDependingOn.getPropertyPath()))) { + if (idPath.equals(context.getAggregatePath(withDependingOn.propertyPath()))) { return action; } - return getIdOwningAction(withDependingOn.getDependingOn(), idPath); + return getIdOwningAction(withDependingOn.dependingOn(), idPath); } private Object getPotentialGeneratedIdFrom(DbAction.WithEntity idOwningAction) { - if (IdValueSource.GENERATED.equals(idOwningAction.getIdValueSource())) { + if (IdValueSource.GENERATED.equals(idOwningAction.idValueSource())) { DbActionExecutionResult dbActionExecutionResult = results.get(idOwningAction); Object generatedId = Optional.ofNullable(dbActionExecutionResult) // @@ -256,7 +247,7 @@ private Object getPotentialGeneratedIdFrom(DbAction.WithEntity idOwningAction private Object getIdFrom(DbAction.WithEntity idOwningAction) { RelationalPersistentEntity persistentEntity = getRequiredPersistentEntity(idOwningAction.getEntityType()); - Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.getEntity()).getIdentifier(); + Object identifier = persistentEntity.getIdentifierAccessor(idOwningAction.entity()).getIdentifier(); Assert.state(identifier != null, () -> "Couldn't obtain a required id value for " + persistentEntity); @@ -290,12 +281,12 @@ List populateIdsIfNecessary() { Pair qualifier = insert.getQualifier(); Object qualifierValue = qualifier == null ? null : qualifier.getSecond(); - if (newEntity != action.getEntity()) { + if (newEntity != action.entity()) { - cascadingValues.stage(insert.getDependingOn(), insert.getPropertyPath(), qualifierValue, newEntity); - } else if (insert.getPropertyPath().getLeafProperty().isCollectionLike()) { + cascadingValues.stage(insert.dependingOn(), insert.propertyPath(), qualifierValue, newEntity); + } else if (insert.propertyPath().getLeafProperty().isCollectionLike()) { - cascadingValues.gather(insert.getDependingOn(), insert.getPropertyPath(), qualifierValue, newEntity); + cascadingValues.gather(insert.dependingOn(), insert.propertyPath(), qualifierValue, newEntity); } } } @@ -315,14 +306,14 @@ List populateIdsIfNecessary() { private Object setIdAndCascadingProperties(DbAction.WithEntity action, @Nullable Object generatedId, StagedValues cascadingValues) { - S originalEntity = action.getEntity(); + S originalEntity = action.entity(); RelationalPersistentEntity persistentEntity = (RelationalPersistentEntity) context .getRequiredPersistentEntity(action.getEntityType()); PersistentPropertyPathAccessor propertyAccessor = converter.getPropertyAccessor(persistentEntity, originalEntity); - if (IdValueSource.GENERATED.equals(action.getIdValueSource())) { + if (IdValueSource.GENERATED.equals(action.idValueSource())) { propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId); } @@ -337,7 +328,7 @@ private Object setIdAndCascadingProperties(DbAction.WithEntity action, @N private PersistentPropertyPath getRelativePath(DbAction action, PersistentPropertyPath pathToValue) { if (action instanceof DbAction.Insert insert) { - return pathToValue.getExtensionForBaseOf(insert.getPropertyPath()); + return pathToValue.getExtensionForBaseOf(insert.propertyPath()); } if (action instanceof DbAction.InsertRoot) { @@ -358,10 +349,10 @@ private RelationalPersistentEntity getRequiredPersistentEntity(Class t private void updateWithoutVersion(DbAction.UpdateRoot update) { - if (!accessStrategy.update(update.getEntity(), update.getEntityType())) { + if (!accessStrategy.update(update.entity(), update.getEntityType())) { throw new IncorrectUpdateSemanticsDataAccessException( - String.format(UPDATE_FAILED, update.getEntity(), getIdFrom(update))); + String.format(UPDATE_FAILED, update.entity(), getIdFrom(update))); } } @@ -370,9 +361,9 @@ private void updateWithVersion(DbAction.UpdateRoot update) { Number previousVersion = update.getPreviousVersion(); Assert.notNull(previousVersion, "The root aggregate cannot be updated because the version property is null"); - if (!accessStrategy.updateWithVersion(update.getEntity(), update.getEntityType(), previousVersion)) { + if (!accessStrategy.updateWithVersion(update.entity(), update.getEntityType(), previousVersion)) { - throw new OptimisticLockingFailureException(String.format(UPDATE_FAILED_OPTIMISTIC_LOCKING, update.getEntity())); + throw new OptimisticLockingFailureException(String.format(UPDATE_FAILED_OPTIMISTIC_LOCKING, update.entity())); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java index 8b9dbd6f33..f98aad06c0 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/JdbcAggregateOperations.java @@ -19,6 +19,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException; import org.springframework.data.domain.Example; import org.springframework.data.domain.Page; @@ -27,7 +28,6 @@ import org.springframework.data.jdbc.core.convert.DataAccessStrategy; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.relational.core.query.Query; -import org.springframework.lang.Nullable; /** * Specifies operations one can perform on a database, based on an Domain Type. @@ -171,8 +171,8 @@ public interface JdbcAggregateOperations { List findAllById(Iterable ids, Class domainType); /** - * Loads all entities that match one of the ids passed as an argument to a {@link Stream}. - * It is not guaranteed that the number of ids passed in matches the number of entities returned. + * Loads all entities that match one of the ids passed as an argument to a {@link Stream}. It is not guaranteed that + * the number of ids passed in matches the number of entities returned. * * @param ids the Ids of the entities to load. Must not be {@code null}. * @param domainType the type of entities to load. Must not be {@code null}. 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 9f5dd08d6b..b1c6e8b2da 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 @@ -28,6 +28,7 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -55,7 +56,6 @@ import org.springframework.data.relational.core.mapping.event.*; import org.springframework.data.relational.core.query.Query; import org.springframework.data.support.PageableExecutionUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -305,7 +305,7 @@ public boolean existsById(Object id, Class domainType) { } @Override - public T findById(Object id, Class domainType) { + public @Nullable T findById(Object id, Class domainType) { Assert.notNull(id, "Id must not be null"); Assert.notNull(domainType, "Domain type must not be null"); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java index be3629f9d7..fb2bf028fb 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/AggregateReader.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.mapping.AggregatePath; @@ -40,7 +41,6 @@ import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; /** * Reads complete Aggregates from the database, by generating appropriate SQL using a {@link SingleQuerySqlGenerator} @@ -94,8 +94,7 @@ public String keyColumn(AggregatePath path) { * @return the found aggregate root, or {@literal null} if not found. * @param aggregator type. */ - @Nullable - public T findById(Object id, RelationalPersistentEntity entity) { + public @Nullable T findById(Object id, RelationalPersistentEntity entity) { Query query = Query.query(Criteria.where(entity.getRequiredIdProperty().getName()).is(id)).limit(1); @@ -110,8 +109,8 @@ public T findById(Object id, RelationalPersistentEntity entity) { * @return the found aggregate root, or {@literal null} if not found. * @param aggregator type. */ - @Nullable - public T findOne(Query query, RelationalPersistentEntity entity) { + @SuppressWarnings("NullAway") // NullAway complains about the ResultSetExtractor returning null, which should be ok. + public @Nullable T findOne(Query query, RelationalPersistentEntity entity) { return doFind(query, entity, rs -> extractZeroOrOne(rs, entity)); } @@ -158,7 +157,8 @@ public List findAll(Query query, RelationalPersistentEntity entity) { } @SuppressWarnings("ConstantConditions") - private R doFind(Query query, RelationalPersistentEntity entity, ResultSetExtractor extractor) { + private R doFind(Query query, RelationalPersistentEntity entity, + ResultSetExtractor extractor) { MapSqlParameterSource parameterSource = new MapSqlParameterSource(); Condition condition = createCondition(query, parameterSource, entity); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchInsertStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchInsertStrategy.java index 7a63d216e6..9be6f860a5 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchInsertStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BatchInsertStrategy.java @@ -15,6 +15,8 @@ */ package org.springframework.data.jdbc.core.convert; +import org.jspecify.annotations.Nullable; + import org.springframework.jdbc.core.namedparam.SqlParameterSource; /** @@ -32,5 +34,6 @@ interface BatchInsertStrategy { * elements will be {@code null}. * @since 2.4 */ + @Nullable Object[] execute(String sql, SqlParameterSource[] sqlParameterSources); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CachingResultSet.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CachingResultSet.java deleted file mode 100644 index d98fb9f5b6..0000000000 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CachingResultSet.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2023-2025 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.jdbc.core.convert; - -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.lang.Nullable; - -/** - * Despite its name not really a {@link ResultSet}, but it offers the part of the {@literal ResultSet} API that is used - * by {@link AggregateReader}. It allows peeking in the next row of a ResultSet by caching one row of the ResultSet. - * - * @author Jens Schauder - * @since 3.2 - */ -class CachingResultSet { - - private final ResultSetAccessor accessor; - private final ResultSet resultSet; - private Cache cache; - - CachingResultSet(ResultSet resultSet) { - - this.accessor = new ResultSetAccessor(resultSet); - this.resultSet = resultSet; - } - - public boolean next() { - - if (isPeeking()) { - - final boolean next = cache.next; - cache = null; - return next; - } - - try { - return resultSet.next(); - } catch (SQLException e) { - throw new RuntimeException("Failed to advance CachingResultSet", e); - } - } - - @Nullable - public Object getObject(String columnLabel) { - - Object returnValue; - if (isPeeking()) { - returnValue = cache.values.get(columnLabel); - } else { - returnValue = safeGetFromDelegate(columnLabel); - } - - return returnValue; - } - - @Nullable - Object peek(String columnLabel) { - - if (!isPeeking()) { - createCache(); - } - - if (!cache.next) { - return null; - } - - return safeGetFromDelegate(columnLabel); - } - - @Nullable - private Object safeGetFromDelegate(String columnLabel) { - return accessor.getObject(columnLabel); - } - - private void createCache() { - cache = new Cache(); - - try { - int columnCount = resultSet.getMetaData().getColumnCount(); - for (int i = 1; i <= columnCount; i++) { - // at least some databases return lower case labels although rs.getObject(UPPERCASE_LABEL) returns the expected - // value. The aliases we use happen to be uppercase. So we transform everything to upper case. - cache.add(resultSet.getMetaData().getColumnLabel(i).toLowerCase(), - accessor.getObject(resultSet.getMetaData().getColumnLabel(i))); - } - - cache.next = resultSet.next(); - } catch (SQLException se) { - throw new RuntimeException("Can't cache result set data", se); - } - - } - - private boolean isPeeking() { - return cache != null; - } - - private static class Cache { - - boolean next; - Map values = new HashMap<>(); - - void add(String columnName, Object value) { - values.put(columnName, value); - } - } -} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java index be81bf1c23..283612d8bf 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/CascadingDataAccessStrategy.java @@ -24,6 +24,8 @@ import java.util.function.Function; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyPath; @@ -74,12 +76,14 @@ public NamedParameterJdbcOperations getJdbcOperations() { } @Override - public Object insert(T instance, Class domainType, Identifier identifier, IdValueSource idValueSource) { + public @Nullable Object insert(T instance, Class domainType, Identifier identifier, + IdValueSource idValueSource) { return collect(das -> das.insert(instance, domainType, identifier, idValueSource)); } @Override - public Object[] insert(List> insertSubjects, Class domainType, IdValueSource idValueSource) { + public @Nullable Object[] insert(List> insertSubjects, Class domainType, + IdValueSource idValueSource) { return collect(das -> das.insert(insertSubjects, domainType, idValueSource)); } @@ -144,7 +148,7 @@ public long count(Class domainType) { } @Override - public T findById(Object id, Class domainType) { + public T findById(Object id, Class domainType) { return collect(das -> das.findById(id, domainType)); } @@ -224,8 +228,7 @@ public long count(Query query, Class domainType) { return collect(das -> das.count(query, domainType)); } - private T collect(Function function) { - + private T collect(Function function) { return strategies.stream().collect(new FunctionCollector<>(function)); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java index 1e8fea9a8c..7a41626841 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DataAccessStrategy.java @@ -20,6 +20,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -31,7 +32,6 @@ import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.sql.LockMode; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; /** * Abstraction for accesses to the database that should be implementable with a single SQL statement per method and @@ -73,8 +73,7 @@ public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationR * @return the id generated by the database if any. * @since 2.4 */ - @Nullable - Object insert(T instance, Class domainType, Identifier identifier, IdValueSource idValueSource); + @Nullable Object insert(T instance, Class domainType, Identifier identifier, IdValueSource idValueSource); /** * Inserts the data of multiple entities. @@ -88,7 +87,8 @@ public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationR * elements will be {@code null}. * @since 2.4 */ - Object[] insert(List> insertSubjects, Class domainType, IdValueSource idValueSource); + @Nullable Object[] insert(List> insertSubjects, Class domainType, + IdValueSource idValueSource); /** * Updates the data of a single entity in the database. Referenced entities don't get handled. @@ -255,8 +255,7 @@ public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationR * @return Might return {@code null}. */ @Override - @Nullable - T findById(Object id, Class domainType); + T findById(Object id, Class domainType); /** * Loads all entities of the given type. @@ -291,8 +290,8 @@ public interface DataAccessStrategy extends ReadingDataAccessStrategy, RelationR Iterable findAllById(Iterable ids, Class domainType); /** - * Loads all entities that match one of the ids passed as an argument to a {@link Stream}. - * It is not guaranteed that the number of ids passed in matches the number of entities returned. + * Loads all entities that match one of the ids passed as an argument to a {@link Stream}. It is not guaranteed that + * the number of ids passed in matches the number of entities returned. * * @param ids the Ids of the entities to load. Must not be {@code null}. * @param domainType the type of entities to load. Must not be {@code null}. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java index fdcf95aa3e..e8cdee7dae 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Pageable; @@ -43,7 +44,6 @@ import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -117,7 +117,8 @@ public NamedParameterJdbcOperations getJdbcOperations() { } @Override - public Object insert(T instance, Class domainType, Identifier identifier, IdValueSource idValueSource) { + public @Nullable Object insert(T instance, Class domainType, Identifier identifier, + IdValueSource idValueSource) { SqlIdentifierParameterSource parameterSource = sqlParametersFactory.forInsert(instance, domainType, identifier, idValueSource); @@ -129,7 +130,8 @@ public Object insert(T instance, Class domainType, Identifier identifier, } @Override - public Object[] insert(List> insertSubjects, Class domainType, IdValueSource idValueSource) { + public @Nullable Object[] insert(List> insertSubjects, Class domainType, + IdValueSource idValueSource) { Assert.notEmpty(insertSubjects, "Batch insert must contain at least one InsertSubject"); @@ -281,7 +283,8 @@ public long count(Class domainType) { } @Override - public T findById(Object id, Class domainType) { + @SuppressWarnings("NullAway") + public T findById(Object id, Class domainType) { String findOneSql = sql(domainType).getFindOne(); SqlIdentifierParameterSource parameter = sqlParametersFactory.forQueryById(id, domainType); @@ -330,7 +333,6 @@ public Stream streamAllByIds(Iterable ids, Class domainType) { } @Override - @SuppressWarnings("unchecked") public List findAllByPath(Identifier identifier, PersistentPropertyPath propertyPath) { @@ -338,7 +340,11 @@ public List findAllByPath(Identifier identifier, Assert.notNull(propertyPath, "propertyPath must not be null"); AggregatePath path = context.getAggregatePath(propertyPath); - Class actualType = path.getLeafEntity().getType(); + RelationalPersistentEntity leafEntity = path.getLeafEntity(); + + Assert.state(leafEntity != null, "leafEntity must not be null"); + + Class actualType = leafEntity.getType(); String findAllByProperty = sql(actualType) // .getFindAllByProperty(identifier, propertyPath); @@ -358,7 +364,8 @@ public Object mapRow(ResultSet rs, int rowNum) throws SQLException { if (!path.hasIdProperty() && path.isQualified()) { TableInfo tableInfo = path.getTableInfo(); - identifierToUse = identifierToUse.withPart(tableInfo.qualifierColumnInfo().name(), rowNum, Object.class); + identifierToUse = identifierToUse.withPart(tableInfo.getRequiredQualifierColumnInfo().name(), rowNum, + Object.class); } return getEntityRowMapper(path, identifierToUse).mapRow(rs, rowNum); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java index ad6404f873..f3d625c4a4 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DelegatingDataAccessStrategy.java @@ -19,6 +19,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentPropertyPath; @@ -47,11 +48,13 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy { private DataAccessStrategy delegate; + @SuppressWarnings("NullAway.Init") public DelegatingDataAccessStrategy() {} public DelegatingDataAccessStrategy(DataAccessStrategy delegate) { Assert.notNull(delegate, "DataAccessStrategy must not be null"); + this.delegate = delegate; } @@ -66,12 +69,14 @@ public NamedParameterJdbcOperations getJdbcOperations() { } @Override - public Object insert(T instance, Class domainType, Identifier identifier, IdValueSource idValueSource) { + public @Nullable Object insert(T instance, Class domainType, Identifier identifier, + IdValueSource idValueSource) { return delegate.insert(instance, domainType, identifier, idValueSource); } @Override - public Object[] insert(List> insertSubjects, Class domainType, IdValueSource idValueSource) { + public @Nullable Object[] insert(List> insertSubjects, Class domainType, + IdValueSource idValueSource) { return delegate.insert(insertSubjects, domainType, idValueSource); } @@ -137,7 +142,7 @@ public long count(Class domainType) { } @Override - public T findById(Object id, Class domainType) { + public T findById(Object id, Class domainType) { Assert.notNull(delegate, "Delegate is null"); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/FunctionCollector.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/FunctionCollector.java index d218e666c1..8456aa21fc 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/FunctionCollector.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/FunctionCollector.java @@ -96,7 +96,7 @@ public Set characteristics() { */ static class ResultOrException { - private T result; + @SuppressWarnings("NullAway.Init") private T result; private final List exceptions = new LinkedList<>(); private boolean hasResult = false; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingBatchInsertStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingBatchInsertStrategy.java index 78337bf52c..4185efacb8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingBatchInsertStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingBatchInsertStrategy.java @@ -20,13 +20,13 @@ import java.util.Map; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.IdGeneration; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.GeneratedKeyHolder; -import org.springframework.lang.Nullable; /** * A {@link BatchInsertStrategy} that expects ids to be generated from the batch insert. When the {@link Dialect} does @@ -42,7 +42,7 @@ class IdGeneratingBatchInsertStrategy implements BatchInsertStrategy { private final InsertStrategy insertStrategy; private final Dialect dialect; private final NamedParameterJdbcOperations jdbcOperations; - private final SqlIdentifier idColumn; + private final @Nullable SqlIdentifier idColumn; IdGeneratingBatchInsertStrategy(InsertStrategy insertStrategy, Dialect dialect, NamedParameterJdbcOperations jdbcOperations, @Nullable SqlIdentifier idColumn) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingInsertStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingInsertStrategy.java index 47b2f9d084..32149c5ba1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingInsertStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingInsertStrategy.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.relational.core.dialect.Dialect; @@ -27,7 +28,6 @@ import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; -import org.springframework.lang.Nullable; /** * An {@link InsertStrategy} that expects an id to be generated from the insert. @@ -40,7 +40,7 @@ class IdGeneratingInsertStrategy implements InsertStrategy { private final Dialect dialect; private final NamedParameterJdbcOperations jdbcOperations; - private final SqlIdentifier idColumn; + private final @Nullable SqlIdentifier idColumn; IdGeneratingInsertStrategy(Dialect dialect, NamedParameterJdbcOperations jdbcOperations, @Nullable SqlIdentifier idColumn) { @@ -50,7 +50,7 @@ class IdGeneratingInsertStrategy implements InsertStrategy { } @Override - public Object execute(String sql, SqlParameterSource sqlParameterSource) { + public @Nullable Object execute(String sql, SqlParameterSource sqlParameterSource) { KeyHolder holder = new GeneratedKeyHolder(); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/Identifier.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/Identifier.java index 711ba330c8..cf3fb292ac 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/Identifier.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/Identifier.java @@ -23,8 +23,8 @@ import java.util.Map; import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -291,7 +291,7 @@ public StringKeyedLinkedHashMap(int initialCapacity) { } @Override - public V get(Object key) { + public @Nullable V get(Object key) { if (key instanceof String) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategy.java index 0c618e2466..0484e773cb 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategy.java @@ -15,8 +15,8 @@ */ package org.springframework.data.jdbc.core.convert; +import org.jspecify.annotations.Nullable; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.lang.Nullable; /** * Strategy for executing an insert. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java index a245e5235f..7ba7635320 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertStrategyFactory.java @@ -15,12 +15,12 @@ */ package org.springframework.data.jdbc.core.convert; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.conversion.IdValueSource; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.lang.Nullable; /** * Factory which selects and builds the appropriate {@link InsertStrategy} or {@link BatchInsertStrategy} based on @@ -70,32 +70,21 @@ BatchInsertStrategy batchInsertStrategy(IdValueSource idValueSource, @Nullable S return new DefaultBatchInsertStrategy(jdbcOperations); } - private static class DefaultInsertStrategy implements InsertStrategy { - - private final NamedParameterJdbcOperations jdbcOperations; - - DefaultInsertStrategy(NamedParameterJdbcOperations jdbcOperations) { - this.jdbcOperations = jdbcOperations; - } + private record DefaultInsertStrategy(NamedParameterJdbcOperations jdbcOperations) implements InsertStrategy { @Override - public Object execute(String sql, SqlParameterSource sqlParameterSource) { + public @Nullable Object execute(String sql, SqlParameterSource sqlParameterSource) { jdbcOperations.update(sql, sqlParameterSource); return null; } } - private static class DefaultBatchInsertStrategy implements BatchInsertStrategy { - - private final NamedParameterJdbcOperations jdbcOperations; - - DefaultBatchInsertStrategy(NamedParameterJdbcOperations jdbcOperations) { - this.jdbcOperations = jdbcOperations; - } + private record DefaultBatchInsertStrategy( + NamedParameterJdbcOperations jdbcOperations) implements BatchInsertStrategy { @Override - public Object[] execute(String sql, SqlParameterSource[] sqlParameterSources) { + public @Nullable Object[] execute(String sql, SqlParameterSource[] sqlParameterSources) { jdbcOperations.batchUpdate(sql, sqlParameterSources); return new Object[sqlParameterSources.length]; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertSubject.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertSubject.java index 6db9fcf7fd..0c9cb3ef07 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertSubject.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/InsertSubject.java @@ -17,7 +17,7 @@ import java.util.Objects; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * The subject of an insert, described by the entity instance and its {@link Identifier}, where identifier contains 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 f5203db269..5d7d6b0a05 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,10 +19,10 @@ 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; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java index 3e73a73cf7..56dbbc0917 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcConverter.java @@ -17,13 +17,13 @@ import java.sql.SQLType; +import org.jspecify.annotations.Nullable; import org.springframework.data.jdbc.core.mapping.JdbcValue; import org.springframework.data.relational.core.conversion.RelationalConverter; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.RowDocument; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; /** * A {@link JdbcConverter} is responsible for converting for values to the native relational representation and vice diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java index 24213662ff..95ac1e3eaa 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcIdentifierBuilder.java @@ -93,7 +93,8 @@ public JdbcIdentifierBuilder withQualifier(AggregatePath path, Object value) { Assert.notNull(value, "Value must not be null"); AggregatePath.TableInfo tableInfo = path.getTableInfo(); - identifier = identifier.withPart(tableInfo.qualifierColumnInfo().name(), value, tableInfo.qualifierColumnType()); + identifier = identifier.withPart(tableInfo.getRequiredQualifierColumnInfo().name(), value, + tableInfo.getRequiredQualifierColumnType()); return this; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java index 4485ef28bc..b40f4cd5ad 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/JdbcPropertyValueProvider.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.core.convert; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.model.PropertyValueProvider; import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; @@ -42,7 +43,7 @@ class JdbcPropertyValueProvider implements PropertyValueProvider T getPropertyValue(RelationalPersistentProperty property) { + public @Nullable T getPropertyValue(RelationalPersistentProperty property) { return (T) resultSet.getObject(getColumnName(property)); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java index ca38e9604d..935b65a355 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MapEntityRowMapper.java @@ -25,6 +25,7 @@ import org.springframework.data.relational.domain.RowDocument; import org.springframework.data.util.TypeInformation; import org.springframework.jdbc.core.RowMapper; +import org.springframework.util.Assert; /** * A {@link RowMapper} that maps a row to a {@link Map.Entry} so an {@link Iterable} of those can be converted to a @@ -55,6 +56,9 @@ public Map.Entry mapRow(ResultSet rs, int rowNum) throws SQLException RowDocument document = RowDocumentResultSetExtractor.toRowDocument(rs); Object key = document.get(keyColumn.getReference()); + + Assert.notNull(key, "Key must not be null"); + Class qualifierColumnType = path.getRequiredLeafProperty().getQualifierColumnType(); Object convertedKey = converter.readValue(key, TypeInformation.of(qualifierColumnType)); 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 31baf0eabe..e01bda8fb1 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 @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContextAware; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.DataAccessException; @@ -49,7 +50,6 @@ import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; import org.springframework.jdbc.support.SQLExceptionSubclassTranslator; import org.springframework.jdbc.support.SQLExceptionTranslator; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -165,7 +165,12 @@ private Class doGetColumnType(RelationalPersistentProperty property) { } if (property.isEntity()) { - Class columnType = getEntityColumnType(property.getTypeInformation().getActualType()); + + TypeInformation actualType = property.getTypeInformation().getActualType(); + + Assert.state(actualType != null, "actualType must not be null"); + + Class columnType = getEntityColumnType(actualType); if (columnType != null) { return columnType; @@ -207,11 +212,17 @@ public Object readValue(@Nullable Object value, TypeInformation targetType) { List> types = targetType.getTypeArguments(); TypeInformation idType = types.get(1); + + Object referencedId; if (value instanceof AggregateReference ref) { - return AggregateReference.to(readValue(ref.getId(), idType)); + referencedId = readValue(ref.getId(), idType); } else { - return AggregateReference.to(readValue(value, idType)); + referencedId = readValue(value, idType); } + + Assert.state(referencedId != null, "Reference must not be null"); + + return AggregateReference.to(referencedId); } return getPotentiallyConvertedSimpleRead(value, targetType); @@ -363,7 +374,7 @@ protected RelationalPropertyValueProvider newValueProvider(RowDocumentAccessor d * @param ex the offending {@code SQLException} * @return a DataAccessException wrapping the {@code SQLException} (never {@code null}) */ - private DataAccessException translateException(String task, @org.jspecify.annotations.Nullable String sql, + private DataAccessException translateException(String task, @Nullable String sql, SQLException ex) { DataAccessException dae = exceptionTranslator.translate(task, sql, ex); return (dae != null ? dae : new UncategorizedSQLException(task, sql, ex)); @@ -545,7 +556,8 @@ private static AggregatePath smartAppend(AggregatePath base, AggregatePath suffi if (owner.equals(base.getRequiredLeafEntity())) { return base.append(suffix); } else { - return smartAppend(base, suffix.getTail()); + AggregatePath tail = suffix.getRequiredTail(); + return smartAppend(base, tail); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java index 1d3ce3095e..08fd83ff0a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMapper.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.mapping.JdbcValue; import org.springframework.data.jdbc.support.JdbcUtil; @@ -42,7 +43,6 @@ import org.springframework.data.util.Pair; import org.springframework.data.util.TypeInformation; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -98,7 +98,8 @@ public List getMappedSort(Table table, Sort sort, @Nullable Relati } - private OrderByField createSimpleOrderByField(Table table, RelationalPersistentEntity entity, Sort.Order order) { + private OrderByField createSimpleOrderByField(Table table, @Nullable RelationalPersistentEntity entity, + Sort.Order order) { if (order instanceof SqlSort.SqlOrder sqlOrder && sqlOrder.isUnsafe()) { return OrderByField.from(Expressions.just(sqlOrder.getProperty())); @@ -125,9 +126,7 @@ Expression getMappedObject(Expression expression, @Nullable RelationalPersistent if (expression instanceof Column column) { Field field = createPropertyField(entity, column.getName()); - TableLike table = column.getTable(); - - Assert.state(table != null, String.format("The column %s must have a table set", column)); + TableLike table = column.getRequiredTable(); Column columnFromTable = table.column(field.getMappedColumnName()); return column instanceof Aliased aliased ? columnFromTable.as(aliased.getAlias()) : columnFromTable; @@ -182,8 +181,10 @@ private Condition unroll(CriteriaDefinition criteria, Table table, @Nullable Rel Map forwardChain = new HashMap<>(); while (current.hasPrevious()) { - forwardChain.put(current.getPrevious(), current); - current = current.getPrevious(); + + CriteriaDefinition previous = current.getRequiredPrevious(); + forwardChain.put(previous, current); + current = previous; } // perform the actual mapping @@ -270,12 +271,19 @@ private Condition combine(@Nullable Condition currentCondition, CriteriaDefiniti private Condition mapCondition(CriteriaDefinition criteria, MapSqlParameterSource parameterSource, Table table, @Nullable RelationalPersistentEntity entity) { - Field propertyField = createPropertyField(entity, criteria.getColumn(), this.mappingContext); + SqlIdentifier criteriaColumn = criteria.getColumn(); + + Assert.notNull(criteriaColumn, "Column must not be null"); + + Field propertyField = createPropertyField(entity, criteriaColumn, this.mappingContext); // Single embedded entity if (propertyField.isEmbedded()) { - return mapEmbeddedObjectCondition(criteria, parameterSource, table, - ((MetadataBackedField) propertyField).getPath().getLeafProperty()); + PersistentPropertyPath path = ((MetadataBackedField) propertyField).getPath(); + + Assert.state(path != null, "Path must not be null"); + + return mapEmbeddedObjectCondition(criteria, parameterSource, table, path.getLeafProperty()); } TypeInformation actualType = propertyField.getTypeHint().getRequiredActualType(); @@ -284,6 +292,8 @@ private Condition mapCondition(CriteriaDefinition criteria, MapSqlParameterSourc SQLType sqlType; Comparator comparator = criteria.getComparator(); + Assert.notNull(comparator, "Comparator must not be null"); + if (criteria.getValue() instanceof JdbcValue settableValue) { mappedValue = convertValue(comparator, settableValue.getValue(), propertyField.getTypeHint()); @@ -317,7 +327,7 @@ private Condition mapCondition(CriteriaDefinition criteria, MapSqlParameterSourc * @param property the property to which the value relates. It determines the type to convert to. Must not be * {@literal null}. * @param value the value to be converted. - * @return a non null {@link JdbcValue} holding the converted value and the appropriate JDBC type information. + * @return a non-null {@link JdbcValue} holding the converted value and the appropriate JDBC type information. */ private JdbcValue convertToJdbcValue(RelationalPersistentProperty property, @Nullable Object value) { @@ -329,12 +339,18 @@ private JdbcValue convertToJdbcValue(RelationalPersistentProperty property, @Nul JdbcValue first = getWriteValue(property, ((Pair) value).getFirst()); JdbcValue second = getWriteValue(property, ((Pair) value).getSecond()); - return JdbcValue.of(Pair.of(first.getValue(), second.getValue()), first.getJdbcType()); + Object firstValue = first.getValue(); + Object secondValue = second.getValue(); + + Assert.state(firstValue != null, "First value must not be null"); + Assert.state(secondValue != null, "Second value must not be null"); + + return JdbcValue.of(Pair.of(firstValue, secondValue), first.getJdbcType()); } if (value instanceof Iterable) { - List mapped = new ArrayList<>(); + List<@Nullable Object> mapped = new ArrayList<>(); SQLType jdbcType = null; for (Object o : (Iterable) value) { @@ -353,7 +369,8 @@ private JdbcValue convertToJdbcValue(RelationalPersistentProperty property, @Nul if (value.getClass().isArray()) { Object[] valueAsArray = (Object[]) value; - Object[] mappedValueArray = new Object[valueAsArray.length]; + @Nullable + Object[] mappedValueArray = new Object @Nullable [valueAsArray.length]; SQLType jdbcType = null; for (int i = 0; i < valueAsArray.length; i++) { @@ -407,8 +424,12 @@ private Condition mapEmbeddedObjectCondition(CriteriaDefinition criteria, MapSql nestedProperty.getTypeInformation()); SQLType sqlType = converter.getTargetSqlType(nestedProperty); + Comparator criteriaComparator = criteria.getComparator(); + + Assert.notNull(criteriaComparator, "Criteria comparator must not be null"); + Condition mappedCondition = createCondition(table.column(sqlIdentifier), mappedNestedValue, sqlType, - parameterSource, criteria.getComparator(), criteria.isIgnoreCase()); + parameterSource, criteriaComparator, criteria.isIgnoreCase()); if (condition != null) { condition = condition.and(mappedCondition); @@ -417,6 +438,8 @@ private Condition mapEmbeddedObjectCondition(CriteriaDefinition criteria, MapSql } } + Assert.state(condition != null, "Condition must not be null"); + return Conditions.nest(condition); } @@ -457,6 +480,9 @@ protected Object convertValue(@Nullable Object value, TypeInformation typeInf ? typeInformation.getRequiredActualType() : TypeInformation.OBJECT); + Assert.state(first != null, "First value must not be null"); + Assert.state(second != null, "Second value must not be null"); + return Pair.of(first, second); } @@ -534,6 +560,8 @@ private Condition createCondition(Column column, @Nullable Object mappedValue, S if (comparator == Comparator.BETWEEN || comparator == Comparator.NOT_BETWEEN) { + Assert.state(mappedValue != null, "Mapped value must not be null"); + Pair pair = (Pair) mappedValue; Expression begin = bind(pair.getFirst(), sqlType, parameterSource, column.getName().getReference(), ignoreCase); @@ -603,11 +631,19 @@ SQLType getTypeHint(@Nullable Object mappedValue, Class propertyType, JdbcVal return JdbcUtil.TYPE_UNKNOWN; } - if (mappedValue.getClass().equals(settableValue.getValue().getClass())) { + Object settableValueValue = settableValue.getValue(); + + Assert.state(settableValueValue != null, "Settable value must not be null"); + + if (mappedValue.getClass().equals(settableValueValue.getClass())) { return JdbcUtil.TYPE_UNKNOWN; } - return settableValue.getJdbcType(); + SQLType jdbcType = settableValue.getJdbcType(); + + Assert.state(jdbcType != null, "JDBC type must not be null"); + + return jdbcType; } private Expression bind(@Nullable Object mappedValue, SQLType sqlType, MapSqlParameterSource parameterSource, @@ -690,7 +726,7 @@ protected static class MetadataBackedField extends Field { private final RelationalPersistentEntity entity; private final MappingContext, RelationalPersistentProperty> mappingContext; - private final RelationalPersistentProperty property; + private final @Nullable RelationalPersistentProperty property; private final @Nullable PersistentPropertyPath path; private final boolean embedded; private final SQLType sqlType; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMappingConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMappingConfiguration.java index 91becb7fb7..85c7d54307 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMappingConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/QueryMappingConfiguration.java @@ -15,8 +15,8 @@ */ package org.springframework.data.jdbc.core.convert; +import org.jspecify.annotations.Nullable; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; /** * Configures a {@link org.springframework.jdbc.core.RowMapper} for each type to be used for extracting entities of that @@ -29,7 +29,7 @@ public interface QueryMappingConfiguration { @Nullable - default RowMapper getRowMapper(Class type) { + default RowMapper getRowMapper(Class type) { return null; } @@ -39,7 +39,7 @@ default RowMapper getRowMapper(Class type) { QueryMappingConfiguration EMPTY = new QueryMappingConfiguration() { @Override - public RowMapper getRowMapper(Class type) { + public @Nullable RowMapper getRowMapper(Class type) { return null; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ReadingDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ReadingDataAccessStrategy.java index 5b00f99dd5..964f8636d1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ReadingDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ReadingDataAccessStrategy.java @@ -19,10 +19,10 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.relational.core.query.Query; -import org.springframework.lang.Nullable; /** * The finding methods of a {@link DataAccessStrategy}. @@ -41,8 +41,7 @@ interface ReadingDataAccessStrategy { * @param the type of the entity. * @return Might return {@code null}. */ - @Nullable - T findById(Object id, Class domainType); + T findById(Object id, Class domainType); /** * Loads all entities of the given type. @@ -74,8 +73,8 @@ interface ReadingDataAccessStrategy { Iterable findAllById(Iterable ids, Class domainType); /** - * Loads all entities that match one of the ids passed as an argument to a {@link Stream}. - * It is not guaranteed that the number of ids passed in matches the number of entities returned. + * Loads all entities that match one of the ids passed as an argument to a {@link Stream}. It is not guaranteed that + * the number of ids passed in matches the number of entities returned. * * @param ids the Ids of the entities to load. Must not be {@code null}. * @param domainType the type of entities to load. Must not be {@code null}. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessor.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessor.java index 5cb10291fb..532a7e6255 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessor.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessor.java @@ -22,9 +22,9 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.MappingException; import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.lang.Nullable; import org.springframework.util.LinkedCaseInsensitiveMap; /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessorPropertyAccessor.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessorPropertyAccessor.java index dc5576a11e..1d9c4c0b56 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessorPropertyAccessor.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ResultSetAccessorPropertyAccessor.java @@ -15,10 +15,10 @@ */ package org.springframework.data.jdbc.core.convert; +import org.jspecify.annotations.Nullable; import org.springframework.expression.EvaluationContext; import org.springframework.expression.PropertyAccessor; import org.springframework.expression.TypedValue; -import org.springframework.lang.Nullable; /** * {@link PropertyAccessor} to access a column from a {@link ResultSetAccessor}. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentExtractorSupport.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentExtractorSupport.java index 5509157847..d7245d6188 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentExtractorSupport.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentExtractorSupport.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.TreeMap; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; @@ -29,7 +30,7 @@ import org.springframework.data.relational.core.mapping.RelationalPredicates; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.domain.RowDocument; -import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Support class for {@code ResultSet}-driven extractor implementations extracting {@link RowDocument documents} from @@ -105,7 +106,12 @@ public boolean containsColumn(String columnName) { @Nullable public Object getObject(RS row, String columnName) { - return adapter.getObject(row, columnMap.get(columnName)); + + Integer index = columnMap.get(columnName); + + Assert.state(index != null, () -> "Column '%s' not found".formatted(columnName)); + + return adapter.getObject(row, index); } /** @@ -176,9 +182,9 @@ protected static class RowDocumentSink extends TabularSink { private final AggregateContext aggregateContext; private final RelationalPersistentEntity entity; private final AggregatePath basePath; - private RowDocument result; + private @Nullable RowDocument result; - private String keyColumnName; + private final String keyColumnName; private @Nullable Object key; private final Map> readerState = new LinkedHashMap<>(); @@ -297,6 +303,8 @@ boolean hasResult() { @Override RowDocument getResult() { + Assert.state(result != null, "Result must not be null"); + readerState.forEach((property, reader) -> { if (reader.hasResult()) { @@ -350,7 +358,12 @@ boolean hasResult() { @Override Object getResult() { - return getValue(); + + Object result = getValue(); + + Assert.state(result != null, "Result must not be null"); + + return result; } @Nullable @@ -375,7 +388,7 @@ private static class ContainerSink extends TabularSink { private final String keyColumn; private final AggregateContext aggregateContext; - private Object key; + private @Nullable Object key; private boolean hasResult = false; private final TabularSink componentReader; @@ -413,6 +426,9 @@ void accept(RS row) { if (keyChange) { if (componentReader.hasResult()) { + + Assert.state(this.key != null, "Key must not be null"); + container.add(this.key, componentReader.getResult()); componentReader.reset(); } @@ -435,6 +451,8 @@ public Object getResult() { if (componentReader.hasResult()) { + Assert.state(this.key != null, "Key must not be null"); + container.add(this.key, componentReader.getResult()); componentReader.reset(); } @@ -481,9 +499,9 @@ public void add(Object key, @Nullable Object value) { } @Override - public List get() { + public List<@Nullable Object> get() { - List result = new ArrayList<>(list.size()); + List<@Nullable Object> result = new ArrayList<>(list.size()); // TODO: How do we go about padding? Should we insert null values? list.forEach((index, o) -> { @@ -501,7 +519,7 @@ public List get() { private static class MapContainer extends CollectionContainer { - private final Map map = new LinkedHashMap<>(); + private final Map map = new LinkedHashMap<>(); @Override public void add(Object key, @Nullable Object value) { @@ -509,7 +527,7 @@ public void add(Object key, @Nullable Object value) { } @Override - public Map get() { + public Map get() { return new LinkedHashMap<>(map); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java index f4e52538a8..05535c246a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/RowDocumentResultSetExtractor.java @@ -24,6 +24,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.dao.DataRetrievalFailureException; import org.springframework.data.jdbc.core.convert.RowDocumentExtractorSupport.AggregateContext; import org.springframework.data.jdbc.core.convert.RowDocumentExtractorSupport.RowDocumentSink; @@ -33,7 +34,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.domain.RowDocument; import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.LinkedCaseInsensitiveMap; /** @@ -73,7 +74,7 @@ static RowDocument toRowDocument(ResultSet resultSet) throws SQLException { for (int i = 0; i < columnCount; i++) { Object rsv = JdbcUtils.getResultSetValue(resultSet, i + 1); - String columnName = JdbcUtils.lookupColumnName(md, i+1); + String columnName = JdbcUtils.lookupColumnName(md, i + 1); Object old = document.putIfAbsent(columnName, rsv instanceof Array a ? a.getArray() : rsv); if (old != null) { log.warn(DUPLICATE_COLUMN_WARNING.formatted(columnName, i)); @@ -91,7 +92,7 @@ enum ResultSetAdapter implements TabularResultAdapter { INSTANCE; @Override - public Object getObject(ResultSet row, int index) { + public @Nullable Object getObject(ResultSet row, int index) { try { @@ -120,7 +121,7 @@ public Map getColumnMap(ResultSet result) { String columnLabel = metaData.getColumnLabel(i + 1); Object old = columns.put(columnLabel, i + 1); if (old != null) { - log.warn(DUPLICATE_COLUMN_WARNING.formatted( columnLabel, i)); + log.warn(DUPLICATE_COLUMN_WARNING.formatted(columnLabel, i)); } } return columns; @@ -207,8 +208,14 @@ private class RowDocumentIterator implements Iterator { this.aggregateContext = new AggregateContext<>(adapter, context, propertyToColumn, columns); this.resultSet = resultSet; - this.identifierIndex = columns.get(idColumn); + + Integer index = columns.get(idColumn); + + Assert.state(index != null, "Identifier index must not be null"); + + this.identifierIndex = index; this.hasNext = hasRow(resultSet); + } private static boolean hasRow(ResultSet resultSet) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SequenceEntityCallbackDelegate.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SequenceEntityCallbackDelegate.java index 00efd7fcff..9f5150d4d1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SequenceEntityCallbackDelegate.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SequenceEntityCallbackDelegate.java @@ -17,7 +17,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.relational.core.dialect.Dialect; @@ -26,7 +26,6 @@ import org.springframework.data.util.ReflectionUtils; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.NumberUtils; @@ -87,7 +86,11 @@ protected boolean hasValue(PersistentProperty property, PersistentPropertyAcc SqlIdentifier sequence = property.getSequence(); - if (sequence != null && !dialect.getIdGeneration().sequencesSupported()) { + if (sequence == null) { + return null; + } + + if (!dialect.getIdGeneration().sequencesSupported()) { LOG.warn(""" Aggregate type '%s' is marked for sequence usage but configured dialect '%s' does not support sequences. Falling back to identity columns. diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryDataAccessStrategy.java index d367c9c0c0..dcd8708379 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryDataAccessStrategy.java @@ -20,6 +20,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.relational.core.dialect.Dialect; @@ -49,7 +50,8 @@ public SingleQueryDataAccessStrategy(Dialect dialect, JdbcConverter converter, } @Override - public T findById(Object id, Class domainType) { + @SuppressWarnings("NullAway") + public T findById(Object id, Class domainType) { return aggregateReader.findById(id, getPersistentEntity(domainType)); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java index 962e19831c..b76bcdec0e 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SingleQueryFallbackDataAccessStrategy.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.query.Query; @@ -55,7 +56,7 @@ public SingleQueryFallbackDataAccessStrategy(SqlGeneratorSource sqlGeneratorSour } @Override - public T findById(Object id, Class domainType) { + public T findById(Object id, Class domainType) { if (isSingleSelectQuerySupported(domainType)) { return singleSelectDelegate.findById(id, domainType); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java index 82f2db5158..0b4d12abb9 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java @@ -15,21 +15,13 @@ */ package org.springframework.data.jdbc.core.convert; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository; @@ -49,7 +41,6 @@ import org.springframework.data.util.Lazy; import org.springframework.data.util.Predicates; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -298,7 +289,7 @@ String getFindAllByProperty(Identifier parentIdentifier, * keyColumn must not be {@code null}. * @return a SQL String. */ - String getFindAllByProperty(Identifier parentIdentifier, @Nullable AggregatePath.ColumnInfo keyColumn, + String getFindAllByProperty(Identifier parentIdentifier, AggregatePath.@Nullable ColumnInfo keyColumn, boolean ordered) { Assert.isTrue(keyColumn != null || !ordered, @@ -315,9 +306,15 @@ String getFindAllByProperty(Identifier parentIdentifier, @Nullable AggregatePath Condition condition = buildConditionForBackReference(parentIdentifier, table); SelectBuilder.SelectWhereAndOr withWhereClause = builder.where(condition); - Select select = ordered // - ? withWhereClause.orderBy(table.column(keyColumn.name()).as(keyColumn.alias())).build() // - : withWhereClause.build(); + Select select; + if (ordered) { + + Assert.isTrue(keyColumn != null, "KeyColumn must not be null"); + + select = withWhereClause.orderBy(table.column(keyColumn.name()).as(keyColumn.alias())).build(); + } else { + select = withWhereClause.build(); + } return render(select); } @@ -514,7 +511,12 @@ private Condition equalityCondition(Map columnMap) { AggregatePath.ColumnInfos idColumnInfos = mappingContext.getAggregatePath(entity).getTableInfo().idColumnInfos(); return createPredicate(columnMap, (aggregatePath, column) -> { - return column.isEqualTo(getBindMarker(idColumnInfos.get(aggregatePath).name())); + + AggregatePath.ColumnInfo columnInfo = idColumnInfos.get(aggregatePath); + + Assert.notNull(columnInfo, "ColumnInfo must not be null"); + + return column.isEqualTo(getBindMarker(columnInfo.name())); }); } @@ -813,8 +815,11 @@ Join getJoin(AggregatePath path) { Condition joinCondition = backRefColumnInfos.reduce(Conditions.unrestricted(), (aggregatePath, columnInfo) -> { - return currentTable.column(columnInfo.name()) - .isEqualTo(parentTable.column(idColumnInfos.get(aggregatePath).name())); + AggregatePath.ColumnInfo idColumnInfo = idColumnInfos.get(aggregatePath); + + Assert.notNull(idColumnInfo, "IdColumnInfo must not be null"); + + return currentTable.column(columnInfo.name()).isEqualTo(parentTable.column(idColumnInfo.name())); }, Condition::and); return new Join(currentTable, joinCondition); @@ -883,6 +888,8 @@ private String createInsertSql(Set additionalColumns) { insertWithValues = (insertWithValues == null ? insert : insertWithValues).values(getBindMarker(cn)); } + Assert.state(insertWithValues != null, "InsertWithValues must not be null"); + return render(insertWithValues.build()); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java index 209aa7108d..530260d6c3 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlIdentifierParameterSource.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.jdbc.core.namedparam.AbstractSqlParameterSource; @@ -44,7 +45,7 @@ public boolean hasValue(String paramName) { } @Override - public Object getValue(String paramName) throws IllegalArgumentException { + public @Nullable Object getValue(String paramName) throws IllegalArgumentException { return namesToValues.get(paramName); } @@ -61,7 +62,7 @@ void addValue(SqlIdentifier name, Object value) { addValue(name, value, Integer.MIN_VALUE); } - void addValue(SqlIdentifier identifier, Object value, int sqlType) { + void addValue(SqlIdentifier identifier, @Nullable Object value, int sqlType) { identifiers.add(identifier); String name = BindParameterNameSanitizer.sanitize(identifier.getReference()); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java index 370b087f52..32a04d8ee8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java @@ -23,6 +23,7 @@ import java.util.function.BiFunction; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.springframework.data.jdbc.core.mapping.JdbcValue; import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.mapping.PersistentProperty; @@ -35,7 +36,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPredicates; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; /** * Creates the {@link SqlIdentifierParameterSource} for various SQL operations, dialect identifier processing rules and @@ -94,7 +94,7 @@ SqlIdentifierParameterSource forInsert(T instance, Class domainType, Iden AggregatePath.ColumnInfos columnInfos = context.getAggregatePath(persistentEntity).getTableInfo().idColumnInfos(); - // fullPath: because we use the result with a PropertyPathAccessor + // fullPath: because we use the result with a PropertyPathAccessor columnInfos.forEach((ap, __) -> { Object idValue = propertyPathAccessor.getProperty(ap.getRequiredPersistentPropertyPath()); RelationalPersistentProperty idProperty = ap.getRequiredLeafProperty(); @@ -192,7 +192,7 @@ private T doWithIdentifiers(Class domainType, IdentifierCallback callb interface IdentifierCallback { T doWithIdentifiers(AggregatePath.ColumnInfos columns, RelationalPersistentProperty idProperty, - RelationalPersistentEntity complexId); + @Nullable RelationalPersistentEntity complexId); } /** @@ -267,7 +267,6 @@ private SqlIdentifierParameterSource getParameterSource(@Nullable S insta PersistentPropertyAccessor propertyAccessor = instance != null ? persistentEntity.getPropertyAccessor(instance) : NoValuePropertyAccessor.instance(); - persistentEntity.doWithAll(property -> { if (skipProperty.test(property) || !property.isWritable()) { @@ -281,7 +280,8 @@ private SqlIdentifierParameterSource getParameterSource(@Nullable S insta if (property.isEmbedded()) { Object value = propertyAccessor.getProperty(property); - RelationalPersistentEntity embeddedEntity = context.getPersistentEntity(property.getTypeInformation()); + RelationalPersistentEntity embeddedEntity = context + .getRequiredPersistentEntity(property.getTypeInformation()); SqlIdentifierParameterSource additionalParameters = getParameterSource((T) value, (RelationalPersistentEntity) embeddedEntity, prefix + property.getEmbeddedPrefix(), skipProperty); parameters.addAll(additionalParameters); @@ -316,13 +316,13 @@ public void setProperty(PersistentProperty property, @Nullable Object value) } @Override - public Object getProperty(PersistentProperty property) { + public @Nullable Object getProperty(PersistentProperty property) { return null; } @Override public T getBean() { - return null; + throw new UnsupportedOperationException("Cannot get bean of NoValuePropertyAccessor"); } } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/package-info.java index 43ca52cb9d..0039b772ab 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/package-info.java @@ -1,7 +1,6 @@ /** * JDBC-specific conversion classes. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.core.convert; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DialectResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DialectResolver.java index 3ec2c9b107..fda3d5ddd1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DialectResolver.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/DialectResolver.java @@ -29,6 +29,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.dao.NonTransientDataAccessException; import org.springframework.data.relational.core.dialect.Dialect; @@ -44,7 +45,6 @@ import org.springframework.data.util.Optionals; import org.springframework.jdbc.core.ConnectionCallback; import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -112,6 +112,7 @@ public interface JdbcDialectProvider { public static class DefaultDialectProvider implements JdbcDialectProvider { @Override + @SuppressWarnings("NullAway") // Nullaway compalins, while IntelliJ seems to befine. public Optional getDialect(JdbcOperations operations) { return Optional.ofNullable(operations.execute((ConnectionCallback) DefaultDialectProvider::getDialect)); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/package-info.java index 645c30d7c6..0d1d6f6d74 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/package-info.java @@ -1,7 +1,6 @@ /** * JDBC-specific Dialect implementations. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.core.dialect; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcValue.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcValue.java index 33d409d79f..a6c402d3f5 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcValue.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/JdbcValue.java @@ -19,7 +19,7 @@ import java.sql.SQLType; import java.util.Objects; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Wraps a value with the JDBCType that should be used to pass it as a bind parameter to a @@ -31,16 +31,19 @@ */ public class JdbcValue { - private final Object value; + private final @Nullable Object value; private final SQLType jdbcType; - protected JdbcValue(@Nullable Object value, @Nullable SQLType jdbcType) { + protected JdbcValue(@Nullable Object value, SQLType jdbcType) { this.value = value; this.jdbcType = jdbcType; } public static JdbcValue of(@Nullable Object value, @Nullable SQLType jdbcType) { + if (jdbcType == null) { + jdbcType = value == null ? JDBCType.NULL : JDBCType.OTHER; + } return new JdbcValue(value, jdbcType); } @@ -49,7 +52,6 @@ public Object getValue() { return this.value; } - @Nullable public SQLType getJdbcType() { return this.jdbcType; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java index e9fddca812..bd3a520cc8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/package-info.java @@ -1,2 +1,2 @@ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.core.mapping; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java index 7ab07a0a00..2b49d1230f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/DefaultSqlTypeMapping.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.UUID; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.util.ClassUtils; @@ -62,12 +63,12 @@ public DefaultSqlTypeMapping() { } @Override - public String getColumnType(RelationalPersistentProperty property) { + public @Nullable String getColumnType(RelationalPersistentProperty property) { return getColumnType(property.getActualType()); } @Override - public String getColumnType(Class type) { + public @Nullable String getColumnType(Class type) { return typeMap.get(ClassUtils.resolvePrimitiveIfNecessary(type)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java index 675eb89b70..877c054d10 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/LiquibaseChangeSetWriter.java @@ -57,12 +57,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.core.io.Resource; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.util.Predicates; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java index 73e468a1a3..23f862031a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SchemaDiff.java @@ -25,6 +25,8 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.springframework.util.Assert; + /** * This class is created to return the difference between a source and target {@link Tables} The difference consists of * Table Additions, Deletions, and Modified Tables (i.e. table exists in both source and target - but has columns to add @@ -90,6 +92,8 @@ private static List diffTable(Tables mappedEntities, Map mappedColumns = createMapping(mappedEntity.columns(), Column::name, nameComparator); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java index 5a7da71aed..229c0b9ba1 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/SqlTypeMapping.java @@ -15,8 +15,8 @@ */ package org.springframework.data.jdbc.core.mapping.schema; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; @@ -48,7 +48,6 @@ public interface SqlTypeMapping { * @param type class for which the type should be determined. * @return the SQL type to use, such as {@code VARCHAR} or {@code NUMERIC}. Can be {@literal null} if the strategy * cannot provide a column type. - * * @since 3.3 */ @Nullable diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java index d72d820df8..4c20f6bef9 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Table.java @@ -17,9 +17,9 @@ import java.util.ArrayList; import java.util.List; - import java.util.stream.Collectors; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; import org.springframework.util.ObjectUtils; /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Tables.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Tables.java index e77cdd6884..1ad91a0e8c 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Tables.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/Tables.java @@ -27,6 +27,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.data.annotation.Id; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.mapping.MappedCollection; @@ -35,7 +36,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPredicates; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -74,7 +74,11 @@ public static Tables from(Stream> persis continue; } - Column column = new Column(property.getColumnName().getReference(), sqlTypeMapping.getColumnType(property), + String columnType = sqlTypeMapping.getColumnType(property); + + Assert.state(columnType != null, "Column type must not be null"); + + Column column = new Column(property.getColumnName().getReference(), columnType, sqlTypeMapping.isNullable(property), identifierColumns.contains(property)); table.columns().add(column); } @@ -103,13 +107,17 @@ private static void applyForeignKeyMetadata(List tables, List parentIdColumnNames = parentIdColumns.stream().map(Column::name).toList(); String foreignKeyName = getForeignKeyName(foreignKeyMetadata.parentTableName, parentIdColumnNames); + String keyColumnType = foreignKeyMetadata.keyColumnType(); + if (parentIdColumnNames.size() == 1) { addIfAbsent(table.columns(), new Column(foreignKeyMetadata.referencingColumnName(), parentIdColumns.get(0).type(), false, table.getIdColumns().isEmpty())); if (foreignKeyMetadata.keyColumnName() != null) { - addIfAbsent(table.columns(), - new Column(foreignKeyMetadata.keyColumnName(), foreignKeyMetadata.keyColumnType(), false, true)); + + Assert.state(keyColumnType != null, "Key column type must not be null"); + + addIfAbsent(table.columns(), new Column(foreignKeyMetadata.keyColumnName(), keyColumnType, false, true)); } addIfAbsent(table.foreignKeys(), new ForeignKey(foreignKeyName, foreignKeyMetadata.tableName(), @@ -118,8 +126,12 @@ private static void applyForeignKeyMetadata(List
tables, List collectParentIdentityColumns(ForeignKeyMetadata chil excludeTables.add(child.tableName()); Table parentTable = findTableByName(tables, child.parentTableName()); + + Assert.state(parentTable != null, "Parent table must not be null"); ForeignKeyMetadata parentMetadata = findMetadataByTableName(foreignKeyMetadataList, child.parentTableName(), excludeTables); List parentIdColumns = parentTable.getIdColumns(); @@ -165,8 +179,11 @@ private static List collectParentIdentityColumns(ForeignKeyMetadata chil parentParentIdColumns = new LinkedList<>(List.of(withChangedName)); } if (parentMetadata.keyColumnName() != null) { - parentParentIdColumns - .add(new Column(parentMetadata.keyColumnName(), parentMetadata.keyColumnType(), false, true)); + String keyColumnType = parentMetadata.keyColumnType(); + + Assert.state(keyColumnType != null, "Key column type must not be null"); + + parentParentIdColumns.add(new Column(parentMetadata.keyColumnName(), keyColumnType, false, true)); } return parentParentIdColumns; } @@ -197,7 +214,11 @@ private static ForeignKeyMetadata createForeignKeyMetadata(RelationalPersistentE if (property.getType() == List.class) { referencedKeyColumnType = sqlTypeMapping.getColumnType(Integer.class); } else if (property.getType() == Map.class) { - referencedKeyColumnType = sqlTypeMapping.getColumnType(property.getComponentType()); + Class componentType = property.getComponentType(); + + Assert.state(componentType != null, "Component type must not be null"); + + referencedKeyColumnType = sqlTypeMapping.getColumnType(componentType); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/package-info.java index 2173c50d6f..74259de15f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/schema/package-info.java @@ -1,7 +1,7 @@ /** * Schema creation and schema update integration with Liquibase. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.core.mapping.schema; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java index 51e8e0fbc1..c9928dd407 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/package-info.java @@ -1,7 +1,6 @@ /** * Core JDBC implementation. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.core; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java index ae43172856..7436f2afda 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisContext.java @@ -18,8 +18,8 @@ import java.util.Collections; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.data.jdbc.core.convert.Identifier; -import org.springframework.lang.Nullable; /** * {@link MyBatisContext} instances get passed to MyBatis mapped statements as arguments, making Ids, instances, diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java index 66ce57e942..0ccc36e9f2 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java @@ -27,8 +27,8 @@ import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.session.SqlSession; +import org.jspecify.annotations.Nullable; import org.mybatis.spring.SqlSessionTemplate; - import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -165,7 +165,8 @@ public void setNamespaceStrategy(NamespaceStrategy namespaceStrategy) { } @Override - public Object insert(T instance, Class domainType, Identifier identifier, IdValueSource idValueSource) { + public @Nullable Object insert(T instance, Class domainType, Identifier identifier, + IdValueSource idValueSource) { MyBatisContext myBatisContext = new MyBatisContext(identifier, instance, domainType); sqlSession().insert(namespace(domainType) + ".insert", myBatisContext); @@ -174,7 +175,8 @@ public Object insert(T instance, Class domainType, Identifier identifier, } @Override - public Object[] insert(List> insertSubjects, Class domainType, IdValueSource idValueSource) { + public @Nullable Object[] insert(List> insertSubjects, Class domainType, + IdValueSource idValueSource) { return insertSubjects.stream().map( insertSubject -> insert(insertSubject.getInstance(), domainType, insertSubject.getIdentifier(), idValueSource)) @@ -277,7 +279,7 @@ public void acquireLockAll(LockMode lockMode, Class domainType) { } @Override - public T findById(Object id, Class domainType) { + public T findById(Object id, Class domainType) { String statement = namespace(domainType) + ".findById"; MyBatisContext parameter = new MyBatisContext(id, null, domainType, Collections.emptyMap()); @@ -413,12 +415,7 @@ private SqlSession sqlSession() { } private static String toDashPath(PersistentPropertyPath propertyPath) { - - String dotPath = propertyPath.toDotPath(); - if (dotPath == null) { - return ""; - } - return dotPath.replaceAll("\\.", "-"); + return propertyPath.toDotPath().replaceAll("\\.", "-"); } private Class getOwnerTyp(PersistentPropertyPath propertyPath) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java index 8c7a6f928c..7f50a3441f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/package-info.java @@ -1,4 +1,3 @@ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.mybatis; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java index b66fc4a2aa..39047b2e8d 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java @@ -25,7 +25,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -80,7 +79,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { private static final Log LOG = LogFactory.getLog(AbstractJdbcConfiguration.class); - private ApplicationContext applicationContext; + @SuppressWarnings("NullAway.Init") private ApplicationContext applicationContext; private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java index 65c461d505..ed5e27f1da 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DefaultQueryMappingConfiguration.java @@ -3,10 +3,10 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -19,10 +19,9 @@ */ public class DefaultQueryMappingConfiguration implements QueryMappingConfiguration { - private Map, RowMapper> mappers = new LinkedHashMap<>(); + private final Map, RowMapper> mappers = new LinkedHashMap<>(); - @Nullable - public RowMapper getRowMapper(Class type) { + public @Nullable RowMapper getRowMapper(Class type) { Assert.notNull(type, "Type must not be null"); @@ -41,7 +40,7 @@ public RowMapper getRowMapper(Class type) { } /** - * Registers a the given {@link RowMapper} as to be used for the given type. + * Registers a given {@link RowMapper} as to be used for the given type. * * @return this instance, so this can be used as a fluent interface. */ diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java index fe1dfb9ec6..ed20a2ed77 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/package-info.java @@ -1,4 +1,3 @@ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.repository.config; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractDelegatingRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractDelegatingRowMapper.java index 7bbeb88dcc..8a7d9f2108 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractDelegatingRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractDelegatingRowMapper.java @@ -18,8 +18,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import org.jspecify.annotations.Nullable; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -29,7 +29,7 @@ * @author Mikhail Polivakha * @since 4.0 */ -public abstract class AbstractDelegatingRowMapper implements RowMapper { +public abstract class AbstractDelegatingRowMapper implements RowMapper { private final RowMapper delegate; @@ -41,7 +41,6 @@ protected AbstractDelegatingRowMapper(RowMapper delegate) { } @Override - @Nullable public T mapRow(ResultSet rs, int rowNum) throws SQLException { T intermediate = delegate.mapRow(rs, rowNum); @@ -53,8 +52,7 @@ public T mapRow(ResultSet rs, int rowNum) throws SQLException { * * @return the mapped entity after applying post-processing logic */ - @Nullable - protected T postProcessMapping(@Nullable T object) { + protected T postProcessMapping(T object) { return object; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java index 0ef27d3f63..2331b3a6dd 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/AbstractJdbcQuery.java @@ -19,6 +19,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.repository.query.RepositoryQuery; @@ -28,7 +29,6 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapperResultSetExtractor; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/CallbackCapableRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/CallbackCapableRowMapper.java index 3b43091ff9..e91c6acc07 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/CallbackCapableRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/CallbackCapableRowMapper.java @@ -17,12 +17,12 @@ import java.sql.ResultSet; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.mapping.callback.EntityCallbacks; import org.springframework.data.relational.core.mapping.event.AfterConvertCallback; import org.springframework.data.relational.core.mapping.event.AfterConvertEvent; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; /** * Delegating {@link RowMapper} implementation that applies post-processing logic after the @@ -48,8 +48,7 @@ public CallbackCapableRowMapper(RowMapper delegate, ApplicationEventPublisher } @Override - @Nullable - public T postProcessMapping(@Nullable T object) { + public T postProcessMapping(T object) { if (object != null) { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ConvertingRowMapper.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ConvertingRowMapper.java index 0efc660e0c..b9bc53a9ad 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ConvertingRowMapper.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ConvertingRowMapper.java @@ -15,9 +15,9 @@ */ package org.springframework.data.jdbc.repository.query; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; /** * Delegating {@link RowMapper} that reads a row into {@code T} and converts it afterward into {@code Object}. @@ -38,8 +38,8 @@ public ConvertingRowMapper(RowMapper delegate, Converter } @Override - @Nullable - public Object postProcessMapping(@Nullable Object object) { + @SuppressWarnings("NullAway") + public @Nullable Object postProcessMapping(@Nullable Object object) { return object != null ? converter.convert(object) : null; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/DefaultRowMapperFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/DefaultRowMapperFactory.java index f1f91ec4bc..bb3fc680af 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/DefaultRowMapperFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/DefaultRowMapperFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.repository.query; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.convert.EntityRowMapper; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -42,11 +43,11 @@ public class DefaultRowMapperFactory implements RowMapperFactory { private final RelationalMappingContext context; private final JdbcConverter converter; private final QueryMappingConfiguration queryMappingConfiguration; - private final EntityCallbacks entityCallbacks; + private final @Nullable EntityCallbacks entityCallbacks; private final ApplicationEventPublisher publisher; public DefaultRowMapperFactory(JdbcConverter converter, QueryMappingConfiguration queryMappingConfiguration, - EntityCallbacks entityCallbacks, ApplicationEventPublisher publisher) { + @Nullable EntityCallbacks entityCallbacks, ApplicationEventPublisher publisher) { this.context = converter.getMappingContext(); this.converter = converter; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java index b8f4031556..fc119f6300 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/EscapingParameterSource.java @@ -16,9 +16,11 @@ package org.springframework.data.jdbc.repository.query; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.dialect.Escaper; import org.springframework.data.relational.core.query.ValueFunction; import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.util.Assert; /** * This {@link SqlParameterSource} will apply escaping to its values. @@ -43,7 +45,7 @@ public boolean hasValue(String paramName) { } @Override - public Object getValue(String paramName) throws IllegalArgumentException { + public @Nullable Object getValue(String paramName) throws IllegalArgumentException { Object value = parameterSource.getValue(paramName); if (value instanceof ValueFunction valueFunction) { @@ -52,19 +54,22 @@ public Object getValue(String paramName) throws IllegalArgumentException { return value; } - @Override public int getSqlType(String paramName) { return parameterSource.getSqlType(paramName); } @Override - public String getTypeName(String paramName) { + public @Nullable String getTypeName(String paramName) { return parameterSource.getTypeName(paramName); } @Override public String[] getParameterNames() { - return parameterSource.getParameterNames(); + String[] parameterNames = parameterSource.getParameterNames(); + + Assert.state(parameterNames != null, "Parameter names must not be null"); + + return parameterNames; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcDeleteQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcDeleteQueryCreator.java index d3968b18a0..9fb8c4dacb 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcDeleteQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcDeleteQueryCreator.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.jdbc.core.convert.QueryMapper; @@ -48,7 +49,6 @@ import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.util.Predicates; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -158,7 +158,7 @@ private void deleteRelations(RelationalPersistentEntity entity, Select parent ).from(table) // .where(inCondition) // .build(); - deleteRelations(aggregatePath.getLeafEntity(), select, deleteConsumer); + deleteRelations(aggregatePath.getRequiredLeafEntity(), select, deleteConsumer); deleteConsumer.accept(StatementBuilder.delete(table).where(inCondition).build()); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java index ddfb1e7431..e90d64341f 100755 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcParameters.java @@ -26,6 +26,7 @@ import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; +import org.springframework.util.Assert; /** * Custom extension of {@link RelationalParameters}. @@ -84,8 +85,13 @@ public static class JdbcParameter extends RelationalParameter { sqlType = JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getType())); - actualSqlType = Lazy.of(() -> JdbcUtil - .targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(typeInformation.getActualType().getType()))); + TypeInformation actualType = typeInformation.getActualType(); + actualSqlType = Lazy.of(() -> { + + Assert.state(actualType != null, "ActualType must not be null"); + + return JdbcUtil.targetSqlTypeFor(JdbcColumnTypes.INSTANCE.resolvePrimitiveType(actualType.getType())); + }); } public SQLType getSqlType() { diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java index fa7202a4a8..cc8faf9458 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -47,7 +48,6 @@ import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -233,7 +233,12 @@ SelectBuilder.SelectWhere applyLimitAndOffset(SelectBuilder.SelectLimitOffset li if (tree.isExistsProjection()) { limitOffsetBuilder = limitOffsetBuilder.limit(1); } else if (tree.isLimiting()) { - limitOffsetBuilder = limitOffsetBuilder.limit(tree.getMaxResults()); + + Integer maxResults = tree.getMaxResults(); + + Assert.state(maxResults != null, "Max results must not be null"); + + limitOffsetBuilder = limitOffsetBuilder.limit(maxResults); } Pageable pageable = accessor.getPageable(); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryExecution.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryExecution.java index bcebc67b5c..dc9381f33a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryExecution.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryExecution.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.repository.query; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.DtoInstantiatingConverter; import org.springframework.data.mapping.context.MappingContext; @@ -25,7 +26,6 @@ import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.Lazy; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java index 7fc7c114c6..0b1e93b8b4 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.mapping.context.MappingContext; @@ -31,12 +32,9 @@ import org.springframework.data.relational.repository.query.SimpleRelationalEntityMetadata; import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.RepositoryMetadata; -import org.springframework.data.repository.query.Parameters; -import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.repository.query.QueryMethod; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ObjectUtils; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java index 6edd6cdc67..aada3772c2 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java @@ -25,6 +25,7 @@ import java.util.function.LongSupplier; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -47,7 +48,6 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -195,7 +195,11 @@ private JdbcQueryExecution getQueryExecution(ResultProcessor processor, Object count = singleObjectQuery((rs, i) -> rs.getLong(1)).execute(countQuery.getQuery(), countQuery.getParameterSource(dialect.getLikeEscaper())); - return converter.getConversionService().convert(count, Long.class); + Long converted = converter.getConversionService().convert(count, Long.class); + + Assert.state(converted != null, "Count must not be null"); + + return converted; }); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java index 814daac7d0..0e02f38609 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java @@ -27,6 +27,7 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; @@ -50,7 +51,6 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -156,7 +156,7 @@ public StringBasedJdbcQuery(String query, JdbcQueryMethod queryMethod, NamedPara } @Override - public Object execute(Object[] objects) { + public @Nullable Object execute(Object[] objects) { RelationalParameterAccessor accessor = new RelationalParametersParameterAccessor(getQueryMethod(), objects); ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor); @@ -242,8 +242,7 @@ private MapSqlParameterSource bindParameters(RelationalParameterAccessor accesso JdbcParameters.JdbcParameter parameter = getQueryMethod().getParameters() .getParameter(bindableParameter.getIndex()); - JdbcValue jdbcValue = writeValue(value, parameter.getTypeInformation(), - parameter); + JdbcValue jdbcValue = writeValue(value, parameter.getTypeInformation(), parameter); SQLType jdbcType = jdbcValue.getJdbcType(); if (jdbcType == null) { @@ -410,6 +409,8 @@ public CachedRowMapperFactory(Supplier> defaultMapper) { return defaultMapper.get(); } + Assert.state(constructor != null, "Constructor must not be null"); + return (RowMapper) BeanUtils.instantiateClass(constructor); }); } @@ -465,6 +466,8 @@ public CachedResultSetExtractorFactory(Supplier> resultSetExtractor return BeanUtils.instantiateClass(rowMapperConstructor, rowMapper.get()); } + Assert.state(constructor != null, "Constructor must not be null"); + return BeanUtils.instantiateClass(constructor); }; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java index 96d41b6bb5..7da1d6c4ec 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/package-info.java @@ -1,7 +1,6 @@ /** * Query derivation mechanism for JDBC specific repositories. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.repository.query; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/BeanFactoryAwareRowMapperFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/BeanFactoryAwareRowMapperFactory.java index b5199fe6e3..cd190829af 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/BeanFactoryAwareRowMapperFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/BeanFactoryAwareRowMapperFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jdbc.repository.support; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -25,7 +26,6 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.jdbc.core.ResultSetExtractor; import org.springframework.jdbc.core.RowMapper; -import org.springframework.lang.Nullable; /** * This {@link RowMapperFactory} implementation extends the {@link DefaultRowMapperFactory} by adding the capabilities @@ -41,7 +41,8 @@ public class BeanFactoryAwareRowMapperFactory extends DefaultRowMapperFactory { private final @Nullable BeanFactory beanFactory; BeanFactoryAwareRowMapperFactory(JdbcConverter converter, QueryMappingConfiguration queryMappingConfiguration, - EntityCallbacks entityCallbacks, ApplicationEventPublisher publisher, @Nullable BeanFactory beanFactory) { + @Nullable EntityCallbacks entityCallbacks, ApplicationEventPublisher publisher, + @Nullable BeanFactory beanFactory) { super(converter, queryMappingConfiguration, entityCallbacks, publisher); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java index d5652d8bab..e69930ddb5 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -19,7 +19,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -39,7 +39,6 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -68,9 +67,8 @@ abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy { protected final ValueExpressionDelegate delegate; JdbcQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, - JdbcConverter converter, Dialect dialect, - QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - ValueExpressionDelegate delegate) { + JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, + NamedParameterJdbcOperations operations, ValueExpressionDelegate delegate) { super(converter.getMappingContext(), dialect); @@ -99,14 +97,13 @@ static class CreateQueryLookupStrategy extends JdbcQueryLookupStrategy { private final RowMapperFactory rowMapperFactory; CreateQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, - JdbcConverter converter, Dialect dialect, - QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - ValueExpressionDelegate delegate) { + JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, + NamedParameterJdbcOperations operations, ValueExpressionDelegate delegate) { super(publisher, callbacks, converter, dialect, queryMappingConfiguration, operations, delegate); - this.rowMapperFactory = new DefaultRowMapperFactory(getConverter(), - getQueryMappingConfiguration(), getCallbacks(), getPublisher()); + this.rowMapperFactory = new DefaultRowMapperFactory(getConverter(), getQueryMappingConfiguration(), + getCallbacks(), getPublisher()); } @Override @@ -115,8 +112,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository JdbcQueryMethod queryMethod = getJdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries); - return new PartTreeJdbcQuery(queryMethod, getDialect(), getConverter(), getOperations(), - rowMapperFactory); + return new PartTreeJdbcQuery(queryMethod, getDialect(), getConverter(), getOperations(), rowMapperFactory); } } @@ -132,13 +128,12 @@ static class DeclaredQueryLookupStrategy extends JdbcQueryLookupStrategy { private final RowMapperFactory rowMapperFactory; DeclaredQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, - JdbcConverter converter, Dialect dialect, - QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - @Nullable BeanFactory beanfactory, ValueExpressionDelegate delegate) { + JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, + NamedParameterJdbcOperations operations, @Nullable BeanFactory beanfactory, ValueExpressionDelegate delegate) { super(publisher, callbacks, converter, dialect, queryMappingConfiguration, operations, delegate); - this.rowMapperFactory = new BeanFactoryAwareRowMapperFactory(converter, queryMappingConfiguration, - callbacks, publisher, beanfactory); + this.rowMapperFactory = new BeanFactoryAwareRowMapperFactory(converter, queryMappingConfiguration, callbacks, + publisher, beanfactory); } @Override @@ -185,10 +180,9 @@ static class CreateIfNotFoundQueryLookupStrategy extends JdbcQueryLookupStrategy * @param lookupStrategy must not be {@literal null}. */ CreateIfNotFoundQueryLookupStrategy(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks, - JdbcConverter converter, Dialect dialect, - QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations, - CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy, - ValueExpressionDelegate delegate) { + JdbcConverter converter, Dialect dialect, QueryMappingConfiguration queryMappingConfiguration, + NamedParameterJdbcOperations operations, CreateQueryLookupStrategy createStrategy, + DeclaredQueryLookupStrategy lookupStrategy, ValueExpressionDelegate delegate) { super(publisher, callbacks, converter, dialect, queryMappingConfiguration, operations, delegate); @@ -223,10 +217,9 @@ JdbcQueryMethod getJdbcQueryMethod(Method method, RepositoryMetadata repositoryM * Creates a {@link QueryLookupStrategy} based on the provided * {@link org.springframework.data.repository.query.QueryLookupStrategy.Key}. * - * @param key the key that decides what {@link QueryLookupStrategy} shozld be used. + * @param key the key that decides what {@link QueryLookupStrategy} should be used. May be {@literal null} * @param publisher must not be {@literal null} * @param callbacks may be {@literal null} - * @param context must not be {@literal null} * @param converter must not be {@literal null} * @param dialect must not be {@literal null} * @param queryMappingConfiguration must not be {@literal null} @@ -245,8 +238,8 @@ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPubl Assert.notNull(operations, "NamedParameterJdbcOperations must not be null"); Assert.notNull(delegate, "ValueExpressionDelegate must not be null"); - CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, - converter, dialect, queryMappingConfiguration, operations, delegate); + CreateQueryLookupStrategy createQueryLookupStrategy = new CreateQueryLookupStrategy(publisher, callbacks, converter, + dialect, queryMappingConfiguration, operations, delegate); DeclaredQueryLookupStrategy declaredQueryLookupStrategy = new DeclaredQueryLookupStrategy(publisher, callbacks, converter, dialect, queryMappingConfiguration, operations, beanFactory, delegate); @@ -258,9 +251,8 @@ public static QueryLookupStrategy create(@Nullable Key key, ApplicationEventPubl return switch (keyToUse) { case CREATE -> createQueryLookupStrategy; case USE_DECLARED_QUERY -> declaredQueryLookupStrategy; - case CREATE_IF_NOT_FOUND -> - new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, converter, dialect, - queryMappingConfiguration, operations, createQueryLookupStrategy, declaredQueryLookupStrategy, delegate); + case CREATE_IF_NOT_FOUND -> new CreateIfNotFoundQueryLookupStrategy(publisher, callbacks, converter, dialect, + queryMappingConfiguration, operations, createQueryLookupStrategy, declaredQueryLookupStrategy, delegate); }; } @@ -276,6 +268,7 @@ QueryMappingConfiguration getQueryMappingConfiguration() { return queryMappingConfiguration; } + @Nullable EntityCallbacks getCallbacks() { return callbacks; } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java index da27a3a16a..eaa82c3c4a 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java @@ -17,6 +17,7 @@ import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -38,7 +39,6 @@ import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -59,7 +59,7 @@ public class JdbcRepositoryFactory extends RepositoryFactorySupport implements A private final JdbcAggregateOperations operations; private final NamedParameterJdbcOperations jdbcOperations; - private EntityCallbacks entityCallbacks = EntityCallbacks.create(); + private @Nullable EntityCallbacks entityCallbacks; private ApplicationEventPublisher publisher = event -> {}; private @Nullable BeanFactory beanFactory; private QueryMappingConfiguration queryMappingConfiguration = QueryMappingConfiguration.EMPTY; @@ -175,7 +175,7 @@ protected Class getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) } @Override - protected Optional getQueryLookupStrategy(@Nullable QueryLookupStrategy.Key key, + protected Optional getQueryLookupStrategy(QueryLookupStrategy.@Nullable Key key, ValueExpressionDelegate valueExpressionDelegate) { DataAccessStrategy strategy = operations.getDataAccessStrategy(); diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java index 3c23a2cdd8..18442fe451 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java @@ -18,7 +18,6 @@ import java.io.Serializable; import org.jspecify.annotations.Nullable; - import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; @@ -255,9 +254,10 @@ public void afterPropertiesSet() { if (this.dataAccessStrategy == null) { Assert.state(this.dialect != null, "Dialect must not be null"); + Assert.state(this.converter != null, "Converter must not be null"); - DataAccessStrategyFactory factory = getDataAccessStrategyFactory(this.converter, - this.dialect, this.jdbcOperations, this.queryMappingConfiguration); + DataAccessStrategyFactory factory = getDataAccessStrategyFactory(this.converter, this.dialect, + this.jdbcOperations, this.queryMappingConfiguration); this.dataAccessStrategy = factory.create(); } @@ -268,9 +268,7 @@ public void afterPropertiesSet() { } private static DataAccessStrategyFactory getDataAccessStrategyFactory(JdbcConverter converter, Dialect dialect, - NamedParameterJdbcOperations operations, - QueryMappingConfiguration queryMapping) { - + NamedParameterJdbcOperations operations, QueryMappingConfiguration queryMapping) { return new DataAccessStrategyFactory(converter, operations, dialect, queryMapping); } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java index d60ca96fde..0ad3e843c8 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/package-info.java @@ -1,4 +1,6 @@ -@NonNullApi +/** + * Support classes for integration of the repository programming model with JDBC. + */ +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.repository.support; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java index 8ff6810baa..717975b6cb 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/support/package-info.java @@ -1,4 +1,3 @@ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jdbc.support; -import org.springframework.lang.NonNullApi; diff --git a/spring-data-jdbc/src/main/kotlin/org/springframework/data/jdbc/core/JdbcAggregateOperationsExtensions.kt b/spring-data-jdbc/src/main/kotlin/org/springframework/data/jdbc/core/JdbcAggregateOperationsExtensions.kt index c0a46a20ea..1400f08fc1 100644 --- a/spring-data-jdbc/src/main/kotlin/org/springframework/data/jdbc/core/JdbcAggregateOperationsExtensions.kt +++ b/spring-data-jdbc/src/main/kotlin/org/springframework/data/jdbc/core/JdbcAggregateOperationsExtensions.kt @@ -33,91 +33,91 @@ import java.util.* * Extension for [JdbcAggregateOperations.count]. */ inline fun JdbcAggregateOperations.count(): Long = - count(T::class.java) + count(T::class.java) /** * Extension for [JdbcAggregateOperations.count] with a query. */ -inline fun JdbcAggregateOperations.count(query: Query): Long = - count(query, T::class.java) +inline fun JdbcAggregateOperations.count(query: Query): Long = + count(query, T::class.java) /** * Extension for [JdbcAggregateOperations.exists]. */ -inline fun JdbcAggregateOperations.exists(query: Query): Boolean = - exists(query, T::class.java) +inline fun JdbcAggregateOperations.exists(query: Query): Boolean = + exists(query, T::class.java) /** * Extension for [JdbcAggregateOperations.existsById]. */ -inline fun JdbcAggregateOperations.existsById(id: Any): Boolean = - existsById(id, T::class.java) +inline fun JdbcAggregateOperations.existsById(id: Any): Boolean = + existsById(id, T::class.java) /** * Extension for [JdbcAggregateOperations.findById]. */ -inline fun JdbcAggregateOperations.findById(id: Any): T? = - findById(id, T::class.java) +inline fun JdbcAggregateOperations.findById(id: Any): T? = + findById(id, T::class.java) /** * Extension for [JdbcAggregateOperations.findAllById]. */ -inline fun JdbcAggregateOperations.findAllById(ids: Iterable<*>): List = - findAllById(ids, T::class.java) +inline fun JdbcAggregateOperations.findAllById(ids: Iterable<*>): List = + findAllById(ids, T::class.java) /** * Extension for [JdbcAggregateOperations.findAll]. */ -inline fun JdbcAggregateOperations.findAll(): List = - findAll(T::class.java) +inline fun JdbcAggregateOperations.findAll(): List = + findAll(T::class.java) /** * Extension for [JdbcAggregateOperations.findAll] with sorting. */ -inline fun JdbcAggregateOperations.findAll(sort: Sort): List = - findAll(T::class.java, sort) +inline fun JdbcAggregateOperations.findAll(sort: Sort): List = + findAll(T::class.java, sort) /** * Extension for [JdbcAggregateOperations.findAll] with pagination. */ inline fun JdbcAggregateOperations.findAll(pageable: Pageable): Page = - findAll(T::class.java, pageable) + findAll(T::class.java, pageable) /** * Extension for [JdbcAggregateOperations.findOne] with a query. */ -inline fun JdbcAggregateOperations.findOne(query: Query): Optional = - findOne(query, T::class.java) +inline fun JdbcAggregateOperations.findOne(query: Query): Optional = + findOne(query, T::class.java) /** * Extension for [JdbcAggregateOperations.findAll] with a query. */ -inline fun JdbcAggregateOperations.findAll(query: Query): List = - findAll(query, T::class.java) +inline fun JdbcAggregateOperations.findAll(query: Query): List = + findAll(query, T::class.java) /** * Extension for [JdbcAggregateOperations.findAll] with query and pagination. */ inline fun JdbcAggregateOperations.findAll( - query: Query, - pageable: Pageable + query: Query, + pageable: Pageable ): Page = - findAll(query, T::class.java, pageable) + findAll(query, T::class.java, pageable) /** * Extension for [JdbcAggregateOperations.deleteById]. */ -inline fun JdbcAggregateOperations.deleteById(id: Any): Unit = - deleteById(id, T::class.java) +inline fun JdbcAggregateOperations.deleteById(id: Any): Unit = + deleteById(id, T::class.java) /** * Extension for [JdbcAggregateOperations.deleteAllById]. */ -inline fun JdbcAggregateOperations.deleteAllById(ids: Iterable<*>): Unit = - deleteAllById(ids, T::class.java) +inline fun JdbcAggregateOperations.deleteAllById(ids: Iterable<*>): Unit = + deleteAllById(ids, T::class.java) /** * Extension for [JdbcAggregateOperations.deleteAll]. */ inline fun JdbcAggregateOperations.deleteAll(): Unit = - deleteAll(T::class.java) + deleteAll(T::class.java) diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java index ba958af4d8..dc5bfbed76 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationImmutableUnitTests.java @@ -15,6 +15,21 @@ */ package org.springframework.data.jdbc.core; +import static java.util.Arrays.*; +import static java.util.Collections.*; +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.SoftAssertions.*; +import static org.mockito.Mockito.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; @@ -30,20 +45,6 @@ import org.springframework.data.relational.core.mapping.Embedded; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.lang.Nullable; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static java.util.Arrays.*; -import static java.util.Collections.*; -import static org.assertj.core.api.Assertions.*; -import static org.assertj.core.api.SoftAssertions.*; -import static org.mockito.Mockito.*; /** * Unit tests for the {@link MutableAggregateChange} testing the setting of generated ids in aggregates consisting of @@ -216,7 +217,7 @@ public void setIdForDeepElementSetElementSet() { .extracting(c -> c.id) // .containsExactly(2); // softly.assertThat(entity.contentSet.stream() // - .flatMap(c -> c.tagSet.stream())) // + .flatMap(c -> c.tagSet.stream())) // .extracting(t -> t.id) // .containsExactlyInAnyOrder(3, 4); // }); @@ -284,8 +285,8 @@ public void setIdForDeepElementListElementList() { .extracting(c -> c.id) // .containsExactly(2, 3); // softly.assertThat(entity.contentList.stream() // - .flatMap(c -> c.tagList.stream()) // - ).extracting(t -> t.id) // + .flatMap(c -> c.tagList.stream()) // + ).extracting(t -> t.id) // .containsExactly(4, 5, 6); // }); } @@ -320,13 +321,13 @@ public void setIdForDeepElementMapElementMap() { .extracting(Map.Entry::getKey, e -> e.getValue().id) // .containsExactly(tuple("one", 2), tuple("two", 3)); // softly.assertThat(entity.contentMap.values().stream() // - .flatMap(c -> c.tagMap.entrySet().stream())) // + .flatMap(c -> c.tagMap.entrySet().stream())) // .extracting(Map.Entry::getKey, e -> e.getValue().id) // .containsExactly( // tuple("111", 4), // tuple("222", 5), // tuple("333", 6) // - ); // + ); // }); } @@ -402,19 +403,18 @@ private static Map createTagMap(Object... keysAndValues) { DbAction.Insert createInsert(String propertyName, Object value, @Nullable Object key) { - return new DbAction.Insert<>(value, - context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert, + return new DbAction.Insert<>(value, context.getPersistentPropertyPath(propertyName, DummyEntity.class), rootInsert, singletonMap(toPath(propertyName), key), IdValueSource.GENERATED); } DbAction.Insert createDeepInsert(String propertyName, Object value, Object key, - @Nullable DbAction.Insert parentInsert) { + DbAction.@Nullable Insert parentInsert) { PersistentPropertyPath propertyPath = toPath( - parentInsert.getPropertyPath().toDotPath() + "." + propertyName); + parentInsert.propertyPath().toDotPath() + "." + propertyName); - return new DbAction.Insert<>(value, propertyPath, parentInsert, - singletonMap(propertyPath, key), IdValueSource.GENERATED); + return new DbAction.Insert<>(value, propertyPath, parentInsert, singletonMap(propertyPath, key), + IdValueSource.GENERATED); } PersistentPropertyPath toPath(String path) { @@ -426,412 +426,328 @@ PersistentPropertyPath toPath(String path) { .orElseThrow(() -> new IllegalArgumentException("No matching path found")); } - private static final class DummyEntity { - - @Id - private final - Integer rootId; - private final Content single; - private final Set contentSet; - private final List contentList; - private final Map contentMap; - private final List contentNoIdList; - @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL, prefix = "fooBar") - private final - ContentNoId embedded; - - DummyEntity() { - - rootId = null; - single = null; - contentSet = emptySet(); - contentList = emptyList(); - contentMap = emptyMap(); - contentNoIdList = emptyList(); - embedded = new ContentNoId(); - } - - public DummyEntity(Integer rootId, Content single, Set contentSet, List contentList, Map contentMap, List contentNoIdList, ContentNoId embedded) { - this.rootId = rootId; - this.single = single; - this.contentSet = contentSet; - this.contentList = contentList; - this.contentMap = contentMap; - this.contentNoIdList = contentNoIdList; - this.embedded = embedded; - } - - public Integer getRootId() { - return this.rootId; - } - - public Content getSingle() { - return this.single; - } - - public Set getContentSet() { - return this.contentSet; - } - - public List getContentList() { - return this.contentList; - } + private record DummyEntity(@Id Integer rootId, Content single, Set contentSet, List contentList, + Map contentMap, List contentNoIdList, + @Embedded(onEmpty = Embedded.OnEmpty.USE_NULL, prefix = "fooBar") ContentNoId embedded) { + + DummyEntity() { + + this(null, null, emptySet(), emptyList(), emptyMap(), emptyList(), new ContentNoId()); + } + + @Override + public Integer rootId() { + return this.rootId; + } + + + @Override + public ContentNoId embedded() { + return this.embedded; + } + + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof DummyEntity other)) + return false; + final Object this$rootId = this.rootId(); + final Object other$rootId = other.rootId(); + if (!Objects.equals(this$rootId, other$rootId)) + return false; + final Object this$single = this.single(); + final Object other$single = other.single(); + if (!Objects.equals(this$single, other$single)) + return false; + final Object this$contentSet = this.contentSet(); + final Object other$contentSet = other.contentSet(); + if (!Objects.equals(this$contentSet, other$contentSet)) + return false; + final Object this$contentList = this.contentList(); + final Object other$contentList = other.contentList(); + if (!Objects.equals(this$contentList, other$contentList)) + return false; + final Object this$contentMap = this.contentMap(); + final Object other$contentMap = other.contentMap(); + if (!Objects.equals(this$contentMap, other$contentMap)) + return false; + final Object this$contentNoIdList = this.contentNoIdList(); + final Object other$contentNoIdList = other.contentNoIdList(); + if (!Objects.equals(this$contentNoIdList, other$contentNoIdList)) + return false; + final Object this$embedded = this.embedded(); + final Object other$embedded = other.embedded(); + return Objects.equals(this$embedded, other$embedded); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $rootId = this.rootId(); + result = result * PRIME + ($rootId == null ? 43 : $rootId.hashCode()); + final Object $single = this.single(); + result = result * PRIME + ($single == null ? 43 : $single.hashCode()); + final Object $contentSet = this.contentSet(); + result = result * PRIME + ($contentSet == null ? 43 : $contentSet.hashCode()); + final Object $contentList = this.contentList(); + result = result * PRIME + ($contentList == null ? 43 : $contentList.hashCode()); + final Object $contentMap = this.contentMap(); + result = result * PRIME + ($contentMap == null ? 43 : $contentMap.hashCode()); + final Object $contentNoIdList = this.contentNoIdList(); + result = result * PRIME + ($contentNoIdList == null ? 43 : $contentNoIdList.hashCode()); + final Object $embedded = this.embedded(); + result = result * PRIME + ($embedded == null ? 43 : $embedded.hashCode()); + return result; + } + + public String toString() { + return "AggregateChangeIdGenerationImmutableUnitTests.DummyEntity(rootId=" + this.rootId() + ", single=" + + this.single() + ", contentSet=" + this.contentSet() + ", contentList=" + this.contentList() + + ", contentMap=" + this.contentMap() + ", contentNoIdList=" + this.contentNoIdList() + ", embedded=" + + this.embedded() + ")"; + } + + public DummyEntity withRootId(Integer rootId) { + return this.rootId == rootId ? this + : new DummyEntity(rootId, this.single, this.contentSet, this.contentList, this.contentMap, + this.contentNoIdList, this.embedded); + } + + public DummyEntity withSingle(Content single) { + return this.single == single ? this + : new DummyEntity(this.rootId, single, this.contentSet, this.contentList, this.contentMap, + this.contentNoIdList, this.embedded); + } + + public DummyEntity withContentSet(Set contentSet) { + return this.contentSet == contentSet ? this + : new DummyEntity(this.rootId, this.single, contentSet, this.contentList, this.contentMap, + this.contentNoIdList, this.embedded); + } + + public DummyEntity withContentList(List contentList) { + return this.contentList == contentList ? this + : new DummyEntity(this.rootId, this.single, this.contentSet, contentList, this.contentMap, + this.contentNoIdList, this.embedded); + } + + public DummyEntity withContentMap(Map contentMap) { + return this.contentMap == contentMap ? this + : new DummyEntity(this.rootId, this.single, this.contentSet, this.contentList, contentMap, + this.contentNoIdList, this.embedded); + } + + public DummyEntity withContentNoIdList(List contentNoIdList) { + return this.contentNoIdList == contentNoIdList ? this + : new DummyEntity(this.rootId, this.single, this.contentSet, this.contentList, this.contentMap, + contentNoIdList, this.embedded); + } + + public DummyEntity withEmbedded(ContentNoId embedded) { + return this.embedded == embedded ? this + : new DummyEntity(this.rootId, this.single, this.contentSet, this.contentList, this.contentMap, + this.contentNoIdList, embedded); + } + } + + private record Content(@Id Integer id, Tag single, Set tagSet, List tagList, Map tagMap) { + + Content() { + + this(null, null, emptySet(), emptyList(), emptyMap()); + } + + @Override + public Integer id() { + return this.id; + } - public Map getContentMap() { - return this.contentMap; - } - - public List getContentNoIdList() { - return this.contentNoIdList; - } - - public ContentNoId getEmbedded() { - return this.embedded; - } public boolean equals(final Object o) { - if (o == this) return true; - if (!(o instanceof DummyEntity other)) return false; - final Object this$rootId = this.getRootId(); - final Object other$rootId = other.getRootId(); - if (this$rootId == null ? other$rootId != null : !this$rootId.equals(other$rootId)) return false; - final Object this$single = this.getSingle(); - final Object other$single = other.getSingle(); - if (this$single == null ? other$single != null : !this$single.equals(other$single)) return false; - final Object this$contentSet = this.getContentSet(); - final Object other$contentSet = other.getContentSet(); - if (this$contentSet == null ? other$contentSet != null : !this$contentSet.equals(other$contentSet)) - return false; - final Object this$contentList = this.getContentList(); - final Object other$contentList = other.getContentList(); - if (this$contentList == null ? other$contentList != null : !this$contentList.equals(other$contentList)) - return false; - final Object this$contentMap = this.getContentMap(); - final Object other$contentMap = other.getContentMap(); - if (this$contentMap == null ? other$contentMap != null : !this$contentMap.equals(other$contentMap)) - return false; - final Object this$contentNoIdList = this.getContentNoIdList(); - final Object other$contentNoIdList = other.getContentNoIdList(); - if (this$contentNoIdList == null ? other$contentNoIdList != null : !this$contentNoIdList.equals(other$contentNoIdList)) - return false; - final Object this$embedded = this.getEmbedded(); - final Object other$embedded = other.getEmbedded(); - if (this$embedded == null ? other$embedded != null : !this$embedded.equals(other$embedded)) return false; - return true; - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $rootId = this.getRootId(); - result = result * PRIME + ($rootId == null ? 43 : $rootId.hashCode()); - final Object $single = this.getSingle(); - result = result * PRIME + ($single == null ? 43 : $single.hashCode()); - final Object $contentSet = this.getContentSet(); - result = result * PRIME + ($contentSet == null ? 43 : $contentSet.hashCode()); - final Object $contentList = this.getContentList(); - result = result * PRIME + ($contentList == null ? 43 : $contentList.hashCode()); - final Object $contentMap = this.getContentMap(); - result = result * PRIME + ($contentMap == null ? 43 : $contentMap.hashCode()); - final Object $contentNoIdList = this.getContentNoIdList(); - result = result * PRIME + ($contentNoIdList == null ? 43 : $contentNoIdList.hashCode()); - final Object $embedded = this.getEmbedded(); - result = result * PRIME + ($embedded == null ? 43 : $embedded.hashCode()); - return result; - } - - public String toString() { - return "AggregateChangeIdGenerationImmutableUnitTests.DummyEntity(rootId=" + this.getRootId() + ", single=" + this.getSingle() + ", contentSet=" + this.getContentSet() + ", contentList=" + this.getContentList() + ", contentMap=" + this.getContentMap() + ", contentNoIdList=" + this.getContentNoIdList() + ", embedded=" + this.getEmbedded() + ")"; - } - - public DummyEntity withRootId(Integer rootId) { - return this.rootId == rootId ? this : new DummyEntity(rootId, this.single, this.contentSet, this.contentList, this.contentMap, this.contentNoIdList, this.embedded); - } - - public DummyEntity withSingle(Content single) { - return this.single == single ? this : new DummyEntity(this.rootId, single, this.contentSet, this.contentList, this.contentMap, this.contentNoIdList, this.embedded); - } - - public DummyEntity withContentSet(Set contentSet) { - return this.contentSet == contentSet ? this : new DummyEntity(this.rootId, this.single, contentSet, this.contentList, this.contentMap, this.contentNoIdList, this.embedded); - } - - public DummyEntity withContentList(List contentList) { - return this.contentList == contentList ? this : new DummyEntity(this.rootId, this.single, this.contentSet, contentList, this.contentMap, this.contentNoIdList, this.embedded); - } - - public DummyEntity withContentMap(Map contentMap) { - return this.contentMap == contentMap ? this : new DummyEntity(this.rootId, this.single, this.contentSet, this.contentList, contentMap, this.contentNoIdList, this.embedded); - } - - public DummyEntity withContentNoIdList(List contentNoIdList) { - return this.contentNoIdList == contentNoIdList ? this : new DummyEntity(this.rootId, this.single, this.contentSet, this.contentList, this.contentMap, contentNoIdList, this.embedded); - } - - public DummyEntity withEmbedded(ContentNoId embedded) { - return this.embedded == embedded ? this : new DummyEntity(this.rootId, this.single, this.contentSet, this.contentList, this.contentMap, this.contentNoIdList, embedded); - } - } - - private static final class Content { + if (o == this) + return true; + if (!(o instanceof Content other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$single = this.single(); + final Object other$single = other.single(); + if (!Objects.equals(this$single, other$single)) + return false; + final Object this$tagSet = this.tagSet(); + final Object other$tagSet = other.tagSet(); + if (!Objects.equals(this$tagSet, other$tagSet)) + return false; + final Object this$tagList = this.tagList(); + final Object other$tagList = other.tagList(); + if (!Objects.equals(this$tagList, other$tagList)) + return false; + final Object this$tagMap = this.tagMap(); + final Object other$tagMap = other.tagMap(); + return Objects.equals(this$tagMap, other$tagMap); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $single = this.single(); + result = result * PRIME + ($single == null ? 43 : $single.hashCode()); + final Object $tagSet = this.tagSet(); + result = result * PRIME + ($tagSet == null ? 43 : $tagSet.hashCode()); + final Object $tagList = this.tagList(); + result = result * PRIME + ($tagList == null ? 43 : $tagList.hashCode()); + final Object $tagMap = this.tagMap(); + result = result * PRIME + ($tagMap == null ? 43 : $tagMap.hashCode()); + return result; + } + + public String toString() { + return "AggregateChangeIdGenerationImmutableUnitTests.Content(id=" + this.id() + ", single=" + this.single() + + ", tagSet=" + this.tagSet() + ", tagList=" + this.tagList() + ", tagMap=" + this.tagMap() + ")"; + } + + public Content withId(Integer id) { + return this.id == id ? this : new Content(id, this.single, this.tagSet, this.tagList, this.tagMap); + } + + public Content withSingle(Tag single) { + return this.single == single ? this : new Content(this.id, single, this.tagSet, this.tagList, this.tagMap); + } + + public Content withTagSet(Set tagSet) { + return this.tagSet == tagSet ? this : new Content(this.id, this.single, tagSet, this.tagList, this.tagMap); + } + + public Content withTagList(List tagList) { + return this.tagList == tagList ? this : new Content(this.id, this.single, this.tagSet, tagList, this.tagMap); + } + + public Content withTagMap(Map tagMap) { + return this.tagMap == tagMap ? this : new Content(this.id, this.single, this.tagSet, this.tagList, tagMap); + } + } + + private record ContentNoId(@Column("single") Tag single, Set tagSet, List tagList, + Map tagMap) { + ContentNoId() { + + this(null, emptySet(), emptyList(), emptyMap()); + } + + @Override + public Tag single() { + return this.single; + } - @Id - private final - Integer id; - private final Tag single; - private final Set tagSet; - private final List tagList; - private final Map tagMap; - - Content() { - - id = null; - single = null; - tagSet = emptySet(); - tagList = emptyList(); - tagMap = emptyMap(); - } - - public Content(Integer id, Tag single, Set tagSet, List tagList, Map tagMap) { - this.id = id; - this.single = single; - this.tagSet = tagSet; - this.tagList = tagList; - this.tagMap = tagMap; - } - - public Integer getId() { - return this.id; - } - - public Tag getSingle() { - return this.single; - } - - public Set getTagSet() { - return this.tagSet; - } - - public List getTagList() { - return this.tagList; - } - - public Map getTagMap() { - return this.tagMap; - } public boolean equals(final Object o) { - if (o == this) return true; - if (!(o instanceof Content other)) return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false; - final Object this$single = this.getSingle(); - final Object other$single = other.getSingle(); - if (this$single == null ? other$single != null : !this$single.equals(other$single)) return false; - final Object this$tagSet = this.getTagSet(); - final Object other$tagSet = other.getTagSet(); - if (this$tagSet == null ? other$tagSet != null : !this$tagSet.equals(other$tagSet)) return false; - final Object this$tagList = this.getTagList(); - final Object other$tagList = other.getTagList(); - if (this$tagList == null ? other$tagList != null : !this$tagList.equals(other$tagList)) return false; - final Object this$tagMap = this.getTagMap(); - final Object other$tagMap = other.getTagMap(); - if (this$tagMap == null ? other$tagMap != null : !this$tagMap.equals(other$tagMap)) return false; - return true; - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - final Object $single = this.getSingle(); - result = result * PRIME + ($single == null ? 43 : $single.hashCode()); - final Object $tagSet = this.getTagSet(); - result = result * PRIME + ($tagSet == null ? 43 : $tagSet.hashCode()); - final Object $tagList = this.getTagList(); - result = result * PRIME + ($tagList == null ? 43 : $tagList.hashCode()); - final Object $tagMap = this.getTagMap(); - result = result * PRIME + ($tagMap == null ? 43 : $tagMap.hashCode()); - return result; - } - - public String toString() { - return "AggregateChangeIdGenerationImmutableUnitTests.Content(id=" + this.getId() + ", single=" + this.getSingle() + ", tagSet=" + this.getTagSet() + ", tagList=" + this.getTagList() + ", tagMap=" + this.getTagMap() + ")"; - } - - public Content withId(Integer id) { - return this.id == id ? this : new Content(id, this.single, this.tagSet, this.tagList, this.tagMap); - } - - public Content withSingle(Tag single) { - return this.single == single ? this : new Content(this.id, single, this.tagSet, this.tagList, this.tagMap); - } - - public Content withTagSet(Set tagSet) { - return this.tagSet == tagSet ? this : new Content(this.id, this.single, tagSet, this.tagList, this.tagMap); - } - - public Content withTagList(List tagList) { - return this.tagList == tagList ? this : new Content(this.id, this.single, this.tagSet, tagList, this.tagMap); - } - - public Content withTagMap(Map tagMap) { - return this.tagMap == tagMap ? this : new Content(this.id, this.single, this.tagSet, this.tagList, tagMap); - } - } - - private static final class ContentNoId { - @Column("single") - private final - Tag single; - private final Set tagSet; - private final List tagList; - private final Map tagMap; - - ContentNoId() { - - single = null; - tagSet = emptySet(); - tagList = emptyList(); - tagMap = emptyMap(); - } - - public ContentNoId(Tag single, Set tagSet, List tagList, Map tagMap) { - this.single = single; - this.tagSet = tagSet; - this.tagList = tagList; - this.tagMap = tagMap; - } + if (o == this) + return true; + if (!(o instanceof ContentNoId other)) + return false; + final Object this$single = this.single(); + final Object other$single = other.single(); + if (!Objects.equals(this$single, other$single)) + return false; + final Object this$tagSet = this.tagSet(); + final Object other$tagSet = other.tagSet(); + if (!Objects.equals(this$tagSet, other$tagSet)) + return false; + final Object this$tagList = this.tagList(); + final Object other$tagList = other.tagList(); + if (!Objects.equals(this$tagList, other$tagList)) + return false; + final Object this$tagMap = this.tagMap(); + final Object other$tagMap = other.tagMap(); + return Objects.equals(this$tagMap, other$tagMap); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $single = this.single(); + result = result * PRIME + ($single == null ? 43 : $single.hashCode()); + final Object $tagSet = this.tagSet(); + result = result * PRIME + ($tagSet == null ? 43 : $tagSet.hashCode()); + final Object $tagList = this.tagList(); + result = result * PRIME + ($tagList == null ? 43 : $tagList.hashCode()); + final Object $tagMap = this.tagMap(); + result = result * PRIME + ($tagMap == null ? 43 : $tagMap.hashCode()); + return result; + } + + public String toString() { + return "AggregateChangeIdGenerationImmutableUnitTests.ContentNoId(single=" + this.single() + ", tagSet=" + + this.tagSet() + ", tagList=" + this.tagList() + ", tagMap=" + this.tagMap() + ")"; + } + + public ContentNoId withSingle(Tag single) { + return this.single == single ? this : new ContentNoId(single, this.tagSet, this.tagList, this.tagMap); + } + + public ContentNoId withTagSet(Set tagSet) { + return this.tagSet == tagSet ? this : new ContentNoId(this.single, tagSet, this.tagList, this.tagMap); + } + + public ContentNoId withTagList(List tagList) { + return this.tagList == tagList ? this : new ContentNoId(this.single, this.tagSet, tagList, this.tagMap); + } + + public ContentNoId withTagMap(Map tagMap) { + return this.tagMap == tagMap ? this : new ContentNoId(this.single, this.tagSet, this.tagList, tagMap); + } + } + + private record Tag(@Id Integer id, String name) { + + Tag(String name) { + this(null, name); + } + + @Override + public Integer id() { + return this.id; + } - public Tag getSingle() { - return this.single; - } - - public Set getTagSet() { - return this.tagSet; - } - - public List getTagList() { - return this.tagList; - } - - public Map getTagMap() { - return this.tagMap; - } public boolean equals(final Object o) { - if (o == this) return true; - if (!(o instanceof ContentNoId other)) return false; - final Object this$single = this.getSingle(); - final Object other$single = other.getSingle(); - if (this$single == null ? other$single != null : !this$single.equals(other$single)) return false; - final Object this$tagSet = this.getTagSet(); - final Object other$tagSet = other.getTagSet(); - if (this$tagSet == null ? other$tagSet != null : !this$tagSet.equals(other$tagSet)) return false; - final Object this$tagList = this.getTagList(); - final Object other$tagList = other.getTagList(); - if (this$tagList == null ? other$tagList != null : !this$tagList.equals(other$tagList)) return false; - final Object this$tagMap = this.getTagMap(); - final Object other$tagMap = other.getTagMap(); - if (this$tagMap == null ? other$tagMap != null : !this$tagMap.equals(other$tagMap)) return false; - return true; + if (o == this) + return true; + if (!(o instanceof Tag other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.name(); + final Object other$name = other.name(); + return Objects.equals(this$name, other$name); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.name(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + return result; + } + + public String toString() { + return "AggregateChangeIdGenerationImmutableUnitTests.Tag(id=" + this.id() + ", name=" + this.name() + ")"; + } + + public Tag withId(Integer id) { + return this.id == id ? this : new Tag(id, this.name); + } + + public Tag withName(String name) { + return this.name == name ? this : new Tag(this.id, name); + } } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $single = this.getSingle(); - result = result * PRIME + ($single == null ? 43 : $single.hashCode()); - final Object $tagSet = this.getTagSet(); - result = result * PRIME + ($tagSet == null ? 43 : $tagSet.hashCode()); - final Object $tagList = this.getTagList(); - result = result * PRIME + ($tagList == null ? 43 : $tagList.hashCode()); - final Object $tagMap = this.getTagMap(); - result = result * PRIME + ($tagMap == null ? 43 : $tagMap.hashCode()); - return result; - } - - public String toString() { - return "AggregateChangeIdGenerationImmutableUnitTests.ContentNoId(single=" + this.getSingle() + ", tagSet=" + this.getTagSet() + ", tagList=" + this.getTagList() + ", tagMap=" + this.getTagMap() + ")"; - } - - public ContentNoId withSingle(Tag single) { - return this.single == single ? this : new ContentNoId(single, this.tagSet, this.tagList, this.tagMap); - } - - public ContentNoId withTagSet(Set tagSet) { - return this.tagSet == tagSet ? this : new ContentNoId(this.single, tagSet, this.tagList, this.tagMap); - } - - public ContentNoId withTagList(List tagList) { - return this.tagList == tagList ? this : new ContentNoId(this.single, this.tagSet, tagList, this.tagMap); - } - - public ContentNoId withTagMap(Map tagMap) { - return this.tagMap == tagMap ? this : new ContentNoId(this.single, this.tagSet, this.tagList, tagMap); - } - } - - private static final class Tag { - - @Id - private final - Integer id; - - private final String name; - - Tag(String name) { - id = null; - this.name = name; - } - - public Tag(Integer id, String name) { - this.id = id; - this.name = name; - } - - public Integer getId() { - return this.id; - } - - public String getName() { - return this.name; - } - - public boolean equals(final Object o) { - if (o == this) return true; - if (!(o instanceof Tag other)) return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (this$id == null ? other$id != null : !this$id.equals(other$id)) return false; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false; - return true; - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 43 : $name.hashCode()); - return result; - } - - public String toString() { - return "AggregateChangeIdGenerationImmutableUnitTests.Tag(id=" + this.getId() + ", name=" + this.getName() + ")"; - } - - public Tag withId(Integer id) { - return this.id == id ? this : new Tag(id, this.name); - } - - public Tag withName(String name) { - return this.name == name ? this : new Tag(this.id, name); - } - } } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java index ce695532c1..927414957b 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AggregateChangeIdGenerationUnitTests.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -42,7 +43,6 @@ import org.springframework.data.relational.core.conversion.RootAggregateChange; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.lang.Nullable; /** * Unit tests for the {@link MutableAggregateChange}. @@ -335,7 +335,7 @@ DbAction.Insert createDeepInsert(String propertyName, Object value, @Nullable DbAction.Insert parentInsert) { PersistentPropertyPath propertyPath = toPath( - parentInsert.getPropertyPath().toDotPath() + "." + propertyName); + parentInsert.propertyPath().toDotPath() + "." + propertyName); return new DbAction.Insert<>(value, propertyPath, parentInsert, key == null ? emptyMap() : singletonMap(propertyPath, key), IdValueSource.GENERATED); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java index bf0b41a044..c432ab9b84 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextImmutableUnitTests.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; @@ -37,7 +38,6 @@ import org.springframework.data.relational.core.mapping.AggregatePath; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.lang.Nullable; /** * Test for the {@link JdbcAggregateChangeExecutionContext} when operating on immutable classes. @@ -180,142 +180,115 @@ PersistentPropertyPath toPath(String path) { .orElseThrow(() -> new IllegalArgumentException("No matching path found")); } - private static final class DummyEntity { + private record DummyEntity(@Id Long id, @Version long version, Content content, List list) { - @Id private final Long id; - @Version private final long version; + DummyEntity() { - private final Content content; + this(null, 0, null, null); + } - private final List list; + @Override + public Long id() { + return this.id; + } - DummyEntity() { + @Override + public long version() { + return this.version; + } - id = null; - version = 0; - content = null; - list = null; - } - - public DummyEntity(Long id, long version, Content content, List list) { - this.id = id; - this.version = version; - this.content = content; - this.list = list; - } - - public Long getId() { - return this.id; - } - - public long getVersion() { - return this.version; - } - - public Content getContent() { - return this.content; - } - - public List getList() { - return this.list; - } public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof final DummyEntity other)) - return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (!Objects.equals(this$id, other$id)) - return false; - if (this.getVersion() != other.getVersion()) - return false; - final Object this$content = this.getContent(); - final Object other$content = other.getContent(); - if (!Objects.equals(this$content, other$content)) - return false; - final Object this$list = this.getList(); - final Object other$list = other.getList(); - return Objects.equals(this$list, other$list); - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - final long $version = this.getVersion(); - result = result * PRIME + (int) ($version >>> 32 ^ $version); - final Object $content = this.getContent(); - result = result * PRIME + ($content == null ? 43 : $content.hashCode()); - final Object $list = this.getList(); - result = result * PRIME + ($list == null ? 43 : $list.hashCode()); - return result; - } - - public String toString() { - return "JdbcAggregateChangeExecutorContextImmutableUnitTests.DummyEntity(id=" + this.getId() + ", version=" - + this.getVersion() + ", content=" + this.getContent() + ", list=" + this.getList() + ")"; + if (o == this) + return true; + if (!(o instanceof final DummyEntity other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + if (!Objects.equals(this$id, other$id)) + return false; + if (this.version() != other.version()) + return false; + final Object this$content = this.content(); + final Object other$content = other.content(); + if (!Objects.equals(this$content, other$content)) + return false; + final Object this$list = this.list(); + final Object other$list = other.list(); + return Objects.equals(this$list, other$list); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final long $version = this.version(); + result = result * PRIME + (int) ($version >>> 32 ^ $version); + final Object $content = this.content(); + result = result * PRIME + ($content == null ? 43 : $content.hashCode()); + final Object $list = this.list(); + result = result * PRIME + ($list == null ? 43 : $list.hashCode()); + return result; + } + + public String toString() { + return "JdbcAggregateChangeExecutorContextImmutableUnitTests.DummyEntity(id=" + this.id() + ", version=" + + this.version() + ", content=" + this.content() + ", list=" + this.list() + ")"; + } + + public DummyEntity withId(Long id) { + return this.id == id ? this : new DummyEntity(id, this.version, this.content, this.list); + } + + public DummyEntity withVersion(long version) { + return this.version == version ? this : new DummyEntity(this.id, version, this.content, this.list); + } + + public DummyEntity withContent(Content content) { + return this.content == content ? this : new DummyEntity(this.id, this.version, content, this.list); + } + + public DummyEntity withList(List list) { + return this.list == list ? this : new DummyEntity(this.id, this.version, this.content, list); + } } - public DummyEntity withId(Long id) { - return this.id == id ? this : new DummyEntity(id, this.version, this.content, this.list); + private record Content(@Id Long id) { + Content() { + this(null); + } + + @Override + public Long id() { + return this.id; + } + + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final Content other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + return Objects.equals(this$id, other$id); + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + return result; + } + + public String toString() { + return "JdbcAggregateChangeExecutorContextImmutableUnitTests.Content(id=" + this.id() + ")"; + } + + public Content withId(Long id) { + return this.id == id ? this : new Content(id); + } } - public DummyEntity withVersion(long version) { - return this.version == version ? this : new DummyEntity(this.id, version, this.content, this.list); - } - - public DummyEntity withContent(Content content) { - return this.content == content ? this : new DummyEntity(this.id, this.version, content, this.list); - } - - public DummyEntity withList(List list) { - return this.list == list ? this : new DummyEntity(this.id, this.version, this.content, list); - } - } - - private static final class Content { - @Id private final Long id; - - Content() { - id = null; - } - - public Content(Long id) { - this.id = id; - } - - public Long getId() { - return this.id; - } - - public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof final Content other)) - return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - return Objects.equals(this$id, other$id); - } - - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - return result; - } - - public String toString() { - return "JdbcAggregateChangeExecutorContextImmutableUnitTests.Content(id=" + this.getId() + ")"; - } - - public Content withId(Long id) { - return this.id == id ? this : new Content(id); - } - } - } diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java index afb0f224c0..5319224401 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateChangeExecutorContextUnitTests.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; @@ -39,7 +40,6 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; /** * Unit tests for {@link JdbcAggregateChangeExecutionContext}. diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java index 80283e4caa..2272e2824c 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorEmbeddedUnitTests.java @@ -18,9 +18,9 @@ import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.PersistentPropertyPathTestUtils; import org.springframework.data.jdbc.core.mapping.AggregateReference; @@ -35,7 +35,6 @@ import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.sql.Aliased; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; /** * Unit tests for the {@link SqlGenerator} in a context of the {@link Embedded} annotation. @@ -46,8 +45,8 @@ */ class SqlGeneratorEmbeddedUnitTests { - private RelationalMappingContext context = JdbcMappingContext.forPlainIdentifiers(); - private JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> { + private final RelationalMappingContext context = JdbcMappingContext.forPlainIdentifiers(); + private final JdbcConverter converter = new MappingJdbcConverter(context, (identifier, path) -> { throw new UnsupportedOperationException(); }); private SqlGenerator sqlGenerator; @@ -416,8 +415,7 @@ void columnForEmbeddedWithReferenceProperty() { SqlIdentifier.unquoted("prefix_other_value")); } - @Nullable - private SqlGenerator.Join generateJoin(String path, Class type) { + private SqlGenerator.@Nullable Join generateJoin(String path, Class type) { return createSqlGenerator(type) .getJoin(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); } @@ -431,8 +429,7 @@ private SqlIdentifier getAlias(Object maybeAliased) { return null; } - @Nullable - private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { + private org.springframework.data.relational.core.sql.@Nullable Column generatedColumn(String path, Class type) { return createSqlGenerator(type) .getColumn(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java index 08831d801a..659b008035 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java @@ -24,9 +24,9 @@ import java.util.Map; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import org.springframework.data.annotation.Id; import org.springframework.data.annotation.ReadOnlyProperty; import org.springframework.data.annotation.Version; @@ -56,7 +56,6 @@ import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.lang.Nullable; /** * Unit tests for the {@link SqlGenerator}. @@ -827,8 +826,7 @@ void joinForOneToOneWithoutId() { }); } - @Nullable - private SqlGenerator.Join generateJoin(String path, Class type) { + private SqlGenerator.@Nullable Join generateJoin(String path, Class type) { return createSqlGenerator(type, AnsiDialect.INSTANCE) .getJoin(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); } @@ -1035,8 +1033,7 @@ private SqlIdentifier getAlias(Object maybeAliased) { return null; } - @Nullable - private org.springframework.data.relational.core.sql.Column generatedColumn(String path, Class type) { + private org.springframework.data.relational.core.sql.@Nullable Column generatedColumn(String path, Class type) { return createSqlGenerator(type, AnsiDialect.INSTANCE) .getColumn(context.getAggregatePath(PersistentPropertyPathTestUtils.getPath(path, type, context))); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java index 31722d9b50..4c8fb4e83d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java @@ -20,9 +20,9 @@ import static org.mockito.Mockito.*; import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; - import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.convert.DataAccessStrategy; @@ -42,7 +42,6 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; -import org.springframework.lang.Nullable; /** * Extracts the SQL statement that results from declared queries of a repository and perform assertions on it. @@ -51,7 +50,7 @@ */ public class DeclaredQueryRepositoryUnitTests { - private NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class, RETURNS_DEEP_STUBS); + private final NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class, RETURNS_DEEP_STUBS); @Test // GH-1856 void plainSql() { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java index 10bd3eced8..00015aed4d 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java @@ -37,6 +37,7 @@ import java.util.function.Consumer; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -89,7 +90,6 @@ import org.springframework.data.util.Streamable; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.lang.Nullable; import org.springframework.test.jdbc.JdbcTestUtils; /** @@ -621,7 +621,7 @@ public void stringQueryProjectionShouldReturnDtoProjectedEntities() { List result = repository.findProjectedWithSql(DtoProjection.class); assertThat(result).hasSize(1); - assertThat(result.get(0).getName()).isEqualTo("Entity Name"); + assertThat(result.get(0).name()).isEqualTo("Entity Name"); } @Test // GH-971 @@ -1697,77 +1697,61 @@ public EvaluationContextExtension evaluationContextExtension() { } - static final class Root { - - @Id private final Long id; - private final String name; - private final Intermediate intermediate; - @MappedCollection(idColumn = "ROOT_ID", keyColumn = "ROOT_KEY") private final List intermediates; - - public Root(Long id, String name, Intermediate intermediate, List intermediates) { - this.id = id; - this.name = name; - this.intermediate = intermediate; - this.intermediates = intermediates; - } + record Root(@Id Long id, String name, Intermediate intermediate, + @MappedCollection(idColumn = "ROOT_ID", keyColumn = "ROOT_KEY") List intermediates) { - public Long getId() { - return this.id; - } - - public String getName() { - return this.name; - } + @Override + public Long id() { + return this.id; + } - public Intermediate getIntermediate() { - return this.intermediate; - } - public List getIntermediates() { - return this.intermediates; - } + @Override + public List intermediates() { + return this.intermediates; + } - public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof final Root other)) - return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (!Objects.equals(this$id, other$id)) - return false; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (!Objects.equals(this$name, other$name)) - return false; - final Object this$intermediate = this.getIntermediate(); - final Object other$intermediate = other.getIntermediate(); - if (!Objects.equals(this$intermediate, other$intermediate)) - return false; - final Object this$intermediates = this.getIntermediates(); - final Object other$intermediates = other.getIntermediates(); - return Objects.equals(this$intermediates, other$intermediates); - } + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final Root other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.name(); + final Object other$name = other.name(); + if (!Objects.equals(this$name, other$name)) + return false; + final Object this$intermediate = this.intermediate(); + final Object other$intermediate = other.intermediate(); + if (!Objects.equals(this$intermediate, other$intermediate)) + return false; + final Object this$intermediates = this.intermediates(); + final Object other$intermediates = other.intermediates(); + return Objects.equals(this$intermediates, other$intermediates); + } - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 43 : $name.hashCode()); - final Object $intermediate = this.getIntermediate(); - result = result * PRIME + ($intermediate == null ? 43 : $intermediate.hashCode()); - final Object $intermediates = this.getIntermediates(); - result = result * PRIME + ($intermediates == null ? 43 : $intermediates.hashCode()); - return result; - } + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.name(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + final Object $intermediate = this.intermediate(); + result = result * PRIME + ($intermediate == null ? 43 : $intermediate.hashCode()); + final Object $intermediates = this.intermediates(); + result = result * PRIME + ($intermediates == null ? 43 : $intermediates.hashCode()); + return result; + } - public String toString() { - return "JdbcRepositoryIntegrationTests.Root(id=" + this.getId() + ", name=" + this.getName() + ", intermediate=" - + this.getIntermediate() + ", intermediates=" + this.getIntermediates() + ")"; + public String toString() { + return "JdbcRepositoryIntegrationTests.Root(id=" + this.id() + ", name=" + this.name() + ", intermediate=" + + this.intermediate() + ", intermediates=" + this.intermediates() + ")"; + } } - } @Table("WITH_DELIMITED_COLUMN") static class WithDelimitedColumn { @@ -1800,124 +1784,98 @@ public void setType(String type) { } } - static final class Intermediate { - - @Id private final Long id; - private final String name; - private final Leaf leaf; - @MappedCollection(idColumn = "INTERMEDIATE_ID", keyColumn = "INTERMEDIATE_KEY") private final List leaves; - - public Intermediate(Long id, String name, Leaf leaf, List leaves) { - this.id = id; - this.name = name; - this.leaf = leaf; - this.leaves = leaves; - } - - public Long getId() { - return this.id; - } + record Intermediate(@Id Long id, String name, Leaf leaf, + @MappedCollection(idColumn = "INTERMEDIATE_ID", keyColumn = "INTERMEDIATE_KEY") List leaves) { - public String getName() { - return this.name; - } + @Override + public Long id() { + return this.id; + } - public Leaf getLeaf() { - return this.leaf; - } - public List getLeaves() { - return this.leaves; - } + @Override + public List leaves() { + return this.leaves; + } - public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof final Intermediate other)) - return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (!Objects.equals(this$id, other$id)) - return false; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - if (!Objects.equals(this$name, other$name)) - return false; - final Object this$leaf = this.getLeaf(); - final Object other$leaf = other.getLeaf(); - if (!Objects.equals(this$leaf, other$leaf)) - return false; - final Object this$leaves = this.getLeaves(); - final Object other$leaves = other.getLeaves(); - return Objects.equals(this$leaves, other$leaves); - } + public boolean equals(final Object o) { + if (o == this) + return true; + if (!(o instanceof final Intermediate other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.name(); + final Object other$name = other.name(); + if (!Objects.equals(this$name, other$name)) + return false; + final Object this$leaf = this.leaf(); + final Object other$leaf = other.leaf(); + if (!Objects.equals(this$leaf, other$leaf)) + return false; + final Object this$leaves = this.leaves(); + final Object other$leaves = other.leaves(); + return Objects.equals(this$leaves, other$leaves); + } - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 43 : $name.hashCode()); - final Object $leaf = this.getLeaf(); - result = result * PRIME + ($leaf == null ? 43 : $leaf.hashCode()); - final Object $leaves = this.getLeaves(); - result = result * PRIME + ($leaves == null ? 43 : $leaves.hashCode()); - return result; - } + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.name(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + final Object $leaf = this.leaf(); + result = result * PRIME + ($leaf == null ? 43 : $leaf.hashCode()); + final Object $leaves = this.leaves(); + result = result * PRIME + ($leaves == null ? 43 : $leaves.hashCode()); + return result; + } - public String toString() { - return "JdbcRepositoryIntegrationTests.Intermediate(id=" + this.getId() + ", name=" + this.getName() + ", leaf=" - + this.getLeaf() + ", leaves=" + this.getLeaves() + ")"; + public String toString() { + return "JdbcRepositoryIntegrationTests.Intermediate(id=" + this.id() + ", name=" + this.name() + ", leaf=" + + this.leaf() + ", leaves=" + this.leaves() + ")"; + } } - } - static final class Leaf { - - @Id private final Long id; - private final String name; + record Leaf(@Id Long id, String name) { - public Leaf(Long id, String name) { - this.id = id; - this.name = name; - } - - public Long getId() { - return this.id; - } + @Override + public Long id() { + return this.id; + } - public String getName() { - return this.name; - } public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof final Leaf other)) - return false; - final Object this$id = this.getId(); - final Object other$id = other.getId(); - if (!Objects.equals(this$id, other$id)) - return false; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - return Objects.equals(this$name, other$name); - } + if (o == this) + return true; + if (!(o instanceof final Leaf other)) + return false; + final Object this$id = this.id(); + final Object other$id = other.id(); + if (!Objects.equals(this$id, other$id)) + return false; + final Object this$name = this.name(); + final Object other$name = other.name(); + return Objects.equals(this$name, other$name); + } - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $id = this.getId(); - result = result * PRIME + ($id == null ? 43 : $id.hashCode()); - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 43 : $name.hashCode()); - return result; - } + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.id(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $name = this.name(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + return result; + } - public String toString() { - return "JdbcRepositoryIntegrationTests.Leaf(id=" + this.getId() + ", name=" + this.getName() + ")"; + public String toString() { + return "JdbcRepositoryIntegrationTests.Leaf(id=" + this.id() + ", name=" + this.name() + ")"; + } } - } static class MyEventListener implements ApplicationListener> { @@ -1980,7 +1938,7 @@ static class EntityWithSequence { @Id @Sequence(sequence = "ENTITY_SEQUENCE") private Long id; - private String name; + private final String name; public EntityWithSequence(Long id, String name) { this.id = id; @@ -2004,9 +1962,9 @@ static class ProvidedIdEntity implements Persistable { @Id private final Long id; - private String name; + private final String name; - @Transient private boolean isNew; + @Transient private final boolean isNew; private ProvidedIdEntity(Long id, String name, boolean isNew) { this.id = id; @@ -2169,39 +2127,31 @@ public AggregateReference getRef() { } } - static final class DtoProjection { - private final String name; - - public DtoProjection(String name) { - this.name = name; - } + record DtoProjection(String name) { - public String getName() { - return this.name; - } public boolean equals(final Object o) { - if (o == this) - return true; - if (!(o instanceof final DtoProjection other)) - return false; - final Object this$name = this.getName(); - final Object other$name = other.getName(); - return Objects.equals(this$name, other$name); - } + if (o == this) + return true; + if (!(o instanceof final DtoProjection other)) + return false; + final Object this$name = this.name(); + final Object other$name = other.name(); + return Objects.equals(this$name, other$name); + } - public int hashCode() { - final int PRIME = 59; - int result = 1; - final Object $name = this.getName(); - result = result * PRIME + ($name == null ? 43 : $name.hashCode()); - return result; - } + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $name = this.name(); + result = result * PRIME + ($name == null ? 43 : $name.hashCode()); + return result; + } - public String toString() { - return "JdbcRepositoryIntegrationTests.DtoProjection(name=" + this.getName() + ")"; + public String toString() { + return "JdbcRepositoryIntegrationTests.DtoProjection(name=" + this.name() + ")"; + } } - } static class CustomRowMapper implements RowMapper { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java index be5b194ffa..827d4559a8 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java @@ -25,6 +25,7 @@ import java.util.HashMap; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.stubbing.Answer; @@ -56,7 +57,6 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.KeyHolder; -import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** @@ -132,7 +132,7 @@ void publishesEventsOnSaveMany() { repository.saveAll(asList(entity1, entity2)); assertThat(publisher.events) // - .extracting(RelationalEvent::getClass, e -> ((DummyEntity) e.getEntity()).getId()) // + .extracting(RelationalEvent::getClass, e -> ((DummyEntity) e.getEntity()).id()) // .containsExactly( // tuple(BeforeConvertEvent.class, null), // tuple(BeforeSaveEvent.class, null), // @@ -299,44 +299,40 @@ private static NamedParameterJdbcOperations createIdGeneratingOperations() { interface DummyEntityRepository extends CrudRepository, PagingAndSortingRepository {} - static final class DummyEntity { - private final @Id Long id; + record DummyEntity(@Id Long id) { - public DummyEntity(Long id) { - this.id = id; - } + @Override + public Long id() { + return this.id; + } - public Long getId() { - return this.id; - } + public DummyEntity withId(Long id) { + return this.id == id ? this : new DummyEntity(id); + } - public DummyEntity withId(Long id) { - return this.id == id ? this : new DummyEntity(id); - } + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; + DummyEntity that = (DummyEntity) o; - DummyEntity that = (DummyEntity) o; + return ObjectUtils.nullSafeEquals(id, that.id); + } - return ObjectUtils.nullSafeEquals(id, that.id); - } + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(id); + } - @Override - public int hashCode() { - return ObjectUtils.nullSafeHashCode(id); - } + public String toString() { + return "SimpleJdbcRepositoryEventsUnitTests.DummyEntity(id=" + this.id() + ")"; + } - public String toString() { - return "SimpleJdbcRepositoryEventsUnitTests.DummyEntity(id=" + this.getId() + ")"; } - } - static class CollectingEventPublisher implements ApplicationEventPublisher { List events = new ArrayList<>(); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java index bd322ee8aa..da76983ed0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/QueryAnnotationHsqlIntegrationTests.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; @@ -39,7 +40,6 @@ import org.springframework.data.jdbc.testing.TestConfiguration; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; -import org.springframework.lang.Nullable; /** * Tests the execution of queries from {@link Query} annotations on repository methods. diff --git a/spring-data-r2dbc/pom.xml b/spring-data-r2dbc/pom.xml index 64ff1ebcb3..07274a3649 100644 --- a/spring-data-r2dbc/pom.xml +++ b/spring-data-r2dbc/pom.xml @@ -6,7 +6,7 @@ 4.0.0 spring-data-r2dbc - 4.0.0-SNAPSHOT + 4.0.0-1980-jspecify-SNAPSHOT Spring Data R2DBC Spring Data module for R2DBC @@ -15,7 +15,7 @@ org.springframework.data spring-data-relational-parent - 4.0.0-SNAPSHOT + 4.0.0-1980-jspecify-SNAPSHOT diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java index 1ca3a89227..f5b741902b 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java @@ -25,6 +25,7 @@ import java.util.Optional; import java.util.Set; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -49,7 +50,6 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.util.TypeScanner; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -193,8 +193,8 @@ public R2dbcMappingContext r2dbcMappingContext(Optional namingSt * @since 3.5 */ @Bean - public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback( - RelationalMappingContext relationalMappingContext, DatabaseClient databaseClient) { + public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(RelationalMappingContext relationalMappingContext, + DatabaseClient databaseClient) { return new IdGeneratingEntityCallback(relationalMappingContext, getDialect(lookupConnectionFactory()), databaseClient); } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/package-info.java index e4e7fb58ab..40c5db6d04 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/package-info.java @@ -1,6 +1,5 @@ /** * Configuration classes for Spring Data R2DBC. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.config; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java index a0198bbee0..3270d9fbc6 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java @@ -31,6 +31,7 @@ import java.util.Optional; import java.util.function.BiFunction; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionService; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.convert.CustomConversions; @@ -46,7 +47,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.RowDocument; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -104,7 +104,12 @@ public R read(Class type, Row row, @Nullable RowMetadata metadata) { if (getConversions().hasCustomReadTarget(Row.class, rawType) && getConversionService().canConvert(Row.class, rawType)) { - return getConversionService().convert(row, rawType); + + R converted = getConversionService().convert(row, rawType); + + Assert.notNull(converted, "Converted must not be null"); + + return converted; } RowDocument document = toRowDocument(type, row, metadata != null ? metadata.getColumnMetadatas() : null); @@ -175,6 +180,9 @@ public void write(Object source, OutboundRow sink) { if (customTarget.isPresent()) { OutboundRow result = getConversionService().convert(source, OutboundRow.class); + + Assert.notNull(result, "Result must not be null"); + sink.putAll(result); return; } @@ -376,6 +384,8 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, Class< } } + Assert.state(value != null, "Value must not be null"); + Optional> customTarget = getConversions().getCustomWriteTarget(value.getClass()); if (customTarget.isPresent()) { @@ -407,7 +417,11 @@ public Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentPrope int depth = value.getClass().isArray() ? ArrayUtils.getDimensionDepth(value.getClass()) : 1; Class targetArrayType = ArrayUtils.getArrayClass(targetType, depth); - return getConversionService().convert(value, targetArrayType); + Object converted = getConversionService().convert(value, targetArrayType); + + Assert.state(converted != null, "Value must not be null"); + + return converted; } return value; 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 7be1e328ee..479102317d 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 @@ -28,6 +28,7 @@ import java.util.List; import java.util.UUID; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.util.Assert; @@ -74,7 +75,7 @@ public enum RowToBooleanConverter implements Converter { INSTANCE; @Override - public Boolean convert(Row row) { + public @Nullable Boolean convert(Row row) { return row.get(0, Boolean.class); } } @@ -89,7 +90,7 @@ public enum RowToLocalDateConverter implements Converter { INSTANCE; @Override - public LocalDate convert(Row row) { + public @Nullable LocalDate convert(Row row) { return row.get(0, LocalDate.class); } } @@ -104,7 +105,7 @@ public enum RowToLocalDateTimeConverter implements Converter INSTANCE; @Override - public LocalDateTime convert(Row row) { + public @Nullable LocalDateTime convert(Row row) { return row.get(0, LocalDateTime.class); } } @@ -119,7 +120,7 @@ public enum RowToLocalTimeConverter implements Converter { INSTANCE; @Override - public LocalTime convert(Row row) { + public @Nullable LocalTime convert(Row row) { return row.get(0, LocalTime.class); } } @@ -150,16 +151,10 @@ public Converter getConverter(Class targetType) { return new RowToNumber<>(targetType); } - static class RowToNumber implements Converter { - - private final Class targetType; - - RowToNumber(Class targetType) { - this.targetType = targetType; - } + record RowToNumber(Class targetType) implements Converter { @Override - public T convert(Row source) { + public @Nullable T convert(Row source) { Object object = source.get(0); @@ -178,7 +173,7 @@ public enum RowToOffsetDateTimeConverter implements Converter { INSTANCE; @Override - public String convert(Row row) { + public @Nullable String convert(Row row) { return row.get(0, String.class); } } @@ -208,7 +203,7 @@ public enum RowToUuidConverter implements Converter { INSTANCE; @Override - public UUID convert(Row row) { + public @Nullable UUID convert(Row row) { return row.get(0, UUID.class); } } @@ -223,7 +218,7 @@ public enum RowToZonedDateTimeConverter implements Converter INSTANCE; @Override - public ZonedDateTime convert(Row row) { + public @Nullable ZonedDateTime convert(Row row) { return row.get(0, ZonedDateTime.class); } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowPropertyAccessor.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowPropertyAccessor.java deleted file mode 100644 index 520a8d9927..0000000000 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/RowPropertyAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2025 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.r2dbc.convert; - -import io.r2dbc.spi.Row; -import io.r2dbc.spi.RowMetadata; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypedValue; -import org.springframework.lang.Nullable; - -/** - * {@link PropertyAccessor} to read values from a {@link Row}. - * - * @author Mark Paluch - * @since 1.2 - */ -class RowPropertyAccessor implements PropertyAccessor { - - private final @Nullable RowMetadata rowMetadata; - - RowPropertyAccessor(@Nullable RowMetadata rowMetadata) { - this.rowMetadata = rowMetadata; - } - - @Override - public Class[] getSpecificTargetClasses() { - return new Class[] { Row.class }; - } - - @Override - public boolean canRead(EvaluationContext context, @Nullable Object target, String name) { - return rowMetadata != null && target != null && RowMetadataUtils.containsColumn(rowMetadata, name); - } - - @Override - public TypedValue read(EvaluationContext context, @Nullable Object target, String name) { - - if (target == null) { - return TypedValue.NULL; - } - - Object value = ((Row) target).get(name); - - if (value == null) { - return TypedValue.NULL; - } - - return new TypedValue(value); - } - - @Override - public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) { - return false; - } - - @Override - public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) { - throw new UnsupportedOperationException(); - } -} diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/SequenceEntityCallbackDelegate.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/SequenceEntityCallbackDelegate.java index 5c3f452d87..710620fbfa 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/SequenceEntityCallbackDelegate.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/SequenceEntityCallbackDelegate.java @@ -19,7 +19,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.r2dbc.mapping.OutboundRow; @@ -90,7 +89,11 @@ private Mono getSequenceValue(RelationalPersistentProperty property) { SqlIdentifier sequence = property.getSequence(); - if (sequence != null && !dialect.getIdGeneration().sequencesSupported()) { + if (sequence == null) { + return Mono.empty(); + } + + if (!dialect.getIdGeneration().sequencesSupported()) { LOG.warn(""" Entity type '%s' is marked for sequence usage but configured dialect '%s' does not support sequences. Falling back to identity columns. diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/package-info.java index cb313f6a8a..b6f4aca395 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/package-info.java @@ -1,6 +1,5 @@ /** * R2DBC-specific conversion and converter implementations. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.convert; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/BindParameterSource.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/BindParameterSource.java index 3049eeb81f..22341103ca 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/BindParameterSource.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/BindParameterSource.java @@ -15,8 +15,8 @@ */ package org.springframework.data.r2dbc.core; +import org.jspecify.annotations.Nullable; import org.springframework.data.util.Streamable; -import org.springframework.lang.Nullable; /** * Interface that defines common functionality for objects that can offer parameter values for named bind parameters, diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java index c1f918f23e..148dba73a8 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultReactiveDataAccessStrategy.java @@ -28,6 +28,7 @@ import java.util.Map; import java.util.function.BiFunction; +import org.jspecify.annotations.Nullable; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.dao.InvalidDataAccessResourceUsageException; import org.springframework.data.mapping.context.MappingContext; @@ -48,7 +49,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.domain.RowDocument; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.util.Assert; @@ -202,21 +202,19 @@ private boolean shouldConvertArrayValue(RelationalPersistentProperty property, P return false; } - if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) { + if (value.hasValue() && value.getValue() instanceof Collection + // use != null, because NullAway doesn't understand the semantics of hasValue + || (value.getValue() != null && value.getValue().getClass().isArray())) { return true; } - if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) { - return true; - } - - return false; + return Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray(); } - private Parameter getArrayValue(Parameter value, RelationalPersistentProperty property) { + private Parameter getArrayValue(Parameter parameter, RelationalPersistentProperty property) { - if (value.getType().equals(byte[].class)) { - return value; + if (parameter.getType().equals(byte[].class)) { + return parameter; } ArrayColumns arrayColumns = this.dialect.getArraySupport(); @@ -227,10 +225,12 @@ private Parameter getArrayValue(Parameter value, RelationalPersistentProperty pr } Class actualType = null; - if (value.getValue() instanceof Collection) { - actualType = CollectionUtils.findCommonElementType((Collection) value.getValue()); - } else if (!value.isEmpty() && value.getValue().getClass().isArray()) { - actualType = value.getValue().getClass().getComponentType(); + + Object value = parameter.getValue(); + if (value instanceof Collection) { + actualType = CollectionUtils.findCommonElementType((Collection) value); + } else if (value != null && value.getClass().isArray()) { + actualType = value.getClass().getComponentType(); } if (actualType == null) { @@ -239,7 +239,7 @@ private Parameter getArrayValue(Parameter value, RelationalPersistentProperty pr actualType = converter.getTargetType(actualType); - if (value.isEmpty()) { + if (parameter.isEmpty()) { Class targetType = arrayColumns.getArrayType(actualType); int depth = actualType.isArray() ? ArrayUtils.getDimensionDepth(actualType) : 1; @@ -247,7 +247,9 @@ private Parameter getArrayValue(Parameter value, RelationalPersistentProperty pr return Parameter.empty(targetArrayType); } - return Parameter.fromOrEmpty(this.converter.getArrayValue(arrayColumns, property, value.getValue()), actualType); + Assert.state(value != null, "value must not be null"); + + return Parameter.fromOrEmpty(this.converter.getArrayValue(arrayColumns, property, value), actualType); } @Override diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java index 24f7f46a5b..49a7057a7a 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.dialect.R2dbcDialect; @@ -32,7 +33,6 @@ import org.springframework.data.relational.core.sql.InsertBuilder.InsertValuesWithBuild; import org.springframework.data.relational.core.sql.render.RenderContext; import org.springframework.data.relational.core.sql.render.SqlRenderer; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.r2dbc.core.binding.BindMarkers; import org.springframework.r2dbc.core.binding.BindTarget; @@ -247,8 +247,7 @@ private PreparedOperation getMappedObject(DeleteSpec deleteSpec, if (criteria != null && !criteria.isEmpty()) { - BoundCondition boundCondition = this.updateMapper.getMappedObject(bindMarkers, deleteSpec.getCriteria(), table, - entity); + BoundCondition boundCondition = this.updateMapper.getMappedObject(bindMarkers, criteria, table, entity); bindings = boundCondition.getBindings(); delete = deleteBuilder.where(boundCondition.getCondition()).build(); diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/MapBindParameterSource.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/MapBindParameterSource.java index 01a4d5e227..c4b5637dad 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/MapBindParameterSource.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/MapBindParameterSource.java @@ -18,6 +18,7 @@ import java.util.LinkedHashMap; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.data.util.Streamable; import org.springframework.r2dbc.core.Parameter; import org.springframework.util.Assert; @@ -93,13 +94,17 @@ public Class getType(String paramName) { } @Override - public Object getValue(String paramName) throws IllegalArgumentException { + public @Nullable Object getValue(String paramName) throws IllegalArgumentException { if (!hasValue(paramName)) { throw new IllegalArgumentException("No value registered for key '" + paramName + "'"); } - return this.values.get(paramName).getValue(); + Parameter parameter = this.values.get(paramName); + + Assert.notNull(parameter, "Parameter must not be null"); + + return parameter.getValue(); } @Override diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java index 812011d2dc..12bd73ff70 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/NamedParameterUtils.java @@ -25,8 +25,8 @@ import java.util.Set; import java.util.TreeMap; +import org.jspecify.annotations.Nullable; import org.springframework.dao.InvalidDataAccessApiUsageException; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.r2dbc.core.binding.BindMarker; import org.springframework.r2dbc.core.binding.BindMarkers; @@ -173,7 +173,7 @@ public static ParsedSql parseSqlStatement(String sql) { } ParsedSql parsedSql = new ParsedSql(sqlToUse); for (ParameterHolder ph : parameterList) { - parsedSql.addNamedParameter(ph.getParameterName(), ph.getStartIndex(), ph.getEndIndex()); + parsedSql.addNamedParameter(ph.parameterName(), ph.startIndex(), ph.endIndex()); } parsedSql.setNamedParameterCount(namedParameterCount); parsedSql.setUnnamedParameterCount(unnamedParameterCount); @@ -354,47 +354,20 @@ public static PreparedOperation substituteNamedParameters(String sql, Bi return substituteNamedParameters(parsedSql, bindMarkersFactory, paramSource); } - private static final class ParameterHolder { + private record ParameterHolder(String parameterName, int startIndex, int endIndex) { - private final String parameterName; - - private final int startIndex; - - private final int endIndex; - - ParameterHolder(String parameterName, int startIndex, int endIndex) { - this.parameterName = parameterName; - this.startIndex = startIndex; - this.endIndex = endIndex; - } - - String getParameterName() { - return this.parameterName; - } - - int getStartIndex() { - return this.startIndex; - } - - int getEndIndex() { - return this.endIndex; - } @Override - public boolean equals(@Nullable Object o) { - if (this == o) - return true; - if (o instanceof ParameterHolder that) { - return this.startIndex == that.startIndex && this.endIndex == that.endIndex - && Objects.equals(this.parameterName, that.parameterName); + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + if (o instanceof ParameterHolder that) { + return this.startIndex == that.startIndex && this.endIndex == that.endIndex + && Objects.equals(this.parameterName, that.parameterName); + } + return false; } - return false; - } - @Override - public int hashCode() { - return Objects.hash(this.parameterName, this.startIndex, this.endIndex); - } } /** @@ -483,127 +456,116 @@ String getPlaceholder(int counter) { * Expanded query that allows binding of parameters using parameter names that were used to expand the query. Binding * unrolls {@link Collection}s and nested arrays. */ - private static class ExpandedQuery implements PreparedOperation { - - private final String expandedSql; - - private final NamedParameters parameters; - - private final BindParameterSource parameterSource; - - ExpandedQuery(String expandedSql, NamedParameters parameters, BindParameterSource parameterSource) { - this.expandedSql = expandedSql; - this.parameters = parameters; - this.parameterSource = parameterSource; - } + private record ExpandedQuery(String expandedSql, NamedParameters parameters, + BindParameterSource parameterSource) implements PreparedOperation { @SuppressWarnings("unchecked") - public void bind(org.springframework.r2dbc.core.binding.BindTarget target, String identifier, Object value) { + public void bind(BindTarget target, String identifier, Object value) { - List> bindMarkers = getBindMarkers(identifier); + List> bindMarkers = getBindMarkers(identifier); - if (bindMarkers == null) { + if (bindMarkers == null) { - target.bind(identifier, value); - return; - } + target.bind(identifier, value); + return; + } - for (List outer : bindMarkers) { - if (value instanceof Collection) { - Collection collection = (Collection) value; + for (List outer : bindMarkers) { + if (value instanceof Collection) { + Collection collection = (Collection) value; - Iterator iterator = collection.iterator(); - Iterator markers = outer.iterator(); + Iterator iterator = collection.iterator(); + Iterator markers = outer.iterator(); - while (iterator.hasNext()) { + while (iterator.hasNext()) { - Object valueToBind = iterator.next(); + Object valueToBind = iterator.next(); - if (valueToBind instanceof Object[] objects) { - for (Object object : objects) { - bind(target, markers, object); + if (valueToBind instanceof Object[] objects) { + for (Object object : objects) { + bind(target, markers, object); + } + } else { + bind(target, markers, valueToBind); } - } else { - bind(target, markers, valueToBind); } - } - } else { - for (BindMarker bindMarker : outer) { - bindMarker.bind(target, value); + } else { + for (BindMarker bindMarker : outer) { + bindMarker.bind(target, value); + } } } } - } - private void bind(org.springframework.r2dbc.core.binding.BindTarget target, Iterator markers, - Object valueToBind) { + private void bind(BindTarget target, Iterator markers, + Object valueToBind) { - Assert.isTrue(markers.hasNext(), - () -> String.format( - "No bind marker for value [%s] in SQL [%s]; Check that the query was expanded using the same arguments", - valueToBind, toQuery())); + Assert.isTrue(markers.hasNext(), + () -> String.format( + "No bind marker for value [%s] in SQL [%s]; Check that the query was expanded using the same arguments", + valueToBind, toQuery())); - markers.next().bind(target, valueToBind); - } + markers.next().bind(target, valueToBind); + } - public void bindNull(org.springframework.r2dbc.core.binding.BindTarget target, String identifier, - Class valueType) { + public void bindNull(BindTarget target, String identifier, + Class valueType) { - List> bindMarkers = getBindMarkers(identifier); + List> bindMarkers = getBindMarkers(identifier); - if (bindMarkers == null) { + if (bindMarkers == null) { - target.bindNull(identifier, valueType); - return; - } + target.bindNull(identifier, valueType); + return; + } - for (List outer : bindMarkers) { - for (BindMarker bindMarker : outer) { - bindMarker.bindNull(target, valueType); + for (List outer : bindMarkers) { + for (BindMarker bindMarker : outer) { + bindMarker.bindNull(target, valueType); + } } } - } - @Nullable - List> getBindMarkers(String identifier) { + @Nullable + List> getBindMarkers(String identifier) { - List parameters = this.parameters.getMarker(identifier); + List parameters = this.parameters.getMarker(identifier); - if (parameters == null) { - return null; - } + if (parameters == null) { + return null; + } - List> markers = new ArrayList<>(); - for (NamedParameters.NamedParameter parameter : parameters) { - markers.add(new ArrayList<>(parameter.placeholders)); - } + List> markers = new ArrayList<>(); + for (NamedParameters.NamedParameter parameter : parameters) { + markers.add(new ArrayList<>(parameter.placeholders)); + } - return markers; - } + return markers; + } - @Override - public String getSource() { - return this.expandedSql; - } + @Override + public String getSource() { + return this.expandedSql; + } - @Override - public void bindTo(BindTarget target) { + @Override + public void bindTo(BindTarget target) { - for (String namedParameter : this.parameterSource.getParameterNames()) { + for (String namedParameter : this.parameterSource.getParameterNames()) { - Object value = this.parameterSource.getValue(namedParameter); + Object value = this.parameterSource.getValue(namedParameter); - if (value == null) { - bindNull(target, namedParameter, this.parameterSource.getType(namedParameter)); - } else { - bind(target, namedParameter, value); + if (value == null) { + bindNull(target, namedParameter, this.parameterSource.getType(namedParameter)); + } else { + bind(target, namedParameter, value); + } } } - } - @Override - public String toQuery() { - return this.expandedSql; + @Override + public String toQuery() { + return this.expandedSql; + } } - } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java index 353c550fdb..6f65043b05 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java @@ -33,8 +33,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; - import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; @@ -75,7 +75,6 @@ import org.springframework.data.relational.domain.RowDocument; import org.springframework.data.util.Predicates; import org.springframework.data.util.ProxyUtils; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; @@ -218,7 +217,11 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } projectionFactory.setBeanFactory(applicationContext); - projectionFactory.setBeanClassLoader(applicationContext.getClassLoader()); + ClassLoader classLoader = applicationContext.getClassLoader(); + + Assert.notNull(classLoader, "ClassLoader must not be null"); + + projectionFactory.setBeanClassLoader(classLoader); } /** @@ -644,6 +647,8 @@ private Mono doUpdate(T entity, SqlIdentifier tableName) { } } + Assert.state(criteria != null, "Criteria must not be null"); + if (matchingVersionCriteria != null) { criteria = criteria.and(matchingVersionCriteria); } @@ -702,11 +707,17 @@ private T incrementVersion(RelationalPersistentEntity persistentEntity, T PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(entity); RelationalPersistentProperty versionProperty = persistentEntity.getVersionProperty(); + Assert.state(versionProperty != null, "Version property must not be null"); + ConversionService conversionService = this.dataAccessStrategy.getConverter().getConversionService(); Object currentVersionValue = propertyAccessor.getProperty(versionProperty); long newVersionValue = 1L; if (currentVersionValue != null) { - newVersionValue = conversionService.convert(currentVersionValue, Long.class) + 1; + Long converted = conversionService.convert(currentVersionValue, Long.class); + + Assert.state(converted != null, "Current version value must not be null"); + + newVersionValue = converted + 1; } Class versionPropertyType = versionProperty.getType(); propertyAccessor.setProperty(versionProperty, conversionService.convert(newVersionValue, versionPropertyType)); @@ -719,6 +730,8 @@ private Criteria createMatchingVersionCriteria(T entity, RelationalPersisten PersistentPropertyAccessor propertyAccessor = persistentEntity.getPropertyAccessor(entity); RelationalPersistentProperty versionProperty = persistentEntity.getVersionProperty(); + Assert.state(versionProperty != null, "Version property must not be null"); + Object version = propertyAccessor.getProperty(versionProperty); Criteria.CriteriaStep versionColumn = Criteria.where(dataAccessStrategy.toSql(versionProperty.getColumnName())); if (version == null) { @@ -896,13 +909,7 @@ public RowsFetchSpec getRowsFetchSpec(DatabaseClient.GenericExecuteSpec e * * @param */ - private static class UnwrapOptionalFetchSpecAdapter implements RowsFetchSpec { - - private final RowsFetchSpec> delegate; - - private UnwrapOptionalFetchSpecAdapter(RowsFetchSpec> delegate) { - this.delegate = delegate; - } + private record UnwrapOptionalFetchSpecAdapter(RowsFetchSpec> delegate) implements RowsFetchSpec { @Override public Mono one() { diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java index dbecbef1a6..b12696e67a 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDataAccessStrategy.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.function.BiFunction; +import org.jspecify.annotations.Nullable; import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.mapping.OutboundRow; import org.springframework.data.relational.core.dialect.AnsiDialect; @@ -30,7 +31,6 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.domain.RowDocument; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.util.Assert; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationSupport.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationSupport.java index 4c43d7ad69..4c6b0fff33 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationSupport.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveDeleteOperationSupport.java @@ -17,9 +17,9 @@ import reactor.core.publisher.Mono; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -28,13 +28,7 @@ * @author Mark Paluch * @since 1.1 */ -class ReactiveDeleteOperationSupport implements ReactiveDeleteOperation { - - private final R2dbcEntityTemplate template; - - ReactiveDeleteOperationSupport(R2dbcEntityTemplate template) { - this.template = template; - } +record ReactiveDeleteOperationSupport(R2dbcEntityTemplate template) implements ReactiveDeleteOperation { @Override public ReactiveDelete delete(Class domainType) { diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationSupport.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationSupport.java index 9f2135d058..ce4e6386f6 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationSupport.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveInsertOperationSupport.java @@ -17,8 +17,8 @@ import reactor.core.publisher.Mono; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -27,13 +27,7 @@ * @author Mark Paluch * @since 1.1 */ -class ReactiveInsertOperationSupport implements ReactiveInsertOperation { - - private final R2dbcEntityTemplate template; - - ReactiveInsertOperationSupport(R2dbcEntityTemplate template) { - this.template = template; - } +record ReactiveInsertOperationSupport(R2dbcEntityTemplate template) implements ReactiveInsertOperation { @Override public ReactiveInsert insert(Class domainType) { diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java index 4210e31242..4879d7b919 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveSelectOperationSupport.java @@ -18,9 +18,9 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.RowsFetchSpec; import org.springframework.util.Assert; @@ -31,13 +31,7 @@ * @author Mikhail Polivakha * @since 1.1 */ -class ReactiveSelectOperationSupport implements ReactiveSelectOperation { - - private final R2dbcEntityTemplate template; - - ReactiveSelectOperationSupport(R2dbcEntityTemplate template) { - this.template = template; - } +record ReactiveSelectOperationSupport(R2dbcEntityTemplate template) implements ReactiveSelectOperation { @Override public ReactiveSelect select(Class domainType) { diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationSupport.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationSupport.java index 883ccbb6fb..8d6b3de7df 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationSupport.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/ReactiveUpdateOperationSupport.java @@ -17,10 +17,10 @@ import reactor.core.publisher.Mono; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.query.Query; import org.springframework.data.relational.core.query.Update; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -29,13 +29,7 @@ * @author Mark Paluch * @since 1.1 */ -class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation { - - private final R2dbcEntityTemplate template; - - ReactiveUpdateOperationSupport(R2dbcEntityTemplate template) { - this.template = template; - } +record ReactiveUpdateOperationSupport(R2dbcEntityTemplate template) implements ReactiveUpdateOperation { @Override public ReactiveUpdate update(Class domainType) { diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java index 88729b49cc..1a9896bce1 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java @@ -26,6 +26,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.convert.R2dbcConverter; @@ -37,7 +38,6 @@ import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.relational.core.sql.render.RenderContext; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.PreparedOperation; import org.springframework.util.Assert; @@ -229,10 +229,11 @@ class SelectSpec { private final long offset; private final int limit; private final boolean distinct; - private final LockMode lockMode; + private final @Nullable LockMode lockMode; protected SelectSpec(Table table, List projectedFields, List selectList, - @Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct, LockMode lockMode) { + @Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct, + @Nullable LockMode lockMode) { this.table = table; this.projectedFields = projectedFields; this.selectList = selectList; @@ -420,7 +421,7 @@ public SelectSpec lock(LockMode lockMode) { /** * The used lockmode - * + * * @return might be null if no lockmode defined. */ @Nullable @@ -536,10 +537,10 @@ public Map getAssignments() { class UpdateSpec { private final SqlIdentifier table; - private final @Nullable org.springframework.data.relational.core.query.Update update; + private final org.springframework.data.relational.core.query.@Nullable Update update; private final @Nullable CriteriaDefinition criteria; - protected UpdateSpec(SqlIdentifier table, @Nullable org.springframework.data.relational.core.query.Update update, + protected UpdateSpec(SqlIdentifier table, org.springframework.data.relational.core.query.@Nullable Update update, @Nullable CriteriaDefinition criteria) { this.table = table; @@ -582,8 +583,7 @@ public SqlIdentifier getTable() { return this.table; } - @Nullable - public org.springframework.data.relational.core.query.Update getUpdate() { + public org.springframework.data.relational.core.query.@Nullable Update getUpdate() { return this.update; } @@ -601,7 +601,7 @@ class DeleteSpec { private final SqlIdentifier table; private final @Nullable CriteriaDefinition criteria; - protected DeleteSpec(SqlIdentifier table, CriteriaDefinition criteria) { + protected DeleteSpec(SqlIdentifier table, @Nullable CriteriaDefinition criteria) { this.table = table; this.criteria = criteria; } @@ -633,7 +633,7 @@ public static DeleteSpec create(SqlIdentifier table) { * @param criteria * @return the {@link DeleteSpec}. */ - public DeleteSpec withCriteria(CriteriaDefinition criteria) { + public DeleteSpec withCriteria(@Nullable CriteriaDefinition criteria) { return new DeleteSpec(this.table, criteria); } @@ -641,8 +641,7 @@ public SqlIdentifier getTable() { return this.table; } - @Nullable - public CriteriaDefinition getCriteria() { + public @Nullable CriteriaDefinition getCriteria() { return this.criteria; } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/package-info.java index 42634076b2..54d58766c7 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/package-info.java @@ -1,6 +1,5 @@ /** * Core domain types around DatabaseClient. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.core; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/BindTargetBinder.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/BindTargetBinder.java index eba27ec96a..1509ad2467 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/BindTargetBinder.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/BindTargetBinder.java @@ -58,7 +58,7 @@ public void bind(int index, Parameter parameter) { if (value == null) { target.bindNull(index, parameter.getType()); } else { - target.bind(index, parameter.getValue()); + target.bind(index, value); } } } 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 bd0f27912c..b006812223 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 @@ -25,6 +25,7 @@ import java.util.Set; import java.util.UUID; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.converter.Converter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; @@ -84,7 +85,7 @@ public enum ByteToBooleanConverter implements Converter { INSTANCE; @Override - public Boolean convert(Byte s) { + public @Nullable Boolean convert(@Nullable Byte s) { if (s == null) { return null; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java index 9643ac2aad..1fa2d25463 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/package-info.java @@ -1,7 +1,7 @@ /** * Dialects abstract the SQL dialect of the underlying database. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.dialect; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java index 523babe811..368e164e1e 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/OutboundRow.java @@ -23,8 +23,8 @@ import java.util.Set; import java.util.function.BiConsumer; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.util.Assert; @@ -152,7 +152,7 @@ public boolean containsValue(Object value) { } @Override - public Parameter get(Object key) { + public @Nullable Parameter get(Object key) { return this.rowAsMap.get(convertKeyIfNecessary(key)); } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/event/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/event/package-info.java index 9412744223..f475aacd2d 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/event/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/event/package-info.java @@ -1,5 +1,5 @@ /** * Mapping event callback infrastructure for the R2DBC row-to-object mapping subsystem. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.mapping.event; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/package-info.java index b3712b5e7a..9892206dd0 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/mapping/package-info.java @@ -1,7 +1,7 @@ /** * Domain objects for R2DBC. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.mapping; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java index 7b9b7d89a5..963e171b9e 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/QueryMapper.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.regex.Pattern; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.MappingException; import org.springframework.data.mapping.PersistentProperty; @@ -42,7 +43,6 @@ import org.springframework.data.relational.domain.SqlSort; import org.springframework.data.util.Pair; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.binding.BindMarker; import org.springframework.r2dbc.core.binding.BindMarkers; @@ -193,7 +193,7 @@ public List getMappedObjects(Expression expression, @Nullable Relati if (expression instanceof Column column) { Field field = createPropertyField(entity, column.getName()); - TableLike table = column.getTable(); + TableLike table = column.getRequiredTable(); if (field.isEmbedded()) { @@ -203,6 +203,7 @@ public List getMappedObjects(Expression expression, @Nullable Relati List expressions = new ArrayList<>(); for (RelationalPersistentProperty embeddedProperty : embeddedEntity) { + expressions.addAll(getMappedObjects(Column.create(embeddedProperty.getName(), table), embeddedEntity)); } @@ -267,8 +268,10 @@ private Condition unroll(CriteriaDefinition criteria, Table table, @Nullable Rel Map forwardChain = new HashMap<>(); while (current.hasPrevious()) { - forwardChain.put(current.getPrevious(), current); - current = current.getPrevious(); + + CriteriaDefinition previous = current.getRequiredPrevious(); + forwardChain.put(previous, current); + current = previous; } // perform the actual mapping @@ -355,7 +358,11 @@ private Condition combine(CriteriaDefinition criteria, @Nullable Condition curre private Condition mapCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table, @Nullable RelationalPersistentEntity entity) { - Field propertyField = createPropertyField(entity, criteria.getColumn(), this.mappingContext); + SqlIdentifier criteriaColumn = criteria.getColumn(); + + Assert.notNull(criteriaColumn, "CriteriaColumn must not be null"); + + Field propertyField = createPropertyField(entity, criteriaColumn, this.mappingContext); if (propertyField.isEmbedded() && entity != null) { @@ -400,6 +407,9 @@ public Object getValue() { Class typeHint; Comparator comparator = criteria.getComparator(); + + Assert.state(comparator != null, "CriteriaComparator must not be null"); + if (criteria.getValue() instanceof Parameter parameter) { mappedValue = convertValue(comparator, parameter.getValue(), propertyField.getTypeHint()); @@ -435,19 +445,20 @@ static PersistentPropertyAccessor getEmbeddedPropertyAccessor(@Nullable } return new PersistentPropertyAccessor<>() { + @Override - public void setProperty(PersistentProperty property, @org.jspecify.annotations.Nullable Object value) { + public void setProperty(PersistentProperty property, @Nullable Object value) { } @Override - public @org.jspecify.annotations.Nullable Object getProperty(PersistentProperty property) { + public @Nullable Object getProperty(PersistentProperty property) { return null; } @Override public Object getBean() { - return null; + throw new UnsupportedOperationException("Can't get bean for null valued embedded property"); } }; } @@ -474,7 +485,11 @@ public Parameter getBindValue(Parameter value) { return Parameter.empty(converter.getTargetType(value.getType())); } - return Parameter.from(convertValue(value.getValue(), TypeInformation.OBJECT)); + Object convertedValue = convertValue(value.getValue(), TypeInformation.OBJECT); + + Assert.state(convertedValue != null, "Value must not be null"); + + return Parameter.from(convertedValue); } @Nullable @@ -508,10 +523,12 @@ protected Object convertValue(@Nullable Object value, TypeInformation typeInf Object first = convertValue(pair.getFirst(), typeInformation.getActualType() != null ? typeInformation.getRequiredActualType() : TypeInformation.OBJECT); - Object second = convertValue(pair.getSecond(), typeInformation.getActualType() != null ? typeInformation.getRequiredActualType() : TypeInformation.OBJECT); + Assert.state(first != null, "First value must not be null"); + Assert.state(second != null, "Second value must not be null"); + return Pair.of(first, second); } @@ -586,6 +603,8 @@ private Condition createCondition(Column column, @Nullable Object mappedValue, C Pair pair = (Pair) mappedValue; + Assert.state(pair != null, "Pair must not be null"); + Expression begin = bind(pair.getFirst(), valueType, bindings, bindings.nextMarker(column.getName().getReference()), ignoreCase); Expression end = bind(pair.getSecond(), valueType, bindings, bindings.nextMarker(column.getName().getReference()), @@ -654,7 +673,11 @@ Class getTypeHint(@Nullable Object mappedValue, Class propertyType, Parame return parameter.getType(); } - if (mappedValue.getClass().equals(parameter.getValue().getClass())) { + Object value = parameter.getValue(); + + Assert.state(value != null, "Value must not be null"); + + if (mappedValue.getClass().equals(value.getClass())) { return parameter.getType(); } @@ -679,8 +702,8 @@ private Expression bind(@Nullable Object mappedValue, Class valueType, Mutabl : SQL.bindMarker(bindMarker.getPlaceholder()); } - private Expression booleanBind(Column column, Object mappedValue, Class valueType, MutableBindings bindings, - boolean ignoreCase) { + private Expression booleanBind(Column column, @Nullable Object mappedValue, Class valueType, + MutableBindings bindings, boolean ignoreCase) { BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); return bind(mappedValue, valueType, bindings, bindMarker, ignoreCase); @@ -721,7 +744,7 @@ public boolean isEmbedded() { return false; } - public @org.jspecify.annotations.Nullable RelationalPersistentProperty getProperty() { + public @Nullable RelationalPersistentProperty getProperty() { return null; } @@ -817,8 +840,7 @@ public SqlIdentifier getMappedColumnName() { * @param pathExpression the path expression to use. * @return */ - @Nullable - private PersistentPropertyPath getPath(String pathExpression) { + private @Nullable PersistentPropertyPath getPath(String pathExpression) { try { @@ -876,7 +898,7 @@ public boolean isEmbedded() { } @Override - public @org.jspecify.annotations.Nullable RelationalPersistentProperty getProperty() { + public @Nullable RelationalPersistentProperty getProperty() { return this.property; } } @@ -953,7 +975,7 @@ public boolean isEmpty() { @Override public Combinator getCombinator() { - return null; + throw new UnsupportedOperationException("No combinator for AbstractCriteria"); } } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/UpdateMapper.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/UpdateMapper.java index 4070959e2c..16f50393a4 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/UpdateMapper.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/UpdateMapper.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.r2dbc.convert.R2dbcConverter; import org.springframework.data.r2dbc.dialect.R2dbcDialect; @@ -36,7 +37,6 @@ import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.Parameter; import org.springframework.r2dbc.core.binding.BindMarker; import org.springframework.r2dbc.core.binding.BindMarkers; @@ -86,8 +86,8 @@ public BoundAssignments getMappedObject(BindMarkers markers, Update update, Tabl * @param entity related {@link RelationalPersistentEntity}, can be {@literal null}. * @return the mapped {@link BoundAssignments}. */ - public BoundAssignments getMappedObject(BindMarkers markers, Map assignments, - Table table, @Nullable RelationalPersistentEntity entity) { + public BoundAssignments getMappedObject(BindMarkers markers, Map assignments, Table table, + @Nullable RelationalPersistentEntity entity) { Assert.notNull(markers, "BindMarkers must not be null"); Assert.notNull(assignments, "Assignments must not be null"); @@ -103,8 +103,8 @@ public BoundAssignments getMappedObject(BindMarkers markers, Map getAssignments(SqlIdentifier columnName, Object value, MutableBindings bindings, - Table table, @Nullable RelationalPersistentEntity entity) { + private Collection getAssignments(SqlIdentifier columnName, @Nullable Object value, + MutableBindings bindings, Table table, @Nullable RelationalPersistentEntity entity) { Field propertyField = createPropertyField(entity, columnName, getMappingContext()); @@ -161,7 +161,7 @@ private Collection getAssignments(SqlIdentifier columnName, Object v return List.of(createAssignment(column, mappedValue, typeHint, bindings)); } - private Assignment createAssignment(Column column, Object value, Class type, MutableBindings bindings) { + private Assignment createAssignment(Column column, @Nullable Object value, Class type, MutableBindings bindings) { BindMarker bindMarker = bindings.nextMarker(column.getName().getReference()); AssignValue assignValue = Assignments.value(column, SQL.bindMarker(bindMarker.getPlaceholder())); diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/package-info.java index 572bc04240..bb628cc86c 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/package-info.java @@ -1,6 +1,5 @@ /** * Query and update support. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.query; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoryConfigurationExtension.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoryConfigurationExtension.java index ef2c055631..754e1d2e14 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoryConfigurationExtension.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/R2dbcRepositoryConfigurationExtension.java @@ -27,7 +27,6 @@ import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryConfigurationExtension; import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport; -import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; import org.springframework.data.repository.core.RepositoryMetadata; /** @@ -61,9 +60,6 @@ protected Collection> getIdentifyingTypes() { return Collections.singleton(R2dbcRepository.class); } - @Override - public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {} - @Override public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) { diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/package-info.java index 4cacf80ec4..cf4aa2877b 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/config/package-info.java @@ -1,6 +1,5 @@ /** * Support infrastructure for the configuration of R2DBC-specific repositories. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.repository.config; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/package-info.java index 7082b8cac9..654e6e397d 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/package-info.java @@ -1,6 +1,5 @@ /** * R2DBC-specific repository implementation. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.repository; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java index e149fa702b..72ce6c8bfc 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionEvaluatingParameterBinder.java @@ -155,7 +155,7 @@ private boolean isNamedParameterReferencedFromQuery(Optional name) { return namedParameters.computeIfAbsent(name.get(), it -> { Pattern namedParameterPattern = Pattern.compile("(\\W)[:#$@]" + Pattern.quote(it) + "(\\W|$)"); - return namedParameterPattern.matcher(expressionQuery.getQuery()).find(); + return namedParameterPattern.matcher(expressionQuery.query()).find(); }); } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java index a95af3ed42..f5be768fa9 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/ExpressionQuery.java @@ -29,18 +29,10 @@ * @author Mark Paluch * @since 1.1 */ -class ExpressionQuery { +record ExpressionQuery(String query, Map parameterMap) { private static final String SYNTHETIC_PARAMETER_TEMPLATE = "__synthetic_%d__"; - private final String query; - private final Map parameterMap; - - private ExpressionQuery(String query, Map parameterMap) { - this.query = query; - this.parameterMap = parameterMap; - } - /** * Create a {@link ExpressionQuery} from a {@code query}. * @@ -49,7 +41,6 @@ private ExpressionQuery(String query, Map parameterMap) */ public static ExpressionQuery create(ValueExpressionParser parser, String query) { - ValueExpressionQueryRewriter rewriter = ValueExpressionQueryRewriter.of(parser, (counter, expression) -> String.format(SYNTHETIC_PARAMETER_TEMPLATE, counter), String::concat); ValueExpressionQueryRewriter.ParsedQuery parsed = rewriter.parse(query); @@ -57,9 +48,6 @@ public static ExpressionQuery create(ValueExpressionParser parser, String query) return new ExpressionQuery(parsed.getQueryString(), parsed.getParameterMap()); } - public String getQuery() { - return query; - } public Map getBindings() { return parameterMap; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java index 6ed3699c64..4ca9f87411 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/PreparedOperationBindableQuery.java @@ -27,20 +27,17 @@ * @author Mark Paluch * @author Will Easterling */ -class PreparedOperationBindableQuery implements BindableQuery { - - private final PreparedOperation preparedQuery; +record PreparedOperationBindableQuery(PreparedOperation preparedQuery) implements BindableQuery { /** * Creates new instance of this class with the given {@link PreparedOperation}. * * @param preparedQuery prepared SQL query, must not be {@literal null}. */ - PreparedOperationBindableQuery(PreparedOperation preparedQuery) { + PreparedOperationBindableQuery { Assert.notNull(preparedQuery, "Prepared query must not be null"); - this.preparedQuery = preparedQuery; } @Override diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcParameterAccessor.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcParameterAccessor.java index 95ecebc615..8d03634490 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcParameterAccessor.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcParameterAccessor.java @@ -24,8 +24,8 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import org.jspecify.annotations.Nullable; import org.reactivestreams.Publisher; - import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor; import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.util.ReactiveWrappers; @@ -39,13 +39,13 @@ */ class R2dbcParameterAccessor extends RelationalParametersParameterAccessor { - private final Object[] values; + private final @Nullable Object[] values; private final R2dbcQueryMethod method; /** * Creates a new {@link R2dbcParameterAccessor}. */ - public R2dbcParameterAccessor(R2dbcQueryMethod method, Object... values) { + public R2dbcParameterAccessor(R2dbcQueryMethod method, @Nullable Object... values) { super(method, values); @@ -57,8 +57,9 @@ public R2dbcParameterAccessor(R2dbcQueryMethod method, Object... values) { * @see org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor#getValues() */ @Override - public Object[] getValues() { + public @Nullable Object[] getValues() { + @Nullable Object[] result = new Object[values.length]; for (int i = 0; i < result.length; i++) { result[i] = getValue(i); @@ -69,7 +70,7 @@ public Object[] getValues() { /* (non-Javadoc) * @see org.springframework.data.repository.query.ParametersParameterAccessor#getBindableValue(int) */ - public Object getBindableValue(int index) { + public @Nullable Object getBindableValue(int index) { return getValue(getParameters().getBindableParameter(index).getIndex()); } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryCreator.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryCreator.java index a4b6b04f5a..f2b68d6f6c 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryCreator.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryCreator.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy; @@ -40,8 +41,8 @@ import org.springframework.data.relational.repository.query.RelationalQueryCreator; import org.springframework.data.repository.query.parser.AbstractQueryCreator; import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.PreparedOperation; +import org.springframework.util.Assert; /** * Implementation of {@link AbstractQueryCreator} that creates {@link PreparedOperation} from a {@link PartTree}. @@ -76,6 +77,7 @@ class R2dbcQueryCreator extends RelationalQueryCreator> { public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStrategy, RelationalEntityMetadata entityMetadata, RelationalParameterAccessor accessor, List projectedProperties, Optional lock) { + super(tree, accessor); this.tree = tree; @@ -123,7 +125,12 @@ private PreparedOperation select(@Nullable Criteria criteria, Sort sort, Stat if (tree.isExistsProjection()) { selectSpec = selectSpec.limit(1); } else if (tree.isLimiting()) { - selectSpec = selectSpec.limit(tree.getMaxResults()); + + Integer maxResults = tree.getMaxResults(); + + Assert.state(maxResults != null, "Max results must be specified when limit is set"); + + selectSpec = selectSpec.limit(maxResults); } Pageable pageable = accessor.getPageable(); diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java index d68bc97fe5..40f38e9ffb 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryExecution.java @@ -30,6 +30,7 @@ import org.springframework.data.util.Lazy; import org.springframework.data.util.ReflectionUtils; import org.springframework.r2dbc.core.RowsFetchSpec; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** @@ -60,7 +61,12 @@ final class ResultProcessingExecution implements R2dbcQueryExecution { */ @Override public Publisher execute(RowsFetchSpec fetchSpec) { - return (Publisher) this.converter.convert(this.delegate.execute(fetchSpec)); + + Publisher publisher = (Publisher) this.converter.convert(this.delegate.execute(fetchSpec)); + + Assert.state(publisher != null, "Publisher must not be null"); + + return publisher; } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java index 5e34272d43..33e5d60c0a 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryMethod.java @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Page; @@ -36,14 +37,12 @@ import org.springframework.data.relational.repository.query.SimpleRelationalEntityMetadata; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.query.Parameter; -import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.util.ReactiveWrapperConverters; import org.springframework.data.util.Lazy; import org.springframework.data.util.ReactiveWrappers; import org.springframework.data.util.ReflectionUtils; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -106,12 +105,12 @@ public R2dbcQueryMethod(Method method, RepositoryMetadata metadata, ProjectionFa if (!multiWrapper) { throw new IllegalStateException(String.format( "Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type; Offending method: %s", - method.toString())); + method)); } if (ReflectionUtils.hasParameterOfType(method, Sort.class)) { throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameter; " - + "Use sorting capabilities on Pageable instead; Offending method: %s", method.toString())); + + "Use sorting capabilities on Pageable instead; Offending method: %s", method)); } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java index fe0a6ccc0b..cbe60b1d74 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/StringBasedR2dbcQuery.java @@ -167,7 +167,7 @@ private Mono getExpressionEvaluator(RelationalParameterA @Override public String toString() { - return getClass().getSimpleName() + " [" + expressionQuery.getQuery() + ']'; + return getClass().getSimpleName() + " [" + expressionQuery.query() + ']'; } private class ExpandedQuery implements PreparedOperation { @@ -187,7 +187,7 @@ public ExpandedQuery(RelationalParameterAccessor accessor, ValueEvaluationContex remainderByName = new LinkedHashMap<>(recordedBindings.byName); remainderByIndex = new LinkedHashMap<>(recordedBindings.byIndex); - expanded = dataAccessStrategy.processNamedParameters(expressionQuery.getQuery(), (index, name) -> { + expanded = dataAccessStrategy.processNamedParameters(expressionQuery.query(), (index, name) -> { if (recordedBindings.byName.containsKey(name)) { remainderByName.remove(name); @@ -205,7 +205,7 @@ public ExpandedQuery(RelationalParameterAccessor accessor, ValueEvaluationContex @Override public String getSource() { - return expressionQuery.getQuery(); + return expressionQuery.query(); } @Override @@ -225,7 +225,7 @@ public String toQuery() { @Override public String toString() { - return String.format("Original: [%s], Expanded: [%s]", expressionQuery.getQuery(), expanded.toQuery()); + return String.format("Original: [%s], Expanded: [%s]", expressionQuery.query(), expanded.toQuery()); } } diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/package-info.java index 0ab85fa3f4..47991f3088 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/package-info.java @@ -1,9 +1,7 @@ /** * Query support for R2DBC repositories. */ -@NonNullApi -@NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.repository.query; -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; + diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java index a18dd815af..223bc0fdbf 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java @@ -18,6 +18,7 @@ import java.lang.reflect.Method; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.r2dbc.convert.R2dbcConverter; @@ -42,7 +43,6 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java index 9f02fba47f..4bfe7795a3 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryBean.java @@ -17,6 +17,7 @@ import java.io.Serializable; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -27,7 +28,6 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.util.Assert; @@ -90,8 +90,14 @@ protected void setMappingContext(MappingContext mappingContext) { @Override protected final RepositoryFactorySupport createRepositoryFactory() { - return this.operations != null ? getFactoryInstance(this.operations) - : getFactoryInstance(this.client, this.dataAccessStrategy); + if (this.operations != null) { + return getFactoryInstance(this.operations); + } + + Assert.state(this.client != null, "DatabaseClient must not be null"); + Assert.state(this.dataAccessStrategy != null, "DataAccessStrategy must not be null"); + + return getFactoryInstance(this.client, this.dataAccessStrategy); } /** diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java index 1fecd89e8a..0d3d362c8c 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/SimpleR2dbcRepository.java @@ -71,7 +71,7 @@ public class SimpleR2dbcRepository implements R2dbcRepository { private final R2dbcEntityOperations entityOperations; private final Lazy idProperty; private final RelationalExampleMapper exampleMapper; - private MappingContext, ? extends RelationalPersistentProperty> mappingContext; + private final MappingContext, ? extends RelationalPersistentProperty> mappingContext; /** * Create a new {@link SimpleR2dbcRepository}. @@ -107,11 +107,11 @@ public SimpleR2dbcRepository(RelationalEntityInformation entity, Database this.entity = entity; this.entityOperations = new R2dbcEntityTemplate(databaseClient, accessStrategy); - this.idProperty = Lazy.of(() -> converter // - .getMappingContext() // + this.mappingContext = converter.getMappingContext(); + this.idProperty = Lazy.of(() -> mappingContext // .getRequiredPersistentEntity(this.entity.getJavaType()) // .getRequiredIdProperty()); - this.exampleMapper = new RelationalExampleMapper(converter.getMappingContext()); + this.exampleMapper = new RelationalExampleMapper(mappingContext); } // ------------------------------------------------------------------------- @@ -377,8 +377,12 @@ private Query getIdQuery(Object id) { idEntity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(RelationalPersistentProperty persistentProperty) { - criteriaHolder[0] = criteriaHolder[0].and(persistentProperty.getName()) - .is(accessor.getProperty(persistentProperty)); + + Object property = accessor.getProperty(persistentProperty); + + Assert.state(property != null, () -> "Property '%s' must not be null".formatted(persistentProperty)); + + criteriaHolder[0] = criteriaHolder[0].and(persistentProperty.getName()).is(property); } }); criteria = criteriaHolder[0]; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/package-info.java index 8b4a962640..0709966f42 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/package-info.java @@ -1,6 +1,5 @@ /** * Support infrastructure for query derivation of R2DBC-specific repositories. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.repository.support; diff --git a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/support/package-info.java b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/support/package-info.java index fe6a259cd5..6431fd9674 100644 --- a/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/support/package-info.java +++ b/spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/support/package-info.java @@ -1,5 +1,5 @@ /** * Support infrastructure for the configuration of R2DBC-specific repositories. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.r2dbc.support; diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java index a596fd0384..875bcdb321 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplateUnitTests.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.List; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.ObjectFactory; @@ -66,7 +67,6 @@ import org.springframework.data.relational.core.query.Update; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.domain.RowDocument; -import org.springframework.lang.Nullable; import org.springframework.r2dbc.core.DatabaseClient; import org.springframework.r2dbc.core.Parameter; import org.springframework.util.CollectionUtils; diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java index 7cc44f4dac..7f208b4dde 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/AbstractR2dbcRepositoryWithMixedCaseNamesIntegrationTests.java @@ -15,7 +15,19 @@ */ package org.springframework.data.r2dbc.repository; +import static org.assertj.core.api.Assertions.*; + import io.r2dbc.spi.ConnectionFactory; +import reactor.test.StepVerifier; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import javax.sql.DataSource; + +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -27,16 +39,6 @@ import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.lang.Nullable; -import reactor.test.StepVerifier; - -import javax.sql.DataSource; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import static org.assertj.core.api.Assertions.*; /** * Abstract base class for integration tests for {@link LegoSetRepository} with table and column names that contain @@ -120,14 +122,11 @@ public static class LegoSet { @Nullable @Column("Id") - @Id - Integer id; + @Id Integer id; - @Column("Name") - String name; + @Column("Name") String name; - @Column("Manual") - Integer manual; + @Column("Manual") Integer manual; @PersistenceCreator LegoSet(@Nullable Integer id, String name, Integer manual) { @@ -136,8 +135,7 @@ public static class LegoSet { this.manual = manual; } - public LegoSet() { - } + public LegoSet() {} @Override public boolean equals(@Nullable Object o) { diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java index 4f75bad392..546ef65277 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/ProjectingRepositoryIntegrationTests.java @@ -26,6 +26,7 @@ import javax.sql.DataSource; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -45,7 +46,6 @@ import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.repository.reactive.ReactiveCrudRepository; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.lang.Nullable; import org.springframework.test.context.junit.jupiter.SpringExtension; /** diff --git a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/query/ExpressionQueryUnitTests.java b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/query/ExpressionQueryUnitTests.java index 882923e826..a089265700 100644 --- a/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/query/ExpressionQueryUnitTests.java +++ b/spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/query/ExpressionQueryUnitTests.java @@ -39,7 +39,7 @@ void bindsMultipleExpressionParametersCorrectly() { ExpressionQuery query = ExpressionQuery .create(ValueExpressionParser.create(), "INSERT IGNORE INTO table (x, y) VALUES (:#{#point.x}, :${point.y})"); - assertThat(query.getQuery()) + assertThat(query.query()) .isEqualTo("INSERT IGNORE INTO table (x, y) VALUES (:__synthetic_0__, :__synthetic_1__)"); Map bindings = query.getBindings(); diff --git a/spring-data-relational/pom.xml b/spring-data-relational/pom.xml index 8fd6d7a6f0..ac7e28ad4b 100644 --- a/spring-data-relational/pom.xml +++ b/spring-data-relational/pom.xml @@ -1,109 +1,181 @@ - - 4.0.0 - - spring-data-relational - 4.0.0-SNAPSHOT - - Spring Data Relational - Spring Data Relational support - - - org.springframework.data - spring-data-relational-parent - 4.0.0-SNAPSHOT - - - - spring.data.relational - ${basedir}/.. - 5.0 - - - - - - ${project.groupId} - spring-data-commons - ${springdata.commons} - - - - org.springframework - spring-tx - - - - org.springframework - spring-context - - - - org.springframework - spring-beans - - - - org.springframework - spring-core - - - - com.google.code.findbugs - jsr305 - ${jsr305.version} - true - - - - org.assertj - assertj-core - ${assertj} - test - - - net.bytebuddy - byte-buddy - - - - - - com.tngtech.archunit - archunit - ${archunit.version} - test - - - - org.jetbrains.kotlin - kotlin-stdlib - true - - - - io.mockk - mockk-jvm - ${mockk} - test - - - - org.testcontainers - testcontainers - ${testcontainers} - test - - - - com.github.jsqlparser - jsqlparser - ${jsqlparser} - test - - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + + 4.0.0 + + spring-data-relational + 4.0.0-1980-jspecify-SNAPSHOT + + Spring Data Relational + Spring Data Relational support + + + org.springframework.data + spring-data-relational-parent + 4.0.0-1980-jspecify-SNAPSHOT + + + + spring.data.relational + ${basedir}/.. + 5.0 + + + + + + ${project.groupId} + spring-data-commons + ${springdata.commons} + + + + org.springframework + spring-tx + + + + org.springframework + spring-context + + + + org.springframework + spring-beans + + + + org.springframework + spring-core + + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + true + + + + org.assertj + assertj-core + ${assertj} + test + + + net.bytebuddy + byte-buddy + + + + + + com.tngtech.archunit + archunit + ${archunit.version} + test + + + + org.jetbrains.kotlin + kotlin-stdlib + true + + + + io.mockk + mockk-jvm + ${mockk} + test + + + + org.testcontainers + testcontainers + ${testcontainers} + test + + + + com.github.jsqlparser + jsqlparser + ${jsqlparser} + test + + + + + + nullaway + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.querydsl + querydsl-apt + ${querydsl} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh} + + + com.google.errorprone + error_prone_core + ${errorprone} + + + com.uber.nullaway + nullaway + ${nullaway} + + + + + + default-compile + none + + + default-testCompile + none + + + java-compile + compile + + compile + + + + -XDcompilePolicy=simple + --should-stop=ifError=FLOW + -Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR + -XepOpt:NullAway:OnlyNullMarked=true + -XepOpt:NullAway:TreatGeneratedAsUnannotated=true + -XepOpt:NullAway:CustomContractAnnotations=org.springframework.lang.Contract + + + + + + java-test-compile + test-compile + + testCompile + + + + + + + + diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/aot/RelationalManagedTypesBeanRegistrationAotProcessor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/aot/RelationalManagedTypesBeanRegistrationAotProcessor.java index 97ef3bfdc4..dce6c784a0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/aot/RelationalManagedTypesBeanRegistrationAotProcessor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/aot/RelationalManagedTypesBeanRegistrationAotProcessor.java @@ -15,9 +15,9 @@ */ package org.springframework.data.relational.aot; +import org.jspecify.annotations.Nullable; import org.springframework.data.aot.ManagedTypesBeanRegistrationAotProcessor; import org.springframework.data.relational.RelationalManagedTypes; -import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/aot/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/aot/package-info.java index 32b576617e..c40c5e326a 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/aot/package-info.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/aot/package-info.java @@ -1,7 +1,7 @@ /** * Ahead of Time processing utilities for Spring Data Relational. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.relational.aot; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/EntityLifecycleEventDelegate.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/EntityLifecycleEventDelegate.java index 3d48dc123d..ef2e13703c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/EntityLifecycleEventDelegate.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/EntityLifecycleEventDelegate.java @@ -17,8 +17,8 @@ import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.lang.Nullable; /** * Delegate class to encapsulate lifecycle event configuration and publishing. Event creation is deferred within an diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchedActions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchedActions.java index a55d853bc6..7712ac6299 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchedActions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/BatchedActions.java @@ -71,7 +71,7 @@ private BatchedActions(Combiner combiner, * @param action the action to combine with other actions. */ void add(S action) { - combiner.merge(actionMap, action.getPropertyPath(), action); + combiner.merge(actionMap, action.propertyPath(), action); } void forEach(Consumer consumer) { @@ -150,9 +150,9 @@ public void merge( actionMap.merge( // propertyPath, // - new HashMap<>(singletonMap(action.getIdValueSource(), new ArrayList<>(singletonList(action)))), // + new HashMap<>(singletonMap(action.idValueSource(), new ArrayList<>(singletonList(action)))), // (map, mapDefaultValue) -> { - map.merge(action.getIdValueSource(), new ArrayList<>(singletonList(action)), + map.merge(action.idValueSource(), new ArrayList<>(singletonList(action)), (actions, listDefaultValue) -> { actions.add(action); return actions; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java index ec5ab5106a..8d2913f050 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbAction.java @@ -23,10 +23,10 @@ import java.util.Set; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.util.Pair; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -49,57 +49,34 @@ public interface DbAction { * * @param type of the entity for which this represents a database interaction. */ - class Insert implements WithDependingOn { - - private final T entity; - private final PersistentPropertyPath propertyPath; - private final WithEntity dependingOn; - private final IdValueSource idValueSource; - - final Map, Object> qualifiers; - - public Insert(T entity, PersistentPropertyPath propertyPath, - WithEntity dependingOn, Map, Object> qualifiers, - IdValueSource idValueSource) { - - this.entity = entity; - this.propertyPath = propertyPath; - this.dependingOn = dependingOn; - this.qualifiers = Map.copyOf(qualifiers); - this.idValueSource = idValueSource; - } - - @Override - public Class getEntityType() { - return WithDependingOn.super.getEntityType(); - } - - public T getEntity() { - return this.entity; - } - - public PersistentPropertyPath getPropertyPath() { - return this.propertyPath; - } - - public DbAction.WithEntity getDependingOn() { - return this.dependingOn; - } + record Insert(T entity, PersistentPropertyPath propertyPath, + WithEntity dependingOn, + Map, Object> qualifiers, + IdValueSource idValueSource) implements WithDependingOn { + + public Insert(T entity, PersistentPropertyPath propertyPath, + WithEntity dependingOn, Map, Object> qualifiers, + IdValueSource idValueSource) { + + this.entity = entity; + this.propertyPath = propertyPath; + this.dependingOn = dependingOn; + this.qualifiers = Map.copyOf(qualifiers); + this.idValueSource = idValueSource; + } - public Map, Object> getQualifiers() { - return this.qualifiers; - } + @Override + public Class getEntityType() { + return WithDependingOn.super.getEntityType(); + } - public IdValueSource getIdValueSource() { - return idValueSource; - } @Override - public String toString() { - return "Insert{" + "entity=" + entity + ", propertyPath=" + propertyPath + ", dependingOn=" + dependingOn - + ", idValueSource=" + idValueSource + ", qualifiers=" + qualifiers + '}'; + public String toString() { + return "Insert{" + "entity=" + entity + ", propertyPath=" + propertyPath + ", dependingOn=" + dependingOn + + ", idValueSource=" + idValueSource + ", qualifiers=" + qualifiers + '}'; + } } - } /** * Represents an insert statement for the root of an aggregate. Upon a successful insert, the initial version and @@ -118,7 +95,7 @@ public InsertRoot(T entity, IdValueSource idValueSource) { this.idValueSource = idValueSource; } - public T getEntity() { + public T entity() { return this.entity; } @@ -127,7 +104,7 @@ public void setEntity(T entity) { this.entity = entity; } - public IdValueSource getIdValueSource() { + public IdValueSource idValueSource() { return idValueSource; } @@ -146,14 +123,14 @@ class UpdateRoot implements WithRoot { private T entity; - @Nullable private final Number previousVersion; + private @Nullable final Number previousVersion; public UpdateRoot(T entity, @Nullable Number previousVersion) { this.entity = entity; this.previousVersion = previousVersion; } - public T getEntity() { + public T entity() { return this.entity; } @@ -163,7 +140,7 @@ public void setEntity(T entity) { } @Override - public IdValueSource getIdValueSource() { + public IdValueSource idValueSource() { return IdValueSource.PROVIDED; } @@ -173,7 +150,7 @@ public Number getPreviousVersion() { } public String toString() { - return "DbAction.UpdateRoot(entity=" + this.getEntity() + ")"; + return "DbAction.UpdateRoot(entity=" + this.entity() + ")"; } } @@ -182,29 +159,14 @@ public String toString() { * * @param type of the entity for which this represents a database interaction. */ - final class Delete implements WithPropertyPath { - - private final Object rootId; + record Delete(Object rootId, + PersistentPropertyPath propertyPath) implements WithPropertyPath { - private final PersistentPropertyPath propertyPath; - - public Delete(Object rootId, PersistentPropertyPath propertyPath) { - this.rootId = rootId; - this.propertyPath = propertyPath; - } - - public Object getRootId() { - return this.rootId; - } - - public PersistentPropertyPath getPropertyPath() { - return this.propertyPath; - } public String toString() { - return "DbAction.Delete(rootId=" + this.getRootId() + ", propertyPath=" + this.getPropertyPath() + ")"; + return "DbAction.Delete(rootId=" + this.rootId() + ", propertyPath=" + this.propertyPath() + ")"; + } } - } /** * Represents a delete statement for a aggregate root when only the ID is known. @@ -215,38 +177,15 @@ public String toString() { * * @param type of the entity for which this represents a database interaction. */ - final class DeleteRoot implements DbAction { - - private final Object id; - private final Class entityType; - @Nullable private final Number previousVersion; - - public DeleteRoot(Object id, Class entityType, @Nullable Number previousVersion) { - - this.id = id; - this.entityType = entityType; - this.previousVersion = previousVersion; - } + record DeleteRoot(Object id, Class getEntityType, @Nullable Number previousVersion) implements DbAction { - public Object getId() { - return this.id; - } - - public Class getEntityType() { - return this.entityType; - } - - @Nullable - public Number getPreviousVersion() { - return this.previousVersion; - } public String toString() { - return "DbAction.DeleteRoot(id=" + this.getId() + ", entityType=" + this.getEntityType() + ", previousVersion=" - + this.getPreviousVersion() + ")"; + return "DbAction.DeleteRoot(id=" + this.id() + ", entityType=" + this.getEntityType() + ", previousVersion=" + + this.previousVersion() + ")"; + } } - } /** * Represents a delete statement for all entities that are reachable via a given path from any aggregate root of a @@ -254,22 +193,14 @@ public String toString() { * * @param type of the entity for which this represents a database interaction. */ - final class DeleteAll implements WithPropertyPath { + record DeleteAll( + PersistentPropertyPath propertyPath) implements WithPropertyPath { - private final PersistentPropertyPath propertyPath; - - public DeleteAll(PersistentPropertyPath propertyPath) { - this.propertyPath = propertyPath; - } - - public PersistentPropertyPath getPropertyPath() { - return this.propertyPath; - } public String toString() { - return "DbAction.DeleteAll(propertyPath=" + this.getPropertyPath() + ")"; + return "DbAction.DeleteAll(propertyPath=" + this.propertyPath() + ")"; + } } - } /** * Represents a delete statement for all aggregate roots of a given type. @@ -280,22 +211,13 @@ public String toString() { * * @param type of the entity for which this represents a database interaction. */ - final class DeleteAllRoot implements DbAction { - - private final Class entityType; + record DeleteAllRoot(Class getEntityType) implements DbAction { - public DeleteAllRoot(Class entityType) { - this.entityType = entityType; - } - - public Class getEntityType() { - return this.entityType; - } public String toString() { - return "DbAction.DeleteAllRoot(entityType=" + this.getEntityType() + ")"; + return "DbAction.DeleteAllRoot(entityType=" + this.getEntityType() + ")"; + } } - } /** * Represents an acquire lock statement for a aggregate root when only the ID is known. @@ -405,7 +327,7 @@ public String toString() { */ final class BatchInsert extends BatchWithValue, IdValueSource> { public BatchInsert(List> actions) { - super(actions, Insert::getIdValueSource); + super(actions, Insert::idValueSource); } } @@ -417,7 +339,7 @@ public BatchInsert(List> actions) { */ final class BatchInsertRoot extends BatchWithValue, IdValueSource> { public BatchInsertRoot(List> actions) { - super(actions, InsertRoot::getIdValueSource); + super(actions, InsertRoot::idValueSource); } } @@ -431,7 +353,7 @@ public BatchInsertRoot(List> actions) { final class BatchDelete extends BatchWithValue, PersistentPropertyPath> { public BatchDelete(List> actions) { - super(actions, Delete::getPropertyPath); + super(actions, Delete::propertyPath); } } @@ -461,9 +383,9 @@ interface WithDependingOn extends WithPropertyPath, WithEntity { * become available once the parent entity got persisted. * * @return guaranteed to be not {@code null}. - * @see #getQualifiers() + * @see #qualifiers() */ - WithEntity getDependingOn(); + WithEntity dependingOn(); /** * Additional values to be set during insert or update statements. @@ -473,7 +395,7 @@ interface WithDependingOn extends WithPropertyPath, WithEntity { * * @return guaranteed to be not {@code null}. */ - Map, Object> getQualifiers(); + Map, Object> qualifiers(); // TODO: Encapsulate propertyPath and qualifier in object: PropertyPathWithListIndex, // PropertyPathWithMapIndex, PropertyPathInSet, PropertyPathWithoutQualifier @@ -481,7 +403,7 @@ interface WithDependingOn extends WithPropertyPath, WithEntity { @Nullable default Pair, Object> getQualifier() { - Map, Object> qualifiers = getQualifiers(); + Map, Object> qualifiers = qualifiers(); if (qualifiers.isEmpty()) { return null; @@ -517,18 +439,18 @@ interface WithEntity extends DbAction { /** * @return the entity to persist. Guaranteed to be not {@code null}. */ - T getEntity(); + T entity(); @SuppressWarnings("unchecked") @Override default Class getEntityType() { - return (Class) getEntity().getClass(); + return (Class) entity().getClass(); } /** * @return the {@link IdValueSource} for the entity to persist. Guaranteed to be not {@code null}. */ - IdValueSource getIdValueSource(); + IdValueSource idValueSource(); } /** @@ -553,12 +475,12 @@ interface WithPropertyPath extends DbAction { /** * @return the path from the aggregate root to the affected entity */ - PersistentPropertyPath getPropertyPath(); + PersistentPropertyPath propertyPath(); @SuppressWarnings("unchecked") @Override default Class getEntityType() { - return (Class) getPropertyPath().getLeafProperty().getActualType(); + return (Class) propertyPath().getLeafProperty().getActualType(); } } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java index e80465cf6d..7b99e6fb88 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DbActionExecutionResult.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.conversion; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Jens Schauder @@ -24,7 +24,7 @@ */ public class DbActionExecutionResult { - private final Object generatedId; + private final @Nullable Object generatedId; private final DbAction.WithEntity action; public DbActionExecutionResult(DbAction.WithEntity action) { @@ -39,6 +39,11 @@ public DbActionExecutionResult(DbAction.WithEntity action, @Nullable Object g this.generatedId = generatedId; } + /** + * @deprecated Use one of the other constructors. + */ + @SuppressWarnings("NullAway") + @Deprecated(since = "4.0", forRemoval = true) public DbActionExecutionResult() { action = null; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java index 056bede333..a6947c9c6b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DefaultRootAggregateChange.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** @@ -37,10 +37,10 @@ class DefaultRootAggregateChange implements RootAggregateChange { private final List> actions = new ArrayList<>(); - private DbAction.WithRoot rootAction; + private DbAction.@Nullable WithRoot rootAction; /** The previous version assigned to the instance being changed, if available */ - @Nullable private final Number previousVersion; + private @Nullable final Number previousVersion; DefaultRootAggregateChange(Kind kind, Class entityType, @Nullable Number previousVersion) { @@ -77,6 +77,7 @@ public void setRoot(T aggregateRoot) { Assert.isInstanceOf(this.entityType, aggregateRoot, String.format("AggregateRoot must be of type %s", entityType.getName())); + Assert.state(rootAction != null, "rootAction must not be null"); rootAction.setEntity(aggregateRoot); } @@ -97,7 +98,10 @@ public Number getPreviousVersion() { @Override public T getRoot() { - return this.rootAction.getEntity(); + + Assert.state(rootAction != null, "rootAction must not be null"); + + return this.rootAction.entity(); } @Override diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteAggregateChange.java index e600291813..91fb2f9cde 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteAggregateChange.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** @@ -38,7 +38,7 @@ public class DeleteAggregateChange implements MutableAggregateChange { private final List> actions = new ArrayList<>(); /** The previous version assigned to the instance being changed, if available */ - @Nullable private final Number previousVersion; + private @Nullable final Number previousVersion; DeleteAggregateChange(Class entityType, @Nullable Number previousVersion) { this.entityType = entityType; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteBatchingAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteBatchingAggregateChange.java index b916dfe9d7..7a27f10fa0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteBatchingAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DeleteBatchingAggregateChange.java @@ -65,7 +65,7 @@ public void add(DeleteAggregateChange aggregateChange) { private void addDeleteRoot(DbAction.DeleteRoot action) { - if (action.getPreviousVersion() == null) { + if (action.previousVersion() == null) { rootActionsWithoutVersion.add(action); } else { rootActionsWithVersion.add(action); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DocumentPropertyAccessor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DocumentPropertyAccessor.java index 6075fa9a10..4e5b2814d9 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DocumentPropertyAccessor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/DocumentPropertyAccessor.java @@ -17,11 +17,11 @@ import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.context.expression.MapAccessor; import org.springframework.data.relational.domain.RowDocument; import org.springframework.expression.EvaluationContext; import org.springframework.expression.TypedValue; -import org.springframework.lang.Nullable; /** * {@link org.springframework.expression.PropertyAccessor} to allow entity based field access to 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 63f3bf5f8f..85f018ed74 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 @@ -25,6 +25,8 @@ import java.util.function.Function; import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -72,7 +74,6 @@ import org.springframework.data.util.TypeInformation; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -152,7 +153,11 @@ public void setApplicationContext(ApplicationContext applicationContext) throws this.spELContext = new SpELContext(this.spELContext, applicationContext); this.environment = applicationContext.getEnvironment(); this.projectionFactory.setBeanFactory(applicationContext); - this.projectionFactory.setBeanClassLoader(applicationContext.getClassLoader()); + ClassLoader classLoader = applicationContext.getClassLoader(); + + Assert.notNull(classLoader, "ClassLoader must not be null"); + + this.projectionFactory.setBeanClassLoader(classLoader); } @Override @@ -284,12 +289,12 @@ static class MapPersistentPropertyAccessor implements PersistentPropertyAccessor Map map = new LinkedHashMap<>(); @Override - public void setProperty(PersistentProperty persistentProperty, Object o) { + public void setProperty(PersistentProperty persistentProperty, @Nullable Object o) { map.put(persistentProperty.getName(), o); } @Override - public Object getProperty(PersistentProperty persistentProperty) { + public @Nullable Object getProperty(PersistentProperty persistentProperty) { return map.get(persistentProperty.getName()); } @@ -344,7 +349,12 @@ protected S readAggregate(ConversionContext context, RowDocumentAccessor doc Class rawType = typeHint.getType(); if (getConversions().hasCustomReadTarget(RowDocument.class, rawType)) { - return doConvert(documentAccessor.getDocument(), rawType, typeHint.getType()); + + S converted = doConvert(documentAccessor.getDocument(), rawType, typeHint.getType()); + + Assert.state(converted != null, "Converted must not be null"); + + return converted; } if (RowDocument.class.isAssignableFrom(rawType)) { @@ -375,7 +385,8 @@ protected S readAggregate(ConversionContext context, RowDocumentAccessor doc * @param targetType the {@link Map} {@link TypeInformation} to be used to unmarshall this {@link RowDocument}. * @return the converted {@link Map}, will never be {@literal null}. */ - protected Map readMap(ConversionContext context, Map source, TypeInformation targetType) { + protected Map<@Nullable Object, @Nullable Object> readMap(ConversionContext context, Map source, + TypeInformation targetType) { Assert.notNull(source, "Document must not be null"); Assert.notNull(targetType, "TypeInformation must not be null"); @@ -388,8 +399,7 @@ protected Map readMap(ConversionContext context, Map sourc Class rawKeyType = keyType != null ? keyType.getType() : Object.class; - Map map = CollectionFactory.createMap(mapType, rawKeyType, - ((Map) source).keySet().size()); + Map<@Nullable Object, @Nullable Object> map = CollectionFactory.createMap(mapType, rawKeyType, source.size()); source.forEach((k, v) -> { @@ -433,7 +443,12 @@ protected Object readCollectionOrArray(ConversionContext context, Collection : CollectionFactory.createCollection(collectionType, rawComponentType, source.size()); if (source.isEmpty()) { - return getPotentiallyConvertedSimpleRead(items, targetType); + + Object converted = getPotentiallyConvertedSimpleRead(items, targetType); + + Assert.state(converted != null, "Converted must not be null"); + + return converted; } for (Object element : source) { @@ -443,12 +458,11 @@ protected Object readCollectionOrArray(ConversionContext context, Collection return getPotentiallyConvertedSimpleRead(items, targetType); } - private T doConvert(Object value, Class target) { + private @Nullable T doConvert(Object value, Class target) { return doConvert(value, target, null); } - @SuppressWarnings("ConstantConditions") - private T doConvert(Object value, Class target, @Nullable Class fallback) { + private @Nullable T doConvert(Object value, Class target, @Nullable Class fallback) { if (getConversionService().canConvert(value.getClass(), target) || fallback == null) { return getConversionService().convert(value, target); @@ -654,7 +668,12 @@ protected Object getPotentiallyConvertedSimpleRead(Object value, TypeInformation Class target = type.getType(); if (getConversions().hasCustomReadTarget(value.getClass(), target)) { - return getConversionService().convert(value, TypeDescriptor.forObject(value), createTypeDescriptor(type)); + Object converted = getConversionService().convert(value, TypeDescriptor.forObject(value), + createTypeDescriptor(type)); + + Assert.state(converted != null, "Converted must not be null"); + + return converted; } if (ClassUtils.isAssignableValue(target, value)) { @@ -665,7 +684,12 @@ protected Object getPotentiallyConvertedSimpleRead(Object value, TypeInformation return Enum.valueOf((Class) target, value.toString()); } - return getConversionService().convert(value, TypeDescriptor.forObject(value), createTypeDescriptor(type)); + Object converted = getConversionService().convert(value, TypeDescriptor.forObject(value), + createTypeDescriptor(type)); + + Assert.state(converted != null, "Converted must not be null"); + + return converted; } private static TypeDescriptor createTypeDescriptor(TypeInformation type) { @@ -794,7 +818,11 @@ private Object writeCollection(Iterable value, TypeInformation type) { targetType = Array.newInstance(targetComponentType, 0).getClass(); } - return getConversionService().convert(mapped, targetType); + Object converted = getConversionService().convert(mapped, targetType); + + Assert.state(converted != null, "Converted must not be null"); + + return converted; } /** @@ -947,8 +975,14 @@ protected ProjectingConversionContext(RelationalConverter sourceConverter, Custo ObjectPath path, ContainerValueConverter> collectionConverter, ContainerValueConverter> mapConverter, ValueConverter elementConverter, EntityProjection projection) { - super(sourceConverter, customConversions, path, - (context, source, typeHint) -> doReadOrProject(context, source, typeHint, projection), + super(sourceConverter, customConversions, path, (context, source, typeHint) -> { + + Object result = doReadOrProject(context, source, typeHint, projection); + + Assert.state(result != null, "Result must not be null"); + + return result; + }, collectionConverter, mapConverter, elementConverter); this.returnedTypeDescriptor = projection; @@ -1053,7 +1087,7 @@ enum NoOpParameterValueProvider implements ParameterValueProvider T getParameterValue(Parameter parameter) { + public @Nullable T getParameterValue(Parameter parameter) { return null; } } @@ -1194,10 +1228,6 @@ public Object getValue(AggregatePath path) { Object value = document.get(path.getColumnInfo().alias().getReference()); - if (value == null) { - return null; - } - return value; } @@ -1226,6 +1256,8 @@ public boolean hasNonEmptyValue(AggregatePath path) { Object value = document.get(path.getColumnInfo().alias().getReference()); + Assert.state(value != null, "Value must not be null"); + if (value instanceof Collection || value.getClass().isArray()) { return !ObjectUtils.isEmpty(value); } @@ -1264,7 +1296,7 @@ class ConvertingParameterValueProvider

> implemen @Override @SuppressWarnings("unchecked") - public T getParameterValue(Parameter parameter) { + public @Nullable T getParameterValue(Parameter parameter) { return (T) readValue(delegate.apply(parameter), parameter.getType()); } } @@ -1318,7 +1350,7 @@ public void setProperty(PersistentProperty property, @Nullable Object value) } @Override - public Object getProperty(PersistentProperty property) { + public @Nullable Object getProperty(PersistentProperty property) { return delegate.getProperty(translate(property)); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java index b872e0dbf6..cc640b928c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/MutableAggregateChange.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.conversion; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/ObjectPath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/ObjectPath.java index 90c0ae68c6..b387df8be6 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/ObjectPath.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/ObjectPath.java @@ -18,8 +18,8 @@ import java.util.ArrayList; import java.util.List; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java index 321d6ab73f..d06fd7b949 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/PathNode.java @@ -15,33 +15,24 @@ */ package org.springframework.data.relational.core.conversion; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.util.Pair; -import org.springframework.lang.Nullable; + +import java.util.List; +import java.util.Map; /** * Represents a single entity in an aggregate along with its property path from the root entity and the chain of objects * to traverse a long this path. * + * @param path The path to this entity + * @param parent The parent {@link PathNode}. This is {@code null} if this is the root entity. + * @param value The value of the entity. * @author Jens Schauder */ -final class PathNode { - - /** - * The path to this entity - */ - private final PersistentPropertyPath path; - - /** - * The parent {@link PathNode}. This is {@code null} if this is the root entity. - */ - @Nullable private final PathNode parent; - - /** - * The value of the entity. - */ - private final Object value; +record PathNode(PersistentPropertyPath path, @Nullable PathNode parent, Object value) { PathNode(PersistentPropertyPath path, @Nullable PathNode parent, Object value) { @@ -51,29 +42,17 @@ final class PathNode { } /** - * If the node represents a qualified property (i.e. a {@link java.util.List} or {@link java.util.Map}) the actual + * If the node represents a qualified property (i.e. a {@link List} or {@link Map}) the actual * value is an element of the {@literal List} or a value of the {@literal Map}, while the {@link #value} is actually a * {@link Pair} with the index or key as the first element and the actual value as second element. */ Object getActualValue() { - return getPath().getLeafProperty().isQualified() // - ? ((Pair) getValue()).getSecond() // - : getValue(); - } - - public PersistentPropertyPath getPath() { - return this.path; + return path().getLeafProperty().isQualified() // + ? ((Pair) value()).getSecond() // + : value(); } - @Nullable - public PathNode getParent() { - return this.parent; - } - - public Object getValue() { - return this.value; - } @Override public String toString() { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java index 2a68dcfc66..5476d8b513 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalConverter.java @@ -15,6 +15,7 @@ */ package org.springframework.data.relational.core.conversion; +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentPropertyAccessor; @@ -27,7 +28,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.RowDocument; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; /** * A {@link RelationalConverter} is responsible for converting for values to the native relational representation and diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java index cc706f7cb5..d5ad10cd75 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityDeleteWriter.java @@ -20,12 +20,12 @@ import java.util.List; import java.util.function.Consumer; +import org.jspecify.annotations.Nullable; import org.springframework.data.convert.EntityWriter; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPredicates; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityVersionUtils.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityVersionUtils.java index 00ea3657aa..63fdcac0f5 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityVersionUtils.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RelationalEntityVersionUtils.java @@ -15,11 +15,11 @@ */ package org.springframework.data.relational.core.conversion; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; -import org.springframework.lang.Nullable; /** * Utilities commonly used to set/get properties for instances of RelationalPersistentEntities. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RowDocumentAccessor.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RowDocumentAccessor.java index 0945cdfd5c..7194012522 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RowDocumentAccessor.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/RowDocumentAccessor.java @@ -17,9 +17,9 @@ import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.domain.RowDocument; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/SaveBatchingAggregateChange.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/SaveBatchingAggregateChange.java index 0e256a0ab3..10ffbde0d4 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/SaveBatchingAggregateChange.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/SaveBatchingAggregateChange.java @@ -83,7 +83,7 @@ public void add(RootAggregateChange aggregateChange) { } else if (action instanceof DbAction.InsertRoot rootAction) { if (!insertRootBatchCandidates.isEmpty() - && !insertRootBatchCandidates.get(0).getIdValueSource().equals(rootAction.getIdValueSource())) { + && !insertRootBatchCandidates.get(0).idValueSource().equals(rootAction.idValueSource())) { combineBatchCandidatesIntoSingleBatchRootAction(); } // noinspection unchecked diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java index 6602e72841..454de2faae 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/WritingContext.java @@ -23,13 +23,13 @@ import java.util.List; import java.util.Map; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyPath; 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.relational.core.mapping.RelationalPredicates; import org.springframework.data.util.Pair; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -50,7 +50,7 @@ class WritingContext { private final Map> previousActions = new HashMap<>(); private final Map, List> nodesCache = new HashMap<>(); private final IdValueSource rootIdValueSource; - @Nullable private final Number previousVersion; + private @Nullable final Number previousVersion; private final RootAggregateChange aggregateChange; WritingContext(RelationalMappingContext context, T root, RootAggregateChange aggregateChange) { @@ -122,28 +122,31 @@ private List> insertReferenced() { @SuppressWarnings("unchecked") private List> insertAll(PersistentPropertyPath path) { - RelationalPersistentEntity persistentEntity = context - .getRequiredPersistentEntity(path.getLeafProperty()); + RelationalPersistentEntity persistentEntity = context.getRequiredPersistentEntity(path.getLeafProperty()); List> inserts = new ArrayList<>(); from(path).forEach(node -> { - DbAction.WithEntity parentAction = getAction(node.getParent()); + DbAction.WithEntity parentAction = getAction(node.parent()); + + Assert.state(parentAction != null, "parentAction must not be null"); + Map, Object> qualifiers = new HashMap<>(); Object instance; - if (node.getPath().getLeafProperty().isQualified()) { + if (node.path().getLeafProperty().isQualified()) { - Pair value = (Pair) node.getValue(); - qualifiers.put(node.getPath(), value.getFirst()); + Pair value = (Pair) node.value(); + qualifiers.put(node.path(), value.getFirst()); RelationalPersistentEntity parentEntity = context.getRequiredPersistentEntity(parentAction.getEntityType()); if (!parentEntity.hasIdProperty() && parentAction instanceof DbAction.Insert) { - qualifiers.putAll(((DbAction.Insert) parentAction).getQualifiers()); + qualifiers.putAll(((DbAction.Insert) parentAction).qualifiers()); } instance = value.getSecond(); } else { - instance = node.getValue(); + instance = node.value(); } + IdValueSource idValueSource = IdValueSource.forInstance(instance, persistentEntity); DbAction.Insert insert = new DbAction.Insert<>(instance, path, parentAction, qualifiers, idValueSource); inserts.add(insert); @@ -166,6 +169,8 @@ private DbAction.Delete deleteReferenced(PersistentPropertyPath(id, path); } @@ -176,8 +181,7 @@ private void setRootAction(DbAction.WithRoot dbAction) { previousActions.put(null, dbAction); } - @Nullable - private DbAction.WithEntity getAction(@Nullable PathNode parent) { + private DbAction.@Nullable WithEntity getAction(@Nullable PathNode parent) { DbAction action = previousActions.get(parent); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java index a54e73fa4f..dee83b5ebc 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/package-info.java @@ -1,4 +1,4 @@ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.relational.core.conversion; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Escaper.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Escaper.java index e8f863189e..bbaf8414f6 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Escaper.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Escaper.java @@ -19,7 +19,7 @@ import java.util.Arrays; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Helper class encapsulating an escape character for LIKE queries and the actually usage of it in escaping @@ -88,8 +88,7 @@ public char getEscapeCharacter() { * @param value value to be escaped * @return escaped value */ - @Nullable - public String escape(@Nullable String value) { + public @Nullable String escape(@Nullable String value) { if (value == null) { return null; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/package-info.java index 00c26658f7..8ebba64bd1 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/package-info.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/package-info.java @@ -1,7 +1,7 @@ /** * Dialects abstract the SQL dialect of the underlying database. */ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.relational.core.dialect; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java index f5befcb930..f603a5ea04 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/AggregatePath.java @@ -29,13 +29,13 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.Table; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -261,6 +261,24 @@ default Stream stream() { @Nullable AggregatePath getTail(); + /** + * The required path resulting from removing the first element of the {@link AggregatePath} or throwing + * {@link IllegalStateException} for any {@link AggregatePath} having less than two elements. + * + * @throws IllegalStateException for any {@link AggregatePath} having less than two elements. + * @since 4.0 + */ + default AggregatePath getRequiredTail() { + + AggregatePath tail = getTail(); + + if (tail == null) { + throw new IllegalStateException("No tail available"); + } + + return tail; + } + /** * Subtract the {@literal basePath} from {@literal this} {@literal AggregatePath} by removing the {@literal basePath} * from the beginning of {@literal this}. @@ -342,6 +360,13 @@ static TableInfo of(AggregatePath path) { } + public ColumnInfo getRequiredQualifierColumnInfo() { + + Assert.notNull(qualifierColumnInfo, "ColumnInfo for qualifier columns must not be null"); + + return qualifierColumnInfo; + } + private static ColumnInfos computeIdColumnInfos(AggregatePath tableOwner, RelationalPersistentEntity leafEntity) { @@ -443,6 +468,15 @@ public ColumnInfo reverseColumnInfo() { public ColumnInfos effectiveIdColumnInfos() { return backReferenceColumnInfos.isEmpty() ? idColumnInfos : backReferenceColumnInfos; } + + public Class getRequiredQualifierColumnType() { + + Class type = qualifierColumnType(); + + Assert.notNull(type, "ColumnInfo for qualifier columns must not be null"); + + return type; + } } /** @@ -640,7 +674,7 @@ public T any(BiFunction mapper) { * @param path for which to return the {@literal ColumnInfo} * @return {@literal ColumnInfo} for the given path. */ - public ColumnInfo get(AggregatePath path) { + public @Nullable ColumnInfo get(AggregatePath path) { return columnInfos.get(path); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntity.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntity.java index 93a6d79199..766666dbd1 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntity.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntity.java @@ -17,6 +17,7 @@ import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.springframework.data.expression.ValueExpression; import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.mapping.model.BasicPersistentEntity; @@ -25,7 +26,6 @@ import org.springframework.data.util.TypeInformation; import org.springframework.expression.Expression; import org.springframework.expression.common.LiteralExpression; -import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -58,7 +58,7 @@ class BasicRelationalPersistentEntity extends BasicPersistentEntity information, NamingStrategy namingStrategy, - SqlIdentifierExpressionEvaluator sqlIdentifierExpressionEvaluator) { + SqlIdentifierExpressionEvaluator sqlIdentifierExpressionEvaluator) { super(information); @@ -97,8 +97,7 @@ class BasicRelationalPersistentEntity extends BasicPersistentEntity columnName; private final boolean hasExplicitColumnName; private final @Nullable ValueExpression columnNameExpression; - private final SqlIdentifier sequence; + private final @Nullable SqlIdentifier sequence; private final Lazy> collectionIdColumnName; private final @Nullable ValueExpression collectionIdColumnNameExpression; private final Lazy collectionKeyColumnName; @@ -65,8 +65,8 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent private final NamingStrategy namingStrategy; private boolean forceQuote = true; - private SqlIdentifierExpressionEvaluator sqlIdentifierExpressionEvaluator = - new SqlIdentifierExpressionEvaluator(EvaluationContextProvider.DEFAULT); + private SqlIdentifierExpressionEvaluator sqlIdentifierExpressionEvaluator = new SqlIdentifierExpressionEvaluator( + EvaluationContextProvider.DEFAULT); /** * Creates a new {@link BasicRelationalPersistentProperty}. @@ -78,7 +78,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent * @since 2.0 */ public BasicRelationalPersistentProperty(Property property, PersistentEntity owner, - SimpleTypeHolder simpleTypeHolder, NamingStrategy namingStrategy) { + SimpleTypeHolder simpleTypeHolder, NamingStrategy namingStrategy) { super(property, owner, simpleTypeHolder); this.namingStrategy = namingStrategy; @@ -154,8 +154,7 @@ void setSqlIdentifierExpressionEvaluator(SqlIdentifierExpressionEvaluator sqlIde * @param potentialExpression can be {@literal null} * @return can be {@literal null}. */ - @Nullable - private static ValueExpression detectExpression(@Nullable String potentialExpression) { + private static @Nullable ValueExpression detectExpression(@Nullable String potentialExpression) { if (!StringUtils.hasText(potentialExpression)) { return null; @@ -224,7 +223,7 @@ public SqlIdentifier getReverseColumnName(RelationalPersistentEntity owner) { } @Override - public SqlIdentifier getKeyColumn() { + public @Nullable SqlIdentifier getKeyColumn() { if (!isQualified()) { return null; @@ -267,7 +266,7 @@ public boolean isEmbedded() { @Override public String getEmbeddedPrefix() { - return isEmbedded() ? embeddedPrefix : null; + return isEmbedded() ? embeddedPrefix : ""; } @Override @@ -284,9 +283,8 @@ public boolean isInsertOnly() { return findAnnotation(InsertOnlyProperty.class) != null; } - @Nullable @Override - public SqlIdentifier getSequence() { + public @Nullable SqlIdentifier getSequence() { return this.sequence; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java index 42f25a2475..0a108588b9 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DefaultAggregatePath.java @@ -19,9 +19,10 @@ import java.util.NoSuchElementException; import java.util.Objects; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyPath; import org.springframework.data.util.Lazy; -import org.springframework.lang.Nullable; +import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; import org.springframework.util.ConcurrentLruCache; @@ -89,7 +90,11 @@ public AggregatePath getParentPath() { return context.getAggregatePath(path.getLeafProperty().getOwner()); } - return context.getAggregatePath(path.getParentPath()); + PersistentPropertyPath parentPath = path.getParentPath(); + + Assert.state(parentPath != null, "Parent path must not be null"); + + return context.getAggregatePath(parentPath); } @Override @@ -107,16 +112,26 @@ public AggregatePath append(AggregatePath path) { RelationalPersistentProperty baseProperty = path.getRequiredBaseProperty(); AggregatePath appended = append(baseProperty); AggregatePath tail = path.getTail(); - return tail == null ? appended : appended.append(tail); + return tail == null ? appended : appended.append(tail); } private AggregatePath doGetAggegatePath(RelationalPersistentProperty property) { - PersistentPropertyPath newPath = isRoot() // - ? context.getPersistentPropertyPath(property.getName(), rootType.getTypeInformation()) // - : context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), - path.getBaseProperty().getOwner().getTypeInformation()); + PersistentPropertyPath newPath; + + if (isRoot()) { + + Assert.state(rootType != null, "Root type must not be null"); + + newPath = context.getPersistentPropertyPath(property.getName(), rootType.getTypeInformation()); + } else { + + Assert.state(path != null, "Path must not be null"); + + newPath = context.getPersistentPropertyPath(path.toDotPath() + "." + property.getName(), + path.getBaseProperty().getOwner().getTypeInformation()); + } return context.getAggregatePath(newPath); } @@ -182,7 +197,13 @@ public boolean hasIdProperty() { @Override public RelationalPersistentProperty getRequiredIdProperty() { - return isRoot() ? rootType.getRequiredIdProperty() : getRequiredLeafEntity().getRequiredIdProperty(); + if (isRoot()) { + + Assert.state(rootType != null, "Root type must not be null"); + + return rootType.getRequiredIdProperty(); + } + return getRequiredLeafEntity().getRequiredIdProperty(); } @Override @@ -193,9 +214,16 @@ public PersistentPropertyPath getRequiredPersisten } @Override - public RelationalPersistentEntity getLeafEntity() { - return isRoot() ? rootType - : context.getPersistentEntity(getRequiredLeafProperty().getTypeInformation().getActualType()); + public @Nullable RelationalPersistentEntity getLeafEntity() { + if (isRoot()) { + return rootType; + } + + TypeInformation actualType = getRequiredLeafProperty().getTypeInformation().getActualType(); + + Assert.state(actualType != null, "Actual type must not be null"); + + return context.getPersistentEntity(actualType); } @Override @@ -216,10 +244,16 @@ public AggregatePath getTail() { return null; } + Assert.state(this.path != null, "Path must not be null"); + AggregatePath tail = null; for (RelationalPersistentProperty prop : this.path) { if (tail == null) { - tail = context.getAggregatePath(context.getPersistentEntity(prop)); + RelationalPersistentEntity entity = context.getPersistentEntity(prop); + + Assert.state(entity != null, "Entity must not be null"); + + tail = context.getAggregatePath(entity); } else { tail = tail.append(prop); } @@ -254,6 +288,9 @@ public AggregatePath subtract(@Nullable AggregatePath basePath) { public AggregatePath getSubPathBasedOn(Class baseType) { if (isRoot()) { + + Assert.state(rootType != null, "Root type must not be null"); + if (rootType.getType() != baseType) { throw new IllegalStateException("No matching path found for [%s]".formatted(baseType)); } @@ -264,8 +301,9 @@ public AggregatePath getSubPathBasedOn(Class baseType) { if (owner.getType() == baseType) { return this; } - - return getTail().getSubPathBasedOn(baseType); + + AggregatePath tail = getRequiredTail(); + return tail.getSubPathBasedOn(baseType); } /** @@ -306,9 +344,27 @@ public int hashCode() { @Override public String toString() { - return "AggregatePath[" - + (rootType == null ? path.getBaseProperty().getOwner().getType().getName() : rootType.getName()) + "]" - + ((isRoot()) ? "/" : path.toDotPath()); + String typeName; + if (rootType == null) { + + Assert.state(path != null, "Path must not be null"); + + typeName = path.getBaseProperty().getOwner().getType().getName(); + } else { + typeName = rootType.getName(); + } + + String pathPart; + if (isRoot()) { + pathPart = "/"; + } else { + + Assert.state(path != null, "Path must not be null"); + + pathPart = path.toDotPath(); + } + + return "AggregatePath[" + typeName + "]" + pathPart; } private static class AggregatePathIterator implements Iterator { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DerivedSqlIdentifier.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DerivedSqlIdentifier.java index c3bd07e1dd..3d0f83b41d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DerivedSqlIdentifier.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/DerivedSqlIdentifier.java @@ -19,9 +19,9 @@ import java.util.Iterator; import java.util.function.UnaryOperator; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java index 984d94cf5e..4cf98ccf9f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedContext.java @@ -23,20 +23,13 @@ */ record EmbeddedContext(RelationalPersistentProperty ownerProperty) { - public String getEmbeddedPrefix() { - return ownerProperty.getEmbeddedPrefix(); - } - public String withEmbeddedPrefix(String name) { if (!ownerProperty.isEmbedded()) { return name; } - String embeddedPrefix = ownerProperty.getEmbeddedPrefix(); - if (embeddedPrefix != null) { - return embeddedPrefix + name; - } - return name; + String embeddedPrefix = ownerProperty.getEmbeddedPrefix(); + return embeddedPrefix + name; } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentEntity.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentEntity.java index 6a3befcdf8..28aadc2ec6 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentEntity.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentEntity.java @@ -18,6 +18,7 @@ import java.lang.annotation.Annotation; import java.util.Iterator; +import org.jspecify.annotations.Nullable; import org.springframework.core.env.Environment; import org.springframework.data.mapping.*; import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; @@ -25,7 +26,8 @@ import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.util.Streamable; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; +import org.springframework.lang.Contract; +import org.springframework.util.Assert; /** * Embedded entity extension for a {@link Embedded entity}. @@ -82,7 +84,13 @@ public boolean hasNext() { @Override public RelationalPersistentProperty next() { - return wrap(iterator.next()); + + RelationalPersistentProperty property = wrap(iterator.next()); + + // NullAway doesn't understand contracts + Assert.state(property != null, "Property must not be null"); + + return property; } }; } @@ -178,28 +186,50 @@ public TypeInformation getTypeInformation() { @Override public void doWithProperties(PropertyHandler handler) { + delegate.doWithProperties((PropertyHandler) persistentProperty -> { - handler.doWithPersistentProperty(wrap(persistentProperty)); + + RelationalPersistentProperty wrapped = wrap(persistentProperty); + + Assert.state(wrapped != null, "Property must not be null"); + + handler.doWithPersistentProperty(wrapped); }); } @Override public void doWithProperties(SimplePropertyHandler handler) { - delegate.doWithProperties((SimplePropertyHandler) property -> handler - .doWithPersistentProperty(wrap((RelationalPersistentProperty) property))); + delegate.doWithProperties((SimplePropertyHandler) property -> { + + RelationalPersistentProperty wrapped = wrap((RelationalPersistentProperty) property); + + Assert.state(wrapped != null, "Property must not be null"); + + handler.doWithPersistentProperty(wrapped); + }); } @Override public void doWithAssociations(AssociationHandler handler) { delegate.doWithAssociations((AssociationHandler) association -> { - handler.doWithAssociation(new Association<>(wrap(association.getInverse()), wrap(association.getObverse()))); + + RelationalPersistentProperty wrapped = wrap(association.getInverse()); + + Assert.state(wrapped != null, "Association must not be null"); + + handler.doWithAssociation(new Association<>(wrapped, wrap(association.getObverse()))); }); } @Override public void doWithAssociations(SimpleAssociationHandler handler) { delegate.doWithAssociations((AssociationHandler) association -> { - handler.doWithAssociation(new Association<>(wrap(association.getInverse()), wrap(association.getObverse()))); + + RelationalPersistentProperty wrapped = wrap(association.getInverse()); + + Assert.state(wrapped != null, "Association must not be null"); + + handler.doWithAssociation(new Association<>(wrapped, wrap(association.getObverse()))); }); } @@ -245,6 +275,7 @@ public boolean requiresPropertyPopulation() { } @Nullable + @Contract("null -> null; !null -> new") private RelationalPersistentProperty wrap(@Nullable RelationalPersistentProperty source) { if (source == null) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java index bcfc2ddb5e..dffd811143 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentProperty.java @@ -19,10 +19,10 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.Association; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** @@ -48,7 +48,6 @@ public boolean isEmbedded() { return delegate.isEmbedded(); } - @Nullable @Override public String getEmbeddedPrefix() { return context.withEmbeddedPrefix(delegate.getEmbeddedPrefix()); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertOnlyProperty.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertOnlyProperty.java index 26d39237ea..fc26673b10 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertOnlyProperty.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/InsertOnlyProperty.java @@ -30,6 +30,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Documented - public @interface InsertOnlyProperty { } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyTranslator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyTranslator.java index b3da570085..08ed1fa20b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyTranslator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/PersistentPropertyTranslator.java @@ -17,8 +17,8 @@ import java.util.function.Predicate; +import org.jspecify.annotations.Nullable; import org.springframework.data.util.Predicates; -import org.springframework.lang.Nullable; /** * Utility to translate a {@link RelationalPersistentProperty} into a corresponding property from a different 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 88176a16f1..e7de7468d1 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 @@ -18,6 +18,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.core.env.Environment; @@ -29,7 +30,6 @@ import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -49,8 +49,8 @@ public class RelationalMappingContext private boolean forceQuote = true; - private final SqlIdentifierExpressionEvaluator sqlIdentifierExpressionEvaluator = - new SqlIdentifierExpressionEvaluator(EvaluationContextProvider.DEFAULT); + private final SqlIdentifierExpressionEvaluator sqlIdentifierExpressionEvaluator = new SqlIdentifierExpressionEvaluator( + EvaluationContextProvider.DEFAULT); private boolean singleQueryLoadingEnabled = false; /** 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 6d05df9239..d75c8d979d 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 @@ -15,9 +15,9 @@ */ package org.springframework.data.relational.core.mapping; +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; /** * A {@link PersistentProperty} with methods for additional RDBMS related metadata based on columns. @@ -77,11 +77,10 @@ default boolean isEmbedded() { } /** - * @return Prefix for embedded columns. If the column is not embedded the return value is null. + * @return Prefix for embedded columns. If the column is not embedded the return value is empty. */ - @Nullable default String getEmbeddedPrefix() { - return null; + return ""; } /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java index 3da6a8c0e3..8b0afd302d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/AfterDeleteEvent.java @@ -15,8 +15,8 @@ */ package org.springframework.data.relational.core.mapping.event; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.conversion.AggregateChange; -import org.springframework.lang.Nullable; /** * Gets published after deletion of an entity. It will have a {@link Identifier} identifier. If the entity is diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java index 6de588bff4..c4ac1c1c3b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/BeforeDeleteEvent.java @@ -15,8 +15,8 @@ */ package org.springframework.data.relational.core.mapping.event; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.conversion.AggregateChange; -import org.springframework.lang.Nullable; /** * Gets published when an entity is about to get deleted. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/Identifier.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/Identifier.java index 3b64d0eb49..91d4c708c5 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/Identifier.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/Identifier.java @@ -17,7 +17,7 @@ import java.util.Objects; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalDeleteEvent.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalDeleteEvent.java index 459650c524..7e148ad775 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalDeleteEvent.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalDeleteEvent.java @@ -15,8 +15,8 @@ */ package org.springframework.data.relational.core.mapping.event; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.conversion.AggregateChange; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalEvent.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalEvent.java index 81fb0d0e61..9145f52e68 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalEvent.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/RelationalEvent.java @@ -15,9 +15,9 @@ */ package org.springframework.data.relational.core.mapping.event; +import org.jspecify.annotations.Nullable; import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableTypeProvider; -import org.springframework.lang.Nullable; /** * an event signalling JDBC processing. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java index 8ab2a2524f..a5946ecfa7 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/event/package-info.java @@ -1,4 +1,4 @@ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.relational.core.mapping.event; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java index c026872132..a79ac42737 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/package-info.java @@ -1,4 +1,4 @@ -@NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.relational.core.mapping; -import org.springframework.lang.NonNullApi; + diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java index 2b2deff2f2..aa24ffbefa 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Criteria.java @@ -24,11 +24,11 @@ import java.util.Objects; import java.util.StringJoiner; +import org.jspecify.annotations.Nullable; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.util.Pair; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -421,8 +421,10 @@ private void unroll(CriteriaDefinition criteria, StringBuilder stringBuilder) { Map forwardChain = new HashMap<>(); while (current.hasPrevious()) { - forwardChain.put(current.getPrevious(), current); - current = current.getPrevious(); + + CriteriaDefinition previous = current.getRequiredPrevious(); + forwardChain.put(previous, current); + current = previous; } // perform the actual mapping @@ -476,13 +478,23 @@ private void render(CriteriaDefinition criteria, StringBuilder stringBuilder) { return; } - stringBuilder.append(criteria.getColumn().toSql(IdentifierProcessing.NONE)).append(' ') - .append(criteria.getComparator().getComparator()); + SqlIdentifier column = criteria.getColumn(); + + Assert.state(column != null, "Column must not be null"); + + Comparator comparator = criteria.getComparator(); + + Assert.state(comparator != null, "Comparator must not be null"); - switch (criteria.getComparator()) { + stringBuilder.append(column.toSql(IdentifierProcessing.NONE)).append(' ').append(comparator.getComparator()); + + switch (comparator) { case BETWEEN: case NOT_BETWEEN: Pair pair = (Pair) criteria.getValue(); + + Assert.state(pair != null, "Pair must not be null"); + stringBuilder.append(' ').append(pair.getFirst()).append(" AND ").append(pair.getSecond()); break; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/CriteriaDefinition.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/CriteriaDefinition.java index c09129a1b6..81092ffaa3 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/CriteriaDefinition.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/CriteriaDefinition.java @@ -18,8 +18,8 @@ import java.util.Arrays; import java.util.List; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -33,19 +33,19 @@ public interface CriteriaDefinition { /** - * Static factory method to create an empty {@link CriteriaDefinition}. + * Static factory method to create an empty {@code CriteriaDefinition}. * - * @return an empty {@link CriteriaDefinition}. + * @return an empty {@code CriteriaDefinition}. */ static CriteriaDefinition empty() { return Criteria.EMPTY; } /** - * Create a new {@link CriteriaDefinition} and combine it as group with {@code AND} using the provided {@link List + * Create a new {@code CriteriaDefinition} and combine it as group with {@code AND} using the provided {@link List * Criterias}. * - * @return new {@link CriteriaDefinition}. + * @return new {@code CriteriaDefinition}. */ static CriteriaDefinition from(CriteriaDefinition... criteria) { @@ -56,10 +56,10 @@ static CriteriaDefinition from(CriteriaDefinition... criteria) { } /** - * Create a new {@link CriteriaDefinition} and combine it as group with {@code AND} using the provided {@link List + * Create a new {@code CriteriaDefinition} and combine it as group with {@code AND} using the provided {@link List * Criterias}. * - * @return new {@link CriteriaDefinition}. + * @return new {@code CriteriaDefinition}. * @since 1.1 */ static CriteriaDefinition from(List criteria) { @@ -79,7 +79,7 @@ static CriteriaDefinition from(List criteria) { } /** - * @return {@literal true} if this {@link Criteria} is empty. + * @return {@literal true} if this {@code CriteriaDefinition} is empty. */ boolean isGroup(); @@ -111,30 +111,47 @@ static CriteriaDefinition from(List criteria) { boolean isIgnoreCase(); /** - * @return the previous {@link CriteriaDefinition} object. Can be {@literal null} if there is no previous - * {@link CriteriaDefinition}. + * @return the previous {@code CriteriaDefinition} object. Can be {@literal null} if there is no previous + * {@code CriteriaDefinition}. * @see #hasPrevious() */ @Nullable CriteriaDefinition getPrevious(); /** - * @return {@literal true} if this {@link Criteria} has a previous one. + * @return the required previous {@code CriteriaDefinition} object or throws an {@link IllegalStateException} if there + * is no previous {@code CriteriaDefinition}. + * @see #hasPrevious() + * @see #getPrevious() + * @since 4.0 + */ + default CriteriaDefinition getRequiredPrevious() { + + CriteriaDefinition previous = getPrevious(); + if (previous == null) { + throw new IllegalStateException("No previous CriteriaDefinition available"); + } + + return previous; + } + + /** + * @return {@literal true} if this {@code CriteriaDefinition} has a previous one. */ boolean hasPrevious(); /** - * @return {@literal true} if this {@link Criteria} is empty. + * @return {@literal true} if this {@code CriteriaDefinition} is empty. */ boolean isEmpty(); /** - * @return {@link Combinator} to combine this criteria with a previous one. + * @return {@link Combinator} to combine this {@code CriteriaDefinition} with a previous one. */ Combinator getCombinator(); enum Combinator { - INITIAL, AND, OR; + INITIAL, AND, OR } enum Comparator { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java index 6d1ed69d4f..5bc6b80cb2 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java @@ -23,10 +23,10 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java index ceaabf69b9..2facc0bfeb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Update.java @@ -20,9 +20,9 @@ import java.util.Map; import java.util.StringJoiner; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.SqlIdentifier; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -36,9 +36,9 @@ public class Update { private static final Update EMPTY = new Update(Collections.emptyMap()); - private final Map columnsToUpdate; + private final Map columnsToUpdate; - private Update(Map columnsToUpdate) { + private Update(Map columnsToUpdate) { this.columnsToUpdate = columnsToUpdate; } @@ -48,7 +48,7 @@ private Update(Map columnsToUpdate) { * @param assignments must not be {@literal null}. * @return */ - public static Update from(Map assignments) { + public static Update from(Map assignments) { return new Update(new LinkedHashMap<>(assignments)); } @@ -94,7 +94,7 @@ public Update set(SqlIdentifier column, @Nullable Object value) { * * @return */ - public Map getAssignments() { + public Map getAssignments() { return Collections.unmodifiableMap(this.columnsToUpdate); } @@ -102,7 +102,7 @@ private Update addMultiFieldOperation(SqlIdentifier key, @Nullable Object value) Assert.notNull(key, "Column for update must not be null"); - Map updates = new LinkedHashMap<>(this.columnsToUpdate); + Map updates = new LinkedHashMap<>(this.columnsToUpdate); updates.put(key, value); return new Update(updates); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/ValueFunction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/ValueFunction.java index 7fcd79129a..c366803076 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/ValueFunction.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/ValueFunction.java @@ -18,8 +18,8 @@ import java.util.function.Function; import java.util.function.Supplier; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.dialect.Escaper; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -32,7 +32,7 @@ * @see Supplier */ @FunctionalInterface -public interface ValueFunction extends Function { +public interface ValueFunction extends Function { /** * Produces a value by considering the given {@link Escaper}. @@ -40,7 +40,6 @@ public interface ValueFunction extends Function { * @param escaper the escaper to use. * @return the return value, may be {@literal null}. */ - @Nullable @Override T apply(Escaper escaper); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/package-info.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/package-info.java index 34fedea567..7243fc2d7f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/package-info.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/query/package-info.java @@ -1,6 +1,5 @@ /** * Query and update support. */ -@org.springframework.lang.NonNullApi -@org.springframework.lang.NonNullFields +@org.jspecify.annotations.NullMarked package org.springframework.data.relational.core.query; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java index 110659620a..73680a1ef2 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractImportValidator.java @@ -18,7 +18,7 @@ import java.util.HashSet; import java.util.Set; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Validator for statements to import columns. @@ -28,8 +28,8 @@ */ abstract class AbstractImportValidator implements Visitor { - Set

requiredByWhere = new HashSet<>(); - Set
from = new HashSet<>(); + final Set requiredByWhere = new HashSet<>(); + final Set from = new HashSet<>(); @Nullable Visitable parent; @Override diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java index 19846935c0..969f2ea4f4 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AbstractSegment.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AnalyticFunction.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AnalyticFunction.java index fb4edc9a9e..86a1145a67 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AnalyticFunction.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/AnalyticFunction.java @@ -20,7 +20,7 @@ /** * Represents an analytic function, also known as windowing function - * + * * @author Jens Schauder * @since 2.7 */ @@ -47,7 +47,7 @@ private AnalyticFunction(SimpleFunction function, Partition partition, OrderBy o /** * Specify the {@literal PARTITION BY} clause of an analytic function - * + * * @param partitionBy Typically, column but other expressions are fine to. * @return a new {@literal AnalyticFunction} is partitioned by the given expressions, overwriting any expression * previously present. @@ -58,7 +58,7 @@ public AnalyticFunction partitionBy(Expression... partitionBy) { /** * Specify the {@literal PARTITION BY} clause of an analytic function - * + * * @param partitionBy Typically, column but other expressions are fine to. * @return a new {@literal AnalyticFunction} is partitioned by the given expressions, overwriting any expression * previously present. @@ -70,7 +70,7 @@ public AnalyticFunction partitionBy(Collection partitionBy /** * Specify the {@literal ORDER BY} clause of an analytic function - * + * * @param orderBy Typically, column but other expressions are fine to. * @return a new {@literal AnalyticFunction} is ordered by the given expressions, overwriting any expression * previously present. @@ -81,7 +81,7 @@ public AnalyticFunction orderBy(OrderByField... orderBy) { /** * Specify the {@literal ORDER BY} clause of an analytic function - * + * * @param orderBy Typically, column but other expressions are fine to. * @return a new {@literal AnalyticFunction} is ordered by the given expressions, overwriting any expression * previously present. @@ -93,7 +93,7 @@ public AnalyticFunction orderBy(Collection orderBy) { /** * Specify the {@literal ORDER BY} clause of an analytic function - * + * * @param orderBy array of {@link Expression}. Typically, column but other expressions are fine to. * @return a new {@literal AnalyticFunction} is ordered by the given expressions, overwriting any expression * previously present. @@ -107,11 +107,11 @@ public AnalyticFunction orderBy(Expression... orderBy) { return new AnalyticFunction(function, partition, new OrderBy(orderByFields)); } - public AliasedAnalyticFunction as(String alias) { + public AnalyticFunction as(String alias) { return new AliasedAnalyticFunction(this, SqlIdentifier.unquoted(alias)); } - public AliasedAnalyticFunction as(SqlIdentifier alias) { + public AnalyticFunction as(SqlIdentifier alias) { return new AliasedAnalyticFunction(this, alias); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BooleanLiteral.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BooleanLiteral.java index 626f1c5213..97a5c1d508 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BooleanLiteral.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/BooleanLiteral.java @@ -15,6 +15,8 @@ */ package org.springframework.data.relational.core.sql; +import org.springframework.util.Assert; + /** * Represents a {@link Boolean} literal. * @@ -29,7 +31,12 @@ public class BooleanLiteral extends Literal { @Override public Boolean getContent() { - return super.getContent(); + + Boolean content = super.getContent(); + + Assert.state(content != null, "Content must not be null"); + + return content; } @Override diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CaseExpression.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CaseExpression.java index fb7b13374e..fb3d7a616f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CaseExpression.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CaseExpression.java @@ -1,16 +1,17 @@ package org.springframework.data.relational.core.sql; -import org.springframework.lang.Nullable; +import static java.util.stream.Collectors.*; import java.util.ArrayList; import java.util.List; -import static java.util.stream.Collectors.*; +import org.jspecify.annotations.Nullable; /** * Case with one or more conditions expression. *

* Results in a rendered condition: + * *

  *   CASE
  *     WHEN condition1 THEN result1
@@ -25,8 +26,7 @@
 public class CaseExpression extends AbstractSegment implements Expression {
 
 	private final List whenList;
-	@Nullable
-	private final Expression elseExpression;
+	private @Nullable final Expression elseExpression;
 
 	private static Segment[] children(List whenList, @Nullable Expression elseExpression) {
 
@@ -81,6 +81,7 @@ public CaseExpression elseExpression(Expression elseExpression) {
 
 	@Override
 	public String toString() {
-		return "CASE " + whenList.stream().map(When::toString).collect(joining(" ")) + (elseExpression != null ? " ELSE " + elseExpression : "") + " END";
+		return "CASE " + whenList.stream().map(When::toString).collect(joining(" "))
+				+ (elseExpression != null ? " ELSE " + elseExpression : "") + " END";
 	}
 }
diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java
index 5f1660ff1b..6e6007ae9a 100644
--- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java
+++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Column.java
@@ -17,15 +17,16 @@
 
 import java.util.Objects;
 
-import org.springframework.lang.Nullable;
+import org.jspecify.annotations.Nullable;
 import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
 
 /**
  * Column name within a {@code SELECT … FROM} clause.
  * 

* Renders to: {@code } or {@code .}. *

- * + * * @author Mark Paluch * @author Jens Schauder * @since 1.1 @@ -33,20 +34,20 @@ public class Column extends AbstractSegment implements Expression, Named { private final SqlIdentifier name; - private final TableLike table; + private final @Nullable TableLike table; - Column(String name, TableLike table) { + Column(String name, @Nullable TableLike table) { - super(table); + super(table == null ? new Segment[0] : new Segment[] { table }); Assert.notNull(name, "Name must not be null"); this.name = SqlIdentifier.unquoted(name); this.table = table; } - Column(SqlIdentifier name, TableLike table) { + Column(SqlIdentifier name, @Nullable TableLike table) { - super(table); + super(table == null ? new Segment[0] : new Segment[] { table }); Assert.notNull(name, "Name must not be null"); this.name = name; @@ -340,14 +341,29 @@ public SqlIdentifier getReferenceName() { * @return the {@link Table}. Can be {@literal null} if the column was not referenced in the context of a * {@link Table}. */ - @Nullable - public TableLike getTable() { + public @Nullable TableLike getTable() { + return table; + } + + /** + * @return the required {@link Table}, throws {@link IllegalStateException} if the column was not referenced in the + * context of a {@link Table}. + * @throws IllegalStateException if the column was not referenced in the context of a {@link Table}. + * @since 4.0 + */ + public TableLike getRequiredTable() { + + TableLike table = getTable(); + + if (table == null) { + throw new IllegalStateException("Column '%s' is not associated with a Table".formatted(getName())); + } + return table; } @Override public String toString() { - return getPrefix() + name; } @@ -372,7 +388,7 @@ public boolean equals(@Nullable Object o) { return false; } Column column = (Column) o; - return name.equals(column.name) && table.equals(column.table); + return name.equals(column.name) && ObjectUtils.nullSafeEquals(table, column.table); } @Override @@ -387,12 +403,12 @@ static class AliasedColumn extends Column implements Aliased { private final SqlIdentifier alias; - private AliasedColumn(String name, TableLike table, String alias) { + private AliasedColumn(String name, @Nullable TableLike table, String alias) { super(name, table); this.alias = SqlIdentifier.unquoted(alias); } - private AliasedColumn(SqlIdentifier name, TableLike table, SqlIdentifier alias) { + private AliasedColumn(SqlIdentifier name, @Nullable TableLike table, SqlIdentifier alias) { super(name, table); this.alias = alias; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CompositeSqlIdentifier.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CompositeSqlIdentifier.java index 0b232b256c..5bee08e26b 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CompositeSqlIdentifier.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/CompositeSqlIdentifier.java @@ -20,7 +20,7 @@ import java.util.StringJoiner; import java.util.function.UnaryOperator; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java index a475d71fe5..0b51e0d5cc 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDelete.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java index 3d2d3169c9..a234b4ff7d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultDeleteBuilder.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** @@ -41,7 +41,7 @@ public DeleteWhere from(Table table) { @Override public DeleteWhereAndOr where(Condition condition) { - Assert.notNull(condition, "Where Condition must not be null"); + Assert.notNull(condition, "where must not be null"); this.where = condition; return this; } @@ -50,6 +50,8 @@ public DeleteWhereAndOr where(Condition condition) { public DeleteWhereAndOr and(Condition condition) { Assert.notNull(condition, "Condition must not be null"); + Assert.state(this.where != null, "where must not be null"); + this.where = this.where.and(condition); return this; } @@ -58,6 +60,8 @@ public DeleteWhereAndOr and(Condition condition) { public DeleteWhereAndOr or(Condition condition) { Assert.notNull(condition, "Condition must not be null"); + Assert.state(this.where != null, "where must not be null"); + this.where = this.where.or(condition); return this; } @@ -65,6 +69,8 @@ public DeleteWhereAndOr or(Condition condition) { @Override public Delete build() { + Assert.state(this.from != null, "from must not be null"); + DefaultDelete delete = new DefaultDelete(this.from, this.where); DeleteValidator.validate(delete); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java index 2a5d4540bd..82483b9434 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsert.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -35,7 +35,9 @@ class DefaultInsert implements Insert { private final Values values; DefaultInsert(@Nullable Table into, List columns, List values) { - this.into = new Into(into); + + // TODO: this is weird. can we really have an insert without table? + this.into = into == null ? new Into() : new Into(into); this.columns = new ArrayList<>(columns); this.values = new Values(new ArrayList<>(values)); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java index 91d2942df5..8d40a4bc2c 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultInsertBuilder.java @@ -20,7 +20,7 @@ import java.util.Collection; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** @@ -33,8 +33,8 @@ class DefaultInsertBuilder implements InsertBuilder, InsertBuilder.InsertIntoColumnsAndValuesWithBuild, InsertBuilder.InsertValuesWithBuild { private @Nullable Table into; - private List columns = new ArrayList<>(); - private List values = new ArrayList<>(); + private final List columns = new ArrayList<>(); + private final List values = new ArrayList<>(); @Override public InsertIntoColumnsAndValuesWithBuild into(Table table) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java index 9aa5975dd1..8e7ea32344 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelect.java @@ -21,7 +21,7 @@ import java.util.OptionalLong; import java.util.function.Consumer; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java index 0bc6fe2d36..561a696846 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java @@ -20,11 +20,12 @@ import java.util.Collection; import java.util.List; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.Join.JoinType; import org.springframework.data.relational.core.sql.SelectBuilder.SelectAndFrom; import org.springframework.data.relational.core.sql.SelectBuilder.SelectFromAndJoin; import org.springframework.data.relational.core.sql.SelectBuilder.SelectWhereAndOr; -import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Default {@link SelectBuilder} implementation. @@ -155,6 +156,8 @@ public SelectWhereAndOr where(Condition condition) { @Override public SelectWhereAndOr and(Condition condition) { + Assert.state(this.where != null, "Where must not be null"); + where = where.and(condition); return this; } @@ -162,6 +165,8 @@ public SelectWhereAndOr and(Condition condition) { @Override public SelectWhereAndOr or(Condition condition) { + Assert.state(this.where != null, "Where must not be null"); + where = where.or(condition); return this; } @@ -274,6 +279,9 @@ private void finishCondition() { return; } + Assert.state(from != null, "from must not be null"); + Assert.state(to != null, "to must not be null"); + Comparison comparison = Comparison.create(from, "=", to); if (condition == null) { @@ -285,7 +293,11 @@ private void finishCondition() { } private Join finishJoin() { + finishCondition(); + + Assert.state(condition != null, "condition must not be null"); + return new Join(joinType, table, condition); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSqlIdentifier.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSqlIdentifier.java index f8de07d72a..0da4a768ff 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSqlIdentifier.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSqlIdentifier.java @@ -19,7 +19,7 @@ import java.util.Iterator; import java.util.function.UnaryOperator; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java index f6c4c0c377..655dbfcd60 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdate.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java index d0a745d783..fcecdcb61f 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultUpdateBuilder.java @@ -20,10 +20,10 @@ import java.util.Collection; import java.util.List; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateAssign; import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhere; import org.springframework.data.relational.core.sql.UpdateBuilder.UpdateWhereAndOr; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -35,7 +35,7 @@ class DefaultUpdateBuilder implements UpdateBuilder, UpdateWhere, UpdateWhereAndOr, UpdateAssign { private @Nullable Table table; - private List assignments = new ArrayList<>(); + private final List assignments = new ArrayList<>(); private @Nullable Condition where; @Override @@ -90,6 +90,7 @@ public UpdateWhereAndOr where(Condition condition) { public UpdateWhereAndOr and(Condition condition) { Assert.notNull(condition, "Condition must not be null"); + Assert.state(this.where != null, "Where must not be null"); this.where = this.where.and(condition); @@ -100,6 +101,7 @@ public UpdateWhereAndOr and(Condition condition) { public UpdateWhereAndOr or(Condition condition) { Assert.notNull(condition, "Condition must not be null"); + Assert.state(this.where != null, "Where must not be null"); this.where = this.where.and(condition); @@ -108,6 +110,9 @@ public UpdateWhereAndOr or(Condition condition) { @Override public Update build() { + + Assert.state(this.table != null, "Table must not be null"); + return new DefaultUpdate(this.table, this.assignments, this.where); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java index 166a28a601..acafc5c049 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DeleteValidator.java @@ -20,7 +20,7 @@ *

* Validates that all {@link Column}s using a table qualifier have a table import from the {@code FROM} clause. *

- * + * * @author Mark Paluch * @since 1.1 */ @@ -40,7 +40,7 @@ private void doValidate(Delete select) { select.visit(this); - for (Table table : requiredByWhere) { + for (TableLike table : requiredByWhere) { if (!from.contains(table)) { throw new IllegalStateException( String.format("Required table [%s] by a WHERE predicate not imported by FROM %s", table, from)); diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java index db6a348ec5..6f1681297d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Expressions.java @@ -29,7 +29,7 @@ */ public abstract class Expressions { - private static Expression ASTERISK = new SimpleExpression("*"); + private static final Expression ASTERISK = new SimpleExpression("*"); /** * @return a new asterisk {@code *} expression. diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IdentifierProcessing.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IdentifierProcessing.java index f5f93e0fbf..1120b8e687 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IdentifierProcessing.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/IdentifierProcessing.java @@ -42,7 +42,7 @@ public interface IdentifierProcessing { * @param letterCasing {@link LetterCasing} rules for identifier normalization. * @return a new {@link IdentifierProcessing} object. */ - static DefaultIdentifierProcessing create(Quoting quoting, LetterCasing letterCasing) { + static IdentifierProcessing create(Quoting quoting, LetterCasing letterCasing) { return new DefaultIdentifierProcessing(quoting, letterCasing); } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java index d9c46e6cfc..f3d09defec 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Literal.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents a literal. @@ -25,7 +25,7 @@ */ public class Literal extends AbstractSegment implements Expression { - private @Nullable T content; + private final @Nullable T content; Literal(@Nullable T content) { this.content = content; @@ -34,8 +34,7 @@ public class Literal extends AbstractSegment implements Expression { /** * @return the content of the literal. */ - @Nullable - public T getContent() { + public @Nullable T getContent() { return content; } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java index 743201e5e1..9a1d55afab 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/NumericLiteral.java @@ -15,7 +15,7 @@ */ package org.springframework.data.relational.core.sql; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Represents a {@link Number} literal. @@ -30,8 +30,7 @@ public class NumericLiteral extends Literal { } @Override - @Nullable - public Number getContent() { + public @Nullable Number getContent() { return super.getContent(); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java index 2044366440..81d861c7e0 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/OrderByField.java @@ -15,10 +15,10 @@ */ package org.springframework.data.relational.core.sql; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.domain.Sort.NullHandling; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -31,7 +31,7 @@ public class OrderByField extends AbstractSegment { private final Expression expression; - private final @Nullable Sort.Direction direction; + private final Sort.@Nullable Direction direction; private final Sort.NullHandling nullHandling; private OrderByField(Expression expression, @Nullable Direction direction, NullHandling nullHandling) { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java index 22dabaf31f..d06f4aad7d 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SQL.java @@ -15,8 +15,8 @@ */ package org.springframework.data.relational.core.sql; +import org.jspecify.annotations.Nullable; import org.springframework.data.relational.core.sql.BindMarker.NamedBindMarker; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java index b04da5cf82..87c3cce8eb 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/Select.java @@ -18,7 +18,7 @@ import java.util.List; import java.util.OptionalLong; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * AST for a {@code SELECT} statement. Visiting order: diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java index 60fddb8459..e3195adc26 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/SelectValidator.java @@ -34,10 +34,10 @@ class SelectValidator extends AbstractImportValidator { private final Stack