From a55bdb46ccb1dc9beb4a85a3e4d106914a2cb21d Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 23 Mar 2021 13:59:34 +0100 Subject: [PATCH] Properly convert primitive array arguments. Closes #945 Original pull request: #949. --- .../data/jdbc/core/convert/ArrayUtil.java | 93 +++++++++++++++++++ .../jdbc/core/convert/BasicJdbcConverter.java | 4 +- .../convert/BasicJdbcConverterUnitTests.java | 45 +++++++-- 3 files changed, 134 insertions(+), 8 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java index 13e4e78e..ef590a12 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/ArrayUtil.java @@ -41,4 +41,97 @@ class ArrayUtil { } return bytes; } + + static Byte[] toObjectArray(byte[] primitiveArray) { + + Byte[] objects = new Byte[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Short[] toObjectArray(short[] primitiveArray) { + + Short[] objects = new Short[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Character[] toObjectArray(char[] primitiveArray) { + + Character[] objects = new Character[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Integer[] toObjectArray(int[] primitiveArray) { + + Integer[] objects = new Integer[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Long[] toObjectArray(long[] primitiveArray) { + + Long[] objects = new Long[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Float[] toObjectArray(float[] primitiveArray) { + + Float[] objects = new Float[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Double[] toObjectArray(double[] primitiveArray) { + + Double[] objects = new Double[primitiveArray.length]; + for (int i = 0; i < primitiveArray.length; i++) { + objects[i] = primitiveArray[i]; + } + return objects; + } + + static Object[] convertToObjectArray(Object unknownArray) { + + Class componentType = unknownArray.getClass().getComponentType(); + + if (componentType.isPrimitive()) { + if (componentType == byte.class) { + return toObjectArray((byte[]) unknownArray); + } + if (componentType == short.class) { + return toObjectArray((short[]) unknownArray); + } + if (componentType == char.class) { + return toObjectArray((char[]) unknownArray); + } + if (componentType == int.class) { + return toObjectArray((int[]) unknownArray); + } + if (componentType == long.class) { + return toObjectArray((long[]) unknownArray); + } + if (componentType == float.class) { + return toObjectArray((float[]) unknownArray); + } + if (componentType == double.class) { + return toObjectArray((double[]) unknownArray); + } + } + return (Object[]) unknownArray; + } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java index c7fa919e..67bb927f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverter.java @@ -297,7 +297,9 @@ public class BasicJdbcConverter extends BasicRelationalConverter implements Jdbc Class componentType = convertedValue.getClass().getComponentType(); if (componentType != byte.class && componentType != Byte.class) { - return JdbcValue.of(typeFactory.createArray((Object[]) convertedValue), JDBCType.ARRAY); + + Object[] objectArray = ArrayUtil.convertToObjectArray(convertedValue); + return JdbcValue.of(typeFactory.createArray(objectArray), JDBCType.ARRAY); } if (componentType == Byte.class) { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java index abef7490..a91015a0 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/BasicJdbcConverterUnitTests.java @@ -16,9 +16,11 @@ package org.springframework.data.jdbc.core.convert; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import lombok.Data; +import java.sql.Array; import java.sql.Timestamp; import java.time.Instant; import java.time.LocalDate; @@ -35,8 +37,10 @@ import org.junit.Test; import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.mapping.AggregateReference; import org.springframework.data.jdbc.core.mapping.JdbcMappingContext; +import org.springframework.data.jdbc.support.JdbcUtil; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; +import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.util.ClassTypeInformation; /** @@ -47,9 +51,15 @@ import org.springframework.data.util.ClassTypeInformation; public class BasicJdbcConverterUnitTests { JdbcMappingContext context = new JdbcMappingContext(); - BasicJdbcConverter converter = new BasicJdbcConverter(context, (identifier, path) -> { - throw new UnsupportedOperationException(); - }); + StubbedJdbcTypeFactory typeFactory = new StubbedJdbcTypeFactory(); + BasicJdbcConverter converter = new BasicJdbcConverter( // + context, // + (identifier, path) -> { + throw new UnsupportedOperationException(); + }, // + new JdbcCustomConversions(), // + typeFactory, IdentifierProcessing.ANSI // + ); @Test // DATAJDBC-104, DATAJDBC-1384 public void testTargetTypesForPropertyType() { @@ -110,14 +120,25 @@ public class BasicJdbcConverterUnitTests { LocalDateTime testLocalDateTime = LocalDateTime.of(2001, 2, 3, 4, 5, 6, 123456789); checkConversionToTimestampAndBack(softly, persistentEntity, "localDateTime", testLocalDateTime); checkConversionToTimestampAndBack(softly, persistentEntity, "localDate", LocalDate.of(2001, 2, 3)); - checkConversionToTimestampAndBack(softly, persistentEntity, "localTime", LocalTime.of(1, 2, 3,123456789)); - checkConversionToTimestampAndBack(softly, persistentEntity, "instant", testLocalDateTime.toInstant(ZoneOffset.UTC)); + checkConversionToTimestampAndBack(softly, persistentEntity, "localTime", LocalTime.of(1, 2, 3, 123456789)); + checkConversionToTimestampAndBack(softly, persistentEntity, "instant", + testLocalDateTime.toInstant(ZoneOffset.UTC)); }); } - private void checkConversionToTimestampAndBack(SoftAssertions softly, RelationalPersistentEntity persistentEntity, String propertyName, - Object value) { + @Test // #945 + public void conversionOfPrimitiveArrays() { + + int[] ints = { 1, 2, 3, 4, 5 }; + JdbcValue converted = converter.writeJdbcValue(ints, ints.getClass(), JdbcUtil.sqlTypeFor(ints.getClass())); + + assertThat(converted.getValue()).isInstanceOf(Array.class); + assertThat(typeFactory.arraySource).containsExactly(1, 2, 3, 4, 5); + } + + private void checkConversionToTimestampAndBack(SoftAssertions softly, RelationalPersistentEntity persistentEntity, + String propertyName, Object value) { RelationalPersistentProperty property = persistentEntity.getRequiredPersistentProperty(propertyName); @@ -165,4 +186,14 @@ public class BasicJdbcConverterUnitTests { @SuppressWarnings("unused") private static class OtherEntity {} + + private static class StubbedJdbcTypeFactory implements JdbcTypeFactory { + public Object[] arraySource; + + @Override + public Array createArray(Object[] value) { + arraySource = value; + return mock(Array.class); + } + } }