Skip to content

Use JDBCType.NULL for null if possible #2069

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1935-jdbc-sql-type-for-null-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data Relational Parent</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-jdbc-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1935-jdbc-sql-type-for-null-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
4 changes: 2 additions & 2 deletions spring-data-jdbc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-data-jdbc</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1935-jdbc-sql-type-for-null-SNAPSHOT</version>

<name>Spring Data JDBC</name>
<description>Spring Data module for JDBC repositories.</description>
Expand All @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-relational-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.0.0-1935-jdbc-sql-type-for-null-SNAPSHOT</version>
</parent>

<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.dialect.NullTypeStrategy;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
Expand Down Expand Up @@ -73,6 +74,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements

private final JdbcTypeFactory typeFactory;
private final RelationResolver relationResolver;
private final NullTypeStrategy nullTypeStrategy;

/**
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
Expand All @@ -84,15 +86,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
*/
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {

super(context, new JdbcCustomConversions());

Assert.notNull(relationResolver, "RelationResolver must not be null");

this.typeFactory = JdbcTypeFactory.unsupported();
this.relationResolver = relationResolver;

registerAggregateReferenceConverters();
this(context, relationResolver, new JdbcCustomConversions(), JdbcTypeFactory.unsupported(), NullTypeStrategy.DEFAULT);
}

/**
Expand All @@ -105,13 +99,20 @@ public MappingJdbcConverter(RelationalMappingContext context, RelationResolver r
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver,
CustomConversions conversions, JdbcTypeFactory typeFactory) {

this(context, relationResolver, conversions, typeFactory, NullTypeStrategy.DEFAULT);
}

public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory, NullTypeStrategy nullTypeStrategy) {

super(context, conversions);

Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
Assert.notNull(relationResolver, "RelationResolver must not be null");
Assert.notNull(nullTypeStrategy, "NullTypeStrategy must not be null");

this.typeFactory = typeFactory;
this.relationResolver = relationResolver;
this.nullTypeStrategy = nullTypeStrategy;

registerAggregateReferenceConverters();
}
Expand Down Expand Up @@ -250,7 +251,11 @@ public JdbcValue writeJdbcValue(@Nullable Object value, TypeInformation<?> colum
return result;
}

if (convertedValue == null || !convertedValue.getClass().isArray()) {
if (convertedValue == null ) {
return JdbcValue.of(null, nullTypeStrategy.getNullType(sqlType));
}

if (!convertedValue.getClass().isArray()) {
return JdbcValue.of(convertedValue, sqlType);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.springframework.data.jdbc.core.convert;

import java.sql.JDBCType;
import java.sql.SQLType;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -41,6 +42,7 @@
* @author Jens Schauder
* @author Chirag Tailor
* @author Mikhail Polivakha
* @author Sergey Korotaev
* @since 2.4
*/
public class SqlParametersFactory {
Expand Down Expand Up @@ -187,11 +189,7 @@ private void addConvertedPropertyValue(SqlIdentifierParameterSource parameterSou
private void addConvertedValue(SqlIdentifierParameterSource parameterSource, @Nullable Object value,
SqlIdentifier paramName, Class<?> javaType, SQLType sqlType) {

JdbcValue jdbcValue = converter.writeJdbcValue( //
value, //
javaType, //
sqlType //
);
JdbcValue jdbcValue = converter.writeJdbcValue(value, javaType, sqlType);

parameterSource.addValue( //
paramName, //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,14 @@ public Timestamp convert(OffsetDateTime source) {
return Timestamp.from(source.toInstant());
}
}

/**
* DB2 does not support {@link java.sql.JDBCType#NULL}. Therefore it uses {@link NullTypeStrategy#NOOP}.
*
* @since 4.0
*/
@Override
public NullTypeStrategy getNullTypeStrategy() {
return NullTypeStrategy.NOOP;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,15 @@ default JdbcArrayColumns getArraySupport() {
return JdbcArrayColumns.Unsupported.INSTANCE;
}

/**
* Determines how to handle the {@link java.sql.JDBCType} of {@literal null} values.
*
* The default is suitable for all databases supporting {@link java.sql.JDBCType#NULL}.
*
* @return a strategy to handle the {@link java.sql.JDBCType} of {@literal null} values. Guaranteed not to be null.
* @since 4.0
*/
default NullTypeStrategy getNullTypeStrategy() {
return NullTypeStrategy.DEFAULT;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,14 @@ public Instant convert(DateTimeOffset source) {
}
}


/**
* SQL Server does not support {@link java.sql.JDBCType#NULL}. Therefore it uses {@link NullTypeStrategy#NOOP}.
*
* @since 4.0
*/
@Override
public NullTypeStrategy getNullTypeStrategy() {
return NullTypeStrategy.NOOP;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.springframework.data.jdbc.core.dialect;

import java.sql.JDBCType;
import java.sql.SQLType;

/**
* Interface for defining what to {@link SQLType} to use for {@literal null} values.
*
* @author Jens Schauder
* @since 4.0
*/
public interface NullTypeStrategy {

/**
* Implementation that always uses {@link JDBCType#NULL}. Suitable for all databases that actually support this
* {@link JDBCType}.
*/
NullTypeStrategy DEFAULT = sqlType -> JDBCType.NULL;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have more meaningful names. Something like NULL_VALUE instead of DEFAULT


/**
* Implementation that uses what ever type was past in as an argument. Suitable for databases that do not support
* {@link JDBCType#NULL}.
*/
NullTypeStrategy NOOP = sqlType -> sqlType;

/**
* {@link SQLType} to use for {@literal null} values.
*
* @param sqlType a fallback value that is considered suitable by the caller.
* @return Guaranteed not to be {@literal null}.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣

*/
SQLType getNullType(SQLType sqlType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,14 +147,12 @@ public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(JdbcMappingCont
*/
@Bean
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations,
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) {
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, JdbcDialect dialect) {

org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect jd
? jd.getArraySupport()
: JdbcArrayColumns.DefaultSupport.INSTANCE;
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect.getArraySupport();
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(), arrayColumns);

return new MappingJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory);
return new MappingJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory, dialect.getNullTypeStrategy());
}

/**
Expand Down Expand Up @@ -222,7 +220,7 @@ public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationContext applicatio
*/
@Bean
public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter,
JdbcMappingContext context, Dialect dialect) {
JdbcMappingContext context, JdbcDialect dialect) {

SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context, jdbcConverter, dialect);
DataAccessStrategyFactory factory = new DataAccessStrategyFactory(sqlGeneratorSource, jdbcConverter, operations,
Expand All @@ -242,7 +240,7 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op
* cannot be determined.
*/
@Bean
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
return DialectResolver.getDialect(operations.getJdbcOperations());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;

/**
Expand All @@ -46,7 +46,7 @@ public class MyBatisJdbcConfiguration extends AbstractJdbcConfiguration {
@Bean
@Override
public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter,
JdbcMappingContext context, Dialect dialect) {
JdbcMappingContext context, JdbcDialect dialect) {

return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, jdbcConverter, operations, session, dialect,
queryMappingConfiguration.orElse(QueryMappingConfiguration.EMPTY));
Expand Down
Loading