Browse Source

DATAJDBC-464 - Convert parameters.

Parameters now get converted to a suitable type supported by JDBC.

In order to support this the logic of finding the "right" type got moved from `BasicRelationalPersistentProperty` to `JdbcCompatibleTypes`.

Original pull request: #186.
pull/188/head
Jens Schauder 6 years ago committed by Mark Paluch
parent
commit
5ed80dfdb6
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 2
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java
  2. 32
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java
  3. 29
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
  4. 23
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java
  5. 7
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
  6. 7
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql
  7. 7
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql
  8. 7
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
  9. 7
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
  10. 20
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java
  11. 57
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/JdbcCompatibleTypes.java

2
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

@ -67,7 +67,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { @@ -67,7 +67,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy {
RowMapper<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);
return new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, mapper);
return new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, mapper, converter);
}
private RowMapper<?> createMapper(JdbcQueryMethod queryMethod) {

32
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQuery.java

@ -16,17 +16,23 @@ @@ -16,17 +16,23 @@
package org.springframework.data.jdbc.repository.support;
import java.lang.reflect.Constructor;
import java.sql.JDBCType;
import java.util.List;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcValue;
import org.springframework.data.jdbc.support.JdbcUtil;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.mapping.JdbcCompatibleTypes;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.event.AfterLoadCallback;
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
import org.springframework.data.relational.core.mapping.event.Identifier;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
@ -56,6 +62,7 @@ class JdbcRepositoryQuery implements RepositoryQuery { @@ -56,6 +62,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
private final JdbcQueryMethod queryMethod;
private final NamedParameterJdbcOperations operations;
private final QueryExecutor<Object> executor;
private final JdbcConverter converter;
/**
* Creates a new {@link JdbcRepositoryQuery} for the given {@link JdbcQueryMethod}, {@link RelationalMappingContext}
@ -69,7 +76,7 @@ class JdbcRepositoryQuery implements RepositoryQuery { @@ -69,7 +76,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
*/
JdbcRepositoryQuery(ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
RelationalMappingContext context, JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
RowMapper<?> defaultRowMapper) {
RowMapper<?> defaultRowMapper, JdbcConverter converter) {
Assert.notNull(publisher, "Publisher must not be null!");
Assert.notNull(context, "Context must not be null!");
@ -93,6 +100,7 @@ class JdbcRepositoryQuery implements RepositoryQuery { @@ -93,6 +100,7 @@ class JdbcRepositoryQuery implements RepositoryQuery {
rowMapper //
);
this.converter = converter;
}
private QueryExecutor<Object> createExecutor(JdbcQueryMethod queryMethod, @Nullable ResultSetExtractor extractor,
@ -209,13 +217,31 @@ class JdbcRepositoryQuery implements RepositoryQuery { @@ -209,13 +217,31 @@ class JdbcRepositoryQuery implements RepositoryQuery {
queryMethod.getParameters().getBindableParameters().forEach(p -> {
String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
parameters.addValue(parameterName, objects[p.getIndex()]);
convertAndAddParameter(parameters, p, objects[p.getIndex()]);
});
return parameters;
}
private void convertAndAddParameter(MapSqlParameterSource parameters, Parameter p, Object value) {
String parameterName = p.getName().orElseThrow(() -> new IllegalStateException(PARAMETER_NEEDS_TO_BE_NAMED));
Class<?> parameterType = queryMethod.getParameters().getParameter(p.getIndex()).getType();
Class conversionTargetType = JdbcCompatibleTypes.INSTANCE.columnTypeForNonEntity(parameterType);
JdbcValue jdbcValue = converter.writeJdbcValue(value, conversionTargetType,
JdbcUtil.sqlTypeFor(conversionTargetType));
JDBCType jdbcType = jdbcValue.getJdbcType();
if (jdbcType == null) {
parameters.addValue(parameterName, jdbcValue.getValue());
}else {
parameters.addValue(parameterName, jdbcValue.getValue(), jdbcType.getVendorTypeNumber());
}
}
@Nullable
private ResultSetExtractor determineResultSetExtractor(@Nullable RowMapper<?> rowMapper) {

29
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

@ -23,6 +23,9 @@ import lombok.Data; @@ -23,6 +23,9 @@ import lombok.Data;
import java.io.IOException;
import java.util.List;
import java.time.Instant;
import java.util.List;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@ -33,11 +36,13 @@ import org.springframework.context.annotation.Configuration; @@ -33,11 +36,13 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
import org.springframework.data.repository.query.Param;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
@ -258,6 +263,24 @@ public class JdbcRepositoryIntegrationTests { @@ -258,6 +263,24 @@ public class JdbcRepositoryIntegrationTests {
assertThat(repository.findById(-1L)).isEmpty();
}
@Test // DATAJDBC-464
public void executeQueryWithParameterRequiringConversion() {
Instant now = Instant.now();
DummyEntity first = repository.save(createDummyEntity());
first.setPointInTime(now.minusSeconds(1000L));
first.setName("first");
DummyEntity second = repository.save(createDummyEntity());
second.setPointInTime(now.plusSeconds(1000L));
second.setName("second");
repository.saveAll(asList(first, second));
assertThat(repository.after(now)).containsExactly(second);
}
@Test // DATAJDBC-234
public void findAllByQueryName() {
@ -269,17 +292,23 @@ public class JdbcRepositoryIntegrationTests { @@ -269,17 +292,23 @@ public class JdbcRepositoryIntegrationTests {
DummyEntity entity = new DummyEntity();
entity.setName("Entity Name");
return entity;
}
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
List<DummyEntity> findAllByNamedQuery();
@Query("SELECT * FROM DUMMY_ENTITY WHERE POINT_IN_TIME > :threshhold")
List<DummyEntity> after(@Param("threshhold")Instant threshhold);
}
@Data
static class DummyEntity {
String name;
@Id private Long idProp;
Instant pointInTime;
}
}

23
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryQueryUnitTests.java

@ -28,6 +28,9 @@ import org.junit.Test; @@ -28,6 +28,9 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DataAccessException;
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.RelationResolver;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.event.AfterLoadCallback;
@ -58,6 +61,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -58,6 +61,7 @@ public class JdbcRepositoryQueryUnitTests {
ApplicationEventPublisher publisher;
EntityCallbacks callbacks;
RelationalMappingContext context;
JdbcConverter converter;
@Before
public void setup() throws NoSuchMethodException {
@ -73,6 +77,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -73,6 +77,7 @@ public class JdbcRepositoryQueryUnitTests {
this.publisher = mock(ApplicationEventPublisher.class);
this.callbacks = mock(EntityCallbacks.class);
this.context = mock(RelationalMappingContext.class, RETURNS_DEEP_STUBS);
this.converter = new BasicJdbcConverter(context, mock(RelationResolver.class));
}
@Test // DATAJDBC-165
@ -82,7 +87,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -82,7 +87,7 @@ public class JdbcRepositoryQueryUnitTests {
Assertions.assertThatExceptionOfType(IllegalStateException.class) //
.isThrownBy(
() -> new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper)
() -> new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter)
.execute(new Object[] {}));
}
@ -92,7 +97,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -92,7 +97,7 @@ public class JdbcRepositoryQueryUnitTests {
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
doReturn(RowMapper.class).when(queryMethod).getRowMapperClass();
JdbcRepositoryQuery query = new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations,
defaultRowMapper);
defaultRowMapper, converter);
query.execute(new Object[] {});
@ -104,7 +109,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -104,7 +109,7 @@ public class JdbcRepositoryQueryUnitTests {
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
JdbcRepositoryQuery query = new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations,
defaultRowMapper);
defaultRowMapper, converter);
query.execute(new Object[] {});
@ -117,7 +122,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -117,7 +122,7 @@ public class JdbcRepositoryQueryUnitTests {
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass();
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper)
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter)
.execute(new Object[] {});
verify(operations) //
@ -130,7 +135,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -130,7 +135,7 @@ public class JdbcRepositoryQueryUnitTests {
doReturn("some sql statement").when(queryMethod).getDeclaredQuery();
doReturn(CustomResultSetExtractor.class).when(queryMethod).getResultSetExtractorClass();
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper)
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter)
.execute(new Object[] {});
ArgumentCaptor<CustomResultSetExtractor> captor = ArgumentCaptor.forClass(CustomResultSetExtractor.class);
@ -149,7 +154,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -149,7 +154,7 @@ public class JdbcRepositoryQueryUnitTests {
doReturn(CustomResultSetExtractor.class).when(queryMethod).getResultSetExtractorClass();
doReturn(CustomRowMapper.class).when(queryMethod).getRowMapperClass();
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper)
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter)
.execute(new Object[] {});
ArgumentCaptor<CustomResultSetExtractor> captor = ArgumentCaptor.forClass(CustomResultSetExtractor.class);
@ -172,7 +177,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -172,7 +177,7 @@ public class JdbcRepositoryQueryUnitTests {
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getIdentifier())
.thenReturn("some identifier");
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper)
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter)
.execute(new Object[] {});
verify(publisher).publishEvent(any(AfterLoadEvent.class));
@ -189,7 +194,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -189,7 +194,7 @@ public class JdbcRepositoryQueryUnitTests {
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getIdentifier())
.thenReturn("some identifier");
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper)
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter)
.execute(new Object[] {});
verify(publisher, times(2)).publishEvent(any(AfterLoadEvent.class));
@ -207,7 +212,7 @@ public class JdbcRepositoryQueryUnitTests { @@ -207,7 +212,7 @@ public class JdbcRepositoryQueryUnitTests {
when(context.getRequiredPersistentEntity(DummyEntity.class).getIdentifierAccessor(any()).getIdentifier())
.thenReturn("some identifier");
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper).execute(new Object[] {});
new JdbcRepositoryQuery(publisher, callbacks, context, queryMethod, operations, defaultRowMapper, converter).execute(new Object[] {});
verify(publisher).publishEvent(any(AfterLoadEvent.class));
verify(callbacks).callback(AfterLoadCallback.class, dummyEntity);

7
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql

@ -1 +1,6 @@ @@ -1 +1,6 @@
CREATE TABLE dummy_entity ( id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100))
CREATE TABLE dummy_entity
(
id_Prop BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
NAME VARCHAR(100),
POINT_IN_TIME TIMESTAMP
);

7
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql

@ -1 +1,6 @@ @@ -1 +1,6 @@
CREATE TABLE dummy_entity (id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE dummy_entity
(
id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100),
POINT_IN_TIME TIMESTAMP(3)
);

7
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mssql.sql

@ -1,2 +1,7 @@ @@ -1,2 +1,7 @@
DROP TABLE IF EXISTS dummy_entity;
CREATE TABLE dummy_entity (id_Prop BIGINT IDENTITY PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE dummy_entity
(
id_Prop BIGINT IDENTITY PRIMARY KEY,
NAME VARCHAR(100),
POINT_IN_TIME TIMESTAMP
);

7
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql

@ -1 +1,6 @@ @@ -1 +1,6 @@
CREATE TABLE dummy_entity (id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE dummy_entity
(
id_Prop BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(100),
POINT_IN_TIME TIMESTAMP(3)
);

7
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql

@ -1,2 +1,7 @@ @@ -1,2 +1,7 @@
DROP TABLE dummy_entity;
CREATE TABLE dummy_entity (id_Prop SERIAL PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE dummy_entity
(
id_Prop SERIAL PRIMARY KEY,
NAME VARCHAR(100),
POINT_IN_TIME TIMESTAMP
);

20
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentProperty.java

@ -49,15 +49,6 @@ import org.springframework.util.StringUtils; @@ -49,15 +49,6 @@ import org.springframework.util.StringUtils;
public class BasicRelationalPersistentProperty extends AnnotationBasedPersistentProperty<RelationalPersistentProperty>
implements RelationalPersistentProperty {
private static final Map<Class<?>, Class<?>> javaToDbType = new LinkedHashMap<>();
static {
javaToDbType.put(Enum.class, String.class);
javaToDbType.put(ZonedDateTime.class, String.class);
javaToDbType.put(Temporal.class, Date.class);
}
private final RelationalMappingContext context;
private final Lazy<SqlIdentifier> columnName;
private final Lazy<Optional<SqlIdentifier>> collectionIdColumnName;
@ -179,7 +170,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -179,7 +170,7 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
return columnType;
}
Class componentColumnType = columnTypeForNonEntity(getActualType());
Class componentColumnType = JdbcCompatibleTypes.INSTANCE.columnTypeForNonEntity(getActualType());
while (componentColumnType.isArray()) {
componentColumnType = componentColumnType.getComponentType();
@ -286,15 +277,6 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent @@ -286,15 +277,6 @@ public class BasicRelationalPersistentProperty extends AnnotationBasedPersistent
return idProperty.getColumnType();
}
private Class columnTypeForNonEntity(Class type) {
return javaToDbType.entrySet().stream() //
.filter(e -> e.getKey().isAssignableFrom(type)) //
.map(e -> (Class) e.getValue()) //
.findFirst() //
.orElseGet(() -> ClassUtils.resolvePrimitiveIfNecessary(type));
}
private Class columnTypeForReference() {
Class<?> componentType = getTypeInformation().getRequiredComponentType().getType();

57
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/JdbcCompatibleTypes.java

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
/*
* Copyright 2019 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.relational.core.mapping;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.util.ClassUtils;
/**
* Utility that determines the necessary type conversions between Java types used in the domain model and types
* compatible with JDBC drivers.
*
* @author Jens Schauder
* @since 2.0
*/
public enum JdbcCompatibleTypes {
INSTANCE {
private final Map<Class<?>, Class<?>> javaToDbType = new LinkedHashMap<>();
{
javaToDbType.put(Enum.class, String.class);
javaToDbType.put(ZonedDateTime.class, String.class);
javaToDbType.put(Temporal.class, Date.class);
}
public Class columnTypeForNonEntity(Class type) {
return javaToDbType.entrySet().stream() //
.filter(e -> e.getKey().isAssignableFrom(type)) //
.map(e -> (Class) e.getValue()) //
.findFirst() //
.orElseGet(() -> ClassUtils.resolvePrimitiveIfNecessary(type));
}
};
public abstract Class columnTypeForNonEntity(Class type);
}
Loading…
Cancel
Save