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:
committed by
Mark Paluch
parent
75ea44536f
commit
83ca6b4739
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user