Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.springframework.data.jdbc.core.convert.SqlGeneratorSource;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.conversion.AbstractRelationalConverter;
import org.springframework.data.relational.core.mapping.AggregatePath;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
Expand All @@ -47,6 +48,7 @@
* @author Jens Schauder
* @author Myeonghyeon Lee
* @author Diego Krupitza
* @author wonderfulrosemari
* @since 2.0
*/
public class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
Expand Down Expand Up @@ -152,7 +154,8 @@ StatementFactory getStatementFactory() {
* @param tree the tree structure defining the predicate of the query.
* @param parameters parameters for the predicate.
*/
static void validate(PartTree tree, Parameters<?, ?> parameters, RelationalMappingContext context) {
static void validate(PartTree tree, Parameters<?, ?> parameters, RelationalMappingContext context,
JdbcConverter converter) {

RelationalQueryCreator.validate(tree, parameters);

Expand All @@ -163,12 +166,12 @@ static void validate(PartTree tree, Parameters<?, ?> parameters, RelationalMappi
.getPersistentPropertyPath(part.getProperty());
AggregatePath path = context.getAggregatePath(propertyPath);

path.forEach(JdbcQueryCreator::validateProperty);
path.forEach(pathElement -> validateProperty(pathElement, converter));
}
}
}

private static void validateProperty(AggregatePath path) {
private static void validateProperty(AggregatePath path, JdbcConverter converter) {

if (path.isRoot()) {
return;
Expand All @@ -183,11 +186,20 @@ private static void validateProperty(AggregatePath path) {
String.format("Cannot query by multi-valued property: %s", path.getRequiredLeafProperty().getName()));
}

if (!path.isEmbedded() && path.isEntity()) {
if (!path.isEmbedded() && path.isEntity() && !hasCustomWriteTarget(path, converter)) {
throw new IllegalArgumentException(String.format("Cannot query by nested entity: %s", path.toDotPath()));
}
}

private static boolean hasCustomWriteTarget(AggregatePath path, JdbcConverter converter) {

if (!(converter instanceof AbstractRelationalConverter relationalConverter)) {
return false;
}

return relationalConverter.getConversions().hasCustomWriteTarget(path.getRequiredLeafProperty().getActualType());
}

/**
* Creates {@link ParametrizedQuery} applying the given {@link Criteria} and {@link Sort} definition.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
* @author Mikhail Polivakha
* @author Yunyoung LEE
* @author Nikita Konev
* @author wonderfulrosemari
* @since 2.0
*/
public class PartTreeJdbcQuery extends AbstractJdbcQuery {
Expand Down Expand Up @@ -145,7 +146,7 @@ public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod query
this.converter = converter;

this.tree = new PartTree(queryMethod.getName(), queryMethod.getResultProcessor().getReturnedType().getDomainType());
JdbcQueryCreator.validate(this.tree, this.parameters, this.converter.getMappingContext());
JdbcQueryCreator.validate(this.tree, this.parameters, this.converter.getMappingContext(), this.converter);

this.cachedRowMapperFactory = new CachedRowMapperFactory(tree, rowMapperFactory, converter,
queryMethod.getResultProcessor());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -73,6 +74,7 @@
* @author Jens Schauder
* @author Myeonghyeon Lee
* @author Diego Krupitza
* @author wonderfulrosemari
*/
@ExtendWith(MockitoExtension.class)
public class PartTreeJdbcQueryUnitTests {
Expand Down Expand Up @@ -753,15 +755,46 @@ void createsQueryForCountProjection() throws Exception {
.isEqualTo("SELECT COUNT(*) FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name");
}

@Test // GH-2059
void createsQueryBySimpleDomainPrimitiveWithCustomConverters() throws Exception {

JdbcCustomConversions conversions = JdbcCustomConversions.create(JdbcPostgresDialect.INSTANCE, it -> {
it.registerConverter(CustomerRefToStringConverter.INSTANCE);
it.registerConverter(StringToCustomerRefConverter.INSTANCE);
});
JdbcMappingContext localContext = new JdbcMappingContext();
JdbcConverter localConverter = new MappingJdbcConverter(localContext, mock(RelationResolver.class), conversions,
JdbcTypeFactory.unsupported());

JdbcQueryMethod queryMethod = getQueryMethod(CustomerRepository.class, localContext, "findAllByRef",
CustomerRef.class);
PartTreeJdbcQuery jdbcQuery = new PartTreeJdbcQuery(localContext, queryMethod, JdbcH2Dialect.INSTANCE,
localConverter, mock(NamedParameterJdbcOperations.class), mock(RowMapper.class));
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { new CustomerRef("abc") }),
returnedType);

QueryAssert.assertThat(query).contains(" WHERE \"customers\".\"REF\" = :ref").hasBindValue("ref", "abc");
}

private PartTreeJdbcQuery createQuery(JdbcQueryMethod queryMethod) {
return new PartTreeJdbcQuery(mappingContext, queryMethod, JdbcH2Dialect.INSTANCE, converter,
mock(NamedParameterJdbcOperations.class), mock(RowMapper.class));
}

private JdbcQueryMethod getQueryMethod(String methodName, Class<?>... parameterTypes) throws Exception {
Method method = UserRepository.class.getMethod(methodName, parameterTypes);
return new JdbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
new SpelAwareProxyProjectionFactory(), new PropertiesBasedNamedQueries(new Properties()), mappingContext);
return getQueryMethod(UserRepository.class, methodName, parameterTypes);
}

private JdbcQueryMethod getQueryMethod(Class<?> repositoryType, String methodName, Class<?>... parameterTypes)
throws Exception {
return getQueryMethod(repositoryType, mappingContext, methodName, parameterTypes);
}

private JdbcQueryMethod getQueryMethod(Class<?> repositoryType, JdbcMappingContext mappingContext, String methodName,
Class<?>... parameterTypes) throws Exception {
Method method = repositoryType.getMethod(methodName, parameterTypes);
return new JdbcQueryMethod(method, new DefaultRepositoryMetadata(repositoryType), new SpelAwareProxyProjectionFactory(),
new PropertiesBasedNamedQueries(new Properties()), mappingContext);
}

private RelationalParametersParameterAccessor getAccessor(JdbcQueryMethod queryMethod, Object[] values) {
Expand Down Expand Up @@ -866,6 +899,12 @@ interface UserRepository extends Repository<User, Long> {
long countByFirstName(String name);
}

@NoRepositoryBean
interface CustomerRepository extends Repository<Customer, Long> {

List<Customer> findAllByRef(CustomerRef ref);
}

@Table("users")
static class User {

Expand All @@ -890,6 +929,16 @@ static class User {
record UserId(Long id, String subId) {
}

@Table("customers")
static class Customer {

@Id Long id;
CustomerRef ref;
}

record CustomerRef(String value) {
}

record Address(String street, String city) {
}

Expand All @@ -914,4 +963,26 @@ public JdbcValue convert(Direction source) {
return JdbcValue.of(source.ordinal(), JdbcPostgresDialect.INSTANCE.createSqlType("foo", 4711));
}
}

@WritingConverter
enum CustomerRefToStringConverter implements Converter<CustomerRef, String> {

INSTANCE;

@Override
public String convert(CustomerRef source) {
return source.value();
}
}

@ReadingConverter
enum StringToCustomerRefConverter implements Converter<String, CustomerRef> {

INSTANCE;

@Override
public CustomerRef convert(String source) {
return new CustomerRef(source);
}
}
}