Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to null value type handling in versions 2.7.X breaks parameter types when using @Convert annotation #2710

Closed
solita-korpimi opened this issue Nov 23, 2022 · 5 comments
Labels
for: external-project For an external project and not something we can fix

Comments

@solita-korpimi
Copy link

There has been a fix in versions 2.7.0 and higher for parameter typing which is causing issues in JPA queries when using converters. #2370

This fix was made to help native queries handle null parameters but is also affecting JPA queries. In our situation we are using a converter to transform boolean values to chars eg. 'Y' or 'N'. When using these values, the data type binding is set as AttributeConverterTypeAdapter. This binding type is generated by @convert annotation in the entity class declaration and we want to keep it that way even in null cases. With the fix previously mentioned, in null cases this data type binding is overwritten by the parameter type.

The entity mapping with @convert annotation is the following:

    @Column(name = "is_deleted")
    @Convert(converter = BooleanCharConverter.class)
    private boolean deleted;

And here is our JPA query:

    @Query("SELECT DISTINCT pkg FROM Package pkg " +
            "WHERE (pkg.deleted = :deleted OR :deleted IS NULL)")
    List<Package> findPackages(@Param("deleted") Boolean deleted);

The change in null value handling breaks our implementation and the JPA queries end up with the following error:

ERROR: operator does not exist: character = boolean
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 802

The binding works in the following way in hibernate:

	public QueryImplementor setParameter(String name, Object value) {
		getProducer().checkOpen();
		if ( value instanceof TypedParameterValue ) {
			final TypedParameterValue  typedValueWrapper = (TypedParameterValue) value;
			setParameter( name, typedValueWrapper.getValue(), typedValueWrapper.getType() );
		}
		else if ( value instanceof Collection && !isRegisteredAsBasicType( value.getClass() ) ) {
			setParameterList( name, (Collection) value );
		}
		else {
			getQueryParameterBindings().getBinding( name ).setBindValue( value );
		}
		return this;
	}

If the value is an instance of TypedParameterValue, the parameter bind type is overwritten by the TypedParameterValue type. Previously null values were just bound without altering the binding type. Either this needs to be reverted for JPA queries or TypedParameterValue needs to fetch the parameter type from the ParameterMetadata where the data type generated by @convert annotation is stored.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Nov 23, 2022
@mp911de
Copy link
Member

mp911de commented Nov 28, 2022

Can you provide a minimal sample that reproduces the problem so we can better understand the context?

@mp911de mp911de added the status: waiting-for-feedback We need additional information before we can continue label Nov 28, 2022
@solita-korpimi
Copy link
Author

solita-korpimi commented Nov 28, 2022

Here is a simplified version of the database table we have.

CREATE TABLE package (
                package_id VARCHAR(36) NOT NULL,
                is_deleted CHAR(1) NOT NULL DEFAULT 'N',
                CONSTRAINT package_pk PRIMARY KEY (package_id)
);

The entity class could look like the following:

@NoArgsConstructor
@Getter
@Entity
public class Package  {
    @Id
    @NotNull
    @Column(name = "package_id")
    private String packageId;

    @Column(name = "is_deleted")
    @Convert(converter = BooleanCharConverter.class)
    private boolean deleted;
}

The converter looks like the following:

@Converter
public class BooleanCharConverter implements AttributeConverter<Boolean, Character> {
    @Override
    public Character convertToDatabaseColumn(Boolean aBoolean) {
        if (aBoolean == null) return null;
        return Mappers.booleanToCharacter(aBoolean);
    }

    @SuppressFBWarnings(
            value = "NP_BOOLEAN_RETURN_NULL",
            justification = "This is jpa converter and because values can be Boolean null this need to be kept like this.")
    @Override
    public Boolean convertToEntityAttribute(Character character) {
        if (character == null) return null;
        return Mappers.charToBoolean(character);
    }
}

There is a @convert annotation used to convert boolean values to Char values ('Y' or 'N'). The idea here is to store booleans as chars but still manage the values in java code as booleans.

The JPA repository call is defined in the following way:

    @Query("SELECT DISTINCT pkg FROM Package pkg " +
            "WHERE (pkg.deleted = :deleted OR :deleted IS NULL)")
    List<Package> findPackages(@Param("deleted") Boolean deleted);

The query has a conditional parameter. If you give it a boolean value, it queries rows where the "deleted" column is the desired value. If you give it null, then the WHERE clause is always true and it queries all rows no matter what the deleted value is.

The converter worked perfectly with both null and non-null values before the 2.7.x update but now if you call the findPackages method with a null value, it breaks. The change that was made changed how null values are handled. It will now default to the type which the parameter was given in and the type for the converter is ignored. This will end up with us getting an error that boolean and char values cannot be compared with each other.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Nov 28, 2022
@chrisaige
Copy link

chrisaige commented Apr 13, 2023

We have the same Issue on our end. Will there be a fix, this is a blocker for us for the latest Spring Updates.

An example was provided, what other feedback is needed?

We also have an issue with "static" variables that need tobe converted.
Something like this also runs into issues as the true is resolved incorrectly

@Query("SELECT DISTINCT pkg FROM Package pkg WHERE pkg.deleted = true") List<Package> findDeletedPackages();

@solita-kattelus
Copy link

Any news about this one?

@mp911de
Copy link
Member

mp911de commented Aug 31, 2023

Hibernate contradicts itself with the entire conversion arrangement. We switched to TypedParameterValue in response to a problem that requires type hints for null values. You're asking for not using TypedParameterValue, which introduces a regression for the fix of #2370.

Spring Data doesn't know about attribute converters and it isn't our business to dive into conversions that are handled by Hibernate. Providing type hints to null values is required and TypedParameterValue is the only way out. That being said, Hibernate should address this shortcoming.

@mp911de mp911de closed this as not planned Won't fix, can't repro, duplicate, stale Aug 31, 2023
@mp911de mp911de added for: external-project For an external project and not something we can fix and removed status: waiting-for-triage An issue we've not yet triaged status: feedback-provided Feedback has been provided labels Aug 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: external-project For an external project and not something we can fix
Projects
None yet
Development

No branches or pull requests

5 participants