diff --git a/.mvn/jvm.config b/.mvn/jvm.config new file mode 100644 index 0000000000..32599cefea --- /dev/null +++ b/.mvn/jvm.config @@ -0,0 +1,10 @@ +--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED diff --git a/org/antlr/v4/tool/templates/codegen/Java/Java.stg b/org/antlr/v4/tool/templates/codegen/Java/Java.stg index fc455cfa1d..7f1701c00f 100644 --- a/org/antlr/v4/tool/templates/codegen/Java/Java.stg +++ b/org/antlr/v4/tool/templates/codegen/Java/Java.stg @@ -48,14 +48,18 @@ ParserFile(file, parser, namedActions, contextSuperClass) ::= << package ; + import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.misc.*; import org.antlr.v4.runtime.tree.*; +import org.jspecify.annotations.NullUnmarked; import java.util.List; import java.util.Iterator; import java.util.ArrayList; +import jakarta.annotation.Generated; + >> @@ -67,11 +71,15 @@ package ;
import org.antlr.v4.runtime.tree.ParseTreeListener; +import org.jspecify.annotations.NullUnmarked; +import jakarta.annotation.Generated; /** * This interface defines a complete listener for a parse tree produced by * {@link }. */ +@NullUnmarked +@Generated("Listener") interface Listener extends ParseTreeListener { ;
- import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.TerminalNode; +import org.jspecify.annotations.NullUnmarked; +import jakarta.annotation.Generated; /** * This class provides an empty implementation of {@link Listener}, * which can be extended to create a listener which only needs to handle a subset * of the available methods. */ -@SuppressWarnings("CheckReturnValue") +@NullUnmarked +@Generated("BaseListener") +@SuppressWarnings({ "CheckReturnValue", "NullAway" }) class BaseListener implements Listener { ;
import org.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.jspecify.annotations.NullUnmarked; +import jakarta.annotation.Generated; /** * This interface defines a complete generic visitor for a parse tree produced @@ -171,6 +184,8 @@ import org.antlr.v4.runtime.tree.ParseTreeVisitor; * @param \ The return type of the visit operation. Use {@link Void} for * operations with no return type. */ +@NullUnmarked +@Generated("Visitor") interface Visitor\ extends ParseTreeVisitor\ { ;
import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; +import org.jspecify.annotations.NullUnmarked; +import jakarta.annotation.Generated; /** * This class provides an empty implementation of {@link Visitor}, @@ -203,7 +220,9 @@ import org.antlr.v4.runtime.tree.AbstractParseTreeVisitor; * @param \ The return type of the visit operation. Use {@link Void} for * operations with no return type. */ -@SuppressWarnings("CheckReturnValue") +@NullUnmarked +@Generated("BaseVisitor") +@SuppressWarnings({ "CheckReturnValue", "NullAway" }) class BaseVisitor\ extends AbstractParseTreeVisitor\ implements Visitor\ { > Parser_(parser, funcs, atn, sempredFuncs, ctor, superClass) ::= << -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) +@NullUnmarked +@Generated("") +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "NullAway"}) class extends { // Customization: Suppress version check // static { RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION); } @@ -895,12 +916,16 @@ import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.atn.*; import org.antlr.v4.runtime.dfa.DFA; import org.antlr.v4.runtime.misc.*; +import org.jspecify.annotations.NullUnmarked; +import jakarta.annotation.Generated; >> Lexer(lexer, atn, actionFuncs, sempredFuncs, superClass) ::= << -@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue"}) +@NullUnmarked +@Generated("") +@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast", "CheckReturnValue", "NullAway"}) class extends { // Customization: Suppress version check // static { RuntimeMetaData.checkVersion("", RuntimeMetaData.VERSION); } diff --git a/pom.xml b/pom.xml index d2592cddbc..99bb310f8f 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-3745-SNAPSHOT pom Spring Data JPA Parent diff --git a/spring-data-envers/pom.xml b/spring-data-envers/pom.xml index 0bdf2c8e7e..ebccb2357b 100755 --- a/spring-data-envers/pom.xml +++ b/spring-data-envers/pom.xml @@ -5,12 +5,12 @@ org.springframework.data spring-data-envers - 4.0.0-SNAPSHOT + 4.0.x-GH-3745-SNAPSHOT org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-3745-SNAPSHOT ../pom.xml diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/package-info.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/package-info.java index 2e79b25c03..ab7c7b3781 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/package-info.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/config/package-info.java @@ -1,5 +1,5 @@ /** * Classes for Envers Repositories configuration support. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.envers.repository.config; diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java index 825a1d1a4e..b152bef044 100755 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/EnversRevisionRepositoryFactoryBean.java @@ -21,6 +21,7 @@ import org.hibernate.envers.DefaultRevisionEntity; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.FactoryBean; import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean; @@ -39,7 +40,7 @@ public class EnversRevisionRepositoryFactoryBean, S, ID, N extends Number & Comparable> extends JpaRepositoryFactoryBean { - private Class revisionEntityClass; + private @Nullable Class revisionEntityClass; /** * Creates a new {@link EnversRevisionRepositoryFactoryBean} for the given repository interface. @@ -81,7 +82,7 @@ private static class RevisionRepositoryFactory revisionEntityClass) { + public RevisionRepositoryFactory(EntityManager entityManager, @Nullable Class revisionEntityClass) { super(entityManager); diff --git a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/package-info.java b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/package-info.java index dd135fdacf..e021667fdb 100644 --- a/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/package-info.java +++ b/spring-data-envers/src/main/java/org/springframework/data/envers/repository/support/package-info.java @@ -1,5 +1,5 @@ /** * Spring Data JPA specific converter infrastructure. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.envers.repository.support; diff --git a/spring-data-jpa-distribution/pom.xml b/spring-data-jpa-distribution/pom.xml index af5244a230..6a445ae8c3 100644 --- a/spring-data-jpa-distribution/pom.xml +++ b/spring-data-jpa-distribution/pom.xml @@ -14,7 +14,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-3745-SNAPSHOT ../pom.xml diff --git a/spring-data-jpa/pom.xml b/spring-data-jpa/pom.xml index abac3419f4..6c9bba4aa6 100644 --- a/spring-data-jpa/pom.xml +++ b/spring-data-jpa/pom.xml @@ -6,7 +6,7 @@ org.springframework.data spring-data-jpa - 4.0.0-SNAPSHOT + 4.0.x-GH-3745-SNAPSHOT Spring Data JPA Spring Data module for JPA repositories. @@ -15,7 +15,7 @@ org.springframework.data spring-data-jpa-parent - 4.0.0-SNAPSHOT + 4.0.x-GH-3745-SNAPSHOT ../pom.xml @@ -420,4 +420,89 @@ + + + nullaway + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + com.querydsl + querydsl-apt + ${querydsl} + jakarta + + + org.hibernate.orm + hibernate-jpamodelgen + ${hibernate} + + + org.hibernate.orm + hibernate-core + ${hibernate} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh} + + + jakarta.persistence + jakarta.persistence-api + ${jakarta-persistence-api} + + + 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-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java index 993f42f3b2..7a41b33735 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/QueryByExamplePredicateBuilder.java @@ -33,13 +33,15 @@ import java.util.Set; import org.springframework.dao.InvalidDataAccessApiUsageException; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.domain.ExampleMatcher.PropertyValueTransformer; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.support.ExampleMatcherAccessor; import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; -import org.springframework.lang.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -78,8 +80,7 @@ public class QueryByExamplePredicateBuilder { * @param example must not be {@literal null}. * @return {@literal null} indicates no {@link Predicate}. */ - @Nullable - public static Predicate getPredicate(Root root, CriteriaBuilder cb, Example example) { + public static @Nullable Predicate getPredicate(Root root, CriteriaBuilder cb, Example example) { return getPredicate(root, cb, example, EscapeCharacter.DEFAULT); } @@ -92,8 +93,7 @@ public static Predicate getPredicate(Root root, CriteriaBuilder cb, Examp * @param escapeCharacter Must not be {@literal null}. * @return {@literal null} indicates no constraints */ - @Nullable - public static Predicate getPredicate(Root root, CriteriaBuilder cb, Example example, + public static @Nullable Predicate getPredicate(Root root, CriteriaBuilder cb, Example example, EscapeCharacter escapeCharacter) { Assert.notNull(root, "Root must not be null"); @@ -238,7 +238,6 @@ private static class PathNode { String name; @Nullable PathNode parent; - List siblings = new ArrayList<>(); @Nullable Object value; PathNode(String edge, @Nullable PathNode parent, @Nullable Object value) { @@ -250,9 +249,7 @@ private static class PathNode { PathNode add(String attribute, @Nullable Object value) { - PathNode node = new PathNode(attribute, this, value); - siblings.add(node); - return node; + return new PathNode(attribute, this, value); } boolean spansCycle() { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/package-info.java index 8b3213871e..1090103cb7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/package-info.java @@ -1,5 +1,5 @@ /** * Spring Data JPA specific converter infrastructure. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.convert; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java index 87aeb9353e..12f29500eb 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/Jsr310JpaConverters.java @@ -27,6 +27,9 @@ import java.util.Date; import org.springframework.data.convert.Jsr310Converters.DateToLocalDateConverter; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.springframework.data.convert.Jsr310Converters.DateToLocalDateTimeConverter; import org.springframework.data.convert.Jsr310Converters.DateToLocalTimeConverter; import org.springframework.data.convert.Jsr310Converters.LocalDateTimeToDateConverter; @@ -36,8 +39,6 @@ import org.springframework.data.convert.Jsr310Converters.ZoneIdToStringConverter; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; /** @@ -53,81 +54,71 @@ public class Jsr310JpaConverters { @Converter(autoApply = true) - public static class LocalDateConverter implements AttributeConverter { + public static class LocalDateConverter implements AttributeConverter<@Nullable LocalDate, @Nullable Date> { - @Nullable @Override - public Date convertToDatabaseColumn(LocalDate date) { + public @Nullable Date convertToDatabaseColumn(@Nullable LocalDate date) { return date == null ? null : LocalDateToDateConverter.INSTANCE.convert(date); } - @Nullable @Override - public LocalDate convertToEntityAttribute(Date date) { + public @Nullable LocalDate convertToEntityAttribute(@Nullable Date date) { return date == null ? null : DateToLocalDateConverter.INSTANCE.convert(date); } } @Converter(autoApply = true) - public static class LocalTimeConverter implements AttributeConverter { + public static class LocalTimeConverter implements AttributeConverter<@Nullable LocalTime, @Nullable Date> { - @Nullable @Override - public Date convertToDatabaseColumn(LocalTime time) { + public @Nullable Date convertToDatabaseColumn(@Nullable LocalTime time) { return time == null ? null : LocalTimeToDateConverter.INSTANCE.convert(time); } - @Nullable @Override - public LocalTime convertToEntityAttribute(Date date) { + public @Nullable LocalTime convertToEntityAttribute(@Nullable Date date) { return date == null ? null : DateToLocalTimeConverter.INSTANCE.convert(date); } } @Converter(autoApply = true) - public static class LocalDateTimeConverter implements AttributeConverter { + public static class LocalDateTimeConverter implements AttributeConverter<@Nullable LocalDateTime, @Nullable Date> { - @Nullable @Override - public Date convertToDatabaseColumn(LocalDateTime date) { + public @Nullable Date convertToDatabaseColumn(@Nullable LocalDateTime date) { return date == null ? null : LocalDateTimeToDateConverter.INSTANCE.convert(date); } - @Nullable @Override - public LocalDateTime convertToEntityAttribute(Date date) { + public @Nullable LocalDateTime convertToEntityAttribute(@Nullable Date date) { return date == null ? null : DateToLocalDateTimeConverter.INSTANCE.convert(date); } } @Converter(autoApply = true) - public static class InstantConverter implements AttributeConverter { + public static class InstantConverter implements AttributeConverter<@Nullable Instant, @Nullable Timestamp> { - @Nullable @Override - public Timestamp convertToDatabaseColumn(Instant instant) { + public @Nullable Timestamp convertToDatabaseColumn(@Nullable Instant instant) { return instant == null ? null : InstantToTimestampConverter.INSTANCE.convert(instant); } - @Nullable @Override - public Instant convertToEntityAttribute(Timestamp timestamp) { + public @Nullable Instant convertToEntityAttribute(@Nullable Timestamp timestamp) { return timestamp == null ? null : TimestampToInstantConverter.INSTANCE.convert(timestamp); } } @Converter(autoApply = true) - public static class ZoneIdConverter implements AttributeConverter { + public static class ZoneIdConverter implements AttributeConverter<@Nullable ZoneId, @Nullable String> { - @Nullable @Override - public String convertToDatabaseColumn(ZoneId zoneId) { + public @Nullable String convertToDatabaseColumn(@Nullable ZoneId zoneId) { return zoneId == null ? null : ZoneIdToStringConverter.INSTANCE.convert(zoneId); } - @Nullable @Override - public ZoneId convertToEntityAttribute(String zoneId) { + public @Nullable ZoneId convertToEntityAttribute(@Nullable String zoneId) { return zoneId == null ? null : StringToZoneIdConverter.INSTANCE.convert(zoneId); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/package-info.java index 0c00cdf218..716d2fe999 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/convert/threeten/package-info.java @@ -1,5 +1,5 @@ /** * Spring Data JPA specific JSR-310 converters. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.convert.threeten; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java index 21637a9be9..0b394d0472 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractAuditable.java @@ -24,8 +24,10 @@ import java.time.ZoneId; import java.util.Optional; +import org.jspecify.annotations.NullUnmarked; import org.springframework.data.domain.Auditable; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * Abstract base class for auditable entities. Stores the audition values in persistent fields. @@ -37,18 +39,19 @@ * @param the type of the auditing type's identifier. */ @MappedSuperclass +@SuppressWarnings("NullAway") // querydsl does not work with jspecify -> 'Did not find type @org.jspecify.annotations.Nullable...' public abstract class AbstractAuditable extends AbstractPersistable implements Auditable { @ManyToOne // - private @Nullable U createdBy; + private U createdBy; - private @Nullable Instant createdDate; + private Instant createdDate; @ManyToOne // - private @Nullable U lastModifiedBy; + private U lastModifiedBy; - private @Nullable Instant lastModifiedDate; + private Instant lastModifiedDate; @Override public Optional getCreatedBy() { @@ -56,7 +59,7 @@ public Optional getCreatedBy() { } @Override - public void setCreatedBy(U createdBy) { + public void setCreatedBy(@Nullable U createdBy) { this.createdBy = createdBy; } @@ -77,7 +80,7 @@ public Optional getLastModifiedBy() { } @Override - public void setLastModifiedBy(U lastModifiedBy) { + public void setLastModifiedBy(@Nullable U lastModifiedBy) { this.lastModifiedBy = lastModifiedBy; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java index 245418357d..fd4029d758 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/AbstractPersistable.java @@ -18,13 +18,14 @@ import java.io.Serializable; import jakarta.persistence.GeneratedValue; + +import org.jspecify.annotations.Nullable; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; import jakarta.persistence.Transient; import org.springframework.data.domain.Persistable; import org.springframework.data.util.ProxyUtils; -import org.springframework.lang.Nullable; /** * Abstract base class for entities. Allows parameterization of id type, chooses auto-generation and implements @@ -37,11 +38,12 @@ * @param the type of the identifier. */ @MappedSuperclass +@SuppressWarnings("NullAway") // querydsl does not work with jspecify -> 'Did not find type @org.jspecify.annotations.Nullable...' public abstract class AbstractPersistable implements Persistable { - @Id @GeneratedValue private @Nullable PK id; - @Nullable + @Id @GeneratedValue private PK id; + @Override public PK getId() { return id; @@ -73,7 +75,7 @@ public String toString() { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (null == obj) { return false; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java index 3337ae5fb1..32278c7ba5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java @@ -25,8 +25,9 @@ import java.util.stream.StreamSupport; import org.springframework.lang.CheckReturnValue; + +import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -150,6 +151,7 @@ default DeleteSpecification or(PredicateSpecification other) { * @param spec can be {@literal null}. * @return guaranteed to be not {@literal null}. */ + @Contract("_ -> new") static DeleteSpecification not(DeleteSpecification spec) { Assert.notNull(spec, "Specification must not be null"); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java index a78307ea29..d4a4c60656 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/JpaSort.java @@ -26,7 +26,10 @@ import java.util.List; import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; +import org.springframework.lang.CheckReturnValue; +import org.springframework.lang.Contract; import org.springframework.util.Assert; /** @@ -102,6 +105,8 @@ public static JpaSort of(Direction direction, Path... paths) { * @param attributes must not be {@literal null}. * @return */ + @Contract("_, _ -> new") + @CheckReturnValue public JpaSort and(@Nullable Direction direction, Attribute... attributes) { Assert.notNull(attributes, "Attributes must not be null"); @@ -116,6 +121,8 @@ public JpaSort and(@Nullable Direction direction, Attribute... attributes) * @param paths must not be {@literal null}. * @return */ + @Contract("_, _ -> new") + @CheckReturnValue public JpaSort and(@Nullable Direction direction, Path... paths) { Assert.notNull(paths, "Paths must not be null"); @@ -136,6 +143,8 @@ public JpaSort and(@Nullable Direction direction, Path... paths) { * @param properties must not be {@literal null} or empty. * @return */ + @Contract("_, _ -> new") + @CheckReturnValue public JpaSort andUnsafe(@Nullable Direction direction, String... properties) { Assert.notEmpty(properties, "Properties must not be empty"); @@ -273,6 +282,8 @@ private Path(List> attributes) { * @param attribute must not be {@literal null}. * @return */ + @Contract("_ -> new") + @CheckReturnValue public , U> Path dot(A attribute) { return new Path<>(add(attribute)); } @@ -283,6 +294,8 @@ public , U> Path dot(A attribute) { * @param attribute must not be {@literal null}. * @return */ + @Contract("_ -> new") + @CheckReturnValue public

, U> Path dot(P attribute) { return new Path<>(add(attribute)); } @@ -370,6 +383,8 @@ public JpaOrder with(NullHandling nullHandling) { * @param properties must not be {@literal null}. * @return */ + @Contract("_ -> new") + @CheckReturnValue public Sort withUnsafe(String... properties) { Assert.notEmpty(properties, "Properties must not be empty"); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java index 5ed394ad1d..5d9bd51065 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java @@ -24,8 +24,9 @@ import java.util.stream.StreamSupport; import org.springframework.lang.CheckReturnValue; + +import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java index b0b44dc0f6..f0c782d7a7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java @@ -26,8 +26,9 @@ import java.util.stream.StreamSupport; import org.springframework.lang.CheckReturnValue; + +import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -231,6 +232,6 @@ static Specification anyOf(Iterable> specifications) { * @return a {@link Predicate}, may be {@literal null}. */ @Nullable - Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder); + Predicate toPredicate(Root root, @Nullable CriteriaQuery query, CriteriaBuilder criteriaBuilder); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java index 0b6e90014c..5600b40f58 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/SpecificationComposition.java @@ -24,7 +24,8 @@ import java.io.Serializable; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; /** * Helper class to support specification compositions. @@ -39,7 +40,8 @@ class SpecificationComposition { interface Combiner extends Serializable { - Predicate combine(CriteriaBuilder builder, @Nullable Predicate lhs, @Nullable Predicate rhs); + @Nullable + Predicate combine(CriteriaBuilder builder, Predicate lhs, Predicate rhs); } static Specification composed(@Nullable Specification lhs, @Nullable Specification rhs, @@ -58,12 +60,13 @@ static Specification composed(@Nullable Specification lhs, @Nullable S }; } - @Nullable - private static Predicate toPredicate(@Nullable Specification specification, Root root, + private static @Nullable Predicate toPredicate(@Nullable Specification specification, Root root, @Nullable CriteriaQuery query, CriteriaBuilder builder) { return specification == null ? null : specification.toPredicate(root, query, builder); } + @Contract("_, _, !null -> new") + @SuppressWarnings("NullAway") static DeleteSpecification composed(@Nullable DeleteSpecification lhs, @Nullable DeleteSpecification rhs, Combiner combiner) { @@ -80,10 +83,10 @@ static DeleteSpecification composed(@Nullable DeleteSpecification lhs, }; } - @Nullable - private static Predicate toPredicate(@Nullable DeleteSpecification specification, Root root, + private static @Nullable Predicate toPredicate(@Nullable DeleteSpecification specification, Root root, @Nullable CriteriaDelete delete, CriteriaBuilder builder) { - return specification == null ? null : specification.toPredicate(root, delete, builder); + + return specification == null || delete == null ? null : specification.toPredicate(root, delete, builder); } static UpdateSpecification composed(@Nullable UpdateSpecification lhs, @Nullable UpdateSpecification rhs, @@ -102,8 +105,8 @@ static UpdateSpecification composed(@Nullable UpdateSpecification lhs, }; } - @Nullable - private static Predicate toPredicate(@Nullable UpdateSpecification specification, Root root, + + private static @Nullable Predicate toPredicate(@Nullable UpdateSpecification specification, Root root, CriteriaUpdate update, CriteriaBuilder builder) { return specification == null ? null : specification.toPredicate(root, update, builder); } @@ -124,8 +127,7 @@ static PredicateSpecification composed(PredicateSpecification lhs, Pre }; } - @Nullable - private static Predicate toPredicate(@Nullable PredicateSpecification specification, Root root, + private static @Nullable Predicate toPredicate(@Nullable PredicateSpecification specification, Root root, CriteriaBuilder builder) { return specification == null ? null : specification.toPredicate(root, builder); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java index 2e9d93b82a..9b4b9f5e4d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java @@ -25,8 +25,9 @@ import java.util.stream.StreamSupport; import org.springframework.lang.CheckReturnValue; + +import org.jspecify.annotations.Nullable; import org.springframework.lang.Contract; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/package-info.java index 46adc19c0a..2ee320ed30 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/package-info.java @@ -1,5 +1,5 @@ /** * JPA specific support classes to implement domain classes. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.domain; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java index 9dc73af957..9c0fe493ca 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/AuditingEntityListener.java @@ -19,10 +19,11 @@ import jakarta.persistence.PreUpdate; import org.springframework.beans.factory.ObjectFactory; + +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.domain.Auditable; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/package-info.java index d14b03294c..b18b18cf18 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/support/package-info.java @@ -1,5 +1,5 @@ /** * Implementation classes for auditing with JPA. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.domain.support; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java index bc5a71c25c..2c0b813370 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaMetamodelMappingContext.java @@ -22,6 +22,8 @@ import java.util.function.Predicate; import org.springframework.data.jpa.provider.PersistenceProvider; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.mapping.PersistentPropertyPaths; import org.springframework.data.mapping.context.AbstractMappingContext; @@ -29,7 +31,6 @@ import org.springframework.data.mapping.model.Property; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -114,8 +115,7 @@ private Metamodels(Set metamodels) { * @param type must not be {@literal null}. * @return */ - @Nullable - public JpaMetamodel getMetamodel(TypeInformation type) { + public @Nullable JpaMetamodel getMetamodel(TypeInformation type) { Metamodel metamodel = getMetamodelFor(type.getType()); @@ -166,8 +166,7 @@ public boolean isMetamodelManagedType(Class type) { * @param type must not be {@literal null}. * @return can be {@literal null}. */ - @Nullable - private Metamodel getMetamodelFor(Class type) { + private @Nullable Metamodel getMetamodelFor(Class type) { for (Metamodel model : metamodels) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java index 761a1600d0..611c82dff5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentEntityImpl.java @@ -17,6 +17,7 @@ import java.util.Comparator; +import org.jspecify.annotations.Nullable; import org.springframework.data.annotation.Version; import org.springframework.data.jpa.provider.ProxyIdAccessor; import org.springframework.data.jpa.util.JpaMetamodel; @@ -63,7 +64,7 @@ public JpaPersistentEntityImpl(TypeInformation information, ProxyIdAccessor p } @Override - protected JpaPersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(JpaPersistentProperty property) { + protected @Nullable JpaPersistentProperty returnPropertyIfBetterIdPropertyCandidateOrNull(JpaPersistentProperty property) { return property.isIdProperty() ? property : null; } @@ -117,7 +118,7 @@ private static class JpaProxyAwareIdentifierAccessor extends IdPropertyIdentifie } @Override - public Object getIdentifier() { + public @Nullable Object getIdentifier() { return proxyIdAccessor.shouldUseAccessorFor(bean) // ? proxyIdAccessor.getIdentifierFrom(bean)// diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java index a63252f8db..38678add2b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java @@ -25,6 +25,8 @@ import java.util.Set; import org.springframework.core.annotation.AnnotationUtils; + +import org.jspecify.annotations.Nullable; import org.springframework.data.annotation.AccessType.Type; import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.mapping.Association; @@ -34,7 +36,6 @@ import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -170,7 +171,7 @@ public boolean isEmbeddable() { } @Override - public TypeInformation getAssociationTargetTypeInformation() { + public @Nullable TypeInformation getAssociationTargetTypeInformation() { if (!isAssociation()) { return null; @@ -193,8 +194,7 @@ public TypeInformation getAssociationTargetTypeInformation() { * * @return */ - @Nullable - private Boolean detectPropertyAccess() { + private @Nullable Boolean detectPropertyAccess() { org.springframework.data.annotation.AccessType accessType = findAnnotation( org.springframework.data.annotation.AccessType.class); @@ -229,8 +229,7 @@ private Boolean detectPropertyAccess() { * * @return */ - @Nullable - private TypeInformation detectAssociationTargetType() { + private @Nullable TypeInformation detectAssociationTargetType() { if (!isAssociation()) { return null; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/package-info.java index 0139f824dc..a16f60cc6f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/mapping/package-info.java @@ -1,5 +1,5 @@ /** * JPA specific support classes for the Spring Data mapping subsystem. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.mapping; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java index 4f85f48a62..037c3c5eb3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/projection/package-info.java @@ -1,5 +1,5 @@ /** * JPA specific support projection support. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.projection; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java index 862cb5a1fb..414d8d5952 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/HibernateUtils.java @@ -17,7 +17,7 @@ import org.hibernate.query.Query; import org.hibernate.query.spi.SqmQuery; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Utility functions to work with Hibernate. Mostly using reflection to make sure common functionality can be executed @@ -41,8 +41,7 @@ private HibernateUtils() {} * @param query * @return */ - @Nullable - public static String getHibernateQuery(Object query) { + public @Nullable static String getHibernateQuery(Object query) { try { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java index f00f4b849d..f6ea036c2b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/JpaClassUtils.java @@ -18,8 +18,9 @@ import jakarta.persistence.EntityManager; import jakarta.persistence.metamodel.Metamodel; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; + +import org.jspecify.annotations.Nullable; import org.springframework.util.ClassUtils; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java index 2b5e0abbeb..14a8db9dcc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java @@ -36,8 +36,9 @@ import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.proxy.HibernateProxy; +import org.jspecify.annotations.Nullable; + import org.springframework.data.util.CloseableIterator; -import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -68,7 +69,7 @@ public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, Quer Collections.singletonList(HIBERNATE_JPA_METAMODEL_TYPE)) { @Override - public String extractQueryString(Query query) { + public @Nullable String extractQueryString(Query query) { return HibernateUtils.getHibernateQuery(query); } @@ -127,9 +128,8 @@ public boolean shouldUseAccessorFor(Object entity) { return false; } - @Nullable @Override - public Object getIdentifierFrom(Object entity) { + public @Nullable Object getIdentifierFrom(Object entity) { return null; } @@ -154,9 +154,8 @@ public String getCommentHintValue(String comment) { */ GENERIC_JPA(Collections.singleton(GENERIC_JPA_ENTITY_MANAGER_INTERFACE), Collections.emptySet()) { - @Nullable @Override - public String extractQueryString(Query query) { + public @Nullable String extractQueryString(Query query) { return null; } @@ -170,20 +169,18 @@ public boolean shouldUseAccessorFor(Object entity) { return false; } - @Nullable @Override - public Object getIdentifierFrom(Object entity) { + public @Nullable Object getIdentifierFrom(Object entity) { return null; } - @Nullable @Override - public String getCommentHintKey() { + public @Nullable String getCommentHintKey() { return null; } }; - private static final Class typedParameterValueClass; + private static final @Nullable Class typedParameterValueClass; static { @@ -334,8 +331,7 @@ public boolean canExtractQuery() { * @return the original value or null. * @since 3.0 */ - @Nullable - public static Object unwrapTypedParameterValue(@Nullable Object value) { + public static @Nullable Object unwrapTypedParameterValue(@Nullable Object value) { return typedParameterValueClass != null && typedParameterValueClass.isInstance(value) // ? null // diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java index d999d7490b..e550368876 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/ProxyIdAccessor.java @@ -15,7 +15,7 @@ */ package org.springframework.data.jpa.provider; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Interface for a persistence provider specific accessor of identifiers held in proxies. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java index aa39144da4..aa6c64abaf 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryComment.java @@ -17,7 +17,7 @@ import jakarta.persistence.Query; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Interface to hide different implementations of query hints that insert comments into a {@link Query}. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java index 6bd6f4bace..b9be1da3bf 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/QueryExtractor.java @@ -17,7 +17,7 @@ import jakarta.persistence.Query; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Interface to hide different implementations to extract the original JPA query string from a {@link Query}. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/package-info.java index 02605bbf3d..87977ed2ce 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/package-info.java @@ -1,5 +1,5 @@ /** * JPA provider-specific utilities. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.provider; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java index 80b3c7071f..7f933b6904 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/JpaSpecificationExecutor.java @@ -22,6 +22,8 @@ import java.util.function.Function; import org.springframework.dao.InvalidDataAccessApiUsageException; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -31,7 +33,6 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.UpdateSpecification; import org.springframework.data.repository.query.FluentQuery; -import org.springframework.lang.Nullable; /** * Interface to allow execution of {@link Specification}s based on the JPA criteria API. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java index 80b67fd896..3b00237d29 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/aot/JpaRuntimeHints.java @@ -22,6 +22,8 @@ import java.util.List; import org.springframework.aot.hint.ExecutableMode; + +import org.jspecify.annotations.Nullable; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.RuntimeHintsRegistrar; @@ -36,7 +38,6 @@ import org.springframework.data.jpa.repository.support.SimpleJpaRepository; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.QuerydslUtils; -import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/package-info.java index c5fb3792d5..a186187b38 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/cdi/package-info.java @@ -1,5 +1,5 @@ /** * CDI support for Spring Data JPA Repositories. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.repository.cdi; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java index 8625119632..53ec098e86 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/AuditingBeanDefinitionParser.java @@ -17,6 +17,7 @@ import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*; +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -45,6 +46,7 @@ public class AuditingBeanDefinitionParser implements BeanDefinitionParser { private final SpringConfiguredBeanDefinitionParser springConfiguredParser = new SpringConfiguredBeanDefinitionParser(); @Override + @SuppressWarnings("NullAway") public BeanDefinition parse(Element element, ParserContext parser) { springConfiguredParser.parse(element, parser); @@ -90,7 +92,7 @@ private static class SpringConfiguredBeanDefinitionParser implements BeanDefinit private static final String BEAN_CONFIGURER_ASPECT_CLASS_NAME = "org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"; @Override - public BeanDefinition parse(Element element, ParserContext parserContext) { + public @Nullable BeanDefinition parse(Element element, ParserContext parserContext) { if (!parserContext.getRegistry().containsBeanDefinition(BEAN_CONFIGURER_ASPECT_BEAN_NAME)) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java index 2bd8cd5ec8..9ccfa3f038 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaMetamodelMappingContextFactoryBean.java @@ -23,6 +23,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.FactoryBean; @@ -32,7 +34,6 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.data.util.StreamUtils; -import org.springframework.lang.Nullable; /** * {@link FactoryBean} to setup {@link JpaMetamodelMappingContext} instances from Spring configuration. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java index 1bcf8073a8..6366a8d5db 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/JpaRepositoryConfigExtension.java @@ -34,6 +34,8 @@ import java.util.Set; import org.springframework.aot.generate.GenerationContext; + +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -56,7 +58,6 @@ import org.springframework.data.repository.config.RepositoryConfigurationSource; import org.springframework.data.repository.config.RepositoryRegistrationAotProcessor; import org.springframework.data.repository.config.XmlRepositoryConfigurationSource; -import org.springframework.lang.Nullable; import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -116,7 +117,9 @@ public void postProcess(BeanDefinitionBuilder builder, RepositoryConfigurationSo Optional transactionManagerRef = source.getAttribute("transactionManagerRef"); builder.addPropertyValue("transactionManager", transactionManagerRef.orElse(DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)); - builder.addPropertyReference("entityManager", entityManagerRefs.get(source)); + if(entityManagerRefs.containsKey(source)) { + builder.addPropertyReference("entityManager", entityManagerRefs.get(source)); + } builder.addPropertyValue(ESCAPE_CHARACTER_PROPERTY, getEscapeCharacter(source).orElse('\\')); builder.addPropertyReference("mappingContext", JPA_MAPPING_CONTEXT_BEAN_NAME); } @@ -228,13 +231,13 @@ private String registerSharedEntityMangerIfNotAlreadyRegistered(BeanDefinitionRe } @Override - protected ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) { + protected @Nullable ClassLoader getConfigurationInspectionClassLoader(ResourceLoader loader) { ClassLoader classLoader = loader.getClassLoader(); return classLoader != null && LazyJvmAgent.isActive(loader.getClassLoader()) - ? new InspectionClassLoader(loader.getClassLoader()) - : loader.getClassLoader(); + ? new InspectionClassLoader(classLoader) + : classLoader; } /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/package-info.java index 6e54455cfe..e2186fa63a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/config/package-info.java @@ -1,5 +1,5 @@ /** * Classes for JPA namespace configuration. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.repository.config; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/package-info.java index 61ce846166..702e410e85 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/package-info.java @@ -1,5 +1,5 @@ /** * Interfaces and annotations for JPA specific repositories. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.repository; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java index 641c16190d..2d75b3970c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractJpaQuery.java @@ -35,6 +35,8 @@ import java.util.stream.Collectors; import org.springframework.beans.BeanUtils; + +import org.jspecify.annotations.Nullable; import org.springframework.core.MethodParameter; import org.springframework.core.convert.converter.Converter; import org.springframework.data.jpa.provider.PersistenceProvider; @@ -55,7 +57,7 @@ import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.Lazy; import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.lang.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -141,9 +143,8 @@ protected JpaMetamodel getMetamodel() { return metamodel; } - @Nullable @Override - public Object execute(Object[] parameters) { + public @Nullable Object execute(Object[] parameters) { return doExecute(getExecution(), parameters); } @@ -152,8 +153,7 @@ public Object execute(Object[] parameters) { * @param values * @return */ - @Nullable - private Object doExecute(JpaQueryExecution execution, Object[] values) { + private @Nullable Object doExecute(JpaQueryExecution execution, Object[] values) { JpaParametersParameterAccessor accessor = obtainParameterAccessor(values); Object result = execution.execute(this, accessor); @@ -193,6 +193,8 @@ protected JpaQueryExecution getExecution() { * @param query * @return */ + @SuppressWarnings("NullAway") + @Contract("_, _ -> param1") protected T applyHints(T query, JpaQueryMethod method) { List hints = method.getHints(); @@ -283,8 +285,7 @@ protected Query createCountQuery(JpaParametersParameterAccessor values) { * @return * @since 2.0.5 */ - @Nullable - protected Class getTypeToRead(ReturnedType returnedType) { + protected @Nullable Class getTypeToRead(ReturnedType returnedType) { if (PersistenceProvider.ECLIPSELINK.equals(provider)) { return null; @@ -525,8 +526,7 @@ public boolean containsValue(Object value) { * @return the value of the backing {@link Tuple} for that key or {@code null}. */ @Override - @Nullable - public Object get(Object key) { + public @Nullable Object get(Object key) { if (!(key instanceof String)) { return null; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java index 9203d08105..055483523a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQuery.java @@ -21,6 +21,8 @@ import java.util.Objects; import org.springframework.data.domain.Pageable; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.expression.ValueEvaluationContextProvider; import org.springframework.data.jpa.repository.QueryRewriter; @@ -28,7 +30,6 @@ import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.util.Lazy; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ConcurrentLruCache; import org.springframework.util.StringUtils; @@ -234,7 +235,7 @@ public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedTyp static class UnsortedCachingQuerySortRewriter implements QuerySortRewriter { - private volatile String cachedQueryString; + private volatile @Nullable String cachedQueryString; public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) { @@ -260,7 +261,7 @@ class CachingQuerySortRewriter implements QuerySortRewriter { private final ConcurrentLruCache queryCache = new ConcurrentLruCache<>(16, AbstractStringBasedJpaQuery.this::applySorting); - private volatile String cachedQueryString; + private volatile @Nullable String cachedQueryString; @Override public String getSorted(DeclaredQuery query, Sort sort, ReturnedType returnedType) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java index ab3b51b7b3..e731d9f3bc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/BadJpqlGrammarException.java @@ -15,8 +15,9 @@ */ package org.springframework.data.jpa.repository.query; +import org.jspecify.annotations.Nullable; + import org.springframework.dao.InvalidDataAccessResourceUsageException; -import org.springframework.lang.Nullable; /** * An exception thrown if the JPQL query is invalid. @@ -29,12 +30,12 @@ public class BadJpqlGrammarException extends InvalidDataAccessResourceUsageExcep private final String jpql; - public BadJpqlGrammarException(String message, String jpql, @Nullable Throwable cause) { + public BadJpqlGrammarException(@Nullable String message, String jpql, @Nullable Throwable cause) { this(message, jpql, "JPQL", cause); } - BadJpqlGrammarException(String message, String grammar, String jpql, @Nullable Throwable cause) { - super(message + "; Bad " + grammar + " grammar [" + jpql + "]", cause); + BadJpqlGrammarException(@Nullable String message, String grammar, String jpql, @Nullable Throwable cause) { + super("%sBad %s grammar [%s]".formatted(message != null ? message + "; " : "", grammar, jpql), cause); this.jpql = jpql; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java index 70bc5c829b..0e6f760ed3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DeclaredQuery.java @@ -17,9 +17,10 @@ import java.util.List; -import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; +import org.jspecify.annotations.Nullable; + /** * A wrapper for a String representation of a query offering information about the query. * diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java index 8dba004f4b..1fe6236621 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java @@ -18,7 +18,8 @@ import java.util.Set; import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * The implementation of the Regex-based {@link QueryEnhancer} using {@link QueryUtils}. @@ -30,7 +31,7 @@ public class DefaultQueryEnhancer implements QueryEnhancer { private final DeclaredQuery query; private final boolean hasConstructorExpression; - private final String alias; + private final @Nullable String alias; private final String projection; private final Set joinAliases; @@ -68,7 +69,7 @@ public boolean hasConstructorExpression() { } @Override - public String detectAlias() { + public @Nullable String detectAlias() { return this.alias; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java index 4593697a4d..d57a83ab99 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DtoProjectionTransformerDelegate.java @@ -57,7 +57,7 @@ public QueryTokenStream transformSelectionList(QueryTokenStream selectionList) { builder.appendInline(QueryTokenStream.concat(returnedType.getInputProperties(), property -> { QueryRenderer.QueryRendererBuilder prop = QueryRenderer.builder(); - prop.append(QueryTokens.token(selectionList.getFirst().value())); + prop.append(QueryTokens.token(selectionList.getRequiredFirst().value())); prop.append(QueryTokens.TOKEN_DOT); prop.append(QueryTokens.token(property)); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java index 850c0919a3..95693e8808 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyDeclaredQuery.java @@ -18,7 +18,7 @@ import java.util.Collections; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * NULL-Object pattern implementation for {@link DeclaredQuery}. @@ -44,7 +44,7 @@ public String getQueryString() { } @Override - public String getAlias() { + public @Nullable String getAlias() { return null; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java index db498281fc..1b05738d5e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EmptyQueryTokenStream.java @@ -18,6 +18,8 @@ import java.util.Collections; import java.util.Iterator; +import org.jspecify.annotations.Nullable; + /** * Empty QueryTokenStream. * @@ -31,12 +33,12 @@ class EmptyQueryTokenStream implements QueryTokenStream { private EmptyQueryTokenStream() {} @Override - public QueryToken getFirst() { + public @Nullable QueryToken getFirst() { return null; } @Override - public QueryToken getLast() { + public @Nullable QueryToken getLast() { return null; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java index 81b5e9a8f6..2d8e27c167 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlCountQueryTransformer.java @@ -18,8 +18,9 @@ import static org.springframework.data.jpa.repository.query.QueryTokens.*; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream; -import org.springframework.lang.Nullable; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed EQL query into a @@ -30,7 +31,7 @@ * @author Christoph Strobl * @since 3.4 */ -@SuppressWarnings("ConstantValue") +@SuppressWarnings({ "ConstantValue", "NullAway" }) class EqlCountQueryTransformer extends EqlQueryRenderer { private final @Nullable String countProjection; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java index 0f006f2388..fa7fa5ec8e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlQueryIntrospector.java @@ -22,7 +22,8 @@ import java.util.List; import org.springframework.data.jpa.repository.query.EqlParser.Range_variable_declarationContext; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * {@link ParsedQueryIntrospector} for EQL queries. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java index 50a3019acc..30e9106d22 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EqlSortedQueryTransformer.java @@ -20,9 +20,10 @@ import java.util.List; import org.springframework.data.domain.Sort; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java index d73680ff62..d6ef5c321b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/EscapeCharacter.java @@ -19,7 +19,8 @@ import java.util.List; import java.util.stream.Stream; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; +import org.springframework.lang.Contract; /** * A value type encapsulating an escape character for LIKE queries and the actually usage of it in escaping @@ -49,8 +50,8 @@ public static EscapeCharacter of(char escapeCharacter) { * @param value may be {@literal null}. * @return */ - @Nullable - public String escape(@Nullable String value) { + @Contract("null -> null") + public @Nullable String escape(@Nullable String value) { return value == null // ? null // diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java index 3007f494ca..a414b52005 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java @@ -18,6 +18,8 @@ import java.util.Objects; import java.util.regex.Pattern; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; import org.springframework.data.expression.ValueEvaluationContext; import org.springframework.data.expression.ValueExpression; import org.springframework.data.expression.ValueExpressionParser; @@ -52,6 +54,12 @@ class ExpressionBasedStringQuery extends StringQuery { private static final String ENTITY_NAME_VARIABLE = "#" + ENTITY_NAME; private static final String ENTITY_NAME_VARIABLE_EXPRESSION = "#{" + ENTITY_NAME_VARIABLE; + private static final Environment DEFAULT_ENVIRONMENT; + + static { + DEFAULT_ENVIRONMENT = new StandardEnvironment(); + } + /** * Creates a new {@link ExpressionBasedStringQuery} for the given query and {@link EntityMetadata}. * @@ -102,7 +110,8 @@ private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEnti ValueExpression expr = parser.parse(query); - String result = Objects.toString(expr.evaluate(ValueEvaluationContext.of(null, evalContext))); + String result = Objects.toString( + expr.evaluate(ValueEvaluationContext.of(DEFAULT_ENVIRONMENT, evalContext))); if (result == null) { return query; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java index 6020c50fa1..af1c4fa0ec 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateJpaParametersParameterAccessor.java @@ -21,10 +21,11 @@ import org.hibernate.query.TypedParameterValue; import org.hibernate.type.BasicType; import org.hibernate.type.BasicTypeRegistry; +import org.jspecify.annotations.Nullable; + import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.lang.Nullable; /** * {@link org.springframework.data.repository.query.ParameterAccessor} based on an {@link Parameters} instance. In @@ -62,9 +63,8 @@ class HibernateJpaParametersParameterAccessor extends JpaParametersParameterAcce } @Override - @Nullable @SuppressWarnings("unchecked") - public Object getValue(Parameter parameter) { + public @Nullable Object getValue(Parameter parameter) { Object value = super.getValue(parameter.getIndex()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateQueryInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateQueryInformation.java index 755dade914..405fa08660 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateQueryInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HibernateQueryInformation.java @@ -17,7 +17,7 @@ import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Hibernate-specific query details capturing common table expression details. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java index c5467460fc..eed9c6115c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlCountQueryTransformer.java @@ -18,9 +18,10 @@ import static org.springframework.data.jpa.repository.query.QueryTokens.*; import org.springframework.data.jpa.repository.query.HqlParser.SelectClauseContext; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream; -import org.springframework.lang.Nullable; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed HQL query into a diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlOrderExpressionVisitor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlOrderExpressionVisitor.java index e5915f19e3..1d73d078f3 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlOrderExpressionVisitor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlOrderExpressionVisitor.java @@ -45,6 +45,7 @@ import org.antlr.v4.runtime.tree.TerminalNode; import org.hibernate.query.criteria.HibernateCriteriaBuilder; +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.JpaSort; import org.springframework.data.mapping.PropertyPath; @@ -58,7 +59,7 @@ * @author Mark Paluch * @since 4.0 */ -@SuppressWarnings({ "unchecked", "rawtypes", "ConstantValue" }) +@SuppressWarnings({ "unchecked", "rawtypes", "ConstantValue", "NullAway" }) class HqlOrderExpressionVisitor extends HqlBaseVisitor> { private static final DateTimeFormatter DATE_TIME = new DateTimeFormatterBuilder().parseCaseInsensitive() @@ -119,7 +120,7 @@ Expression createCriteriaExpression(Sort.Order jpaOrder) { } @Override - public Expression visitSortExpression(HqlParser.SortExpressionContext ctx) { + public @Nullable Expression visitSortExpression(HqlParser.SortExpressionContext ctx) { if (ctx.identifier() != null) { HqlParser.IdentifierContext identifier = ctx.identifier(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java index 5ccc7b3556..d3ba055bb9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlQueryIntrospector.java @@ -22,7 +22,8 @@ import java.util.List; import org.springframework.data.jpa.repository.query.HqlParser.VariableContext; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * {@link ParsedQueryIntrospector} for HQL queries. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java index 202a2107b4..9a3220dd1f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/HqlSortedQueryTransformer.java @@ -20,9 +20,10 @@ import java.util.List; import org.springframework.data.domain.Sort; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java index 99aec3ddf8..5600043375 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java @@ -38,6 +38,7 @@ import net.sf.jsqlparser.statement.select.SetOperationList; import net.sf.jsqlparser.statement.select.Values; import net.sf.jsqlparser.statement.update.Update; +import org.jspecify.annotations.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -50,9 +51,9 @@ import java.util.StringJoiner; import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.SerializationUtils; import org.springframework.util.StringUtils; @@ -76,7 +77,7 @@ public class JSqlParserQueryEnhancer implements QueryEnhancer { private final String projection; private final Set joinAliases; private final Set selectAliases; - private final byte[] serialized; + private final byte @Nullable[] serialized; /** * @param query the query we want to enhance. Must not be {@literal null}. @@ -92,6 +93,8 @@ public JSqlParserQueryEnhancer(DeclaredQuery query) { this.projection = detectProjection(this.statement); this.selectAliases = Collections.unmodifiableSet(getSelectionAliases(this.statement)); this.joinAliases = Collections.unmodifiableSet(getJoinAliases(this.statement)); + byte[] tmp = SerializationUtils.serialize(this.statement); +// this.serialized = tmp != null ? tmp : new byte[0]; this.serialized = SerializationUtils.serialize(this.statement); } @@ -131,8 +134,7 @@ static T parseStatement(String sql, Class classOfT) { * * @return Might return {@literal null}. */ - @Nullable - private static String detectAlias(ParsedType parsedType, Statement statement) { + private static @Nullable String detectAlias(ParsedType parsedType, Statement statement) { if (ParsedType.MERGE.equals(parsedType)) { @@ -273,7 +275,7 @@ public boolean hasConstructorExpression() { } @Override - public String detectAlias() { + public @Nullable String detectAlias() { return this.primaryAlias; } @@ -319,17 +321,21 @@ private String doApplySorting(Sort sort, @Nullable String alias) { return queryString; } - return applySorting((Select) deserialize(this.serialized), sort, alias); + return applySorting(deserializeRequired(this.serialized, Select.class), sort, alias); } - private String applySorting(Select selectStatement, Sort sort, @Nullable String alias) { + private String applySorting(@Nullable Select selectStatement, Sort sort, @Nullable String alias) { if (selectStatement instanceof SetOperationList setOperationList) { return applySortingToSetOperationList(setOperationList, sort); } if (!(selectStatement instanceof PlainSelect selectBody)) { - return selectStatement.toString(); + if(selectStatement != null) { + return selectStatement.toString(); + } else { + throw new IllegalArgumentException("Select must not be null"); + } } List orderByElements = new ArrayList<>(16); @@ -363,10 +369,10 @@ public String createCountQueryFor(@Nullable String countProjection) { return this.query.getQueryString(); } - return createCountQueryFor(this.query, selectBody, countProjection, primaryAlias); + return createCountQueryFor(selectBody, countProjection, primaryAlias); } - private static String createCountQueryFor(DeclaredQuery query, PlainSelect selectBody, + private static String createCountQueryFor(PlainSelect selectBody, @Nullable String countProjection, @Nullable String primaryAlias) { // remove order by @@ -520,7 +526,10 @@ enum ParsedType { * @param bytes a serialized object * @return the result of deserializing the bytes */ - private static Object deserialize(byte[] bytes) { + private static @Nullable Object deserialize(byte @Nullable[] bytes) { + if(ObjectUtils.isEmpty(bytes)) { + return null; + } try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) { return ois.readObject(); } catch (IOException ex) { @@ -530,4 +539,12 @@ private static Object deserialize(byte[] bytes) { } } + private static T deserializeRequired(byte @Nullable[] bytes, Class type) { + Object deserialize = deserialize(bytes); + if(deserialize != null) { + return type.cast(deserialize); + } + throw new IllegalStateException("Failed to deserialize object type"); + } + } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java index 4530aac26b..ff1ce50b54 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Jpa21Utils.java @@ -26,8 +26,9 @@ import java.util.List; import org.springframework.data.jpa.repository.support.MutableQueryHints; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.support.QueryHints; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -188,8 +189,7 @@ private static boolean exists(String attributeNodeName, List> n * @param parent * @return {@literal null} if not found. */ - @Nullable - private static AttributeNode findAttributeNode(String attributeNodeName, EntityGraph entityGraph, + private static @Nullable AttributeNode findAttributeNode(String attributeNodeName, EntityGraph entityGraph, @Nullable Subgraph parent) { return findAttributeNode(attributeNodeName, parent != null ? parent.getAttributeNodes() : entityGraph.getAttributeNodes()); @@ -203,8 +203,7 @@ private static AttributeNode findAttributeNode(String attributeNodeName, Enti * @param nodes * @return {@literal null} if not found. */ - @Nullable - private static AttributeNode findAttributeNode(String attributeNodeName, List> nodes) { + private static @Nullable AttributeNode findAttributeNode(String attributeNodeName, List> nodes) { for (AttributeNode node : nodes) { if (ObjectUtils.nullSafeEquals(node.getAttributeName(), attributeNodeName)) { @@ -223,8 +222,7 @@ private static AttributeNode findAttributeNode(String attributeNodeName, List * @param node * @return */ - @Nullable - private static Subgraph getSubgraph(AttributeNode node) { + private static @Nullable Subgraph getSubgraph(AttributeNode node) { return node.getSubgraphs().isEmpty() ? null : node.getSubgraphs().values().iterator().next(); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java index 3a7d9421b8..cf85c65d47 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaEntityGraph.java @@ -18,8 +18,9 @@ import java.util.List; import org.springframework.data.jpa.repository.EntityGraph; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -56,7 +57,7 @@ public JpaEntityGraph(EntityGraph entityGraph, String nameFallback) { * @param attributePaths may be {@literal null}. * @since 1.9 */ - public JpaEntityGraph(String name, EntityGraphType type, @Nullable String[] attributePaths) { + public JpaEntityGraph(String name, EntityGraphType type, String @Nullable[] attributePaths) { Assert.hasText(name, "The name of an EntityGraph must not be null or empty"); Assert.notNull(type, "FetchGraphType must not be null"); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java index ce0d5a5a1f..1acb62d768 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java @@ -25,12 +25,13 @@ import java.util.concurrent.atomic.AtomicInteger; import org.springframework.data.domain.KeysetScrollPosition; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.data.jpa.repository.support.JpqlQueryTemplates; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.lang.Nullable; /** * Extension to {@link JpaQueryCreator} to create queries considering {@link KeysetScrollPosition keyset scrolling}. @@ -68,7 +69,7 @@ public List getBindings() { } @Override - protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) { + protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(JpqlQueryBuilder.@Nullable Predicate predicate, Sort sort) { KeysetScrollSpecification keysetSpec = new KeysetScrollSpecification<>(scrollPosition, sort, entityInformation); @@ -90,9 +91,9 @@ protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuil return query; } - @Nullable - private static JpqlQueryBuilder.Predicate getPredicate(@Nullable JpqlQueryBuilder.Predicate predicate, - @Nullable JpqlQueryBuilder.Predicate keysetPredicate) { + + private static JpqlQueryBuilder.@Nullable Predicate getPredicate(JpqlQueryBuilder.@Nullable Predicate predicate, + JpqlQueryBuilder.@Nullable Predicate keysetPredicate) { if (keysetPredicate != null) { if (predicate != null) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java index b7c49ffc64..f94f4ba8c6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParameters.java @@ -23,13 +23,14 @@ import java.util.function.Function; import org.springframework.core.MethodParameter; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.Temporal; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; /** * Custom extension of {@link Parameters} discovering additional query parameter annotations. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java index 2093e0d3d6..9d22c7bbb4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaParametersParameterAccessor.java @@ -15,11 +15,12 @@ */ package org.springframework.data.jpa.repository.query; +import org.jspecify.annotations.Nullable; + import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.ParametersParameterAccessor; -import org.springframework.lang.Nullable; /** * {@link org.springframework.data.repository.query.ParameterAccessor} based on an {@link Parameters} instance. It also @@ -48,8 +49,7 @@ public JpaParameters getParameters() { return parameters; } - @Nullable - public T getValue(Parameter parameter) { + public @Nullable T getValue(Parameter parameter) { return super.getValue(parameter.getIndex()); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java index 12073a595d..9a828a9b3f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java @@ -34,6 +34,8 @@ import java.util.stream.Collectors; import org.springframework.data.domain.Sort; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.domain.JpaSort; import org.springframework.data.jpa.repository.query.JpqlQueryBuilder.ParameterPlaceholder; import org.springframework.data.jpa.repository.query.ParameterBinding.PartTreeParameterBinding; @@ -45,7 +47,6 @@ import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -142,13 +143,13 @@ protected JpqlQueryBuilder.Predicate or(JpqlQueryBuilder.Predicate base, JpqlQue * it the current {@link JpqlQueryBuilder.Predicate}. */ @Override - protected final String complete(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) { + protected final String complete(JpqlQueryBuilder.@Nullable Predicate predicate, Sort sort) { JpqlQueryBuilder.AbstractJpqlQuery query = createQuery(predicate, sort); return query.render(); } - protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) { + protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(JpqlQueryBuilder.@Nullable Predicate predicate, Sort sort) { JpqlQueryBuilder.Select query = buildQuery(sort); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java index 005c707e55..1cbfd6beb8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryEnhancer.java @@ -29,9 +29,10 @@ import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.tree.ParseTreeVisitor; +import org.jspecify.annotations.Nullable; + import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -175,7 +176,7 @@ public boolean hasConstructorExpression() { * already find the alias when generating sorted and count queries, this is mainly to serve test cases. */ @Override - public String detectAlias() { + public @Nullable String detectAlias() { return this.queryInformation.getAlias(); } @@ -232,7 +233,7 @@ public String rewrite(QueryRewriteInformation rewriteInformation) { * @return */ @Override - public String applySorting(Sort sort, String alias) { + public String applySorting(Sort sort, @Nullable String alias) { return applySorting(sort); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java index 1fca772ed7..961123b94e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryExecution.java @@ -26,6 +26,8 @@ import java.util.Optional; import org.springframework.core.convert.ConversionService; + +import org.jspecify.annotations.Nullable; import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -39,7 +41,6 @@ import org.springframework.data.support.PageableExecutionUtils; import org.springframework.data.util.CloseableIterator; import org.springframework.data.util.StreamUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -80,8 +81,7 @@ public abstract class JpaQueryExecution { * @param accessor must not be {@literal null}. * @return */ - @Nullable - public Object execute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { + public @Nullable Object execute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { Assert.notNull(query, "AbstractJpaQuery must not be null"); Assert.notNull(accessor, "JpaParametersParameterAccessor must not be null"); @@ -110,8 +110,7 @@ public Object execute(AbstractJpaQuery query, JpaParametersParameterAccessor acc * @param query must not be {@literal null}. * @param accessor must not be {@literal null}. */ - @Nullable - protected abstract Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor); + protected abstract @Nullable Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor); /** * Executes the query to return a simple collection of entities. @@ -142,7 +141,7 @@ static class ScrollExecution extends JpaQueryExecution { } @Override - @SuppressWarnings("unchecked") + @SuppressWarnings("NullAway") protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { ScrollPosition scrollPosition = accessor.getScrollPosition(); @@ -212,7 +211,7 @@ private long count(AbstractJpaQuery repositoryQuery, JpaParametersParameterAcces static class SingleEntityExecution extends JpaQueryExecution { @Override - protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { + protected @Nullable Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { return query.createQuery(accessor).getSingleResultOrNull(); } @@ -327,7 +326,7 @@ static class ProcedureExecution extends JpaQueryExecution { } @Override - protected Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAccessor accessor) { + protected @Nullable Object doExecute(AbstractJpaQuery jpaQuery, JpaParametersParameterAccessor accessor) { Assert.isInstanceOf(StoredProcedureJpaQuery.class, jpaQuery); @@ -372,10 +371,10 @@ static class StreamExecution extends JpaQueryExecution { private static final String NO_SURROUNDING_TRANSACTION = "You're trying to execute a streaming query method without a surrounding transaction that keeps the connection open so that the Stream can actually be consumed; Make sure the code consuming the stream uses @Transactional or any other way of declaring a (read-only) transaction"; - private static final Method streamMethod = ReflectionUtils.findMethod(Query.class, "getResultStream"); + private static final @Nullable Method streamMethod = ReflectionUtils.findMethod(Query.class, "getResultStream"); @Override - protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { + protected @Nullable Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) { if (!SurroundingTransactionDetectorMethodInterceptor.INSTANCE.isSurroundingTransactionActive()) { throw new InvalidDataAccessApiUsageException(NO_SURROUNDING_TRANSACTION); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java index 82babfb9e4..384330af14 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryFactory.java @@ -18,10 +18,11 @@ import jakarta.persistence.EntityManager; import org.springframework.data.jpa.repository.QueryRewriter; + +import org.jspecify.annotations.Nullable; import org.springframework.data.repository.query.QueryCreationException; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; /** * Factory to create the appropriate {@link RepositoryQuery} for a {@link JpaQueryMethod}. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java index 6302702a19..db4c492eb7 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java @@ -21,6 +21,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.QueryRewriter; @@ -32,7 +33,6 @@ import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -186,8 +186,7 @@ protected RepositoryQuery resolveQuery(JpaQueryMethod method, QueryRewriter quer : NO_QUERY; } - @Nullable - private String getCountQuery(JpaQueryMethod method, NamedQueries namedQueries, EntityManager em) { + private @Nullable String getCountQuery(JpaQueryMethod method, NamedQueries namedQueries, EntityManager em) { if (StringUtils.hasText(method.getCountQuery())) { return method.getCountQuery(); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java index 97b6390d22..d6f2fc3d89 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryMethod.java @@ -27,6 +27,8 @@ import java.util.Set; import org.springframework.core.annotation.AnnotatedElementUtils; + +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.jpa.repository.EntityGraph; @@ -44,7 +46,6 @@ import org.springframework.data.repository.util.QueryExecutionConverters; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -148,7 +149,6 @@ private static Class potentiallyUnwrapReturnTypeFor(RepositoryMetadata metada } @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) public JpaEntityMetadata getEntityInformation() { return this.entityMetadata.get(); } @@ -285,8 +285,7 @@ public org.springframework.data.jpa.repository.query.Meta getQueryMetaAttributes * * @return */ - @Nullable - public String getAnnotatedQuery() { + public @Nullable String getAnnotatedQuery() { String query = getAnnotationValue("value", String.class); return StringUtils.hasText(query) ? query : null; @@ -324,8 +323,7 @@ public String getRequiredAnnotatedQuery() throws IllegalStateException { * * @return */ - @Nullable - public String getCountQuery() { + public @Nullable String getCountQuery() { String countQuery = getAnnotationValue("countQuery", String.class); return StringUtils.hasText(countQuery) ? countQuery : null; @@ -402,7 +400,7 @@ private T getAnnotationValue(String attribute, Class type) { return getMergedOrDefaultAnnotationValue(attribute, Query.class, type); } - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({ "rawtypes", "unchecked", "NullAway" }) private T getMergedOrDefaultAnnotationValue(String attribute, Class annotationType, Class targetType) { Annotation annotation = AnnotatedElementUtils.findMergedAnnotation(method, annotationType); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java index 6cb8f11104..79a31e556f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryTransformerSupport.java @@ -9,10 +9,11 @@ import java.util.regex.Pattern; import org.springframework.dao.InvalidDataAccessApiUsageException; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.NullHandling; import org.springframework.data.jpa.domain.JpaSort; -import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java index 06382e5e9b..9ec1c5f1e5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaResultConverters.java @@ -22,9 +22,10 @@ import java.sql.SQLException; import org.springframework.core.convert.converter.Converter; + +import org.jspecify.annotations.Nullable; import org.springframework.dao.CleanupFailureDataAccessException; import org.springframework.dao.DataRetrievalFailureException; -import org.springframework.lang.Nullable; import org.springframework.util.StreamUtils; /** @@ -50,9 +51,9 @@ enum BlobToByteArrayConverter implements Converter { INSTANCE; - @Nullable + @Override - public byte[] convert(@Nullable Blob source) { + public byte @Nullable[] convert(@Nullable Blob source) { if (source == null) { return null; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java index 289e6a5b64..480ec3426d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlCountQueryTransformer.java @@ -18,8 +18,10 @@ import static org.springframework.data.jpa.repository.query.QueryTokens.*; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.QueryTransformers.CountSelectionTokenStream; -import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; /** * An ANTLR {@link org.antlr.v4.runtime.tree.ParseTreeVisitor} that transforms a parsed JPQL query into a @@ -80,8 +82,10 @@ public QueryRendererBuilder visitSelect_clause(JpqlParser.Select_clauseContext c if (usesDistinct) { nested.append(QueryTokens.expression(ctx.DISTINCT())); nested.append(getDistinctCountSelection(QueryTokenStream.concat(ctx.select_item(), this::visit, TOKEN_COMMA))); - } else { + } else if(StringUtils.hasText(primaryFromAlias)) { nested.append(QueryTokens.token(primaryFromAlias)); + } else { + throw new IllegalStateException("No primary alias present"); } } else { builder.append(QueryTokens.token(countProjection)); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java index e99e825338..45c804e124 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java @@ -29,9 +29,12 @@ import java.util.function.Supplier; import org.springframework.data.domain.Sort; + +import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.util.Predicates; -import org.springframework.lang.Nullable; +import org.springframework.lang.CheckReturnValue; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; @@ -364,8 +367,7 @@ public Predicate neq(Expression value) { }; } - @Nullable - public static Predicate and(List intermediate) { + public static @Nullable Predicate and(List intermediate) { Predicate predicate = null; @@ -381,8 +383,7 @@ public static Predicate and(List intermediate) { return predicate; } - @Nullable - public static Predicate or(List intermediate) { + public static @Nullable Predicate or(List intermediate) { Predicate predicate = null; @@ -406,16 +407,19 @@ public interface SelectStep { /** * Apply {@code DISTINCT}. */ + @CheckReturnValue SelectStep distinct(); /** * Select the entity. */ + @CheckReturnValue Select entity(); /** * Select the count. */ + @CheckReturnValue Select count(); /** @@ -426,6 +430,7 @@ public interface SelectStep { * @param paths * @return */ + @CheckReturnValue default Select instantiate(Class resultType, Collection paths) { return instantiate(resultType.getName(), paths); } @@ -437,6 +442,7 @@ default Select instantiate(Class resultType, Collection paths); /** @@ -445,6 +451,7 @@ default Select instantiate(Class resultType, Collection paths); /** @@ -453,6 +460,7 @@ default Select instantiate(Class resultType, Collection new") + @CheckReturnValue default Predicate or(Predicate other) { return new OrPredicate(this, other); } @@ -637,6 +647,8 @@ default Predicate or(Predicate other) { * @param other * @return a composed predicate combining this and {@code other} using the AND operator. */ + @Contract("_ -> new") + @CheckReturnValue default Predicate and(Predicate other) { // don't like the structuring of this and the nest() thing return new AndPredicate(this, other); } @@ -646,6 +658,8 @@ default Predicate and(Predicate other) { // don't like the structuring of this a * * @return a nested variant of this predicate. */ + @Contract("-> new") + @CheckReturnValue default Predicate nest() { return new NestedPredicate(this); } @@ -701,6 +715,7 @@ private Select(Selection selection, Entity entity) { * @param join * @return */ + @Contract("_ -> this") public Select join(Join join) { if (join.source() instanceof Join parent) { @@ -717,6 +732,7 @@ public Select join(Join join) { * @param orderBy * @return */ + @Contract("_ -> this") public Select orderBy(Expression orderBy) { this.orderBy.add(orderBy); return this; @@ -784,8 +800,7 @@ public AbstractJpqlQuery where(Predicate predicate) { return this; } - @Nullable - public Predicate getWhere() { + public @Nullable Predicate getWhere() { return where; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java index 48f6fef46b..43f6f7fd1f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryIntrospector.java @@ -21,7 +21,7 @@ import java.util.Collections; import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * {@link ParsedQueryIntrospector} for JPQL queries. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java index 0b6a610614..654fb7df88 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlSortedQueryTransformer.java @@ -20,9 +20,10 @@ import java.util.List; import org.springframework.data.domain.Sort; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.QueryRenderer.QueryRendererBuilder; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlUtils.java index 354ce28aad..f3e20a1d6c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlUtils.java @@ -26,7 +26,8 @@ import java.util.Objects; import org.springframework.data.mapping.PropertyPath; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** @@ -34,12 +35,12 @@ */ class JpqlUtils { - static JpqlQueryBuilder.PathExpression toExpressionRecursively(Metamodel metamodel, JpqlQueryBuilder.Origin source, + static JpqlQueryBuilder.PathExpression toExpressionRecursively(@Nullable Metamodel metamodel, JpqlQueryBuilder.Origin source, Bindable from, PropertyPath property) { return toExpressionRecursively(metamodel, source, from, property, false); } - static JpqlQueryBuilder.PathExpression toExpressionRecursively(Metamodel metamodel, JpqlQueryBuilder.Origin source, + static JpqlQueryBuilder.PathExpression toExpressionRecursively(@Nullable Metamodel metamodel, JpqlQueryBuilder.Origin source, Bindable from, PropertyPath property, boolean isForSelection) { return toExpressionRecursively(metamodel, source, from, property, isForSelection, false); } @@ -53,7 +54,7 @@ static JpqlQueryBuilder.PathExpression toExpressionRecursively(Metamodel metamod * @param hasRequiredOuterJoin has a parent already required an outer join? * @return the expression */ - static JpqlQueryBuilder.PathExpression toExpressionRecursively(Metamodel metamodel, JpqlQueryBuilder.Origin source, + static JpqlQueryBuilder.PathExpression toExpressionRecursively(@Nullable Metamodel metamodel, JpqlQueryBuilder.Origin source, Bindable from, PropertyPath property, boolean isForSelection, boolean hasRequiredOuterJoin) { String segment = property.getSegment(); @@ -80,6 +81,10 @@ static JpqlQueryBuilder.PathExpression toExpressionRecursively(Metamodel metamod ManagedType managedTypeForModel = QueryUtils.getManagedTypeForModel(from); Attribute nextAttribute = getModelForPath(metamodel, property, managedTypeForModel, from); + if(nextAttribute == null) { + throw new IllegalStateException("Binding property is null"); + } + return toExpressionRecursively(metamodel, joinSource, (Bindable) nextAttribute, nextProperty, isForSelection, requiresOuterJoin); } @@ -96,7 +101,7 @@ static JpqlQueryBuilder.PathExpression toExpressionRecursively(Metamodel metamod * @param hasRequiredOuterJoin * @return */ - static boolean requiresOuterJoin(Metamodel metamodel, Bindable bindable, PropertyPath propertyPath, + static boolean requiresOuterJoin(@Nullable Metamodel metamodel, Bindable bindable, PropertyPath propertyPath, boolean isForSelection, boolean hasRequiredOuterJoin) { ManagedType managedType = QueryUtils.getManagedTypeForModel(bindable); @@ -127,8 +132,7 @@ static boolean requiresOuterJoin(Metamodel metamodel, Bindable bindable, Prop return hasRequiredOuterJoin || QueryUtils.getAnnotationProperty(attribute, "optional", true); } - @Nullable - private static Attribute getModelForPath(Metamodel metamodel, PropertyPath path, + private static @Nullable Attribute getModelForPath(@Nullable Metamodel metamodel, PropertyPath path, @Nullable ManagedType managedType, Bindable fallback) { String segment = path.getSegment(); @@ -140,11 +144,14 @@ static boolean requiresOuterJoin(Metamodel metamodel, Bindable bindable, Prop } } - Class fallbackType = fallback.getBindableJavaType(); - try { - return metamodel.managedType(fallbackType).getAttribute(segment); - } catch (IllegalArgumentException e) { + if(metamodel != null) { + Class fallbackType = fallback.getBindableJavaType(); + try { + return metamodel.managedType(fallbackType).getAttribute(segment); + } catch (IllegalArgumentException e) { + // nothing to do here + } } return null; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java index 0ff9902525..cfa65ccd17 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollDelegate.java @@ -23,11 +23,12 @@ import java.util.Map; import org.springframework.data.domain.KeysetScrollPosition; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.ScrollPosition.Direction; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.repository.support.JpaEntityInformation; -import org.springframework.lang.Nullable; /** * Delegate for keyset scrolling. @@ -69,8 +70,7 @@ public static Collection getProjectionInputProperties(JpaEntityInformati return properties; } - @Nullable - public P createPredicate(KeysetScrollPosition keyset, Sort sort, QueryStrategy strategy) { + public @Nullable P createPredicate(KeysetScrollPosition keyset, Sort sort, QueryStrategy strategy) { Map keysetValues = keyset.getKeys(); @@ -207,16 +207,16 @@ public interface QueryStrategy { * * @param order must not be {@literal null}. * @param propertyExpression must not be {@literal null}. - * @param value the value to compare with. Must not be {@literal null}. + * @param value the value to compare with. Can be {@literal null}. * @return an object representing the comparison predicate. */ - P compare(Order order, E propertyExpression, Object value); + P compare(Order order, E propertyExpression, @Nullable Object value); /** * Create an equals-comparison object. * * @param propertyExpression must not be {@literal null}. - * @param value the value to compare with. Must not be {@literal null}. + * @param value the value to compare with. Can be {@literal null}. * @return an object representing the comparison predicate. */ P compare(E propertyExpression, @Nullable Object value); @@ -227,7 +227,7 @@ public interface QueryStrategy { * @param intermediate the predicates to combine. Must not be {@literal null}. * @return a single predicate. */ - P and(List

intermediate); + @Nullable P and(List

intermediate); /** * OR-combine the {@code intermediate} predicates. @@ -235,7 +235,7 @@ public interface QueryStrategy { * @param intermediate the predicates to combine. Must not be {@literal null}. * @return a single predicate. */ - P or(List

intermediate); + @Nullable P or(List

intermediate); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java index 9ef9d4e790..f39505222f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/KeysetScrollSpecification.java @@ -27,13 +27,14 @@ import java.util.List; import org.springframework.data.domain.KeysetScrollPosition; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.query.KeysetScrollDelegate.QueryStrategy; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.data.mapping.PropertyPath; -import org.springframework.lang.Nullable; /** * {@link Specification} to create scroll queries using keyset-scrolling. @@ -67,19 +68,18 @@ public static Sort createSort(KeysetScrollPosition position, Sort sort, JpaEntit } @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) { + public @Nullable Predicate toPredicate(Root root, @Nullable CriteriaQuery query, CriteriaBuilder criteriaBuilder) { return createPredicate(root, criteriaBuilder); } - @Nullable - public Predicate createPredicate(Root root, CriteriaBuilder criteriaBuilder) { + public @Nullable Predicate createPredicate(Root root, CriteriaBuilder criteriaBuilder) { KeysetScrollDelegate delegate = KeysetScrollDelegate.of(position.getDirection()); return delegate.createPredicate(position, sort, new CriteriaBuilderStrategy(root, criteriaBuilder)); } - @Nullable - public JpqlQueryBuilder.Predicate createJpqlPredicate(Bindable from, JpqlQueryBuilder.Entity entity, + + public JpqlQueryBuilder.@Nullable Predicate createJpqlPredicate(Bindable from, JpqlQueryBuilder.Entity entity, ParameterFactory factory) { KeysetScrollDelegate delegate = KeysetScrollDelegate.of(position.getDirection()); @@ -106,10 +106,14 @@ public Expression createExpression(String property) { } @Override - public Predicate compare(Order order, Expression propertyExpression, Object value) { + public Predicate compare(Order order, Expression propertyExpression, @Nullable Object value) { + + if(value instanceof Comparable compareValue) { + return order.isAscending() ? cb.greaterThan(propertyExpression, compareValue) + : cb.lessThan(propertyExpression, compareValue); + } + return order.isAscending() ? cb.isNull(propertyExpression) : cb.isNotNull(propertyExpression); - return order.isAscending() ? cb.greaterThan(propertyExpression, (Comparable) value) - : cb.lessThan(propertyExpression, (Comparable) value); } @Override @@ -133,9 +137,9 @@ private static class JpqlStrategy implements QueryStrategy from; private final JpqlQueryBuilder.Entity entity; private final ParameterFactory factory; - private final Metamodel metamodel; + private final @Nullable Metamodel metamodel; - public JpqlStrategy(Metamodel metamodel, Bindable from, JpqlQueryBuilder.Entity entity, ParameterFactory factory) { + public JpqlStrategy(@Nullable Metamodel metamodel, Bindable from, JpqlQueryBuilder.Entity entity, ParameterFactory factory) { this.from = from; this.entity = entity; @@ -152,9 +156,12 @@ public JpqlQueryBuilder.Expression createExpression(String property) { @Override public JpqlQueryBuilder.Predicate compare(Order order, JpqlQueryBuilder.Expression propertyExpression, - Object value) { + @Nullable Object value) { JpqlQueryBuilder.WhereStep where = JpqlQueryBuilder.where(propertyExpression); + if(value == null) { + return order.isAscending() ? where.isNull() : where.isNotNull(); + } return order.isAscending() ? where.gt(factory.capture(value)) : where.lt(factory.capture(value)); } @@ -167,12 +174,12 @@ public JpqlQueryBuilder.Predicate compare(JpqlQueryBuilder.Expression propertyEx } @Override - public JpqlQueryBuilder.Predicate and(List intermediate) { + public JpqlQueryBuilder.@Nullable Predicate and(List intermediate) { return JpqlQueryBuilder.and(intermediate); } @Override - public JpqlQueryBuilder.Predicate or(List intermediate) { + public JpqlQueryBuilder.@Nullable Predicate or(List intermediate) { return JpqlQueryBuilder.or(intermediate); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java index 53790bcf4f..a7e8dc35e6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/Meta.java @@ -19,8 +19,9 @@ import java.util.LinkedHashMap; import java.util.Map; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; + +import org.jspecify.annotations.Nullable; import org.springframework.util.StringUtils; /** @@ -69,8 +70,7 @@ public void setComment(String comment) { /** * @return {@literal null} if not set. */ - @Nullable - public String getComment() { + public @Nullable String getComment() { return getValue(MetaKey.COMMENT.key); } @@ -106,9 +106,8 @@ void setValue(String key, @Nullable Object value) { this.values.put(key, value); } - @Nullable @SuppressWarnings("unchecked") - private T getValue(String key) { + private @Nullable T getValue(String key) { return (T) this.values.get(key); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java index 39d39954b0..7e3825aad8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NamedQuery.java @@ -22,6 +22,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; + import org.springframework.data.jpa.provider.QueryExtractor; import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.QueryCreationException; @@ -29,7 +31,6 @@ import org.springframework.data.repository.query.ResultProcessor; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.util.Lazy; -import org.springframework.lang.Nullable; /** * Implementation of {@link RepositoryQuery} based on {@link jakarta.persistence.NamedQuery}s. @@ -126,8 +127,7 @@ static boolean hasNamedQuery(EntityManager em, String queryName) { * @param method must not be {@literal null}. * @param em must not be {@literal null}. */ - @Nullable - public static RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em) { + public static @Nullable RepositoryQuery lookupFrom(JpaQueryMethod method, EntityManager em) { String queryName = method.getNamedQueryName(); @@ -191,7 +191,7 @@ protected TypedQuery doCreateCountQuery(JpaParametersParameterAccessor acc } @Override - protected Class getTypeToRead(ReturnedType returnedType) { + protected @Nullable Class getTypeToRead(ReturnedType returnedType) { if (getQueryMethod().isNativeQuery()) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java index 9221cc3807..ae240942d5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/NativeJpaQuery.java @@ -20,6 +20,8 @@ import jakarta.persistence.Tuple; import org.springframework.core.annotation.MergedAnnotation; + +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -28,7 +30,6 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; import org.springframework.util.ObjectUtils; /** @@ -71,7 +72,7 @@ public NativeJpaQuery(JpaQueryMethod method, EntityManager em, String queryStrin } @Override - protected Query createJpaQuery(String queryString, Sort sort, Pageable pageable, ReturnedType returnedType) { + protected Query createJpaQuery(String queryString, Sort sort, @Nullable Pageable pageable, ReturnedType returnedType) { EntityManager em = getEntityManager(); String query = potentiallyRewriteQuery(queryString, sort, pageable); @@ -84,8 +85,7 @@ protected Query createJpaQuery(String queryString, Sort sort, Pageable pageable, return type == null ? em.createNativeQuery(query) : em.createNativeQuery(query, type); } - @Nullable - private Class getTypeToQueryFor(ReturnedType returnedType) { + private @Nullable Class getTypeToQueryFor(ReturnedType returnedType) { Class result = queryForEntity ? returnedType.getDomainType() : null; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java index 664e6539c9..a382d11f5c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterBinding.java @@ -26,12 +26,14 @@ import java.util.stream.Collectors; import org.springframework.data.expression.ValueExpression; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.repository.support.JpqlQueryTemplates; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.Part.Type; -import org.springframework.lang.Nullable; +import org.springframework.lang.Contract; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; @@ -75,8 +77,7 @@ public ParameterOrigin getOrigin() { /** * @return the name if available or {@literal null}. */ - @Nullable - public String getName() { + public @Nullable String getName() { return identifier.hasName() ? identifier.getName() : null; } @@ -150,8 +151,7 @@ public String toString() { /** * @param valueToBind value to prepare */ - @Nullable - public Object prepare(@Nullable Object valueToBind) { + public @Nullable Object prepare(@Nullable Object valueToBind) { return valueToBind; } @@ -230,7 +230,7 @@ public boolean isIsNullParameter() { } @Override - public Object prepare(@Nullable Object value) { + public @Nullable Object prepare(@Nullable Object value) { if (value == null || parameterType == null) { return value; @@ -251,9 +251,10 @@ public Object prepare(@Nullable Object value) { : value; } - @Nullable + @SuppressWarnings("unchecked") - private Collection potentiallyIgnoreCase(boolean ignoreCase, @Nullable Collection collection) { + @Contract("false, _ -> param2; _, null -> null; true, !null -> new)") + private @Nullable Collection potentiallyIgnoreCase(boolean ignoreCase, @Nullable Collection collection) { if (!ignoreCase || CollectionUtils.isEmpty(collection)) { return collection; @@ -274,8 +275,7 @@ private Collection potentiallyIgnoreCase(boolean ignoreCase, @Nullable Collec * @param value the value to be converted to a {@link Collection}. * @return the object itself as a {@link Collection} or a {@link Collection} constructed from the value. */ - @Nullable - private static Collection toCollection(@Nullable Object value) { + private static @Nullable Collection toCollection(@Nullable Object value) { if (value == null) { return null; @@ -312,7 +312,7 @@ static class InParameterBinding extends ParameterBinding { } @Override - public Object prepare(@Nullable Object value) { + public @Nullable Object prepare(@Nullable Object value) { if (!ObjectUtils.isArray(value)) { return value; @@ -374,9 +374,8 @@ public Type getType() { /** * Extracts the raw value properly. */ - @Nullable @Override - public Object prepare(@Nullable Object value) { + public @Nullable Object prepare(@Nullable Object value) { Object unwrapped = PersistenceProvider.unwrapTypedParameterValue(value); if (unwrapped == null) { @@ -643,8 +642,10 @@ static MethodInvocationArgument ofParameter(@Nullable String name, @Nullable Int identifier = BindingIdentifier.of(name, position); } else if (!ObjectUtils.isEmpty(name)) { identifier = BindingIdentifier.of(name); - } else { + } else if (position != null) { identifier = BindingIdentifier.of(position); + } else { + throw new IllegalStateException("Neither name nor position available for binding"); } return ofParameter(identifier); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java index 65d3538d04..5071e23ff4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ParameterMetadataProvider.java @@ -28,6 +28,8 @@ import java.util.stream.Collectors; import org.springframework.data.jpa.provider.PersistenceProvider; + +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.support.JpqlQueryTemplates; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.Parameters; @@ -36,7 +38,6 @@ import org.springframework.data.repository.query.parser.Part.IgnoreCaseType; import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.expression.Expression; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -264,8 +265,7 @@ public boolean isIsNullParameter() { * * @param value can be {@literal null}. */ - @Nullable - public Object prepare(@Nullable Object value) { + public @Nullable Object prepare(@Nullable Object value) { if (value == null || parameterType == null) { return value; @@ -294,8 +294,7 @@ public Object prepare(@Nullable Object value) { * @param value the value to be converted to a {@link Collection}. * @return the object itself as a {@link Collection} or a {@link Collection} constructed from the value. */ - @Nullable - private static Collection toCollection(@Nullable Object value) { + private static @Nullable Collection toCollection(@Nullable Object value) { if (value == null) { return null; @@ -314,9 +313,8 @@ private static Collection toCollection(@Nullable Object value) { return Collections.singleton(value); } - @Nullable @SuppressWarnings("unchecked") - private Collection potentiallyIgnoreCase(boolean ignoreCase, @Nullable Collection collection) { + private @Nullable Collection potentiallyIgnoreCase(boolean ignoreCase, @Nullable Collection collection) { if (!ignoreCase || CollectionUtils.isEmpty(collection)) { return collection; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java index e5107ee7c2..66dac47929 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeJpaQuery.java @@ -24,6 +24,7 @@ import java.util.List; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +44,6 @@ import org.springframework.data.repository.query.parser.Part.Type; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.util.Streamable; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -246,7 +246,7 @@ public Query createQuery(JpaParametersParameterAccessor accessor) { * Restricts the max results of the given {@link Query} if the current {@code tree} marks this {@code query} as * limited. */ - @SuppressWarnings("ConstantConditions") + @SuppressWarnings({ "ConstantConditions", "NullAway" }) private Query restrictMaxResultsIfNecessary(Query query, @Nullable ScrollPosition scrollPosition) { if (scrollPosition instanceof OffsetScrollPosition offset && !offset.isInitial()) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeQueryCache.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeQueryCache.java index 21bead5d27..707ee20518 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeQueryCache.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/PartTreeQueryCache.java @@ -22,7 +22,8 @@ import java.util.Objects; import org.springframework.data.domain.Sort; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; import org.springframework.util.ObjectUtils; /** diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java index 0cef0b0a0f..a2f9546d59 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ProcedureParameter.java @@ -20,7 +20,7 @@ import java.util.Objects; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * This class represents a Stored Procedure Parameter and an instance of the annotation diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java index 88d4716d88..65304dcbba 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryEnhancer.java @@ -18,8 +18,9 @@ import java.util.Set; import org.springframework.data.domain.Sort; + +import org.jspecify.annotations.Nullable; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.lang.Nullable; /** * This interface describes the API for enhancing a given Query. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryInformation.java index 07c1def305..b681037cbf 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryInformation.java @@ -17,7 +17,7 @@ import java.util.List; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * Value object capturing introspection details of a parsed query. @@ -44,8 +44,7 @@ class QueryInformation { * * @return */ - @Nullable - public String getAlias() { + public @Nullable String getAlias() { return alias; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java index d88589d6ef..caeb8fd78f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetter.java @@ -29,8 +29,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ErrorHandler; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java index 27e55e2f7e..005ece6ce9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryParameterSetterFactory.java @@ -21,6 +21,8 @@ import java.util.function.Function; import org.springframework.data.expression.ValueEvaluationContext; + +import org.jspecify.annotations.Nullable; import org.springframework.data.expression.ValueEvaluationContextProvider; import org.springframework.data.expression.ValueExpression; import org.springframework.data.expression.ValueExpressionParser; @@ -32,7 +34,6 @@ import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -53,8 +54,7 @@ abstract class QueryParameterSetterFactory { * @param binding the parameter binding to create a {@link QueryParameterSetter} for. * @return */ - @Nullable - abstract QueryParameterSetter create(ParameterBinding binding); + abstract @Nullable QueryParameterSetter create(ParameterBinding binding); /** * Creates a new {@link QueryParameterSetterFactory} for the given {@link JpaParameters}. @@ -109,7 +109,7 @@ static QueryParameterSetterFactory parsing(ValueExpressionParser parser, * @param binding the binding of the query parameter to be set. * @param parameter the method parameter to bind. */ - private static QueryParameterSetter createSetter(Function valueExtractor, + private static QueryParameterSetter createSetter(Function valueExtractor, ParameterBinding binding, @Nullable JpaParameter parameter) { TemporalType temporalType = parameter != null && parameter.isTemporalParameter() // @@ -120,8 +120,7 @@ private static QueryParameterSetter createSetter(Function parameters, String name) { + static @Nullable JpaParameter findParameterForBinding(Parameters parameters, String name) { JpaParameters bindableParameters = parameters.getBindableParameters(); @@ -180,9 +179,8 @@ private static class ExpressionBasedQueryParameterSetterFactory extends QueryPar this.evaluationContextProvider = evaluationContextProvider; } - @Nullable @Override - public QueryParameterSetter create(ParameterBinding binding) { + public @Nullable QueryParameterSetter create(ParameterBinding binding) { if (!(binding.getOrigin() instanceof ParameterBinding.Expression e)) { return null; @@ -198,8 +196,7 @@ public QueryParameterSetter create(ParameterBinding binding) { * @param accessor must not be {@literal null}. * @return the result of the evaluation. */ - @Nullable - private Object evaluateExpression(ValueExpression expression, JpaParametersParameterAccessor accessor) { + private @Nullable Object evaluateExpression(ValueExpression expression, JpaParametersParameterAccessor accessor) { ValueEvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(accessor.getValues()); return expression.evaluate(evaluationContext); @@ -215,7 +212,7 @@ private Object evaluateExpression(ValueExpression expression, JpaParametersParam private static class SyntheticParameterSetterFactory extends QueryParameterSetterFactory { @Override - public QueryParameterSetter create(ParameterBinding binding) { + public @Nullable QueryParameterSetter create(ParameterBinding binding) { if (!(binding.getOrigin() instanceof ParameterBinding.Synthetic s)) { return null; @@ -251,7 +248,7 @@ private static class BasicQueryParameterSetterFactory extends QueryParameterSett } @Override - public QueryParameterSetter create(ParameterBinding binding) { + public @Nullable QueryParameterSetter create(ParameterBinding binding) { Assert.notNull(binding, "Binding must not be null"); @@ -273,8 +270,7 @@ public QueryParameterSetter create(ParameterBinding binding) { : createSetter(values -> getValue(values, parameter), binding, parameter); } - @Nullable - protected Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) { + protected @Nullable Object getValue(JpaParametersParameterAccessor accessor, Parameter parameter) { return accessor.getValue(parameter); } } @@ -295,7 +291,7 @@ private PartTreeQueryParameterSetterFactory(JpaParameters parameters) { } @Override - public QueryParameterSetter create(ParameterBinding binding) { + public @Nullable QueryParameterSetter create(ParameterBinding binding) { if (!binding.getOrigin().isMethodArgument()) { return null; @@ -349,15 +345,13 @@ public ParameterImpl(BindingIdentifier identifier, Class parameterType) { this.parameterType = parameterType; } - @Nullable @Override - public String getName() { + public @Nullable String getName() { return identifier.hasName() ? identifier.getName() : null; } - @Nullable @Override - public Integer getPosition() { + public @Nullable Integer getPosition() { return identifier.hasPosition() ? identifier.getPosition() : null; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java index b7f0b45123..5c0969ea2b 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryRenderer.java @@ -22,9 +22,10 @@ import java.util.List; import java.util.stream.Stream; -import org.springframework.lang.Nullable; import org.springframework.util.CompositeIterator; +import org.jspecify.annotations.Nullable; + /** * Abstraction to encapsulate query expressions and render a query. *

@@ -271,8 +272,7 @@ QueryRenderer append(QueryTokenStream tokens) { } @Override - @Nullable - public QueryToken getLast() { + public @Nullable QueryToken getLast() { for (int i = nested.size() - 1; i > -1; i--) { @@ -368,14 +368,12 @@ public List toList() { } @Override - @Nullable - public QueryToken getFirst() { + public @Nullable QueryToken getFirst() { return tokens.isEmpty() ? null : tokens.get(0); } @Override - @Nullable - public QueryToken getLast() { + public @Nullable QueryToken getLast() { return tokens.isEmpty() ? null : tokens.get(tokens.size() - 1); } @@ -438,14 +436,12 @@ public Iterator iterator() { } @Override - @Nullable - public QueryToken getFirst() { + public @Nullable QueryToken getFirst() { return tokens.getFirst(); } @Override - @Nullable - public QueryToken getLast() { + public @Nullable QueryToken getLast() { return tokens.getLast(); } @@ -574,14 +570,12 @@ public Stream stream() { } @Override - @Nullable - public QueryToken getFirst() { + public @Nullable QueryToken getFirst() { return current.getFirst(); } @Override - @Nullable - public QueryToken getLast() { + public @Nullable QueryToken getLast() { return current.getLast(); } @@ -645,14 +639,12 @@ public Iterator iterator() { } @Override - @Nullable - public QueryToken getFirst() { + public @Nullable QueryToken getFirst() { return delegate.getFirst(); } @Override - @Nullable - public QueryToken getLast() { + public @Nullable QueryToken getLast() { return delegate.getLast(); } @@ -701,14 +693,12 @@ public Iterator iterator() { } @Override - @Nullable - public QueryToken getFirst() { + public @Nullable QueryToken getFirst() { return delegate.getFirst(); } @Override - @Nullable - public QueryToken getLast() { + public @Nullable QueryToken getLast() { return delegate.getLast(); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java index 0b3b659c8d..5b68191cfd 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryTokenStream.java @@ -23,9 +23,9 @@ import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.TerminalNode; +import org.jspecify.annotations.Nullable; import org.springframework.data.util.Streamable; -import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; /** @@ -142,8 +142,7 @@ static QueryTokenStream concat(Collection elements, Function it = iterator(); return it.hasNext() ? it.next() : null; @@ -167,8 +166,7 @@ default QueryToken getRequiredFirst() { /** * @return the last query token or {@code null} if empty. */ - @Nullable - default QueryToken getLast() { + default @Nullable QueryToken getLast() { return CollectionUtils.lastElement(toList()); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 7d5497d45c..1be6b8a5ab 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -47,13 +47,14 @@ import java.util.stream.Collectors; import org.springframework.core.annotation.AnnotationUtils; + +import org.jspecify.annotations.Nullable; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.JpaSort.JpaOrder; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.util.Streamable; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -439,9 +440,8 @@ private static String toJpaDirection(Order order) { * @return Might return {@literal null}. * @deprecated use {@link DeclaredQuery#getAlias()} instead. */ - @Nullable @Deprecated - public static String detectAlias(String query) { + public static @Nullable String detectAlias(String query) { String alias = null; Matcher matcher = ALIAS_MATCH.matcher(removeSubqueries(query)); @@ -852,6 +852,7 @@ static boolean requiresOuterJoin(From from, PropertyPath property, boolean return hasRequiredOuterJoin || getAnnotationProperty(attribute, "optional", true); } + @SuppressWarnings("unchecked") static T getAnnotationProperty(Attribute attribute, String propertyName, T defaultValue) { Class associationAnnotation = ASSOCIATION_TYPES.get(attribute.getPersistentAttributeType()); @@ -867,7 +868,12 @@ static T getAnnotationProperty(Attribute attribute, String propertyNam } Annotation annotation = AnnotationUtils.getAnnotation(annotatedMember, associationAnnotation); - return annotation == null ? defaultValue : (T) AnnotationUtils.getValue(annotation, propertyName); + if(annotation == null) { + return defaultValue; + } + + T value = (T) AnnotationUtils.getValue(annotation, propertyName); + return value != null ? value : defaultValue; } /** @@ -948,8 +954,7 @@ static void checkSortExpression(Order order) { * @see https://github.com/jakartaee/persistence/issues/562 */ - @Nullable - private static Bindable getModelForPath(PropertyPath path, @Nullable ManagedType managedType, + private static @Nullable Bindable getModelForPath(PropertyPath path, @Nullable ManagedType managedType, Path fallback) { String segment = path.getSegment(); @@ -973,8 +978,7 @@ private static Bindable getModelForPath(PropertyPath path, @Nullable ManagedT * @param model * @return */ - @Nullable - static ManagedType getManagedTypeForModel(Bindable model) { + static @Nullable ManagedType getManagedTypeForModel(Bindable model) { if (model instanceof ManagedType managedType) { return managedType; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java index b90648223b..b43f555c12 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/SimpleJpaQuery.java @@ -19,9 +19,10 @@ import jakarta.persistence.Query; import org.springframework.data.jpa.repository.QueryRewriter; + +import org.jspecify.annotations.Nullable; import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; /** * {@link RepositoryQuery} implementation that inspects a {@link org.springframework.data.repository.query.QueryMethod} diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java index 2616c3d796..2463b64c6a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributeSource.java @@ -27,7 +27,8 @@ import java.util.List; import org.springframework.core.annotation.AnnotatedElementUtils; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -174,8 +175,7 @@ private List extractOutputParametersFrom(NamedStoredProcedur * @param procedure must not be {@literal null}. * @return */ - @Nullable - private NamedStoredProcedureQuery tryFindAnnotatedNamedStoredProcedureQuery(Method method, + private @Nullable NamedStoredProcedureQuery tryFindAnnotatedNamedStoredProcedureQuery(Method method, JpaEntityMetadata entityMetadata, Procedure procedure) { Assert.notNull(method, "Method must not be null"); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java index e7ef76a3eb..0429ac5f6f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureAttributes.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.jspecify.annotations.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; @@ -93,7 +94,7 @@ private ProcedureParameter getParameterWithCompletedName(ProcedureParameter para parameter.getType()); } - private String completeOutputParameterName(int i, String paramName) { + private String completeOutputParameterName(int i, @Nullable String paramName) { return StringUtils.hasText(paramName) // ? paramName // diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java index e91ffbffb1..3423c71e45 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StoredProcedureJpaQuery.java @@ -26,9 +26,10 @@ import java.util.Map; import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; + +import org.jspecify.annotations.Nullable; import org.springframework.data.repository.query.Parameter; import org.springframework.data.repository.query.QueryMethod; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java index 10424d702c..9ef64d6e3e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/StringQuery.java @@ -27,6 +27,8 @@ import java.util.regex.Pattern; import org.springframework.data.expression.ValueExpression; + +import org.jspecify.annotations.Nullable; import org.springframework.data.expression.ValueExpressionParser; import org.springframework.data.jpa.repository.query.ParameterBinding.BindingIdentifier; import org.springframework.data.jpa.repository.query.ParameterBinding.InParameterBinding; @@ -35,7 +37,6 @@ import org.springframework.data.jpa.repository.query.ParameterBinding.ParameterOrigin; import org.springframework.data.repository.query.ValueExpressionQueryRewriter; import org.springframework.data.repository.query.parser.Part.Type; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -139,8 +140,7 @@ public String getQueryString() { } @Override - @Nullable - public String getAlias() { + public @Nullable String getAlias() { return queryEnhancer.detectAlias(); } @@ -296,8 +296,10 @@ String parseParameterBindingsOfQueryIntoBindingsAndReturnCleanedQuery(String que BindingIdentifier queryParameter; if (parameterIndex != null) { queryParameter = BindingIdentifier.of(parameterIndex); - } else { + } else if (parameterName != null) { queryParameter = BindingIdentifier.of(parameterName); + } else { + throw new IllegalStateException("No bindable expression found"); } ParameterOrigin origin = ObjectUtils.isEmpty(expression) ? ParameterOrigin.ofParameter(parameterName, parameterIndex) @@ -364,8 +366,7 @@ private static ValueExpressionQueryRewriter.ParsedQuery createSpelExtractor(Stri return rewriter.parse(queryWithSpel); } - @Nullable - private static Integer getParameterIndex(@Nullable String parameterIndexString) { + private static @Nullable Integer getParameterIndex(@Nullable String parameterIndexString) { if (parameterIndexString == null || parameterIndexString.isEmpty()) { return null; @@ -425,8 +426,7 @@ private enum ParameterBindingType { * * @return the keyword */ - @Nullable - public String getKeyword() { + public @Nullable String getKeyword() { return keyword; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/package-info.java index efbf2d7af3..9f42b926da 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/package-info.java @@ -1,5 +1,5 @@ /** * Query implementation to execute queries against JPA. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.repository.query; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java index 4b0b7bacaf..6ac031cc56 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadata.java @@ -21,7 +21,8 @@ import java.util.Optional; import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * Interface to abstract {@link CrudMethodMetadata} that provide the {@link LockModeType} to be used for query @@ -76,7 +77,8 @@ public interface CrudMethodMetadata { * @return * @since 1.9 */ - Optional getEntityGraph(); + @Nullable + EntityGraph getEntityGraph(); /** * Returns the {@link Method} to be used. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java index 135d3c6e44..0a9a902e00 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/CrudMethodMetadataPostProcessor.java @@ -28,6 +28,7 @@ import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.jspecify.annotations.Nullable; import org.springframework.aop.TargetSource; import org.springframework.aop.framework.ProxyFactory; @@ -41,7 +42,6 @@ import org.springframework.data.jpa.repository.QueryHints; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; -import org.springframework.lang.Nullable; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -61,11 +61,12 @@ */ class CrudMethodMetadataPostProcessor implements RepositoryProxyPostProcessor, BeanClassLoaderAware { - private @Nullable ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); + private @Nullable ClassLoader classLoader; @Override - public void setBeanClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; + public void setBeanClassLoader(@Nullable ClassLoader classLoader) { + this.classLoader = classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader(); + } @Override @@ -120,15 +121,16 @@ static MethodInvocation currentInvocation() throws IllegalStateException { MethodInvocation mi = currentInvocation.get(); - if (mi == null) - throw new IllegalStateException( - "No MethodInvocation found: Check that an AOP invocation is in progress, and that the " - + "CrudMethodMetadataPopulatingMethodInterceptor is upfront in the interceptor chain."); - return mi; + if (mi != null) { + return mi; + } + throw new IllegalStateException( + "No MethodInvocation found: Check that an AOP invocation is in progress, and that the " + + "CrudMethodMetadataPopulatingMethodInterceptor is upfront in the interceptor chain."); } @Override - public Object invoke(MethodInvocation invocation) throws Throwable { + public @Nullable Object invoke(MethodInvocation invocation) throws Throwable { Method method = invocation.getMethod(); @@ -184,7 +186,7 @@ private static class DefaultCrudMethodMetadata implements CrudMethodMetadata { private final org.springframework.data.jpa.repository.support.QueryHints queryHints; private final org.springframework.data.jpa.repository.support.QueryHints queryHintsForCount; private final @Nullable String comment; - private final Optional entityGraph; + private final @Nullable EntityGraph entityGraph; private final Method method; /** @@ -204,12 +206,11 @@ private static class DefaultCrudMethodMetadata implements CrudMethodMetadata { this.method = method; } - private static Optional findEntityGraph(Method method) { - return Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class)); + private static @Nullable EntityGraph findEntityGraph(Method method) { + return AnnotatedElementUtils.findMergedAnnotation(method, EntityGraph.class); } - @Nullable - private static LockModeType findLockModeType(Method method) { + private static @Nullable LockModeType findLockModeType(Method method) { Lock annotation = AnnotatedElementUtils.findMergedAnnotation(method, Lock.class); return annotation == null ? null : (LockModeType) AnnotationUtils.getValue(annotation); @@ -238,16 +239,14 @@ private static org.springframework.data.jpa.repository.support.QueryHints findQu return queryHints; } - @Nullable - private static String findComment(Method method) { + private static @Nullable String findComment(Method method) { Meta annotation = AnnotatedElementUtils.findMergedAnnotation(method, Meta.class); return annotation == null ? null : (String) AnnotationUtils.getValue(annotation, "comment"); } - @Nullable @Override - public LockModeType getLockModeType() { + public @Nullable LockModeType getLockModeType() { return lockModeType; } @@ -262,12 +261,12 @@ public org.springframework.data.jpa.repository.support.QueryHints getQueryHintsF } @Override - public String getComment() { + public @Nullable String getComment() { return comment; } @Override - public Optional getEntityGraph() { + public @Nullable EntityGraph getEntityGraph() { return entityGraph; } @@ -291,7 +290,7 @@ public boolean isStatic() { } @Override - public Object getTarget() { + public @Nullable Object getTarget() { MethodInvocation invocation = CrudMethodMetadataPopulatingMethodInterceptor.currentInvocation(); return TransactionSynchronizationManager.getResource(invocation.getMethod()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java index 228251d4f2..12c05b6e76 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/DefaultQueryHints.java @@ -17,13 +17,12 @@ import jakarta.persistence.EntityManager; -import java.util.Optional; import java.util.function.BiConsumer; +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.query.Jpa21Utils; import org.springframework.data.jpa.repository.query.JpaEntityGraph; -import org.springframework.data.util.Optionals; import org.springframework.util.Assert; /** @@ -38,7 +37,7 @@ class DefaultQueryHints implements QueryHints { private final JpaEntityInformation information; private final CrudMethodMetadata metadata; - private final Optional entityManager; + private final @Nullable EntityManager entityManager; private final boolean forCounts; /** @@ -46,12 +45,12 @@ class DefaultQueryHints implements QueryHints { * {@link CrudMethodMetadata}, {@link EntityManager} and whether to include fetch graphs. * * @param information must not be {@literal null}. - * @param metadata must not be {@literal null}. + * @param metadata can be {@literal null}. * @param entityManager must not be {@literal null}. * @param forCounts */ private DefaultQueryHints(JpaEntityInformation information, CrudMethodMetadata metadata, - Optional entityManager, boolean forCounts) { + @Nullable EntityManager entityManager, boolean forCounts) { this.information = information; this.metadata = metadata; @@ -72,12 +71,12 @@ public static QueryHints of(JpaEntityInformation information, CrudMethodMe Assert.notNull(information, "JpaEntityInformation must not be null"); Assert.notNull(metadata, "CrudMethodMetadata must not be null"); - return new DefaultQueryHints(information, metadata, Optional.empty(), false); + return new DefaultQueryHints(information, metadata, null, false); } @Override public QueryHints withFetchGraphs(EntityManager em) { - return new DefaultQueryHints(this.information, this.metadata, Optional.of(em), this.forCounts); + return new DefaultQueryHints(this.information, this.metadata, em, this.forCounts); } @Override @@ -96,10 +95,10 @@ private QueryHints combineHints() { private QueryHints getFetchGraphs() { - return Optionals - .mapIfAllPresent(entityManager, metadata.getEntityGraph(), - (em, graph) -> Jpa21Utils.getFetchGraphHint(em, getEntityGraph(graph), information.getJavaType())) - .orElseGet(MutableQueryHints::new); + if(entityManager != null && metadata.getEntityGraph() != null) { + return Jpa21Utils.getFetchGraphHint(entityManager, getEntityGraph(metadata.getEntityGraph()), information.getJavaType()); + } + return new MutableQueryHints(); } private JpaEntityGraph getEntityGraph(EntityGraph entityGraph) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java index 5308fa64b8..6a63a8260e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/EntityGraphFactory.java @@ -61,10 +61,13 @@ public static EntityGraph create(EntityManager entityManager, Class do currentFullPath += path.getSegment() + "."; if (path.hasNext()) { - final Subgraph finalCurrent = current; - current = current == null - ? existingSubgraphs.computeIfAbsent(currentFullPath, k -> entityGraph.addSubgraph(path.getSegment())) - : existingSubgraphs.computeIfAbsent(currentFullPath, k -> finalCurrent.addSubgraph(path.getSegment())); + + if(current == null) { + current = existingSubgraphs.computeIfAbsent(currentFullPath, k -> entityGraph.addSubgraph(path.getSegment())); + } else { + final Subgraph finalCurrent = current; + current = existingSubgraphs.computeIfAbsent(currentFullPath, k -> finalCurrent.addSubgraph(path.getSegment())); + } continue; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java index 8bd1aa575d..01e3c99ebc 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryByPredicate.java @@ -39,7 +39,6 @@ import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.support.PageableExecutionUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.querydsl.core.types.EntityPath; @@ -50,6 +49,7 @@ import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.JPQLSerializer; import com.querydsl.jpa.impl.AbstractJPAQuery; +import org.jspecify.annotations.Nullable; /** * Immutable implementation of {@link FetchableFluentQuery} based on a Querydsl {@link Predicate}. All methods that @@ -145,7 +145,7 @@ public FetchableFluentQuery project(Collection properties) { } @Override - public R oneValue() { + public @Nullable R oneValue() { List results = createSortedAndProjectedQuery(this.sort) // .limit(2) // Never need more than 2 values @@ -159,7 +159,7 @@ public R oneValue() { } @Override - public R firstValue() { + public @Nullable R firstValue() { List results = createSortedAndProjectedQuery(this.sort) // .limit(1) // Never need more than 1 value diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java index 5d87904ec3..feed747f0c 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FetchableFluentQueryBySpecification.java @@ -27,6 +27,8 @@ import java.util.stream.Stream; import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -42,7 +44,6 @@ import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.support.PageableExecutionUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -132,7 +133,7 @@ public SpecificationFluentQuery project(Collection properties) { } @Override - public R oneValue() { + public @Nullable R oneValue() { List results = createSortedAndProjectedQuery(this.sort) // .setMaxResults(2) // Never need more than 2 values @@ -146,7 +147,7 @@ public R oneValue() { } @Override - public R firstValue() { + public @Nullable R firstValue() { List results = createSortedAndProjectedQuery(this.sort) // .setMaxResults(1) // Never need more than 1 value diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java index 10b484d98a..f530e6eade 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/FluentQuerySupport.java @@ -22,6 +22,8 @@ import java.util.function.Function; import org.springframework.core.convert.support.DefaultConversionService; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.ScrollPosition; @@ -29,7 +31,6 @@ import org.springframework.data.jpa.repository.query.AbstractJpaQuery; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.repository.query.ReturnedType; -import org.springframework.lang.Nullable; /** * Supporting class containing some state and convenience methods for building and executing fluent queries. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JakartaTuple.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JakartaTuple.java index 9c367343c5..6b2e6361e9 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JakartaTuple.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JakartaTuple.java @@ -20,8 +20,6 @@ import java.util.Arrays; import java.util.List; -import org.springframework.lang.Nullable; - import com.querydsl.core.types.Expression; import com.querydsl.core.types.ExpressionBase; import com.querydsl.core.types.ExpressionUtils; @@ -31,6 +29,7 @@ import com.querydsl.core.types.Projections; import com.querydsl.core.types.Visitor; import com.querydsl.jpa.JPQLSerializer; +import org.jspecify.annotations.Nullable; /** * Expression based on a {@link Tuple}. It's a simplified variant of {@link com.querydsl.core.types.QTuple} without @@ -72,8 +71,7 @@ protected JakartaTuple(List> args) { } @Override - @Nullable - public R accept(Visitor v, @Nullable C context) { + public @Nullable R accept(Visitor v, @Nullable C context) { if (v instanceof JPQLSerializer) { return Projections.tuple(args).accept(v, context); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java index 98828424ab..1e378c3308 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEntityInformation.java @@ -21,8 +21,9 @@ import java.util.Map; import org.springframework.data.jpa.repository.query.JpaEntityMetadata; + +import org.jspecify.annotations.Nullable; import org.springframework.data.repository.core.EntityInformation; -import org.springframework.lang.Nullable; /** * Extension of {@link EntityInformation} to capture additional JPA specific information about entities. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java index f635a221a4..347b10fde5 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaEvaluationContextExtension.java @@ -15,6 +15,7 @@ */ package org.springframework.data.jpa.repository.support; +import org.jspecify.annotations.Nullable; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.spel.spi.EvaluationContextExtension; @@ -66,7 +67,7 @@ public static JpaRootObject of(EscapeCharacter character) { * @return * @see EscapeCharacter#escape(String) */ - public String escape(String source) { + public @Nullable String escape(@Nullable String source) { return character.escape(source); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java index 4f337709cc..66994749da 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaMetamodelEntityInformation.java @@ -38,11 +38,12 @@ import java.util.function.Function; import org.springframework.beans.BeanWrapper; + +import org.jspecify.annotations.Nullable; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.jpa.util.JpaMetamodel; import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -143,9 +144,8 @@ public String getEntityName() { } @Override - @Nullable @SuppressWarnings("unchecked") - public ID getId(T entity) { + public @Nullable ID getId(T entity) { // check if this is a proxy. If so use Proxy mechanics to access the id. PersistenceProvider persistenceProvider = PersistenceProvider.fromMetamodel(metamodel); @@ -215,7 +215,7 @@ public Collection getIdAttributeNames() { } @Override - public Object getCompositeIdAttributeValue(Object id, String idAttribute) { + public @Nullable Object getCompositeIdAttributeValue(Object id, String idAttribute) { Assert.isTrue(hasCompositeId(), "Model must have a composite Id"); @@ -312,8 +312,7 @@ public Class getType() { return this.idType; } - @Nullable - private Class tryExtractIdTypeWithFallbackToIdTypeLookup() { + private @Nullable Class tryExtractIdTypeWithFallbackToIdTypeLookup() { try { @@ -330,8 +329,7 @@ private Class tryExtractIdTypeWithFallbackToIdTypeLookup() { } } - @Nullable - private static Class lookupIdClass(IdentifiableType type) { + private static @Nullable Class lookupIdClass(IdentifiableType type) { IdClass annotation = type.getJavaType() != null ? AnnotationUtils.findAnnotation(type.getJavaType(), IdClass.class) diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java index aaaff2050c..5832047303 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaPersistableEntityInformation.java @@ -19,7 +19,8 @@ import jakarta.persistence.metamodel.Metamodel; import org.springframework.data.domain.Persistable; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * Extension of {@link JpaMetamodelEntityInformation} that consideres methods of {@link Persistable} to lookup the id. @@ -48,9 +49,8 @@ public boolean isNew(T entity) { return entity.isNew(); } - @Nullable @Override - public ID getId(T entity) { + public @Nullable ID getId(T entity) { return entity.getId(); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java index e14658773b..2d5a95a27a 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.java @@ -27,6 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeansException; @@ -61,7 +62,6 @@ import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; @@ -123,7 +123,7 @@ public JpaRepositoryFactory(EntityManager entityManager) { } @Override - public void setBeanClassLoader(ClassLoader classLoader) { + public void setBeanClassLoader(@Nullable ClassLoader classLoader) { super.setBeanClassLoader(classLoader); this.crudMethodMetadataPostProcessor.setBeanClassLoader(classLoader); @@ -226,11 +226,15 @@ protected Class getRepositoryBaseClass(RepositoryMetadata metadata) { } @Override - protected ProjectionFactory getProjectionFactory(ClassLoader classLoader, BeanFactory beanFactory) { + protected ProjectionFactory getProjectionFactory(@Nullable ClassLoader classLoader, @Nullable BeanFactory beanFactory) { CollectionAwareProjectionFactory factory = new CollectionAwareProjectionFactory(); - factory.setBeanClassLoader(classLoader); - factory.setBeanFactory(beanFactory); + if(classLoader != null) { + factory.setBeanClassLoader(classLoader); + } + if(beanFactory != null) { + factory.setBeanFactory(beanFactory); + } return factory; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java index 86f2f14d6c..e0a1b00e62 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/JpaRepositoryFactoryBean.java @@ -19,6 +19,8 @@ import jakarta.persistence.PersistenceContext; import org.springframework.beans.factory.ObjectProvider; + +import org.jspecify.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.query.EscapeCharacter; import org.springframework.data.jpa.repository.query.JpaQueryMethodFactory; @@ -28,7 +30,6 @@ import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.TransactionalRepositoryFactoryBeanSupport; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -48,7 +49,7 @@ public class JpaRepositoryFactoryBean, S, ID> private @Nullable EntityManager entityManager; private EntityPathResolver entityPathResolver; private EscapeCharacter escapeCharacter = EscapeCharacter.DEFAULT; - private JpaQueryMethodFactory queryMethodFactory; + private @Nullable JpaQueryMethodFactory queryMethodFactory; /** * Creates a new {@link JpaRepositoryFactoryBean} for the given repository interface. diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java index 62155b8f0b..da00e05368 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/Querydsl.java @@ -25,7 +25,6 @@ import org.springframework.data.jpa.provider.PersistenceProvider; import org.springframework.data.mapping.PropertyPath; import org.springframework.data.querydsl.QSort; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.querydsl.core.types.EntityPath; @@ -41,6 +40,7 @@ import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.AbstractJPAQuery; import com.querydsl.jpa.impl.JPAQuery; +import org.jspecify.annotations.Nullable; /** * Helper instance to ease access to Querydsl JPA query API. @@ -87,10 +87,9 @@ public AbstractJPAQuery> createQuery() { * Obtains the {@link JPQLTemplates} for the configured {@link EntityManager}. Can return {@literal null} to use the * default templates. * - * @return the {@link JPQLTemplates} for the configured {@link EntityManager} or {@literal null} to use the default. + * @return the {@link JPQLTemplates} for the configured {@link EntityManager}, {@link JPQLTemplates#DEFAULT} by default. * @since 3.5 */ - @Nullable public JPQLTemplates getTemplates() { return switch (provider) { diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java index b37a6e0209..c28660cb92 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslJpaPredicateExecutor.java @@ -44,7 +44,6 @@ import org.springframework.data.repository.query.FluentQuery; import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery; import org.springframework.data.support.PageableExecutionUtils; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; import com.querydsl.core.NonUniqueResultException; @@ -60,6 +59,7 @@ import com.querydsl.core.types.dsl.PathBuilder; import com.querydsl.jpa.JPQLQuery; import com.querydsl.jpa.impl.AbstractJPAQuery; +import org.jspecify.annotations.Nullable; /** * Querydsl specific fragment for extending {@link SimpleJpaRepository} with an implementation of @@ -297,8 +297,7 @@ protected JPQLQuery createCountQuery(@Nullable Predicate... predicate) { return doCreateQuery(getQueryHintsForCount(), predicate); } - @Nullable - private CrudMethodMetadata getRepositoryMethodMetadata() { + private @Nullable CrudMethodMetadata getRepositoryMethodMetadata() { return metadata; } @@ -375,7 +374,12 @@ public Expression createExpression(String property) { } @Override - public BooleanExpression compare(Order order, Expression propertyExpression, Object value) { + public BooleanExpression compare(Order order, Expression propertyExpression, @Nullable Object value) { + + if(value == null) { + return Expressions.booleanOperation(order.isAscending() ? Ops.IS_NULL : Ops.IS_NOT_NULL, propertyExpression); + } + return Expressions.booleanOperation(order.isAscending() ? Ops.GT : Ops.LT, propertyExpression, ConstantImpl.create(value)); } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java index 09c43e198b..562b2ad25d 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/QuerydslRepositorySupport.java @@ -16,10 +16,11 @@ package org.springframework.data.jpa.repository.support; import jakarta.annotation.PostConstruct; +import org.jspecify.annotations.Nullable; + import jakarta.persistence.EntityManager; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.lang.Nullable; import org.springframework.stereotype.Repository; import org.springframework.util.Assert; @@ -84,8 +85,7 @@ public void validate() { * * @return the entityManager */ - @Nullable - protected EntityManager getEntityManager() { + protected @Nullable EntityManager getEntityManager() { return entityManager; } @@ -145,8 +145,7 @@ protected PathBuilder getBuilder() { * * @return */ - @Nullable - protected Querydsl getQuerydsl() { + protected @Nullable Querydsl getQuerydsl() { return this.querydsl; } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java index aad76c29ca..48632c09b6 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java @@ -41,6 +41,8 @@ import java.util.function.Function; import org.springframework.dao.InvalidDataAccessApiUsageException; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.Example; import org.springframework.data.domain.KeysetScrollPosition; import org.springframework.data.domain.OffsetScrollPosition; @@ -72,7 +74,7 @@ import org.springframework.data.support.PageableExecutionUtils; import org.springframework.data.util.ProxyUtils; import org.springframework.data.util.Streamable; -import org.springframework.lang.Nullable; +import org.springframework.lang.Contract; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -169,8 +171,7 @@ public void setProjectionFactory(ProjectionFactory projectionFactory) { this.projectionFactory = projectionFactory; } - @Nullable - protected CrudMethodMetadata getRepositoryMethodMetadata() { + protected @Nullable CrudMethodMetadata getRepositoryMethodMetadata() { return metadata; } @@ -252,7 +253,7 @@ public void deleteAllByIdInBatch(Iterable ids) { } else { String queryString = String.format(DELETE_ALL_QUERY_BY_ID_STRING, entityInformation.getEntityName(), - entityInformation.getIdAttribute().getName()); + entityInformation.getRequiredIdAttribute().getName()); Query query = entityManager.createQuery(queryString); @@ -717,8 +718,9 @@ protected Page readPage(TypedQuery query, Pageable pageable, Specification * @param spec must not be {@literal null}. * @param pageable can be {@literal null}. */ + @Contract("_, _, _, null -> fail") protected Page readPage(TypedQuery query, Class domainClass, Pageable pageable, - Specification spec) { + @Nullable Specification spec) { Assert.notNull(spec, "Specification must not be null"); @@ -737,7 +739,7 @@ protected Page readPage(TypedQuery query, Class domainCla * @param spec must not be {@literal null}. * @param pageable must not be {@literal null}. */ - protected TypedQuery getQuery(Specification spec, Pageable pageable) { + protected TypedQuery getQuery(@Nullable Specification spec, Pageable pageable) { return getQuery(spec, getDomainClass(), pageable.getSort()); } @@ -1109,7 +1111,7 @@ private record ExampleSpecification(Example example, } @Override - public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + public @Nullable Predicate toPredicate(Root root, @Nullable CriteriaQuery query, CriteriaBuilder cb) { return QueryByExamplePredicateBuilder.getPredicate(root, cb, example, escapeCharacter); } } diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SpringDataJpaQuery.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SpringDataJpaQuery.java index 2ee289253a..d5d518d004 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SpringDataJpaQuery.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SpringDataJpaQuery.java @@ -22,8 +22,6 @@ import java.util.Collection; import java.util.Map; -import org.springframework.lang.Nullable; - import com.querydsl.core.QueryModifiers; import com.querydsl.core.types.Expression; import com.querydsl.core.types.FactoryExpression; @@ -31,6 +29,7 @@ import com.querydsl.jpa.JPQLTemplates; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAUtil; +import org.jspecify.annotations.Nullable; /** * Customized String-Query implementation that specifically routes tuple query creation to diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/package-info.java index 2f75e71375..c40a1ae92f 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/package-info.java @@ -1,5 +1,5 @@ /** * JPA repository implementations. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.repository.support; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java index dd4690086b..686d2ab7ed 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/ClasspathScanningPersistenceUnitPostProcessor.java @@ -26,6 +26,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.jspecify.annotations.Nullable; + import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.EnvironmentAware; import org.springframework.context.ResourceLoaderAware; @@ -39,7 +41,6 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.lang.Nullable; import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo; import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor; import org.springframework.util.Assert; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/package-info.java index ad7b5e7f45..6e60ae77b4 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/support/package-info.java @@ -1,5 +1,5 @@ /** * Various helper classes useful when working with JPA. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.support; diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java index 149742c0b7..2caa4ea9a8 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/HibernateProxyDetector.java @@ -18,8 +18,9 @@ import java.util.Optional; import org.hibernate.proxy.HibernateProxy; +import org.jspecify.annotations.Nullable; + import org.springframework.data.util.ProxyUtils.ProxyDetector; -import org.springframework.lang.Nullable; import org.springframework.util.ClassUtils; /** @@ -40,8 +41,7 @@ public Class getUserType(Class type) { .orElse(type); } - @Nullable - private static Class loadHibernateProxyType() { + private static @Nullable Class loadHibernateProxyType() { try { return ClassUtils.forName("org.hibernate.proxy.HibernateProxy", HibernateProxyDetector.class.getClassLoader()); diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/package-info.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/package-info.java index f49bdb7cc1..264664d04e 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/package-info.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/util/package-info.java @@ -1,5 +1,5 @@ /** * Spring Data JPA utilities. */ -@org.springframework.lang.NonNullApi +@org.jspecify.annotations.NullMarked package org.springframework.data.jpa.util; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java index 7c18f5d466..489f29326e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/AntlrVersionTests.java @@ -22,6 +22,7 @@ import org.antlr.v4.runtime.RuntimeMetaData; import org.hibernate.grammars.hql.HqlParser; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.springframework.asm.ClassReader; @@ -29,7 +30,6 @@ import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; import org.springframework.data.jpa.util.DisabledOnHibernate; -import org.springframework.lang.Nullable; /** * Test to verify that we use the same Antlr version as Hibernate. We parse {@code org.hibernate.grammars.hql.HqlParser} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java index dac929f40d..c64d55f7f7 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/JpaSortTests.java @@ -23,6 +23,7 @@ import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.PluralAttribute; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -30,7 +31,6 @@ import org.springframework.data.jpa.domain.sample.MailMessage_; import org.springframework.data.jpa.domain.sample.MailSender_; import org.springframework.data.jpa.domain.sample.User_; -import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java index 65d4e6e2ad..59b561968f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/AuditableUser.java @@ -24,7 +24,8 @@ import java.util.Set; import org.springframework.data.jpa.domain.AbstractAuditable; -import org.springframework.lang.Nullable; + +import org.jspecify.annotations.Nullable; /** * Sample auditable user to demonstrate working with {@code AbstractAuditableEntity}. No declaration of an ID is diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java index cdfb9a3bfc..2833123509 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/UserWithOptionalField.java @@ -21,7 +21,7 @@ import java.util.Optional; -import org.springframework.lang.Nullable; +import org.jspecify.annotations.Nullable; /** * @author Greg Turnquist diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java index 3fb97409f8..44d061094f 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/AbstractStringBasedJpaQueryUnitTests.java @@ -27,6 +27,7 @@ import org.assertj.core.api.Assertions; import org.assertj.core.util.Arrays; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -43,7 +44,6 @@ import org.springframework.data.repository.query.ParametersSource; import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.ValueExpressionDelegate; -import org.springframework.lang.Nullable; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.ReflectionUtils; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java index 3c1fec2ed3..782c460a24 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/EqlQueryTransformerTests.java @@ -20,6 +20,7 @@ import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -28,7 +29,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.JpaSort; -import org.springframework.lang.Nullable; /** * Verify that EQL queries are properly transformed through the {@link JpaQueryEnhancer} and the diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java index 867e3f87b2..167f0fe82c 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/HqlQueryTransformerTests.java @@ -22,6 +22,7 @@ import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -32,7 +33,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.JpaSort; -import org.springframework.lang.Nullable; import org.springframework.util.StringUtils; /** @@ -1183,8 +1183,7 @@ private String createCountQueryFor(String query, @Nullable String countProjectio return newParser(query).createCountQueryFor(countProjection); } - @Nullable - private String alias(String query) { + private @Nullable String alias(String query) { return newParser(query).detectAlias(); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java index aebad09360..937568e01d 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/Jpa21UtilsTests.java @@ -33,12 +33,12 @@ import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.Assertions; import org.assertj.core.api.SoftAssertions; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.sample.User; import org.springframework.data.jpa.repository.EntityGraph.EntityGraphType; -import org.springframework.lang.Nullable; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.transaction.annotation.Transactional; @@ -191,8 +191,7 @@ void allowsEmptyGraph() { /** * Lookup the {@link AttributeNode} with given {@literal nodeName} in the {@link List} of given {@literal nodes}. */ - @Nullable - static AttributeNode findNode(String nodeName, List> nodes) { + static @Nullable AttributeNode findNode(String nodeName, List> nodes) { if (CollectionUtils.isEmpty(nodes)) { return null; @@ -211,8 +210,7 @@ static AttributeNode findNode(String nodeName, List> nodes) * Lookup the {@link AttributeNode} with given {@literal nodeName} in the first {@link Subgraph} of the given * {@literal node}. */ - @Nullable - static AttributeNode findNode(String attributeName, AttributeNode node) { + static @Nullable AttributeNode findNode(String attributeName, AttributeNode node) { if (CollectionUtils.isEmpty(node.getSubgraphs())) { return null; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorTests.java index f73b45e92d..55e9f39122 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpaQueryCreatorTests.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.function.Function; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -49,7 +50,6 @@ import org.springframework.data.repository.query.ReturnedType; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.data.util.Lazy; -import org.springframework.lang.Nullable; /** * Unit tests for {@link JpaQueryCreator}. @@ -979,9 +979,8 @@ public int bindingIndexFor(String placeholder) { public ParameterAccessor bindableParameters() { return new ParameterAccessor() { - @Nullable @Override - public ScrollPosition getScrollPosition() { + public @Nullable ScrollPosition getScrollPosition() { return null; } @@ -995,15 +994,13 @@ public Sort getSort() { return null; } - @Nullable @Override - public Class findDynamicProjection() { + public @Nullable Class findDynamicProjection() { return null; } - @Nullable @Override - public Object getBindableValue(int index) { + public @Nullable Object getBindableValue(int index) { ParameterBinding parameterBinding = queryCreator.get().getBindings().get(index); return parameterBinding.prepare(parameterAccessor.get().getBindableValue(index)); diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java index 660f3c9a7d..acc6617811 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/JpqlQueryTransformerTests.java @@ -20,6 +20,7 @@ import java.util.stream.Stream; import org.assertj.core.api.SoftAssertions; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -28,7 +29,6 @@ import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.jpa.domain.JpaSort; -import org.springframework.lang.Nullable; /** * Verify that JPQL queries are properly transformed through the {@link JpaQueryEnhancer} and the diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java index 99b8a7a730..f95e9007b1 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryEnhancerFactoryUnitTests.java @@ -19,6 +19,7 @@ import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -26,7 +27,6 @@ import org.springframework.data.jpa.repository.query.QueryEnhancerFactory.NativeQueryEnhancer; import org.springframework.data.jpa.util.ClassPathExclusions; -import org.springframework.lang.Nullable; /** * Unit tests for {@link QueryEnhancerFactory}. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java index 9f7e2da8ea..5cf31423a9 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/QueryWithNullLikeIntegrationTests.java @@ -24,6 +24,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; @@ -38,7 +39,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.data.repository.query.Param; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.lang.Nullable; import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java index 5d2beb3d9b..4b53c362c3 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/query/SimpleJpaQueryUnitTests.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Optional; +import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -55,7 +56,6 @@ import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.ValueExpressionDelegate; import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; /** * Unit test for {@link SimpleJpaQuery}. diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java index fe8e0dd4b6..b02a606673 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/RepositoryMethodsWithEntityGraphConfigRepository.java @@ -27,9 +27,9 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.CrudRepository; -import org.springframework.lang.Nullable; import com.querydsl.core.types.Predicate; +import org.jspecify.annotations.Nullable; /** * Custom repository interface that customizes the fetching behavior of querys of well known repository interface diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java index a206207b78..d6ca7932dd 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java @@ -29,6 +29,8 @@ import java.util.stream.Stream; import org.springframework.data.domain.Limit; + +import org.jspecify.annotations.Nullable; import org.springframework.data.domain.OffsetScrollPosition; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -50,7 +52,6 @@ import org.springframework.data.querydsl.ListQuerydslPredicateExecutor; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; -import org.springframework.lang.Nullable; import org.springframework.transaction.annotation.Transactional; /** diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java index 86a73b1f3c..925a2b9d6e 100644 --- a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/support/SimpleJpaRepositoryUnitTests.java @@ -136,7 +136,7 @@ void shouldPropagateConfiguredEntityGraphToFindOne() throws Exception { String entityGraphName = "User.detail"; when(entityGraphAnnotation.value()).thenReturn(entityGraphName); when(entityGraphAnnotation.type()).thenReturn(EntityGraphType.LOAD); - when(metadata.getEntityGraph()).thenReturn(Optional.of(entityGraphAnnotation)); + when(metadata.getEntityGraph()).thenReturn(entityGraphAnnotation); when(em.getEntityGraph(entityGraphName)).thenReturn((EntityGraph) entityGraph); when(information.getEntityName()).thenReturn("User"); when(metadata.getMethod()).thenReturn(CrudRepository.class.getMethod("findById", Object.class));