Add support for arbitrary where clauses in Single Query Loading.
Closes #1601 Original pull request: #1617
This commit is contained in:
committed by
Mark Paluch
parent
6fb6110ca0
commit
0fdeaebbee
@@ -21,15 +21,22 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.mapping.AggregatePath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.Condition;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sqlgeneration.AliasFactory;
|
||||
import org.springframework.data.relational.core.sqlgeneration.SingleQuerySqlGenerator;
|
||||
import org.springframework.data.relational.core.sqlgeneration.SqlGenerator;
|
||||
import org.springframework.data.relational.domain.RowDocument;
|
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -89,6 +96,35 @@ class AggregateReader<T> {
|
||||
return jdbcTemplate.query(sqlGenerator.findAllById(), Map.of("ids", convertedIds), this::extractAll);
|
||||
}
|
||||
|
||||
public Iterable<T> findAllBy(Query query) {
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
BiFunction<Table, RelationalPersistentEntity, Condition> condition = createConditionSource(query, parameterSource);
|
||||
return jdbcTemplate.query(sqlGenerator.findAllByCondition(condition), parameterSource, this::extractAll);
|
||||
}
|
||||
|
||||
public Optional<T> findOneByQuery(Query query) {
|
||||
|
||||
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
|
||||
BiFunction<Table, RelationalPersistentEntity, Condition> condition = createConditionSource(query, parameterSource);
|
||||
|
||||
return Optional.ofNullable(
|
||||
jdbcTemplate.query(sqlGenerator.findAllByCondition(condition), parameterSource, this::extractZeroOrOne));
|
||||
}
|
||||
|
||||
private BiFunction<Table, RelationalPersistentEntity, Condition> createConditionSource(Query query, MapSqlParameterSource parameterSource) {
|
||||
|
||||
QueryMapper queryMapper = new QueryMapper(converter);
|
||||
|
||||
BiFunction<Table, RelationalPersistentEntity, Condition> condition = (table, aggregate) -> {
|
||||
Optional<CriteriaDefinition> criteria = query.getCriteria();
|
||||
return criteria
|
||||
.map(criteriaDefinition -> queryMapper.getMappedObject(parameterSource, criteriaDefinition, table, aggregate))
|
||||
.orElse(null);
|
||||
};
|
||||
return condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a list of aggregates from the given {@link ResultSet} by utilizing the
|
||||
* {@link RowDocumentResultSetExtractor} and the {@link JdbcConverter}. When used as a method reference this conforms
|
||||
@@ -115,7 +151,8 @@ class AggregateReader<T> {
|
||||
* to the {@link org.springframework.jdbc.core.ResultSetExtractor} contract.
|
||||
*
|
||||
* @param @param rs the {@link ResultSet} from which to extract the data. Must not be {(}@literal null}.
|
||||
* @return The single instance when the conversion results in exactly one instance. If the {@literal ResultSet} is empty, null is returned.
|
||||
* @return The single instance when the conversion results in exactly one instance. If the {@literal ResultSet} is
|
||||
* empty, null is returned.
|
||||
* @throws SQLException
|
||||
* @throws IncorrectResultSizeDataAccessException when the conversion yields more than one instance.
|
||||
*/
|
||||
@@ -190,9 +227,15 @@ class AggregateReader<T> {
|
||||
return findAllById;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findAllByCondition(BiFunction<Table, RelationalPersistentEntity, Condition> conditionSource) {
|
||||
return delegate.findAllByCondition(conditionSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AliasFactory getAliasFactory() {
|
||||
return delegate.getAliasFactory();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ public class QueryMapper {
|
||||
sqlType = getTypeHint(mappedValue, actualType.getType(), settableValue);
|
||||
} else if (criteria.getValue() instanceof ValueFunction valueFunction) {
|
||||
|
||||
mappedValue = valueFunction;
|
||||
mappedValue = valueFunction.transform(v -> convertValue(comparator, v, propertyField.getTypeHint()));
|
||||
sqlType = propertyField.getSqlType();
|
||||
|
||||
} else if (propertyField instanceof MetadataBackedField metadataBackedField //
|
||||
|
||||
@@ -77,12 +77,13 @@ class SingleQueryDataAccessStrategy implements ReadingDataAccessStrategy {
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> findOne(Query query, Class<T> domainType) {
|
||||
return Optional.empty();
|
||||
return getReader(domainType).findOneByQuery(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Query query, Class<T> domainType) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
return getReader(domainType).findAllBy(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
package org.springframework.data.jdbc.core.convert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -85,13 +87,37 @@ class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy
|
||||
return super.findAllById(ids, domainType);
|
||||
}
|
||||
|
||||
private boolean isSingleSelectQuerySupported(Class<?> entityType) {
|
||||
public <T> Optional<T> findOne(Query query, Class<T> domainType) {
|
||||
|
||||
return sqlGeneratorSource.getDialect().supportsSingleQueryLoading()//
|
||||
&& entityQualifiesForSingleSelectQuery(entityType);
|
||||
if (isSingleSelectQuerySupported(domainType) && isSingleSelectQuerySupported(query)) {
|
||||
return singleSelectDelegate.findOne(query, domainType);
|
||||
}
|
||||
|
||||
return super.findOne(query, domainType);
|
||||
}
|
||||
|
||||
private boolean entityQualifiesForSingleSelectQuery(Class<?> entityType) {
|
||||
@Override
|
||||
public <T> Iterable<T> findAll(Query query, Class<T> domainType) {
|
||||
|
||||
if (isSingleSelectQuerySupported(domainType) && isSingleSelectQuerySupported(query)) {
|
||||
return singleSelectDelegate.findAll(query, domainType);
|
||||
}
|
||||
|
||||
return super.findAll(query, domainType);
|
||||
}
|
||||
|
||||
private static boolean isSingleSelectQuerySupported(Query query) {
|
||||
return !query.isSorted() && !query.isLimited();
|
||||
}
|
||||
|
||||
private boolean isSingleSelectQuerySupported(Class<?> entityType) {
|
||||
|
||||
return converter.getMappingContext().isSingleQueryLoadingEnabled()
|
||||
&& sqlGeneratorSource.getDialect().supportsSingleQueryLoading()//
|
||||
&& entityQualifiesForSingleQueryLoading(entityType);
|
||||
}
|
||||
|
||||
private boolean entityQualifiesForSingleQueryLoading(Class<?> entityType) {
|
||||
|
||||
boolean referenceFound = false;
|
||||
for (PersistentPropertyPath<RelationalPersistentProperty> path : converter.getMappingContext()
|
||||
@@ -113,9 +139,9 @@ class SingleQueryFallbackDataAccessStrategy extends DelegatingDataAccessStrategy
|
||||
}
|
||||
|
||||
// AggregateReferences aren't supported yet
|
||||
if (property.isAssociation()) {
|
||||
return false;
|
||||
}
|
||||
// if (property.isAssociation()) {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ public class EscapingParameterSource implements SqlParameterSource {
|
||||
public Object getValue(String paramName) throws IllegalArgumentException {
|
||||
|
||||
Object value = parameterSource.getValue(paramName);
|
||||
if (value instanceof ValueFunction<?>) {
|
||||
return ((ValueFunction<?>) value).apply(escaper);
|
||||
if (value instanceof ValueFunction<?> valueFunction) {
|
||||
return valueFunction.apply(escaper);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -23,15 +23,8 @@ import static org.springframework.data.jdbc.testing.TestConfiguration.*;
|
||||
import static org.springframework.data.jdbc.testing.TestDatabaseFeatures.Feature.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
@@ -42,6 +35,7 @@ import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.annotation.Id;
|
||||
@@ -64,6 +58,9 @@ import org.springframework.data.relational.core.mapping.InsertOnlyProperty;
|
||||
import org.springframework.data.relational.core.mapping.MappedCollection;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
@@ -223,6 +220,62 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
.containsExactlyInAnyOrder(tuple(entity.id, "entity"), tuple(yetAnother.id, "yetAnother"));
|
||||
}
|
||||
|
||||
@Test // GH-1601
|
||||
void findAllByQuery() {
|
||||
|
||||
template.save(SimpleListParent.of("one", "one_1"));
|
||||
SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2"));
|
||||
template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3"));
|
||||
|
||||
CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").is(two.id));
|
||||
Query query = Query.query(criteria);
|
||||
Iterable<SimpleListParent> reloadedById = template.findAll(query, SimpleListParent.class);
|
||||
|
||||
assertThat(reloadedById).extracting(e -> e.id, e -> e.content.size()).containsExactly(tuple(two.id, 2));
|
||||
}
|
||||
|
||||
@Test // GH-1601
|
||||
void findOneByQuery() {
|
||||
|
||||
template.save(SimpleListParent.of("one", "one_1"));
|
||||
SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2"));
|
||||
template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3"));
|
||||
|
||||
CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").is(two.id));
|
||||
Query query = Query.query(criteria);
|
||||
Optional<SimpleListParent> reloadedById = template.findOne(query, SimpleListParent.class);
|
||||
|
||||
assertThat(reloadedById).get().extracting(e -> e.id, e -> e.content.size()).containsExactly(two.id, 2);
|
||||
}
|
||||
|
||||
@Test // GH-1601
|
||||
void findOneByQueryNothingFound() {
|
||||
|
||||
template.save(SimpleListParent.of("one", "one_1"));
|
||||
SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2"));
|
||||
template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3"));
|
||||
|
||||
CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").is(4711));
|
||||
Query query = Query.query(criteria);
|
||||
Optional<SimpleListParent> reloadedById = template.findOne(query, SimpleListParent.class);
|
||||
|
||||
assertThat(reloadedById).isEmpty();
|
||||
}
|
||||
|
||||
@Test // GH-1601
|
||||
void findOneByQueryToManyResults() {
|
||||
|
||||
template.save(SimpleListParent.of("one", "one_1"));
|
||||
SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2"));
|
||||
template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3"));
|
||||
|
||||
CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").not(two.id));
|
||||
Query query = Query.query(criteria);
|
||||
|
||||
assertThatExceptionOfType(IncorrectResultSizeDataAccessException.class)
|
||||
.isThrownBy(() -> template.findOne(query, SimpleListParent.class));
|
||||
}
|
||||
|
||||
@Test // DATAJDBC-112
|
||||
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
|
||||
void saveAndLoadAnEntityWithReferencedEntityById() {
|
||||
@@ -1266,6 +1319,29 @@ abstract class AbstractJdbcAggregateTemplateIntegrationTests {
|
||||
private String content;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static class SimpleListParent {
|
||||
|
||||
@Id private Long id;
|
||||
String name;
|
||||
List<ElementNoId> content = new ArrayList<>();
|
||||
|
||||
static SimpleListParent of(String name, String... contents) {
|
||||
|
||||
SimpleListParent parent = new SimpleListParent();
|
||||
parent.name = name;
|
||||
|
||||
for (String content : contents) {
|
||||
|
||||
ElementNoId element = new ElementNoId();
|
||||
element.content = content;
|
||||
parent.content.add(element);
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
@Table("LIST_PARENT")
|
||||
@SuppressWarnings("unused")
|
||||
static class ListParent {
|
||||
|
||||
@@ -6,6 +6,7 @@ DROP TABLE ONE_TO_ONE_PARENT;
|
||||
|
||||
DROP TABLE ELEMENT_NO_ID;
|
||||
DROP TABLE LIST_PARENT;
|
||||
DROP TABLE SIMPLE_LIST_PARENT;
|
||||
|
||||
DROP TABLE BYTE_ARRAY_OWNER;
|
||||
|
||||
@@ -74,11 +75,18 @@ CREATE TABLE LIST_PARENT
|
||||
"id4" BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID 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
|
||||
SIMPLE_LIST_PARENT_KEY BIGINT,
|
||||
LIST_PARENT BIGINT,
|
||||
SIMPLE_LIST_PARENT BIGINT
|
||||
);
|
||||
ALTER TABLE ELEMENT_NO_ID
|
||||
ADD FOREIGN KEY (LIST_PARENT)
|
||||
|
||||
@@ -32,9 +32,17 @@ CREATE TABLE LIST_PARENT
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID SERIAL PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE element_no_id
|
||||
(
|
||||
content VARCHAR(100),
|
||||
SIMPLE_LIST_PARENT_key BIGINT,
|
||||
SIMPLE_LIST_PARENT INTEGER,
|
||||
LIST_PARENT_key BIGINT,
|
||||
LIST_PARENT INTEGER
|
||||
);
|
||||
|
||||
@@ -26,6 +26,11 @@ CREATE TABLE Child_No_Id
|
||||
content VARCHAR(30)
|
||||
);
|
||||
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE LIST_PARENT
|
||||
(
|
||||
"id4" BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY,
|
||||
@@ -34,6 +39,8 @@ CREATE TABLE LIST_PARENT
|
||||
CREATE TABLE ELEMENT_NO_ID
|
||||
(
|
||||
CONTENT VARCHAR(100),
|
||||
SIMPLE_LIST_PARENT_KEY BIGINT,
|
||||
SIMPLE_LIST_PARENT BIGINT,
|
||||
LIST_PARENT_KEY BIGINT,
|
||||
LIST_PARENT BIGINT
|
||||
);
|
||||
|
||||
@@ -31,9 +31,16 @@ CREATE TABLE LIST_PARENT
|
||||
`id4` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE element_no_id
|
||||
(
|
||||
CONTENT VARCHAR(100),
|
||||
SIMPLE_LIST_PARENT_key BIGINT,
|
||||
SIMPLE_LIST_PARENT BIGINT,
|
||||
LIST_PARENT_key BIGINT,
|
||||
LIST_PARENT BIGINT
|
||||
);
|
||||
|
||||
@@ -30,14 +30,22 @@ CREATE TABLE Child_No_Id
|
||||
|
||||
DROP TABLE IF EXISTS element_no_id;
|
||||
DROP TABLE IF EXISTS LIST_PARENT;
|
||||
DROP TABLE IF EXISTS SIMPLE_LIST_PARENT;
|
||||
CREATE TABLE LIST_PARENT
|
||||
(
|
||||
[id4] BIGINT IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID BIGINT IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE element_no_id
|
||||
(
|
||||
CONTENT VARCHAR(100),
|
||||
SIMPLE_LIST_PARENT_key BIGINT,
|
||||
SIMPLE_LIST_PARENT BIGINT,
|
||||
LIST_PARENT_key BIGINT,
|
||||
LIST_PARENT BIGINT
|
||||
);
|
||||
|
||||
@@ -26,6 +26,11 @@ CREATE TABLE Child_No_Id
|
||||
`content` VARCHAR(30)
|
||||
);
|
||||
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE LIST_PARENT
|
||||
(
|
||||
`id4` BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
@@ -35,7 +40,9 @@ CREATE TABLE element_no_id
|
||||
(
|
||||
CONTENT VARCHAR(100),
|
||||
LIST_PARENT_key BIGINT,
|
||||
LIST_PARENT BIGINT
|
||||
SIMPLE_LIST_PARENT_key BIGINT,
|
||||
LIST_PARENT BIGINT,
|
||||
SIMPLE_LIST_PARENT BIGINT
|
||||
);
|
||||
|
||||
CREATE TABLE BYTE_ARRAY_OWNER
|
||||
|
||||
@@ -4,6 +4,7 @@ DROP TABLE CHILD_NO_ID CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE ONE_TO_ONE_PARENT CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE ELEMENT_NO_ID CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE LIST_PARENT CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE SIMPLE_LIST_PARENT CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE BYTE_ARRAY_OWNER CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE CHAIN0 CASCADE CONSTRAINTS PURGE;
|
||||
DROP TABLE CHAIN1 CASCADE CONSTRAINTS PURGE;
|
||||
@@ -64,9 +65,16 @@ CREATE TABLE LIST_PARENT
|
||||
"id4" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
CREATE TABLE element_no_id
|
||||
(
|
||||
CONTENT VARCHAR(100),
|
||||
SIMPLE_LIST_PARENT_key NUMBER,
|
||||
SIMPLE_LIST_PARENT NUMBER,
|
||||
LIST_PARENT_key NUMBER,
|
||||
LIST_PARENT NUMBER
|
||||
);
|
||||
|
||||
@@ -4,6 +4,7 @@ DROP TABLE ONE_TO_ONE_PARENT;
|
||||
DROP TABLE Child_No_Id;
|
||||
DROP TABLE element_no_id;
|
||||
DROP TABLE "LIST_PARENT";
|
||||
DROP TABLE SIMPLE_LIST_PARENT;
|
||||
DROP TABLE "ARRAY_OWNER";
|
||||
DROP TABLE DOUBLE_LIST_OWNER;
|
||||
DROP TABLE FLOAT_LIST_OWNER;
|
||||
@@ -68,11 +69,19 @@ CREATE TABLE "LIST_PARENT"
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE SIMPLE_LIST_PARENT
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
NAME VARCHAR(100)
|
||||
);
|
||||
|
||||
CREATE TABLE element_no_id
|
||||
(
|
||||
content VARCHAR(100),
|
||||
LIST_PARENT_key BIGINT,
|
||||
"LIST_PARENT" INTEGER
|
||||
SIMPLE_LIST_PARENT_key BIGINT,
|
||||
"LIST_PARENT" INTEGER,
|
||||
SIMPLE_LIST_PARENT INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE "ARRAY_OWNER"
|
||||
|
||||
@@ -373,12 +373,10 @@ public class QueryMapper {
|
||||
|
||||
mappedValue = convertValue(comparator, parameter.getValue(), propertyField.getTypeHint());
|
||||
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
|
||||
} else if (criteria.getValue() instanceof ValueFunction) {
|
||||
} else if (criteria.getValue() instanceof ValueFunction<?> valueFunction) {
|
||||
|
||||
ValueFunction<Object> valueFunction = (ValueFunction<Object>) criteria.getValue();
|
||||
Object value = valueFunction.apply(getEscaper(comparator));
|
||||
mappedValue = valueFunction.transform(v -> convertValue(comparator, v, propertyField.getTypeHint())).apply(getEscaper(comparator));
|
||||
|
||||
mappedValue = convertValue(comparator, value, propertyField.getTypeHint());
|
||||
typeHint = actualType.getType();
|
||||
} else {
|
||||
|
||||
|
||||
@@ -111,18 +111,14 @@ public class UpdateMapper extends QueryMapper {
|
||||
Object mappedValue;
|
||||
Class<?> typeHint;
|
||||
|
||||
if (value instanceof Parameter) {
|
||||
|
||||
Parameter parameter = (Parameter) value;
|
||||
if (value instanceof Parameter parameter) {
|
||||
|
||||
mappedValue = convertValue(parameter.getValue(), propertyField.getTypeHint());
|
||||
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
|
||||
|
||||
} else if (value instanceof ValueFunction) {
|
||||
} else if (value instanceof ValueFunction<?> valueFunction) {
|
||||
|
||||
ValueFunction<Object> valueFunction = (ValueFunction<Object>) value;
|
||||
|
||||
mappedValue = convertValue(valueFunction.apply(Escaper.DEFAULT), propertyField.getTypeHint());
|
||||
mappedValue = valueFunction.transform(v -> convertValue(v, propertyField.getTypeHint())).apply(Escaper.DEFAULT);
|
||||
|
||||
if (mappedValue == null) {
|
||||
return Assignments.value(column, SQL.nullLiteral());
|
||||
|
||||
@@ -56,4 +56,16 @@ public interface ValueFunction<T> extends Function<Escaper, T> {
|
||||
|
||||
return () -> apply(escaper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the inner value of the ValueFunction using the profided transformation.
|
||||
*
|
||||
* The default implementation just return the current {@literal ValueFunction}.
|
||||
* This is not a valid implementation and serves just to maintain backward compatibility.
|
||||
*
|
||||
* @param transformation to be applied to the underlying value.
|
||||
* @return a new {@literal ValueFunction}.
|
||||
* @since 3.2
|
||||
*/
|
||||
default ValueFunction<T> transform(Function<Object, Object> transformation) {return this;};
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
@@ -81,6 +82,12 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
|
||||
return createSelect(condition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findAllByCondition(BiFunction<Table, RelationalPersistentEntity, Condition> conditionSource) {
|
||||
Condition condition = conditionSource.apply(table, aggregate);
|
||||
return createSelect(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link AggregatePath} to the id property of the aggregate root.
|
||||
*/
|
||||
@@ -88,13 +95,7 @@ public class SingleQuerySqlGenerator implements SqlGenerator {
|
||||
return context.getAggregatePath(aggregate).append(aggregate.getRequiredIdProperty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SQL suitable of loading all the data required for constructing complete aggregates.
|
||||
*
|
||||
* @param condition a constraint for limiting the aggregates to be loaded.
|
||||
* @return a {@literal String} containing the generated SQL statement
|
||||
*/
|
||||
private String createSelect(Condition condition) {
|
||||
String createSelect(Condition condition) {
|
||||
|
||||
AggregatePath rootPath = context.getAggregatePath(aggregate);
|
||||
QueryMeta queryMeta = createInlineQuery(rootPath, condition);
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.relational.core.sqlgeneration;
|
||||
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.sql.Condition;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Generates SQL statements for loading aggregates.
|
||||
*
|
||||
@@ -28,5 +34,7 @@ public interface SqlGenerator {
|
||||
|
||||
String findAllById();
|
||||
|
||||
String findAllByCondition(BiFunction<Table, RelationalPersistentEntity, Condition> conditionSource);
|
||||
|
||||
AliasFactory getAliasFactory();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2023 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.repository.query;
|
||||
|
||||
import org.springframework.data.relational.core.dialect.Escaper;
|
||||
import org.springframework.data.relational.core.query.ValueFunction;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Value function that has an underlying value and a modifier that gets applied after the escaper.
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @since 3.2
|
||||
*/
|
||||
record ModifyingValueFunction(Object value, Function<String, String> modifier) implements ValueFunction<String> {
|
||||
|
||||
static ModifyingValueFunction of(Object value, Function<String, String> modifier) {
|
||||
return new ModifyingValueFunction(value, modifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(Escaper escaper) {
|
||||
return modifier.apply(escaper.escape(value.toString()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValueFunction<String> transform(Function<Object, Object> transformation) {
|
||||
return new ModifyingValueFunction(transformation.apply(value), modifier);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.relational.core.dialect.Escaper;
|
||||
import org.springframework.data.relational.core.query.ValueFunction;
|
||||
import org.springframework.data.repository.query.Parameter;
|
||||
import org.springframework.data.repository.query.Parameters;
|
||||
import org.springframework.data.repository.query.parser.Part;
|
||||
@@ -139,12 +138,12 @@ class ParameterMetadataProvider implements Iterable<ParameterMetadata> {
|
||||
|
||||
switch (partType) {
|
||||
case STARTING_WITH:
|
||||
return (ValueFunction<String>) escaper -> escaper.escape(value.toString()) + "%";
|
||||
return ModifyingValueFunction.of(value, s -> s + "%");
|
||||
case ENDING_WITH:
|
||||
return (ValueFunction<String>) escaper -> "%" + escaper.escape(value.toString());
|
||||
return ModifyingValueFunction.of(value, s -> "%" + s);
|
||||
case CONTAINING:
|
||||
case NOT_CONTAINING:
|
||||
return (ValueFunction<String>) escaper -> "%" + escaper.escape(value.toString()) + "%";
|
||||
return ModifyingValueFunction.of(value, s -> "%" + s + "%");
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user