DATAJDBC-259 - Reading and writing to SQL array types.

Currently works for HsqlDb and Postgres, since the others do not support an array column type.

Original pull request: #113.
This commit is contained in:
Jens Schauder
2019-01-24 08:50:59 +01:00
committed by Mark Paluch
parent 75ea44536f
commit 83ca6b4739
13 changed files with 228 additions and 30 deletions

View File

@@ -18,6 +18,8 @@ package org.springframework.data.jdbc.core;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.sql.Connection;
import java.sql.JDBCType;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -301,13 +303,30 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
} else {
Object value = propertyAccessor.getProperty(property);
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
Object convertedValue = convertForWrite(property, value);
parameters.addValue(prefix + property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
}
});
return parameters;
}
@Nullable
private Object convertForWrite(RelationalPersistentProperty property, @Nullable Object value) {
Object convertedValue = converter.writeValue(value, ClassTypeInformation.from(property.getColumnType()));
if (convertedValue == null || !convertedValue.getClass().isArray()) {
return convertedValue;
}
Class<?> componentType = convertedValue.getClass().getComponentType();
String typeName = JDBCType.valueOf(JdbcUtil.sqlTypeFor(componentType)).getName();
return operations.getJdbcOperations().execute(
(Connection c) -> c.createArrayOf(typeName, (Object[]) convertedValue)
);
}
@SuppressWarnings("unchecked")
@Nullable

View File

@@ -110,7 +110,7 @@ public class EntityRowMapper<T> implements RowMapper<T> {
private Object readOrLoadProperty(ResultSet resultSet, @Nullable Object id, RelationalPersistentProperty property,
String prefix) {
if (property.isCollectionLike() && id != null) {
if (property.isCollectionOfEntitiesLike() && id != null) {
return accessStrategy.findAllByProperty(id, property);
} else if (property.isMap() && id != null) {
return ITERABLE_OF_ENTRY_TO_MAP_CONVERTER.convert(accessStrategy.findAllByProperty(id, property));

View File

@@ -15,6 +15,9 @@
*/
package org.springframework.data.jdbc.core.convert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.ConverterNotFoundException;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.mapping.context.MappingContext;
@@ -26,6 +29,9 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import java.sql.Array;
import java.sql.SQLException;
/**
* {@link RelationalConverter} that uses a {@link MappingContext} to apply basic conversion of relational values to
* property values.
@@ -40,6 +46,8 @@ import org.springframework.lang.Nullable;
*/
public class BasicJdbcConverter extends BasicRelationalConverter {
private static final Logger LOG = LoggerFactory.getLogger(BasicJdbcConverter.class);
/**
* Creates a new {@link BasicRelationalConverter} given {@link MappingContext}.
*
@@ -85,6 +93,14 @@ public class BasicJdbcConverter extends BasicRelationalConverter {
return AggregateReference.to(readValue(value, idType));
}
if (value instanceof Array) {
try {
return readValue(((Array) value).getArray(), type);
} catch (SQLException | ConverterNotFoundException e ) {
LOG.info("Failed to extract a value of type %s from an Array. Attempting to use standard conversions.", e);
}
}
return super.readValue(value, type);
}

View File

@@ -63,6 +63,10 @@ public class JdbcMappingContext extends RelationalMappingContext {
@Override
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
return super.shouldCreatePersistentEntityFor(type) && !AggregateReference.class.isAssignableFrom(type.getType());
return super.shouldCreatePersistentEntityFor(type) //
&& !AggregateReference.class.isAssignableFrom(type.getType()) //
&& !type.isCollectionLike();
}
}

View File

@@ -21,9 +21,13 @@ import static org.assertj.core.api.Assertions.*;
import lombok.Data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.assertj.core.api.SoftAssertions;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -38,8 +42,10 @@ import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.relational.core.conversion.RelationalConverter;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
import org.springframework.test.annotation.ProfileValueUtils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
@@ -318,6 +324,102 @@ public class JdbcAggregateTemplateIntegrationTests {
assertThat(reloaded.content).extracting(e -> e.content).containsExactly("content");
}
@Test // DATAJDBC-259
public void saveAndLoadAnEntityWithArray() {
// MySQL and other do not support array datatypes. See
// https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html
assumeNot("mysql");
assumeNot("mariadb");
assumeNot("mssql");
ArrayOwner arrayOwner = new ArrayOwner();
arrayOwner.digits = new String[] { "one", "two", "three" };
ArrayOwner saved = template.save(arrayOwner);
assertThat(saved.id).isNotNull();
ArrayOwner reloaded = template.findById(saved.id, ArrayOwner.class);
assertThat(reloaded).isNotNull();
assertThat(reloaded.id).isEqualTo(saved.id);
assertThat(reloaded.digits).isEqualTo(new String[] { "one", "two", "three" });
}
@Test // DATAJDBC-259
public void saveAndLoadAnEntityWithList() {
// MySQL and others do not support array datatypes. See
// https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html
assumeNot("mysql");
assumeNot("mariadb");
assumeNot("mssql");
ListOwner arrayOwner = new ListOwner();
arrayOwner.digits.addAll(Arrays.asList("one", "two", "three"));
ListOwner saved = template.save(arrayOwner);
assertThat(saved.id).isNotNull();
ListOwner reloaded = template.findById(saved.id, ListOwner.class);
assertThat(reloaded).isNotNull();
assertThat(reloaded.id).isEqualTo(saved.id);
assertThat(reloaded.digits).isEqualTo(Arrays.asList("one", "two", "three"));
}
@Test // DATAJDBC-259
public void saveAndLoadAnEntityWithSet() {
// MySQL and others do not support array datatypes. See
// https://dev.mysql.com/doc/refman/8.0/en/data-type-overview.html
assumeNot("mysql");
assumeNot("mariadb");
assumeNot("mssql");
SetOwner setOwner = new SetOwner();
setOwner.digits.addAll(Arrays.asList("one", "two", "three"));
SetOwner saved = template.save(setOwner);
assertThat(saved.id).isNotNull();
SetOwner reloaded = template.findById(saved.id, SetOwner.class);
assertThat(reloaded).isNotNull();
assertThat(reloaded.id).isEqualTo(saved.id);
assertThat(reloaded.digits).isEqualTo(new HashSet<>(Arrays.asList("one", "two", "three")));
}
private static void assumeNot(String dbProfileName) {
Assume.assumeTrue("true"
.equalsIgnoreCase(ProfileValueUtils.retrieveProfileValueSource(JdbcAggregateTemplateIntegrationTests.class)
.get("current.database.is.not." + dbProfileName)));
}
private static class ArrayOwner {
@Id Long id;
String[] digits;
}
@Table("ARRAY_OWNER")
private static class ListOwner {
@Id Long id;
List<String> digits = new ArrayList<>();
}
@Table("ARRAY_OWNER")
private static class SetOwner {
@Id Long id;
Set<String> digits = new HashSet<>();
}
private static LegoSet createLegoSet() {
LegoSet entity = new LegoSet();
@@ -333,8 +435,7 @@ public class JdbcAggregateTemplateIntegrationTests {
@Data
static class LegoSet {
@Column("id1")
@Id private Long id;
@Column("id1") @Id private Long id;
private String name;
@@ -345,16 +446,14 @@ public class JdbcAggregateTemplateIntegrationTests {
@Data
static class Manual {
@Column("id2")
@Id private Long id;
@Column("id2") @Id private Long id;
private String content;
}
static class OneToOneParent {
@Column("id3")
@Id private Long id;
@Column("id3") @Id private Long id;
private String content;
private ChildNoId child;
@@ -366,8 +465,7 @@ public class JdbcAggregateTemplateIntegrationTests {
static class ListParent {
@Column("id4")
@Id private Long id;
@Column("id4") @Id private Long id;
String name;
List<ElementNoId> content = new ArrayList<>();
}

View File

@@ -9,3 +9,5 @@ CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR
CREATE TABLE LIST_PARENT ( id4 BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT);
CREATE TABLE ARRAY_OWNER (ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY, DIGITS VARCHAR(20) ARRAY[10] NOT NULL);

View File

@@ -8,4 +8,4 @@ CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT AUTO_INCREMENT PRIMARY KEY, content
CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR(30));
CREATE TABLE LIST_PARENT ( id4 BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT);
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT);

View File

@@ -1,15 +1,15 @@
DROP TABLE IF EXISTS LEGO_SET;
DROP TABLE IF EXISTS MANUAL;
DROP TABLE IF EXISTS LEGO_SET;
CREATE TABLE LEGO_SET ( id1 BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(30));
CREATE TABLE MANUAL ( id2 BIGINT IDENTITY PRIMARY KEY, LEGO_SET BIGINT, ALTERNATIVE BIGINT, CONTENT VARCHAR(2000));
ALTER TABLE MANUAL ADD FOREIGN KEY (LEGO_SET) REFERENCES LEGO_SET(id1);
DROP TABLE IF EXISTS ONE_TO_ONE_PARENT;
DROP TABLE IF EXISTS Child_No_Id;
DROP TABLE IF EXISTS ONE_TO_ONE_PARENT;
CREATE TABLE ONE_TO_ONE_PARENT ( id3 BIGINT IDENTITY PRIMARY KEY, content VARCHAR(30));
CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT BIGINT PRIMARY KEY, content VARCHAR(30));
DROP TABLE IF EXISTS LIST_PARENT;
DROP TABLE IF EXISTS element_no_id;
DROP TABLE IF EXISTS LIST_PARENT;
CREATE TABLE LIST_PARENT ( id4 BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT);
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT BIGINT);

View File

@@ -12,3 +12,5 @@ CREATE TABLE Child_No_Id (ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, content VARCHAR
CREATE TABLE LIST_PARENT ( id4 SERIAL PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element_no_id ( content VARCHAR(100), LIST_PARENT_key BIGINT, LIST_PARENT INTEGER);
CREATE TABLE ARRAY_OWNER (ID SERIAL PRIMARY KEY, DIGITS VARCHAR(20) ARRAY[10] NOT NULL);