Skip to content

Commit

Permalink
Merge pull request #890 from jeffgbutler/jspecify
Browse files Browse the repository at this point in the history
Adopt JSpecify
  • Loading branch information
jeffgbutler authored Jan 4, 2025
2 parents fb789ac + 4f81c07 commit 697b1dc
Show file tree
Hide file tree
Showing 180 changed files with 1,459 additions and 710 deletions.
62 changes: 60 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ you are unable to move to this version of Java then the releases in the 1.x line
In addition, we have taken the opportunity to make changes to the library that may break existing code. We have
worked to make these changes as minimal as possible.

**Potentially Breaking Changes:**
### Potentially Breaking Changes:

- If you use this library with MyBatis' Spring Batch integration, you will need to make changes as we have
refactored that support to be more flexible. Please see the
Expand All @@ -25,7 +25,65 @@ worked to make these changes as minimal as possible.
[Extending the Library](https://mybatis.org/mybatis-dynamic-sql/docs/extending.html) page, the change should be
limited to changing the private constructor to accept `BasicColumn` rather than `BindableColumn`.

Other important changes:
### Adoption of JSpecify (https://jspecify.dev/)

Following the lead of many other projects (including The Spring Framework), we have adopted JSpecify to fully
document the null handling properties of this library. JSpecify is now a runtime dependency - as is
recommended practice with JSpecify.

This change should not impact the running of any existing code, but depending on your usage you may see new IDE or
tooling warnings based on the declared nullability of methods in the library. You may choose to ignore the
warnings and things should continue to function. Of course, we recommend that you do not ignore these warnings!

In general, the library does not expect that you will pass a null value into any method. There are two exceptions to
this rule:

1. Some builder methods will accept a null value if the target object will properly handle null values through the
use of java.util.Optional
2. Methods with names that include "WhenPresent" will properly handle null parameters
(for example, "isEqualToWhenPresent")

As you might expect, standardizing null handling revealed some issues in the library that may impact you.

Fixing compiler warnings and errors:

1. We expect that most of the warnings you encounter will be related to passing null values into a where condition.
These warnings should be resolved by changing your code to use the "WhenPresent" versions of methods as those
methods handle null values in a predictable way.
2. Java Classes that extend "AliasableSqlTable" will likely see IDE warnings about non-null type arguments. This can be
resolved by adding a "@NullMarked" annotation to the class or package. This issue does not affect Kotlin classes
that extend "AliasableSqlTable".
3. Similarly, if you have coded any functions for use with your queries, you can resolve most IDE warnings by adding
the "@NullMarked" annotation.
4. If you have coded any Kotlin functions that operate on a generic Java class from the library, then you should
change the type parameter definition to specify a non-nullable type. For example...

```kotlin
import org.mybatis.dynamic.sql.SqlColumn

fun <T> foo(column: SqlColumn<T>) {
}
```

Should change to:

```kotlin
import org.mybatis.dynamic.sql.SqlColumn

fun <T : Any> foo(column: SqlColumn<T>) {
}
```

Runtime behavior changes:

1. The where conditions (isEqualTo, isLessThan, etc.) can be filtered and result in an "empty" condition -
similar to java.util.Optional. Previously, calling a "value" method of the condition would return null. Now
those methods will throw "NoSuchElementException". This should not impact you in normal usage.
2. We have updated the "ParameterTypeConverter" used in Spring applications to maintain compatibility with Spring's
"Converter" interface. The primary change is that the framework will no longer call a type converter if the
input value is null. This should simplify the coding of converters and foster reuse with existing Spring converters.

### Other important changes:

- The library now requires Java 17
- Deprecated code from prior releases is removed
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ The library test cases provide several complete examples of using the library in
| Kotlin | Spring JDBC | Example using Kotlin utility classes for Spring JDBC Template | [../examples/kotlin/spring/canonical](src/test/kotlin/examples/kotlin/spring/canonical) |


## Requirements
## Requirements and Dependencies

The library has no dependencies. Version 2.x requires Java 17. Version 1.x requires Java 8.
Version 2.x requires Java 17 and has a required runtime dependency on JSpecify (https://jspecify.dev/). Version 1.x
requires Java 8 and has no required runtime dependencies.

All versions have support for MyBatis3, Spring Framework, and Kotlin - all those dependencies are optional. The library
should work in those environments as the dependencies will be made available at runtime.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@
</properties>

<dependencies>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/mybatis/dynamic/sql/AliasableSqlTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import java.util.Optional;
import java.util.function.Supplier;

import org.jspecify.annotations.Nullable;

public abstract class AliasableSqlTable<T extends AliasableSqlTable<T>> extends SqlTable {

private String tableAlias;
private @Nullable String tableAlias;
private final Supplier<T> constructor;

protected AliasableSqlTable(String tableName, Supplier<T> constructor) {
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/org/mybatis/dynamic/sql/AndOrCriteriaGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.Objects;
import java.util.Optional;

import org.jspecify.annotations.Nullable;

/**
* This class represents a criteria group with either an AND or an OR connector.
* This class is intentionally NOT derived from SqlCriterion because we only want it to be
Expand All @@ -32,7 +34,7 @@
*/
public class AndOrCriteriaGroup {
private final String connector;
private final SqlCriterion initialCriterion;
private final @Nullable SqlCriterion initialCriterion;
private final List<AndOrCriteriaGroup> subCriteria;

private AndOrCriteriaGroup(Builder builder) {
Expand All @@ -54,16 +56,16 @@ public List<AndOrCriteriaGroup> subCriteria() {
}

public static class Builder {
private String connector;
private SqlCriterion initialCriterion;
private @Nullable String connector;
private @Nullable SqlCriterion initialCriterion;
private final List<AndOrCriteriaGroup> subCriteria = new ArrayList<>();

public Builder withConnector(String connector) {
this.connector = connector;
return this;
}

public Builder withInitialCriterion(SqlCriterion initialCriterion) {
public Builder withInitialCriterion(@Nullable SqlCriterion initialCriterion) {
this.initialCriterion = initialCriterion;
return this;
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/mybatis/dynamic/sql/BindableColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.util.Optional;

import org.jspecify.annotations.Nullable;

/**
* Describes a column with a known data type. The type is only used by the compiler to assure type safety
* when building clauses with conditions.
Expand All @@ -34,7 +36,7 @@ public interface BindableColumn<T> extends BasicColumn {
@Override
BindableColumn<T> as(String alias);

default Object convertParameterType(T value) {
default @Nullable Object convertParameterType(T value) {
return value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.util.Objects;

import org.jspecify.annotations.Nullable;

public class ColumnAndConditionCriterion<T> extends SqlCriterion {
private final BindableColumn<T> column;
private final VisitableCondition<T> condition;
Expand Down Expand Up @@ -45,8 +47,8 @@ public static <T> Builder<T> withColumn(BindableColumn<T> column) {
}

public static class Builder<T> extends AbstractBuilder<Builder<T>> {
private BindableColumn<T> column;
private VisitableCondition<T> condition;
private @Nullable BindableColumn<T> column;
private @Nullable VisitableCondition<T> condition;

public Builder<T> withColumn(BindableColumn<T> column) {
this.column = column;
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/org/mybatis/dynamic/sql/Constant.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@
import java.util.Objects;
import java.util.Optional;

import org.jspecify.annotations.Nullable;
import org.mybatis.dynamic.sql.render.RenderingContext;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;

public class Constant<T> implements BindableColumn<T> {

private final String alias;
private final @Nullable String alias;
private final String value;

private Constant(String value) {
this(value, null);
}

private Constant(String value, String alias) {
private Constant(String value, @Nullable String alias) {
this.value = Objects.requireNonNull(value);
this.alias = alias;
}
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/org/mybatis/dynamic/sql/CriteriaGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.util.Optional;

import org.jspecify.annotations.Nullable;

/**
* This class represents a criteria group without an AND or an OR connector. This is useful
* in situations where the initial SqlCriterion in a list should be further grouped
Expand All @@ -27,7 +29,7 @@
* @since 1.4.0
*/
public class CriteriaGroup extends SqlCriterion {
private final SqlCriterion initialCriterion;
private final @Nullable SqlCriterion initialCriterion;

protected CriteriaGroup(AbstractGroupBuilder<?> builder) {
super(builder);
Expand All @@ -44,9 +46,9 @@ public <R> R accept(SqlCriterionVisitor<R> visitor) {
}

public abstract static class AbstractGroupBuilder<T extends AbstractGroupBuilder<T>> extends AbstractBuilder<T> {
private SqlCriterion initialCriterion;
private @Nullable SqlCriterion initialCriterion;

public T withInitialCriterion(SqlCriterion initialCriterion) {
public T withInitialCriterion(@Nullable SqlCriterion initialCriterion) {
this.initialCriterion = initialCriterion;
return getThis();
}
Expand Down
25 changes: 13 additions & 12 deletions src/main/java/org/mybatis/dynamic/sql/DerivedColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Objects;
import java.util.Optional;

import org.jspecify.annotations.Nullable;
import org.mybatis.dynamic.sql.render.RenderingContext;
import org.mybatis.dynamic.sql.util.FragmentAndParameters;

Expand All @@ -34,10 +35,10 @@
*/
public class DerivedColumn<T> implements BindableColumn<T> {
private final String name;
private final String tableQualifier;
private final String columnAlias;
private final JDBCType jdbcType;
private final String typeHandler;
private final @Nullable String tableQualifier;
private final @Nullable String columnAlias;
private final @Nullable JDBCType jdbcType;
private final @Nullable String typeHandler;

protected DerivedColumn(Builder<T> builder) {
this.name = Objects.requireNonNull(builder.name);
Expand Down Expand Up @@ -93,18 +94,18 @@ public static <T> DerivedColumn<T> of(String name, String tableQualifier) {
}

public static class Builder<T> {
private String name;
private String tableQualifier;
private String columnAlias;
private JDBCType jdbcType;
private String typeHandler;
private @Nullable String name;
private @Nullable String tableQualifier;
private @Nullable String columnAlias;
private @Nullable JDBCType jdbcType;
private @Nullable String typeHandler;

public Builder<T> withName(String name) {
this.name = name;
return this;
}

public Builder<T> withTableQualifier(String tableQualifier) {
public Builder<T> withTableQualifier(@Nullable String tableQualifier) {
this.tableQualifier = tableQualifier;
return this;
}
Expand All @@ -114,12 +115,12 @@ public Builder<T> withColumnAlias(String columnAlias) {
return this;
}

public Builder<T> withJdbcType(JDBCType jdbcType) {
public Builder<T> withJdbcType(@Nullable JDBCType jdbcType) {
this.jdbcType = jdbcType;
return this;
}

public Builder<T> withTypeHandler(String typeHandler) {
public Builder<T> withTypeHandler(@Nullable String typeHandler) {
this.typeHandler = typeHandler;
return this;
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/mybatis/dynamic/sql/ExistsCriterion.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import java.util.Objects;

import org.jspecify.annotations.Nullable;

public class ExistsCriterion extends SqlCriterion {
private final ExistsPredicate existsPredicate;

Expand All @@ -35,7 +37,7 @@ public <R> R accept(SqlCriterionVisitor<R> visitor) {
}

public static class Builder extends AbstractBuilder<Builder> {
private ExistsPredicate existsPredicate;
private @Nullable ExistsPredicate existsPredicate;

public Builder withExistsPredicate(ExistsPredicate existsPredicate) {
this.existsPredicate = existsPredicate;
Expand Down
3 changes: 0 additions & 3 deletions src/main/java/org/mybatis/dynamic/sql/ExistsPredicate.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import java.util.Objects;

import org.jetbrains.annotations.NotNull;
import org.mybatis.dynamic.sql.select.SelectModel;
import org.mybatis.dynamic.sql.util.Buildable;

Expand All @@ -38,12 +37,10 @@ public Buildable<SelectModel> selectModelBuilder() {
return selectModelBuilder;
}

@NotNull
public static ExistsPredicate exists(Buildable<SelectModel> selectModelBuilder) {
return new ExistsPredicate("exists", selectModelBuilder); //$NON-NLS-1$
}

@NotNull
public static ExistsPredicate notExists(Buildable<SelectModel> selectModelBuilder) {
return new ExistsPredicate("not exists", selectModelBuilder); //$NON-NLS-1$
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.mybatis.dynamic.sql;

import org.jspecify.annotations.Nullable;

/**
* A parameter type converter is used to change a parameter value from one type to another
* during statement rendering and before the parameter is placed into the parameter map. This can be used
Expand Down Expand Up @@ -50,5 +52,13 @@
*/
@FunctionalInterface
public interface ParameterTypeConverter<S, T> {
T convert(S source);
/**
* Convert the value from one value to another.
*
* <p>The input value will never be null - the framework will automatically handle nulls.
*
* @param source value as specified in the condition, or after a map operation. Never null.
* @return Possibly null converted value.
*/
@Nullable T convert(S source);
}
Loading

0 comments on commit 697b1dc

Please sign in to comment.