Browse Source

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
Original pull request: #1037.
pull/1064/head
Jens Schauder 4 years ago committed by Mark Paluch
parent
commit
bc0d307415
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 15
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultJdbcTypeFactory.java
  2. 60
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcArrayColumns.java
  3. 37
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java
  4. 44
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcPostgresDialect.java
  5. 17
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java
  6. 10
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/DialectResolver.java
  7. 26
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
  8. 9
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
  9. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql
  10. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql
  11. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql
  12. 3
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java

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

@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core.convert; @@ -17,6 +17,7 @@ package org.springframework.data.jdbc.core.convert;
import java.sql.Array;
import java.sql.JDBCType;
import java.util.function.Function;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.jdbc.core.ConnectionCallback;
@ -33,6 +34,7 @@ import org.springframework.util.Assert; @@ -33,6 +34,7 @@ import org.springframework.util.Assert;
public class DefaultJdbcTypeFactory implements JdbcTypeFactory {
private final JdbcOperations operations;
private final Function<JDBCType, String> jdbcTypeToSqlName;
/**
* Creates a new {@link DefaultJdbcTypeFactory}.
@ -40,10 +42,21 @@ public class DefaultJdbcTypeFactory implements JdbcTypeFactory { @@ -40,10 +42,21 @@ public class DefaultJdbcTypeFactory implements JdbcTypeFactory {
* @param operations must not be {@literal null}.
*/
public DefaultJdbcTypeFactory(JdbcOperations operations) {
this(operations, JDBCType::getName);
}
/**
* Creates a new {@link DefaultJdbcTypeFactory}.
*
* @param operations must not be {@literal null}.
*/
public DefaultJdbcTypeFactory(JdbcOperations operations, Function<JDBCType, String> jdbcTypeToSqlName) {
Assert.notNull(operations, "JdbcOperations must not be null");
Assert.notNull(jdbcTypeToSqlName, "JdbcTypeToSqlName must not be null");
this.operations = operations;
this.jdbcTypeToSqlName = jdbcTypeToSqlName;
}
@Override
@ -55,7 +68,7 @@ public class DefaultJdbcTypeFactory implements JdbcTypeFactory { @@ -55,7 +68,7 @@ public class DefaultJdbcTypeFactory implements JdbcTypeFactory {
JDBCType jdbcType = JdbcUtil.jdbcTypeFor(componentType);
Assert.notNull(jdbcType, () -> String.format("Couldn't determine JDBCType for %s", componentType));
String typeName = jdbcType.getName();
String typeName = jdbcTypeToSqlName.apply(jdbcType);
return operations.execute((ConnectionCallback<Array>) c -> c.createArrayOf(typeName, value));
}

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

@ -0,0 +1,60 @@ @@ -0,0 +1,60 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core.dialect;
import java.sql.JDBCType;
import org.springframework.data.relational.core.dialect.ArrayColumns;
/**
* {@link org.springframework.data.relational.core.dialect.ArrayColumns} that offer JDBC specific functionality.
*
* @author Jens Schauder
* @since 2.3
*/
public interface JdbcArrayColumns extends ArrayColumns {
JdbcArrayColumns UNSUPPORTED = new JdbcArrayColumns() {
@Override
public boolean isSupported() {
return false;
}
@Override
public Class<?> getArrayType(Class<?> userType) {
throw new UnsupportedOperationException("Array types not supported");
}
@Override
public String getSqlTypeRepresentation(JDBCType jdbcType) {
throw new UnsupportedOperationException("Array types not supported");
}
};
/**
* The appropriate SQL type as a String which should be used to represent the given {@link JDBCType} in an
* {@link java.sql.Array}. Defaults to the name of the argument.
*
* @param jdbcType the {@link JDBCType} value representing the type that should be stored in the
* {@link java.sql.Array}. Must not be {@literal null}.
* @return the appropriate SQL type as a String which should be used to represent the given {@link JDBCType} in an
* {@link java.sql.Array}. Guaranteed to be not {@literal null}.
*/
default String getSqlTypeRepresentation(JDBCType jdbcType) {
return jdbcType.getName();
}
}

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

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core.dialect;
import org.springframework.data.relational.core.dialect.Dialect;
/**
* {@link org.springframework.data.relational.core.dialect.ArrayColumns} that offer JDBC specific functionality.
*
* @author Jens Schauder
* @since 2.3
*/
public interface JdbcDialect extends Dialect {
/**
* Returns the JDBC specific array support object that describes how array-typed columns are supported by this
* dialect.
*
* @return the JDBC specific array support object that describes how array-typed columns are supported by this
* dialect.
*/
@Override
JdbcArrayColumns getArraySupport();
}

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

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.core.dialect;
import java.sql.JDBCType;
import org.springframework.data.relational.core.dialect.PostgresDialect;
/**
* JDBC specific Postgres Dialect.
*
* @author Jens Schauder
* @since 2.3
*/
public class JdbcPostgresDialect extends PostgresDialect implements JdbcDialect {
public static final JdbcPostgresDialect INSTANCE = new JdbcPostgresDialect();
private static final JdbcPostgresArrayColumns ARRAY_COLUMNS = new JdbcPostgresArrayColumns();
@Override
public JdbcArrayColumns getArraySupport() {
return ARRAY_COLUMNS;
}
static class JdbcPostgresArrayColumns extends PostgresArrayColumns implements JdbcArrayColumns {
@Override
public String getSqlTypeRepresentation(JDBCType jdbcType) {
return jdbcType == JDBCType.DOUBLE ? "FLOAT8" : jdbcType.getName();
}
}
}

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

@ -15,10 +15,12 @@ @@ -15,10 +15,12 @@
*/
package org.springframework.data.jdbc.repository.config;
import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -31,7 +33,6 @@ import org.springframework.context.annotation.Configuration; @@ -31,7 +33,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.CustomConversions.StoreConversions;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
@ -42,12 +43,11 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -42,12 +43,11 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
import org.springframework.data.mapping.model.SimpleTypeHolder;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.dialect.Db2Dialect;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@ -66,7 +66,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; @@ -66,7 +66,7 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@Configuration(proxyBeanMethods = false)
public class AbstractJdbcConfiguration implements ApplicationContextAware {
private static Logger LOG = LoggerFactory.getLogger(AbstractJdbcConfiguration.class);
private static final Logger LOG = LoggerFactory.getLogger(AbstractJdbcConfiguration.class);
private ApplicationContext applicationContext;
@ -100,7 +100,11 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { @@ -100,7 +100,11 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
public JdbcConverter jdbcConverter(JdbcMappingContext mappingContext, NamedParameterJdbcOperations operations,
@Lazy RelationResolver relationResolver, JdbcCustomConversions conversions, Dialect dialect) {
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations());
Function<JDBCType, String> jdbcTypeToSqlName = dialect instanceof JdbcDialect
? ((JdbcDialect) dialect).getArraySupport()::getSqlTypeRepresentation
: JDBCType::getName;
DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(operations.getJdbcOperations(),
jdbcTypeToSqlName);
return new BasicJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory,
dialect.getIdentifierProcessing());
@ -120,7 +124,8 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware { @@ -120,7 +124,8 @@ public class AbstractJdbcConfiguration implements ApplicationContextAware {
try {
Dialect dialect = applicationContext.getBean(Dialect.class);
SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER : new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
SimpleTypeHolder simpleTypeHolder = dialect.simpleTypes().isEmpty() ? JdbcSimpleTypes.HOLDER
: new SimpleTypeHolder(dialect.simpleTypes(), JdbcSimpleTypes.HOLDER);
return new JdbcCustomConversions(
CustomConversions.StoreConversions.of(simpleTypeHolder, storeConverters(dialect)), userConverters());

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

@ -31,16 +31,12 @@ import org.springframework.dao.NonTransientDataAccessException; @@ -31,16 +31,12 @@ import org.springframework.dao.NonTransientDataAccessException;
import org.springframework.data.jdbc.core.dialect.JdbcDb2Dialect;
import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect;
import org.springframework.data.jdbc.core.dialect.JdbcMySqlDialect;
import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect;
import org.springframework.data.jdbc.core.dialect.JdbcSqlServerDialect;
import org.springframework.data.relational.core.dialect.Db2Dialect;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.H2Dialect;
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
import org.springframework.data.relational.core.dialect.MariaDbDialect;
import org.springframework.data.relational.core.dialect.MySqlDialect;
import org.springframework.data.relational.core.dialect.OracleDialect;
import org.springframework.data.relational.core.dialect.PostgresDialect;
import org.springframework.data.relational.core.dialect.SqlServerDialect;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.util.Optionals;
import org.springframework.jdbc.core.ConnectionCallback;
@ -132,7 +128,7 @@ public class DialectResolver { @@ -132,7 +128,7 @@ public class DialectResolver {
return new MariaDbDialect(getIdentifierProcessing(metaData));
}
if (name.contains("postgresql")) {
return PostgresDialect.INSTANCE;
return JdbcPostgresDialect.INSTANCE;
}
if (name.contains("microsoft")) {
return JdbcSqlServerDialect.INSTANCE;
@ -144,7 +140,7 @@ public class DialectResolver { @@ -144,7 +140,7 @@ public class DialectResolver {
return OracleDialect.INSTANCE;
}
LOG.info(String.format("Couldn't determine Dialect for \"%s\"", name) );
LOG.info(String.format("Couldn't determine Dialect for \"%s\"", name));
return null;
}

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

@ -555,6 +555,24 @@ public class JdbcAggregateTemplateIntegrationTests { @@ -555,6 +555,24 @@ public class JdbcAggregateTemplateIntegrationTests {
assertThat(reloaded.digits).isEqualTo(Arrays.asList("one", "two", "three"));
}
@Test // GH-1033
@EnabledOnFeature(SUPPORTS_ARRAYS)
public void saveAndLoadAnEntityWithListOfDouble() {
DoubleListOwner doubleListOwner = new DoubleListOwner();
doubleListOwner.digits.addAll(Arrays.asList(1.2, 1.3, 1.4));
DoubleListOwner saved = template.save(doubleListOwner);
assertThat(saved.id).isNotNull();
DoubleListOwner reloaded = template.findById(saved.id, DoubleListOwner.class);
assertThat(reloaded).isNotNull();
assertThat(reloaded.id).isEqualTo(saved.id);
assertThat(reloaded.digits).isEqualTo(Arrays.asList(1.2, 1.3, 1.4));
}
@Test // DATAJDBC-259
@EnabledOnFeature(SUPPORTS_ARRAYS)
public void saveAndLoadAnEntityWithSet() {
@ -911,7 +929,6 @@ public class JdbcAggregateTemplateIntegrationTests { @@ -911,7 +929,6 @@ public class JdbcAggregateTemplateIntegrationTests {
List<String> digits = new ArrayList<>();
}
@Table("ARRAY_OWNER")
private static class SetOwner {
@Id Long id;
@ -919,6 +936,13 @@ public class JdbcAggregateTemplateIntegrationTests { @@ -919,6 +936,13 @@ public class JdbcAggregateTemplateIntegrationTests {
Set<String> digits = new HashSet<>();
}
private static class DoubleListOwner {
@Id Long id;
List<Double> digits = new ArrayList<>();
}
@Data
static class LegoSet {

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

@ -15,10 +15,12 @@ @@ -15,10 +15,12 @@
*/
package org.springframework.data.jdbc.testing;
import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import javax.sql.DataSource;
@ -40,6 +42,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -40,6 +42,7 @@ import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
import org.springframework.data.jdbc.repository.config.DialectResolver;
@ -136,11 +139,15 @@ public class TestConfiguration { @@ -136,11 +139,15 @@ public class TestConfiguration {
CustomConversions conversions, @Qualifier("namedParameterJdbcTemplate") NamedParameterJdbcOperations template,
Dialect dialect) {
Function<JDBCType, String> jdbcTypeToSqlName = dialect instanceof JdbcDialect
? ((JdbcDialect) dialect).getArraySupport()::getSqlTypeRepresentation
: JDBCType::getName;
return new BasicJdbcConverter( //
mappingContext, //
relationResolver, //
conversions, //
new DefaultJdbcTypeFactory(template.getJdbcOperations()), //
new DefaultJdbcTypeFactory(template.getJdbcOperations(), jdbcTypeToSqlName), //
dialect.getIdentifierProcessing());
}

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

@ -52,6 +52,12 @@ CREATE TABLE BYTE_ARRAY_OWNER @@ -52,6 +52,12 @@ CREATE TABLE BYTE_ARRAY_OWNER
BINARY_DATA BYTEA NOT NULL
);
CREATE TABLE DOUBLE_LIST_OWNER
(
ID SERIAL PRIMARY KEY,
DIGITS ARRAY[10]
);
CREATE TABLE CHAIN4
(
FOUR SERIAL PRIMARY KEY,

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

@ -54,6 +54,12 @@ CREATE TABLE BYTE_ARRAY_OWNER @@ -54,6 +54,12 @@ CREATE TABLE BYTE_ARRAY_OWNER
BINARY_DATA VARBINARY(20) NOT NULL
);
CREATE TABLE DOUBLE_LIST_OWNER
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
DIGITS DOUBLE PRECISION ARRAY[10]
);
CREATE TABLE CHAIN4
(
FOUR BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 40) PRIMARY KEY,

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

@ -61,6 +61,12 @@ CREATE TABLE "ARRAY_OWNER" @@ -61,6 +61,12 @@ CREATE TABLE "ARRAY_OWNER"
MULTIDIMENSIONAL VARCHAR(20)[10][10]
);
CREATE TABLE DOUBLE_LIST_OWNER
(
ID SERIAL PRIMARY KEY,
DIGITS DOUBLE PRECISION[10]
);
CREATE TABLE BYTE_ARRAY_OWNER
(
ID SERIAL PRIMARY KEY,

3
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.relational.core.dialect;
import java.sql.JDBCType;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -178,7 +179,7 @@ public class PostgresDialect extends AbstractDialect { @@ -178,7 +179,7 @@ public class PostgresDialect extends AbstractDialect {
}
}
static class PostgresArrayColumns implements ArrayColumns {
protected static class PostgresArrayColumns implements ArrayColumns {
/*
* (non-Javadoc)

Loading…
Cancel
Save