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.
This commit is contained in:
committed by
Mark Paluch
parent
d77e9cd837
commit
5ed80dfdb6
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
*/
|
||||
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 {
|
||||
rowMapper //
|
||||
);
|
||||
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
private QueryExecutor<Object> createExecutor(JdbcQueryMethod queryMethod, @Nullable ResultSetExtractor extractor,
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
ApplicationEventPublisher publisher;
|
||||
EntityCallbacks callbacks;
|
||||
RelationalMappingContext context;
|
||||
JdbcConverter converter;
|
||||
|
||||
@Before
|
||||
public void setup() throws NoSuchMethodException {
|
||||
@@ -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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user