Browse Source

Dialect dependent handling of JDBC types for NULL.

Not all databases support the JDBCSqlType.NULL.
Therefore this handling was made dialect dependent, with SQL Server and DB2 using the old approach, while all others use JDBCSqlType.NULL

In the process modified AbstractJdbcConfiguration to use JdbcDialect instead of Dialect.

Original pull request #2068
See #1935
See #2031
issue/1935-jdbc-sql-type-for-null
Jens Schauder 7 months ago
parent
commit
bf1842c8cd
No known key found for this signature in database
GPG Key ID: 2BE5D185CD2A1CE6
  1. 25
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java
  2. 4
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java
  3. 10
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java
  4. 11
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java
  5. 10
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java
  6. 33
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/NullTypeStrategy.java
  7. 12
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java
  8. 4
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java
  9. 7
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryUnitTests.java
  10. 5
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java
  11. 3
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfigurationIntegrationTests.java
  12. 11
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

25
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/MappingJdbcConverter.java

@ -31,6 +31,7 @@ import org.springframework.core.convert.ConverterNotFoundException; @@ -31,6 +31,7 @@ import org.springframework.core.convert.ConverterNotFoundException;
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;
@ -73,6 +74,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements @@ -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()
@ -84,15 +86,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements @@ -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);
}
/**
@ -105,13 +99,20 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements @@ -105,13 +99,20 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
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();
}
@ -250,7 +251,11 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements @@ -250,7 +251,11 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
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);
}

4
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlParametersFactory.java

@ -189,9 +189,7 @@ public class SqlParametersFactory { @@ -189,9 +189,7 @@ public class SqlParametersFactory {
private void addConvertedValue(SqlIdentifierParameterSource parameterSource, @Nullable Object value,
SqlIdentifier paramName, Class<?> javaType, SQLType sqlType) {
JdbcValue jdbcValue = value != null
? converter.writeJdbcValue(value, javaType, sqlType)
: JdbcValue.of(null, JDBCType.NULL);
JdbcValue jdbcValue = converter.writeJdbcValue(value, javaType, sqlType);
parameterSource.addValue( //
paramName, //

10
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDb2Dialect.java

@ -66,4 +66,14 @@ public class JdbcDb2Dialect extends Db2Dialect implements JdbcDialect { @@ -66,4 +66,14 @@ public class JdbcDb2Dialect extends Db2Dialect implements JdbcDialect {
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;
}
}

11
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java

@ -37,4 +37,15 @@ public interface JdbcDialect extends Dialect { @@ -37,4 +37,15 @@ public interface JdbcDialect extends Dialect {
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;
}
}

10
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcSqlServerDialect.java

@ -70,4 +70,14 @@ public class JdbcSqlServerDialect extends SqlServerDialect implements JdbcDialec @@ -70,4 +70,14 @@ public class JdbcSqlServerDialect extends SqlServerDialect implements JdbcDialec
}
}
/**
* 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;
}
}

33
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/NullTypeStrategy.java

@ -0,0 +1,33 @@ @@ -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;
/**
* 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}.
*/
SQLType getNullType(SQLType sqlType);
}

12
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java

@ -147,14 +147,12 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { @@ -147,14 +147,12 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
*/
@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());
}
/**
@ -222,7 +220,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { @@ -222,7 +220,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
*/
@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,
@ -242,7 +240,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { @@ -242,7 +240,7 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
* cannot be determined.
*/
@Bean
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
return DialectResolver.getDialect(operations.getJdbcOperations());
}

4
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfiguration.java

@ -24,9 +24,9 @@ import org.springframework.context.annotation.Configuration; @@ -24,9 +24,9 @@ import org.springframework.context.annotation.Configuration;
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;
/**
@ -46,7 +46,7 @@ public class MyBatisJdbcConfiguration extends AbstractJdbcConfiguration { @@ -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));

7
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlParametersFactoryUnitTests.java

@ -22,6 +22,7 @@ import static org.mockito.Mockito.*; @@ -22,6 +22,7 @@ import static org.mockito.Mockito.*;
import static org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategyUnitTests.*;
import java.sql.JDBCType;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -173,7 +174,7 @@ class SqlParametersFactoryUnitTests { @@ -173,7 +174,7 @@ class SqlParametersFactoryUnitTests {
assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L);
assertThat(sqlParameterSource.getValue("dummy_enum")).isEqualTo(DummyEnum.ONE.name());
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(1111);
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(Types.OTHER);
}
@Test // GH-1935
@ -187,8 +188,8 @@ class SqlParametersFactoryUnitTests { @@ -187,8 +188,8 @@ class SqlParametersFactoryUnitTests {
Identifier.empty(), IdValueSource.PROVIDED);
assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L);
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(JDBCType.NULL.getVendorTypeNumber());
assertThat(sqlParameterSource.getValue("dummy_enum")).isNull();
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(Types.NULL);
}
@Test // GH-1935
@ -201,7 +202,7 @@ class SqlParametersFactoryUnitTests { @@ -201,7 +202,7 @@ class SqlParametersFactoryUnitTests {
assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L);
assertThat(sqlParameterSource.getValue("dummy_enum")).isEqualTo(DummyEnum.ONE.name());
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(12);
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(Types.VARCHAR);
}

5
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfigurationIntegrationTests.java

@ -36,6 +36,7 @@ import org.springframework.data.jdbc.core.JdbcAggregateTemplate; @@ -36,6 +36,7 @@ import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.relational.RelationalManagedTypes;
import org.springframework.data.relational.core.dialect.Dialect;
@ -142,7 +143,7 @@ class AbstractJdbcConfigurationIntegrationTests { @@ -142,7 +143,7 @@ class AbstractJdbcConfigurationIntegrationTests {
@Override
@Bean
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
return new DummyDialect();
}
@ -165,7 +166,7 @@ class AbstractJdbcConfigurationIntegrationTests { @@ -165,7 +166,7 @@ class AbstractJdbcConfigurationIntegrationTests {
private static class Blubb {}
private static class DummyDialect implements Dialect {
private static class DummyDialect implements JdbcDialect {
@Override
public LimitClause limit() {
return null;

3
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/config/MyBatisJdbcConfigurationIntegrationTests.java

@ -26,6 +26,7 @@ import org.springframework.context.annotation.Bean; @@ -26,6 +26,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect;
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
import org.springframework.data.relational.core.dialect.Dialect;
@ -70,7 +71,7 @@ public class MyBatisJdbcConfigurationIntegrationTests extends AbstractJdbcConfig @@ -70,7 +71,7 @@ public class MyBatisJdbcConfigurationIntegrationTests extends AbstractJdbcConfig
@Override
@Bean
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
return JdbcHsqlDbDialect.INSTANCE;
}
}

11
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

@ -36,7 +36,6 @@ import org.springframework.context.annotation.Primary; @@ -36,7 +36,6 @@ import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.convert.*;
import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
@ -162,17 +161,15 @@ public class TestConfiguration { @@ -162,17 +161,15 @@ public class TestConfiguration {
@Bean
JdbcConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy RelationResolver relationResolver,
CustomConversions conversions, @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
Dialect dialect) {
JdbcDialect dialect) {
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect
? ((JdbcDialect) dialect).getArraySupport()
: JdbcArrayColumns.DefaultSupport.INSTANCE;
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect.getArraySupport();
return new MappingJdbcConverter( //
mappingContext, //
relationResolver, //
conversions, //
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns));
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns), dialect.getNullTypeStrategy());
}
/**
@ -188,7 +185,7 @@ public class TestConfiguration { @@ -188,7 +185,7 @@ public class TestConfiguration {
}
@Bean
Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
return DialectResolver.getDialect(operations.getJdbcOperations());
}

Loading…
Cancel
Save