Skip to content

Commit bf1842c

Browse files
committed
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
1 parent 2ac4bbd commit bf1842c

File tree

12 files changed

+100
-35
lines changed

12 files changed

+100
-35
lines changed

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

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.core.convert.converter.Converter;
3232
import org.springframework.core.convert.converter.ConverterRegistry;
3333
import org.springframework.data.convert.CustomConversions;
34+
import org.springframework.data.jdbc.core.dialect.NullTypeStrategy;
3435
import org.springframework.data.jdbc.core.mapping.AggregateReference;
3536
import org.springframework.data.jdbc.core.mapping.JdbcValue;
3637
import org.springframework.data.jdbc.support.JdbcUtil;
@@ -73,6 +74,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
7374

7475
private final JdbcTypeFactory typeFactory;
7576
private final RelationResolver relationResolver;
77+
private final NullTypeStrategy nullTypeStrategy;
7678

7779
/**
7880
* Creates a new {@link MappingJdbcConverter} given {@link MappingContext} and a {@link JdbcTypeFactory#unsupported()
@@ -84,15 +86,7 @@ public class MappingJdbcConverter extends MappingRelationalConverter implements
8486
* @param relationResolver used to fetch additional relations from the database. Must not be {@literal null}.
8587
*/
8688
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver) {
87-
88-
super(context, new JdbcCustomConversions());
89-
90-
Assert.notNull(relationResolver, "RelationResolver must not be null");
91-
92-
this.typeFactory = JdbcTypeFactory.unsupported();
93-
this.relationResolver = relationResolver;
94-
95-
registerAggregateReferenceConverters();
89+
this(context, relationResolver, new JdbcCustomConversions(), JdbcTypeFactory.unsupported(), NullTypeStrategy.DEFAULT);
9690
}
9791

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

102+
this(context, relationResolver, conversions, typeFactory, NullTypeStrategy.DEFAULT);
103+
}
104+
105+
public MappingJdbcConverter(RelationalMappingContext context, RelationResolver relationResolver, CustomConversions conversions, JdbcTypeFactory typeFactory, NullTypeStrategy nullTypeStrategy) {
106+
108107
super(context, conversions);
109108

110109
Assert.notNull(typeFactory, "JdbcTypeFactory must not be null");
111110
Assert.notNull(relationResolver, "RelationResolver must not be null");
111+
Assert.notNull(nullTypeStrategy, "NullTypeStrategy must not be null");
112112

113113
this.typeFactory = typeFactory;
114114
this.relationResolver = relationResolver;
115+
this.nullTypeStrategy = nullTypeStrategy;
115116

116117
registerAggregateReferenceConverters();
117118
}
@@ -250,7 +251,11 @@ public JdbcValue writeJdbcValue(@Nullable Object value, TypeInformation<?> colum
250251
return result;
251252
}
252253

253-
if (convertedValue == null || !convertedValue.getClass().isArray()) {
254+
if (convertedValue == null ) {
255+
return JdbcValue.of(null, nullTypeStrategy.getNullType(sqlType));
256+
}
257+
258+
if (!convertedValue.getClass().isArray()) {
254259
return JdbcValue.of(convertedValue, sqlType);
255260
}
256261

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,7 @@ private void addConvertedPropertyValue(SqlIdentifierParameterSource parameterSou
189189
private void addConvertedValue(SqlIdentifierParameterSource parameterSource, @Nullable Object value,
190190
SqlIdentifier paramName, Class<?> javaType, SQLType sqlType) {
191191

192-
JdbcValue jdbcValue = value != null
193-
? converter.writeJdbcValue(value, javaType, sqlType)
194-
: JdbcValue.of(null, JDBCType.NULL);
192+
JdbcValue jdbcValue = converter.writeJdbcValue(value, javaType, sqlType);
195193

196194
parameterSource.addValue( //
197195
paramName, //

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,14 @@ public Timestamp convert(OffsetDateTime source) {
6666
return Timestamp.from(source.toInstant());
6767
}
6868
}
69+
70+
/**
71+
* DB2 does not support {@link java.sql.JDBCType#NULL}. Therefore it uses {@link NullTypeStrategy#NOOP}.
72+
*
73+
* @since 4.0
74+
*/
75+
@Override
76+
public NullTypeStrategy getNullTypeStrategy() {
77+
return NullTypeStrategy.NOOP;
78+
}
6979
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,15 @@ default JdbcArrayColumns getArraySupport() {
3737
return JdbcArrayColumns.Unsupported.INSTANCE;
3838
}
3939

40+
/**
41+
* Determines how to handle the {@link java.sql.JDBCType} of {@literal null} values.
42+
*
43+
* The default is suitable for all databases supporting {@link java.sql.JDBCType#NULL}.
44+
*
45+
* @return a strategy to handle the {@link java.sql.JDBCType} of {@literal null} values. Guaranteed not to be null.
46+
* @since 4.0
47+
*/
48+
default NullTypeStrategy getNullTypeStrategy() {
49+
return NullTypeStrategy.DEFAULT;
50+
}
4051
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,14 @@ public Instant convert(DateTimeOffset source) {
7070
}
7171
}
7272

73+
74+
/**
75+
* SQL Server does not support {@link java.sql.JDBCType#NULL}. Therefore it uses {@link NullTypeStrategy#NOOP}.
76+
*
77+
* @since 4.0
78+
*/
79+
@Override
80+
public NullTypeStrategy getNullTypeStrategy() {
81+
return NullTypeStrategy.NOOP;
82+
}
7383
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.springframework.data.jdbc.core.dialect;
2+
3+
import java.sql.JDBCType;
4+
import java.sql.SQLType;
5+
6+
/**
7+
* Interface for defining what to {@link SQLType} to use for {@literal null} values.
8+
*
9+
* @author Jens Schauder
10+
* @since 4.0
11+
*/
12+
public interface NullTypeStrategy {
13+
14+
/**
15+
* Implementation that always uses {@link JDBCType#NULL}. Suitable for all databases that actually support this
16+
* {@link JDBCType}.
17+
*/
18+
NullTypeStrategy DEFAULT = sqlType -> JDBCType.NULL;
19+
20+
/**
21+
* Implementation that uses what ever type was past in as an argument. Suitable for databases that do not support
22+
* {@link JDBCType#NULL}.
23+
*/
24+
NullTypeStrategy NOOP = sqlType -> sqlType;
25+
26+
/**
27+
* {@link SQLType} to use for {@literal null} values.
28+
*
29+
* @param sqlType a fallback value that is considered suitable by the caller.
30+
* @return Guaranteed not to be {@literal null}.
31+
*/
32+
SQLType getNullType(SQLType sqlType);
33+
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,12 @@ public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(JdbcMappingCont
147147
*/
148148
@Bean
149149
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations,
150-
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) {
150+
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, JdbcDialect dialect) {
151151

152-
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect jd
153-
? jd.getArraySupport()
154-
: JdbcArrayColumns.DefaultSupport.INSTANCE;
152+
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect.getArraySupport();
155153
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(), arrayColumns);
156154

157-
return new MappingJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory);
155+
return new MappingJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory, dialect.getNullTypeStrategy());
158156
}
159157

160158
/**
@@ -222,7 +220,7 @@ public JdbcAggregateTemplate jdbcAggregateTemplate(ApplicationContext applicatio
222220
*/
223221
@Bean
224222
public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter,
225-
JdbcMappingContext context, Dialect dialect) {
223+
JdbcMappingContext context, JdbcDialect dialect) {
226224

227225
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context, jdbcConverter, dialect);
228226
DataAccessStrategyFactory factory = new DataAccessStrategyFactory(sqlGeneratorSource, jdbcConverter, operations,
@@ -242,7 +240,7 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op
242240
* cannot be determined.
243241
*/
244242
@Bean
245-
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
243+
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
246244
return DialectResolver.getDialect(operations.getJdbcOperations());
247245
}
248246

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
2525
import org.springframework.data.jdbc.core.convert.JdbcConverter;
2626
import org.springframework.data.jdbc.core.convert.QueryMappingConfiguration;
27+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
2728
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
2829
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
29-
import org.springframework.data.relational.core.dialect.Dialect;
3030
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
3131

3232
/**
@@ -46,7 +46,7 @@ public class MyBatisJdbcConfiguration extends AbstractJdbcConfiguration {
4646
@Bean
4747
@Override
4848
public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations operations, JdbcConverter jdbcConverter,
49-
JdbcMappingContext context, Dialect dialect) {
49+
JdbcMappingContext context, JdbcDialect dialect) {
5050

5151
return MyBatisDataAccessStrategy.createCombinedAccessStrategy(context, jdbcConverter, operations, session, dialect,
5252
queryMappingConfiguration.orElse(QueryMappingConfiguration.EMPTY));

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategyUnitTests.*;
2323

2424
import java.sql.JDBCType;
25+
import java.sql.Types;
2526
import java.util.ArrayList;
2627
import java.util.HashMap;
2728
import java.util.List;
@@ -173,7 +174,7 @@ void enumParameterIsNotNullReturnCorrectSqlTypeFromConverter() {
173174

174175
assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L);
175176
assertThat(sqlParameterSource.getValue("dummy_enum")).isEqualTo(DummyEnum.ONE.name());
176-
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(1111);
177+
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(Types.OTHER);
177178
}
178179

179180
@Test // GH-1935
@@ -187,8 +188,8 @@ void enumParameterIsNullReturnCorrectSqlTypeFromConverter() {
187188
Identifier.empty(), IdValueSource.PROVIDED);
188189

189190
assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L);
190-
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(JDBCType.NULL.getVendorTypeNumber());
191191
assertThat(sqlParameterSource.getValue("dummy_enum")).isNull();
192+
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(Types.NULL);
192193
}
193194

194195
@Test // GH-1935
@@ -201,7 +202,7 @@ void enumParameterIsNotNullReturnCorrectSqlTypeWithoutConverter() {
201202

202203
assertThat(sqlParameterSource.getValue("id")).isEqualTo(23L);
203204
assertThat(sqlParameterSource.getValue("dummy_enum")).isEqualTo(DummyEnum.ONE.name());
204-
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(12);
205+
assertThat(sqlParameterSource.getSqlType("dummy_enum")).isEqualTo(Types.VARCHAR);
205206

206207
}
207208

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
3737
import org.springframework.data.jdbc.core.convert.JdbcConverter;
3838
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
39+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
3940
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4041
import org.springframework.data.relational.RelationalManagedTypes;
4142
import org.springframework.data.relational.core.dialect.Dialect;
@@ -142,7 +143,7 @@ static class AbstractJdbcConfigurationUnderTest extends AbstractJdbcConfiguratio
142143

143144
@Override
144145
@Bean
145-
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
146+
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
146147
return new DummyDialect();
147148
}
148149

@@ -165,7 +166,7 @@ private static class Blah {}
165166

166167
private static class Blubb {}
167168

168-
private static class DummyDialect implements Dialect {
169+
private static class DummyDialect implements JdbcDialect {
169170
@Override
170171
public LimitClause limit() {
171172
return null;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.context.annotation.Configuration;
2727
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
2828
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
29+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
2930
import org.springframework.data.jdbc.core.dialect.JdbcHsqlDbDialect;
3031
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy;
3132
import org.springframework.data.relational.core.dialect.Dialect;
@@ -70,7 +71,7 @@ public static class MyBatisJdbcConfigurationUnderTest extends MyBatisJdbcConfigu
7071

7172
@Override
7273
@Bean
73-
public Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
74+
public JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
7475
return JdbcHsqlDbDialect.INSTANCE;
7576
}
7677
}

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.springframework.context.annotation.Profile;
3737
import org.springframework.data.convert.CustomConversions;
3838
import org.springframework.data.jdbc.core.convert.*;
39-
import org.springframework.data.jdbc.core.dialect.JdbcArrayColumns;
4039
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
4140
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4241
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
@@ -162,17 +161,15 @@ private List<Object> storeConverters(Dialect dialect) {
162161
@Bean
163162
JdbcConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy RelationResolver relationResolver,
164163
CustomConversions conversions, @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
165-
Dialect dialect) {
164+
JdbcDialect dialect) {
166165

167-
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect instanceof JdbcDialect
168-
? ((JdbcDialect) dialect).getArraySupport()
169-
: JdbcArrayColumns.DefaultSupport.INSTANCE;
166+
org.springframework.data.jdbc.core.dialect.JdbcArrayColumns arrayColumns = dialect.getArraySupport();
170167

171168
return new MappingJdbcConverter( //
172169
mappingContext, //
173170
relationResolver, //
174171
conversions, //
175-
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns));
172+
new DefaultJdbcTypeFactory(template.getJdbcOperations(), arrayColumns), dialect.getNullTypeStrategy());
176173
}
177174

178175
/**
@@ -188,7 +185,7 @@ public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(JdbcMappingCont
188185
}
189186

190187
@Bean
191-
Dialect jdbcDialect(NamedParameterJdbcOperations operations) {
188+
JdbcDialect jdbcDialect(NamedParameterJdbcOperations operations) {
192189
return DialectResolver.getDialect(operations.getJdbcOperations());
193190
}
194191

0 commit comments

Comments
 (0)