Skip to content

Commit 40d97c6

Browse files
committed
SQL type used in array construction depends on Dialect.
Postgres requires the non standard type "FLOAT8" for "DOUBLE". This is accomplished by making the conversion dependent on the dialect. This required a new JdbcDialect interface in order to keep the JDBC annotation out of the relational module. Closes #1033
1 parent 2fff789 commit 40d97c6

File tree

12 files changed

+222
-17
lines changed

12 files changed

+222
-17
lines changed

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

+14-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.sql.Array;
1919
import java.sql.JDBCType;
20+
import java.util.function.Function;
2021

2122
import org.springframework.data.jdbc.support.JdbcUtil;
2223
import org.springframework.jdbc.core.ConnectionCallback;
@@ -33,17 +34,29 @@
3334
public class DefaultJdbcTypeFactory implements JdbcTypeFactory {
3435

3536
private final JdbcOperations operations;
37+
private final Function<JDBCType, String> jdbcTypeToSqlName;
3638

3739
/**
3840
* Creates a new {@link DefaultJdbcTypeFactory}.
3941
*
4042
* @param operations must not be {@literal null}.
4143
*/
4244
public DefaultJdbcTypeFactory(JdbcOperations operations) {
45+
this(operations, JDBCType::getName);
46+
}
47+
48+
/**
49+
* Creates a new {@link DefaultJdbcTypeFactory}.
50+
*
51+
* @param operations must not be {@literal null}.
52+
*/
53+
public DefaultJdbcTypeFactory(JdbcOperations operations, Function<JDBCType, String> jdbcTypeToSqlName) {
4354

4455
Assert.notNull(operations, "JdbcOperations must not be null");
56+
Assert.notNull(jdbcTypeToSqlName, "JdbcTypeToSqlName must not be null");
4557

4658
this.operations = operations;
59+
this.jdbcTypeToSqlName = jdbcTypeToSqlName;
4760
}
4861

4962
@Override
@@ -55,7 +68,7 @@ public Array createArray(Object[] value) {
5568

5669
JDBCType jdbcType = JdbcUtil.jdbcTypeFor(componentType);
5770
Assert.notNull(jdbcType, () -> String.format("Couldn't determine JDBCType for %s", componentType));
58-
String typeName = jdbcType.getName();
71+
String typeName = jdbcTypeToSqlName.apply(jdbcType);
5972

6073
return operations.execute((ConnectionCallback<Array>) c -> c.createArrayOf(typeName, value));
6174
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.core.dialect;
17+
18+
import java.sql.JDBCType;
19+
20+
import org.springframework.data.relational.core.dialect.ArrayColumns;
21+
22+
/**
23+
* {@link org.springframework.data.relational.core.dialect.ArrayColumns} that offer JDBC specific functionality.
24+
*
25+
* @author Jens Schauder
26+
* @since 2.3
27+
*/
28+
public interface JdbcArrayColumns extends ArrayColumns {
29+
30+
JdbcArrayColumns UNSUPPORTED = new JdbcArrayColumns() {
31+
@Override
32+
public boolean isSupported() {
33+
return false;
34+
}
35+
36+
@Override
37+
public Class<?> getArrayType(Class<?> userType) {
38+
throw new UnsupportedOperationException("Array types not supported");
39+
}
40+
41+
@Override
42+
public String getSqlTypeRepresentation(JDBCType jdbcType) {
43+
throw new UnsupportedOperationException("Array types not supported");
44+
}
45+
};
46+
47+
/**
48+
* The appropriate SQL type as a String which should be used to represent the given {@link JDBCType} in an
49+
* {@link java.sql.Array}. Defaults to the name of the argument.
50+
*
51+
* @param jdbcType the {@link JDBCType} value representing the type that should be stored in the
52+
* {@link java.sql.Array}. Must not be {@literal null}.
53+
* @return the appropriate SQL type as a String which should be used to represent the given {@link JDBCType} in an
54+
* {@link java.sql.Array}. Guaranteed to be not {@literal null}.
55+
*/
56+
default String getSqlTypeRepresentation(JDBCType jdbcType) {
57+
return jdbcType.getName();
58+
}
59+
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.core.dialect;
17+
18+
import org.springframework.data.relational.core.dialect.Dialect;
19+
20+
/**
21+
* {@link org.springframework.data.relational.core.dialect.ArrayColumns} that offer JDBC specific functionality.
22+
*
23+
* @author Jens Schauder
24+
* @since 2.3
25+
*/
26+
public interface JdbcDialect extends Dialect {
27+
28+
/**
29+
* Returns the JDBC specific array support object that describes how array-typed columns are supported by this
30+
* dialect.
31+
*
32+
* @return the JDBC specific array support object that describes how array-typed columns are supported by this
33+
* dialect.
34+
*/
35+
@Override
36+
JdbcArrayColumns getArraySupport();
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.core.dialect;
17+
18+
import java.sql.JDBCType;
19+
20+
import org.springframework.data.relational.core.dialect.PostgresDialect;
21+
22+
/**
23+
* JDBC specific Postgres Dialect.
24+
*
25+
* @author Jens Schauder
26+
* @since 2.3
27+
*/
28+
public class JdbcPostgresDialect extends PostgresDialect implements JdbcDialect {
29+
30+
public static final JdbcPostgresDialect INSTANCE = new JdbcPostgresDialect();
31+
private static final JdbcPostgresArrayColumns ARRAY_COLUMNS = new JdbcPostgresArrayColumns();
32+
33+
@Override
34+
public JdbcArrayColumns getArraySupport() {
35+
return ARRAY_COLUMNS;
36+
}
37+
38+
static class JdbcPostgresArrayColumns extends PostgresArrayColumns implements JdbcArrayColumns {
39+
@Override
40+
public String getSqlTypeRepresentation(JDBCType jdbcType) {
41+
return jdbcType == JDBCType.DOUBLE ? "FLOAT8" : jdbcType.getName();
42+
}
43+
}
44+
}

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

+11-6
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.config;
1717

18+
import java.sql.JDBCType;
1819
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Optional;
23+
import java.util.function.Function;
2224

2325
import org.slf4j.Logger;
2426
import org.slf4j.LoggerFactory;
@@ -31,7 +33,6 @@
3133
import org.springframework.context.annotation.Lazy;
3234
import org.springframework.core.convert.converter.Converter;
3335
import org.springframework.data.convert.CustomConversions;
34-
import org.springframework.data.convert.CustomConversions.StoreConversions;
3536
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
3637
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
3738
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
@@ -42,12 +43,11 @@
4243
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
4344
import org.springframework.data.jdbc.core.convert.RelationResolver;
4445
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
45-
import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect;
46+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
4647
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4748
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
4849
import org.springframework.data.mapping.model.SimpleTypeHolder;
4950
import org.springframework.data.relational.core.conversion.RelationalConverter;
50-
import org.springframework.data.relational.core.dialect.Db2Dialect;
5151
import org.springframework.data.relational.core.dialect.Dialect;
5252
import org.springframework.data.relational.core.mapping.NamingStrategy;
5353
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@@ -66,7 +66,7 @@
6666
@Configuration(proxyBeanMethods = false)
6767
public class AbstractJdbcConfiguration implements ApplicationContextAware {
6868

69-
private static Logger LOG = LoggerFactory.getLogger(AbstractJdbcConfiguration.class);
69+
private static final Logger LOG = LoggerFactory.getLogger(AbstractJdbcConfiguration.class);
7070

7171
private ApplicationContext applicationContext;
7272

@@ -100,7 +100,11 @@ public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStra
100100
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations,
101101
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) {
102102

103-
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations());
103+
Function<JDBCType, String> jdbcTypeToSqlName = dialect instanceof JdbcDialect
104+
? ((JdbcDialect) dialect).getArraySupport()::getSqlTypeRepresentation
105+
: JDBCType::getName;
106+
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(),
107+
jdbcTypeToSqlName);
104108

105109
return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory,
106110
dialect.getIdentifierProcessing());
@@ -120,7 +124,8 @@ public JdbcCustomConversions jdbcCustomConversions() {
120124
try {
121125

122126
Dialect dialect = applicationContext.getBean(Dialect.class);
123-
SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER : new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
127+
SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER
128+
: new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
124129

125130
return new JdbcCustomConversions(
126131
CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)), userConverters());

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

+3-7
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,12 @@
3131
import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect;
3232
import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect;
3333
import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect;
34+
import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect;
3435
import org.springframework.data.jdbc.core.dialect.JdbcSqlServerDialect;
35-
import org.springframework.data.relational.core.dialect.Db2Dialect;
3636
import org.springframework.data.relational.core.dialect.Dialect;
37-
import org.springframework.data.relational.core.dialect.H2Dialect;
3837
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
3938
import org.springframework.data.relational.core.dialect.MariaDbDialect;
40-
import org.springframework.data.relational.core.dialect.MySqlDialect;
4139
import org.springframework.data.relational.core.dialect.OracleDialect;
42-
import org.springframework.data.relational.core.dialect.PostgresDialect;
43-
import org.springframework.data.relational.core.dialect.SqlServerDialect;
4440
import org.springframework.data.relational.core.sql.IdentifierProcessing;
4541
import org.springframework.data.util.Optionals;
4642
import org.springframework.jdbc.core.ConnectionCallback;
@@ -132,7 +128,7 @@ private static Dialect getDialect(Connection connection) throws SQLException {
132128
return new MariaDbDialect(getIdentifierProcessing(metaData));
133129
}
134130
if (name.contains("postgresql")) {
135-
return PostgresDialect.INSTANCE;
131+
return JdbcPostgresDialect.INSTANCE;
136132
}
137133
if (name.contains("microsoft")) {
138134
return JdbcSqlServerDialect.INSTANCE;
@@ -144,7 +140,7 @@ private static Dialect getDialect(Connection connection) throws SQLException {
144140
return OracleDialect.INSTANCE;
145141
}
146142

147-
LOG.info(String.format("Couldn't determine Dialect for \"%s\"", name) );
143+
LOG.info(String.format("Couldn't determine Dialect for \"%s\"", name));
148144
return null;
149145
}
150146

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

+25-1
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,24 @@ public void saveAndLoadAnEntityWithList() {
555555
assertThat(reloaded.digits).isEqualTo(Arrays.asList("one", "two", "three"));
556556
}
557557

558+
@Test // GH-1033
559+
@EnabledOnFeature(SUPPORTS_ARRAYS)
560+
public void saveAndLoadAnEntityWithListOfDouble() {
561+
562+
DoubleListOwner doubleListOwner = new DoubleListOwner();
563+
doubleListOwner.digits.addAll(Arrays.asList(1.2, 1.3, 1.4));
564+
565+
DoubleListOwner saved = template.save(doubleListOwner);
566+
567+
assertThat(saved.id).isNotNull();
568+
569+
DoubleListOwner reloaded = template.findById(saved.id, DoubleListOwner.class);
570+
571+
assertThat(reloaded).isNotNull();
572+
assertThat(reloaded.id).isEqualTo(saved.id);
573+
assertThat(reloaded.digits).isEqualTo(Arrays.asList(1.2, 1.3, 1.4));
574+
}
575+
558576
@Test // DATAJDBC-259
559577
@EnabledOnFeature(SUPPORTS_ARRAYS)
560578
public void saveAndLoadAnEntityWithSet() {
@@ -911,14 +929,20 @@ private static class ListOwner {
911929

912930
List<String> digits = new ArrayList<>();
913931
}
914-
915932
@Table("ARRAY_OWNER")
916933
private static class SetOwner {
917934
@Id Long id;
918935

919936
Set<String> digits = new HashSet<>();
920937
}
921938

939+
private static class DoubleListOwner {
940+
941+
@Id Long id;
942+
943+
List<Double> digits = new ArrayList<>();
944+
}
945+
922946
@Data
923947
static class LegoSet {
924948

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
*/
1616
package org.springframework.data.jdbc.testing;
1717

18+
import java.sql.JDBCType;
1819
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Optional;
23+
import java.util.function.Function;
2224

2325
import javax.sql.DataSource;
2426

@@ -40,6 +42,7 @@
4042
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
4143
import org.springframework.data.jdbc.core.convert.RelationResolver;
4244
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
45+
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
4346
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4447
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
4548
import org.springframework.data.jdbc.repository.config.DialectResolver;
@@ -136,11 +139,15 @@ JdbcConverter relationalConverter(RelationalMappingContext mappingContext, @Lazy
136139
CustomConversions conversions, @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
137140
Dialect dialect) {
138141

142+
Function<JDBCType, String> jdbcTypeToSqlName = dialect instanceof JdbcDialect
143+
? ((JdbcDialect) dialect).getArraySupport()::getSqlTypeRepresentation
144+
: JDBCType::getName;
145+
139146
return new BasicJdbcConverter( //
140147
mappingContext, //
141148
relationResolver, //
142149
conversions, //
143-
new DefaultJdbcTypeFactory(template.getJdbcOperations()), //
150+
new DefaultJdbcTypeFactory(template.getJdbcOperations(), jdbcTypeToSqlName), //
144151
dialect.getIdentifierProcessing());
145152
}
146153

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ CREATE TABLE BYTE_ARRAY_OWNER
5252
BINARY_DATA BYTEA NOT NULL
5353
);
5454

55+
CREATE TABLE DOUBLE_LIST_OWNER
56+
(
57+
ID SERIAL PRIMARY KEY,
58+
DIGITS ARRAY[10]
59+
);
60+
5561
CREATE TABLE CHAIN4
5662
(
5763
FOUR SERIAL PRIMARY KEY,

0 commit comments

Comments
 (0)