Skip to content

Commit 8ce50b8

Browse files
committed
Polishing.
Introduce API for easier SQLType creation. Add test for query derivation. See #2187 Original pull request: #2193
1 parent 40d2fd1 commit 8ce50b8

File tree

5 files changed

+128
-33
lines changed

5 files changed

+128
-33
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2026-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jdbc.core.dialect;
17+
18+
import java.sql.SQLType;
19+
20+
/**
21+
* Record implementation of {@link SQLType} to be used for custom types in {@link JdbcDialect}.
22+
*
23+
* @author Mark Paluch
24+
* @since 4.0.4
25+
*/
26+
record CustomSQLType(String name, String vendor, int oid) implements SQLType {
27+
28+
@Override
29+
public String getName() {
30+
return name;
31+
}
32+
33+
@Override
34+
public String getVendor() {
35+
return vendor;
36+
}
37+
38+
@Override
39+
public Integer getVendorTypeNumber() {
40+
return oid;
41+
}
42+
43+
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcDialect.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.jdbc.core.dialect;
1717

18+
import java.sql.SQLType;
19+
1820
import org.springframework.data.relational.core.dialect.Dialect;
1921

2022
/**
@@ -50,4 +52,15 @@ static JdbcArrayColumns getArraySupport(Dialect dialect) {
5052
: JdbcArrayColumns.DefaultSupport.INSTANCE;
5153
}
5254

55+
/**
56+
* Creates a {@link SQLType} for the given name and vendor type number.
57+
*
58+
* @param name type name that represents a SQL data type.
59+
* @param vendorTypeNumber vendor-specific type number for the data type.
60+
* @return a new {@link SQLType} for the given name and vendor type number.
61+
*/
62+
default SQLType createSqlType(String name, int vendorTypeNumber) {
63+
return new CustomSQLType(name, "Spring", vendorTypeNumber);
64+
}
65+
5366
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/dialect/JdbcPostgresDialect.java

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,18 @@ public JdbcArrayColumns getArraySupport() {
7878
return ARRAY_COLUMNS;
7979
}
8080

81+
/**
82+
* Creates a Postgres {@link SQLType} for the given name and vendor type number.
83+
*
84+
* @param name type name that represents a SQL data type.
85+
* @param vendorTypeNumber vendor-specific type number for the data type.
86+
* @return a new {@link SQLType} for the given name and vendor type number.
87+
*/
88+
@Override
89+
public SQLType createSqlType(String name, int vendorTypeNumber) {
90+
return new CustomSQLType(name, "Postgres", vendorTypeNumber);
91+
}
92+
8193
/**
8294
* If the class is present on the class path, invoke the specified consumer {@code action} with the class object,
8395
* otherwise do nothing.
@@ -184,7 +196,7 @@ public TypeInfoCacheWrapper() {
184196
continue;
185197
}
186198

187-
arrayTypes.put(javaClass, new PGSQLType(pgTypeName, arrayOid));
199+
arrayTypes.put(javaClass, JdbcPostgresDialect.INSTANCE.createSqlType(pgTypeName, arrayOid));
188200
}
189201
} catch (SQLException | ClassNotFoundException e) {
190202
throw new IllegalStateException("Cannot create type info mapping", e);
@@ -200,22 +212,5 @@ Map<Class<?>, SQLType> getArrayTypeMap() {
200212
return arrayTypes;
201213
}
202214

203-
record PGSQLType(String name, int oid) implements SQLType {
204-
205-
@Override
206-
public String getName() {
207-
return name;
208-
}
209-
210-
@Override
211-
public String getVendor() {
212-
return "Postgres";
213-
}
214-
215-
@Override
216-
public Integer getVendorTypeNumber() {
217-
return oid;
218-
}
219-
}
220215
}
221216
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,27 @@
2626
import java.util.List;
2727
import java.util.Properties;
2828

29+
import org.junit.jupiter.api.BeforeEach;
2930
import org.junit.jupiter.api.Test;
3031
import org.junit.jupiter.api.extension.ExtendWith;
3132
import org.mockito.junit.jupiter.MockitoExtension;
3233

34+
import org.springframework.core.convert.converter.Converter;
3335
import org.springframework.data.annotation.Id;
36+
import org.springframework.data.convert.WritingConverter;
3437
import org.springframework.data.domain.PageRequest;
3538
import org.springframework.data.domain.Pageable;
3639
import org.springframework.data.domain.Sort;
3740
import org.springframework.data.jdbc.core.convert.JdbcConverter;
41+
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
42+
import org.springframework.data.jdbc.core.convert.JdbcTypeFactory;
3843
import org.springframework.data.jdbc.core.convert.MappingJdbcConverter;
3944
import org.springframework.data.jdbc.core.convert.RelationResolver;
4045
import org.springframework.data.jdbc.core.dialect.JdbcH2Dialect;
46+
import org.springframework.data.jdbc.core.dialect.JdbcPostgresDialect;
4147
import org.springframework.data.jdbc.core.mapping.AggregateReference;
4248
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
49+
import org.springframework.data.jdbc.core.mapping.JdbcValue;
4350
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
4451
import org.springframework.data.relational.core.dialect.Escaper;
4552
import org.springframework.data.relational.core.mapping.Embedded;
@@ -74,7 +81,7 @@ public class PartTreeJdbcQueryUnitTests {
7481
private static final String JOIN_CLAUSE = "FROM \"users\" LEFT OUTER JOIN \"HOBBY\" \"hated\" ON \"hated\".\"USERS_ID\" = \"users\".\"ID\" AND \"hated\".\"USERS_SUB_ID\" = \"users\".\"SUB_ID\"";
7582

7683
private JdbcMappingContext mappingContext = new JdbcMappingContext();
77-
private JdbcConverter converter = new MappingJdbcConverter(mappingContext, mock(RelationResolver.class));
84+
private JdbcConverter converter;
7885
private ReturnedType returnedType = mock(ReturnedType.class);
7986

8087
private org.springframework.data.relational.core.sql.Table users = org.springframework.data.relational.core.sql.Table
@@ -94,6 +101,18 @@ public class PartTreeJdbcQueryUnitTests {
94101
users.column("HOBBY_REFERENCE"), //
95102
hobby.column("NAME").as("HATED_NAME"));
96103

104+
@BeforeEach
105+
void setUp() {
106+
107+
JdbcCustomConversions conversions = JdbcCustomConversions.create(JdbcPostgresDialect.INSTANCE, it -> {
108+
it.registerConverter(DirectionToOtherJdbcTypeConverter.INSTANCE);
109+
});
110+
111+
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
112+
converter = new MappingJdbcConverter(mappingContext, mock(RelationResolver.class), conversions,
113+
JdbcTypeFactory.unsupported());
114+
}
115+
97116
@Test // DATAJDBC-318
98117
void shouldFailForQueryByReference() throws Exception {
99118

@@ -699,6 +718,18 @@ void createsQueryByEmbeddedObject() throws Exception {
699718
assertThat(query.getParameterSource(Escaper.DEFAULT).getValue("user_city")).isEqualTo("World");
700719
}
701720

721+
@Test // GH-2187
722+
void createsQueryByConvertedEnumAttribute() throws Exception {
723+
724+
JdbcQueryMethod queryMethod = getQueryMethod("findAllByDirection", Direction.class);
725+
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
726+
RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { Direction.RIGHT });
727+
ParametrizedQuery query = jdbcQuery.createQuery(accessor, returnedType);
728+
729+
QueryAssert.assertThat(query).containsQuotedAliasedColumns(columns)
730+
.contains(" WHERE " + TABLE + ".\"DIRECTION\" = :direction").hasBindValue("direction", 2);
731+
}
732+
702733
@Test // DATAJDBC-318
703734
void createsQueryByEmbeddedProperty() throws Exception {
704735

@@ -810,6 +841,8 @@ interface UserRepository extends Repository<User, Long> {
810841

811842
List<User> findAllByActiveFalse();
812843

844+
List<User> findAllByDirection(Direction direction);
845+
813846
List<User> findAllByFirstNameIgnoreCase(String firstName);
814847

815848
List<User> findAllByFirstName(String firstName, Pageable pageable);
@@ -849,6 +882,7 @@ static class User {
849882

850883
List<Hobby> hobbies;
851884
Hobby hated;
885+
Direction direction;
852886

853887
AggregateReference<Hobby, String> hobbyReference;
854888
}
@@ -865,4 +899,19 @@ record AnotherEmbedded(@MappedCollection(idColumn = "ID", keyColumn = "ORDER_KEY
865899
static class Hobby {
866900
@Id String name;
867901
}
902+
903+
enum Direction {
904+
LEFT, CENTER, RIGHT
905+
}
906+
907+
@WritingConverter
908+
enum DirectionToOtherJdbcTypeConverter implements Converter<Direction, JdbcValue> {
909+
910+
INSTANCE;
911+
912+
@Override
913+
public JdbcValue convert(Direction source) {
914+
return JdbcValue.of(source.ordinal(), JdbcPostgresDialect.INSTANCE.createSqlType("foo", 4711));
915+
}
916+
}
868917
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQueryUnitTests.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -320,12 +320,8 @@ void spelParametersSqlTypesArePropagatedCorrectly() {
320320
.map(n -> tuple(sqlParameterSource.getValue(n), sqlParameterSource.getSqlType(n))) //
321321
.collect(Collectors.toSet());
322322

323-
assertThat(valueTypePairs).containsExactlyInAnyOrder(
324-
tuple(type, Types.VARCHAR),
325-
tuple(score, Types.INTEGER),
326-
tuple(creationDate, Types.TIMESTAMP),
327-
tuple(dayOfWeek, Types.VARCHAR)
328-
);
323+
assertThat(valueTypePairs).containsExactlyInAnyOrder(tuple(type, Types.VARCHAR), tuple(score, Types.INTEGER),
324+
tuple(creationDate, Types.TIMESTAMP), tuple(dayOfWeek, Types.VARCHAR));
329325
}
330326

331327
@Test // GH-1212
@@ -416,16 +412,15 @@ void spelCanBeUsedInsideQueries() {
416412
assertThat(paramSource.getValue().getValue("__$synthetic$__2")).isEqualTo("test-value2");
417413
}
418414

419-
@Test // GH-2188
415+
@Test // GH-2187
420416
void shouldPreserveJdbcTypeOtherFromJdbcValueInStringBasedQuery() {
421417

422418
SqlParameterSource parameterSource = forMethod("findByCustomValue", Direction.class)
423-
.withCustomConverters(DirectionToOtherJdbcTypeConverter.INSTANCE)
424-
.withArguments(Direction.LEFT)
419+
.withCustomConverters(DirectionToOtherJdbcTypeConverter.INSTANCE) //
420+
.withArguments(Direction.LEFT) //
425421
.extractParameterSource();
426422

427-
assertThat(parameterSource.getSqlType("value"))
428-
.isEqualTo(JDBCType.OTHER.getVendorTypeNumber());
423+
assertThat(parameterSource.getSqlType("value")).isEqualTo(JDBCType.OTHER.getVendorTypeNumber());
429424
}
430425

431426
QueryFixture forMethod(String name, Class... paramTypes) {
@@ -575,7 +570,7 @@ interface MyRepository extends Repository<Object, Long> {
575570
@Query("SELECT * FROM person WHERE id = :id")
576571
DummyEntity unsupportedWithLock(Long id);
577572

578-
@Query(value = "some sql statement") // GH-2188
573+
@Query(value = "some sql statement")
579574
List<DummyEntity> findByCustomValue(@Param("value") Direction value);
580575
}
581576

@@ -606,7 +601,7 @@ public Object extractData(ResultSet rs) throws DataAccessException {
606601
}
607602
}
608603

609-
private enum Direction {
604+
enum Direction {
610605
LEFT, CENTER, RIGHT
611606
}
612607

@@ -670,7 +665,7 @@ public String convert(ListContainer source) {
670665
}
671666
}
672667

673-
@WritingConverter // GH-2188
668+
@WritingConverter
674669
enum DirectionToOtherJdbcTypeConverter implements Converter<Direction, JdbcValue> {
675670

676671
INSTANCE;

0 commit comments

Comments
 (0)