Skip to content

Commit b29aec0

Browse files
committed
ydb-platformGH-173 Added custom YdbRepositoryFactory infrastructure
1 parent 49cde3a commit b29aec0

16 files changed

+365
-12
lines changed

spring-data-jdbc-ydb/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ spring.datasource.driver-class-name=tech.ydb.jdbc.YdbDriver
6060
spring.datasource.url=jdbc:ydb:grpc://localhost:2136/local
6161
```
6262

63-
Java configuration for @YdbType annotation:
63+
Java configuration for `@YdbType` annotation:
6464

6565
```java
6666
@Import(AbstractYdbJdbcConfiguration.class)
@@ -76,4 +76,4 @@ See [connect to YDB](../README.md/#connect-to-ydb).
7676

7777
## Support and Contact
7878

79-
For support, you can open issues in the repository issue tracker with tag `spring-data-jdbc`.
79+
For support, you can open issues in the repository issue tracker with tag `spring-data-jdbc`.

spring-data-jdbc-ydb/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
<dependency>
8383
<groupId>org.springframework.data</groupId>
8484
<artifactId>spring-data-jdbc</artifactId>
85+
<version>3.5.0-SNAPSHOT</version>
8586
<scope>provided</scope>
8687
</dependency>
8788
<dependency>
@@ -90,7 +91,6 @@
9091
<version>${ydb.jdbc.version}</version>
9192
<scope>provided</scope>
9293
</dependency>
93-
9494
<dependency>
9595
<groupId>tech.ydb.test</groupId>
9696
<artifactId>ydb-junit5-support</artifactId>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package tech.ydb.data.core;
2+
3+
import java.lang.reflect.Method;
4+
5+
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
6+
import org.springframework.data.mapping.context.MappingContext;
7+
import org.springframework.data.projection.ProjectionFactory;
8+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
9+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
10+
import org.springframework.data.repository.core.NamedQueries;
11+
import org.springframework.data.repository.core.RepositoryMetadata;
12+
import org.springframework.data.repository.query.Parameters;
13+
import org.springframework.data.repository.query.ParametersSource;
14+
15+
/**
16+
* Custom {@link JdbcQueryMethod} implementation specific to YDB.
17+
*
18+
* @author Mikhail Polivakha
19+
*/
20+
public class YdbQueryMethod extends JdbcQueryMethod {
21+
22+
public YdbQueryMethod(
23+
Method method,
24+
RepositoryMetadata metadata,
25+
ProjectionFactory factory,
26+
NamedQueries namedQueries,
27+
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext
28+
) {
29+
super(method, metadata, factory, namedQueries, mappingContext);
30+
}
31+
32+
@Override
33+
protected Parameters<?, ?> createParameters(ParametersSource parametersSource) {
34+
return new YdbQueryParameters(parametersSource);
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package tech.ydb.data.core;
2+
3+
import java.lang.annotation.Annotation;
4+
import java.sql.SQLType;
5+
6+
import org.springframework.core.MethodParameter;
7+
import org.springframework.data.jdbc.repository.query.JdbcParameter;
8+
import org.springframework.data.jdbc.repository.query.JdbcParameters;
9+
import org.springframework.data.relational.repository.query.RelationalParameters;
10+
import org.springframework.data.repository.query.ParametersSource;
11+
import org.springframework.data.util.Lazy;
12+
import org.springframework.data.util.TypeInformation;
13+
14+
import tech.ydb.data.core.convert.YQLType;
15+
import tech.ydb.data.core.convert.YdbType;
16+
import tech.ydb.table.values.PrimitiveType;
17+
18+
/**
19+
* Parameters of the YDB query. Custom implementation of {@link JdbcParameters}, emerged from requirement
20+
* to support {@link YdbType} on method parameters:
21+
* <p/>
22+
* <pre class="code">
23+
* &#064;Query("SELECT * FROM my_table WHERE type = :type")
24+
* Optional<MyEntity> findByType(@Param("type") @YdbType("Text") String type);
25+
* </pre>
26+
*
27+
* @author Mikhail Polivakha
28+
*/
29+
public class YdbQueryParameters extends JdbcParameters {
30+
31+
public YdbQueryParameters(ParametersSource parametersSource) {
32+
super(parametersSource, methodParameter -> {
33+
34+
YdbType ydbType = methodParameter.getParameterAnnotation(YdbType.class);
35+
36+
if (ydbType != null) {
37+
YQLType sqlType = new YQLType(PrimitiveType.valueOf(ydbType.value()));
38+
return new YdbMethodParameter(
39+
methodParameter,
40+
parametersSource.getDomainTypeInformation(),
41+
sqlType,
42+
Lazy.of(() -> sqlType) // TODO: Do we support the array/collection-like types?
43+
);
44+
} else {
45+
return new YdbMethodParameter(methodParameter, parametersSource.getDomainTypeInformation());
46+
}
47+
});
48+
}
49+
50+
static class YdbMethodParameter extends JdbcParameter {
51+
52+
public YdbMethodParameter(MethodParameter parameter, TypeInformation<?> domainType) {
53+
super(parameter, domainType);
54+
}
55+
56+
public YdbMethodParameter(MethodParameter parameter, TypeInformation<?> domainType, SQLType sqlType, Lazy<SQLType> actualSqlType) {
57+
super(parameter, domainType, sqlType, actualSqlType);
58+
}
59+
}
60+
}

spring-data-jdbc-ydb/src/main/java/tech/ydb/data/core/convert/YdbType.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55
import java.lang.annotation.RetentionPolicy;
66
import java.lang.annotation.Target;
77

8+
import tech.ydb.table.values.PrimitiveType;
9+
810
/**
911
* @author Madiyar Nurgazin
12+
* @author Mikhail Polivakha
1013
*/
1114
@Retention(RetentionPolicy.RUNTIME)
12-
@Target(ElementType.FIELD)
15+
@Target({ElementType.FIELD, ElementType.PARAMETER})
1316
public @interface YdbType {
1417
String value();
1518
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package tech.ydb.data.repository.support;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.Optional;
5+
6+
import org.apache.commons.logging.Log;
7+
import org.apache.commons.logging.LogFactory;
8+
import org.springframework.beans.factory.BeanFactory;
9+
import org.springframework.context.ApplicationEventPublisher;
10+
import org.springframework.data.jdbc.core.convert.DataAccessStrategy;
11+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
12+
import org.springframework.data.jdbc.repository.QueryMappingConfiguration;
13+
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
14+
import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery;
15+
import org.springframework.data.jdbc.repository.query.RowMapperFactory;
16+
import org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery;
17+
import org.springframework.data.jdbc.repository.support.BeanFactoryAwareRowMapperFactory;
18+
import org.springframework.data.jdbc.repository.support.JdbcQueryLookupStrategy;
19+
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
20+
import org.springframework.data.mapping.callback.EntityCallbacks;
21+
import org.springframework.data.projection.ProjectionFactory;
22+
import org.springframework.data.relational.core.dialect.Dialect;
23+
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
24+
import org.springframework.data.repository.core.NamedQueries;
25+
import org.springframework.data.repository.core.RepositoryMetadata;
26+
import org.springframework.data.repository.query.CachingValueExpressionDelegate;
27+
import org.springframework.data.repository.query.QueryLookupStrategy;
28+
import org.springframework.data.repository.query.RepositoryQuery;
29+
import org.springframework.data.repository.query.ValueExpressionDelegate;
30+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
31+
import org.springframework.lang.Nullable;
32+
33+
import tech.ydb.data.core.YdbQueryMethod;
34+
35+
/**
36+
* Custom {@link JdbcRepositoryFactory repository factory} to allow for query tuning.
37+
*
38+
* @author Mikhail Polivakha
39+
*/
40+
public class YdbRepositoryFactory extends JdbcRepositoryFactory {
41+
42+
private final EntityCallbacks entityCallbacks;
43+
private final QueryMappingConfiguration queryMappingConfiguration;
44+
45+
private static final Log LOG = LogFactory.getLog(JdbcQueryLookupStrategy.class);
46+
47+
/**
48+
* Creates a new {@link JdbcRepositoryFactory} for the given {@link DataAccessStrategy},
49+
* {@link RelationalMappingContext} and {@link ApplicationEventPublisher}.
50+
*
51+
* @param dataAccessStrategy must not be {@literal null}.
52+
* @param context must not be {@literal null}.
53+
* @param converter must not be {@literal null}.
54+
* @param dialect must not be {@literal null}.
55+
* @param publisher must not be {@literal null}.
56+
* @param operations must not be {@literal null}.
57+
*/
58+
public YdbRepositoryFactory(
59+
DataAccessStrategy dataAccessStrategy,
60+
RelationalMappingContext context,
61+
JdbcConverter converter,
62+
Dialect dialect,
63+
ApplicationEventPublisher publisher,
64+
NamedParameterJdbcOperations operations,
65+
EntityCallbacks entityCallbacks,
66+
QueryMappingConfiguration queryMappingConfiguration
67+
) {
68+
super(dataAccessStrategy, context, converter, dialect, publisher, operations);
69+
70+
this.entityCallbacks = entityCallbacks;
71+
this.queryMappingConfiguration = queryMappingConfiguration;
72+
}
73+
74+
@Override
75+
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key, ValueExpressionDelegate valueExpressionDelegate) {
76+
return Optional.of(
77+
new YdbQueryLookupStrategy(
78+
publisher,
79+
entityCallbacks,
80+
context,
81+
converter,
82+
dialect,
83+
beanFactory,
84+
queryMappingConfiguration,
85+
operations,
86+
new CachingValueExpressionDelegate(valueExpressionDelegate)
87+
)
88+
);
89+
}
90+
91+
static class YdbQueryLookupStrategy extends JdbcQueryLookupStrategy {
92+
93+
private final RowMapperFactory rowMapperFactory;
94+
95+
public YdbQueryLookupStrategy(
96+
ApplicationEventPublisher publisher, @Nullable EntityCallbacks callbacks,
97+
RelationalMappingContext context, JdbcConverter converter, Dialect dialect, BeanFactory beanFactory,
98+
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
99+
ValueExpressionDelegate delegate
100+
) {
101+
super(publisher, callbacks, context, converter, dialect, queryMappingConfiguration, operations, delegate);
102+
103+
this.rowMapperFactory = new BeanFactoryAwareRowMapperFactory(context, converter, queryMappingConfiguration, callbacks, publisher, beanFactory);
104+
}
105+
106+
@Override
107+
public RepositoryQuery resolveQuery(
108+
Method method,
109+
RepositoryMetadata repositoryMetadata,
110+
ProjectionFactory projectionFactory,
111+
NamedQueries namedQueries
112+
) {
113+
JdbcQueryMethod queryMethod = new YdbQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries, getMappingContext());
114+
115+
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
116+
117+
if (queryMethod.hasAnnotatedQuery() && queryMethod.hasAnnotatedQueryName()) {
118+
LOG.warn(String.format(
119+
"Query method %s is annotated with both, a query and a query name; Using the declared query", method));
120+
}
121+
122+
String queryString = evaluateTableExpressions(repositoryMetadata, queryMethod.getRequiredQuery());
123+
124+
return new StringBasedJdbcQuery(queryString, queryMethod, getOperations(), rowMapperFactory, getConverter(),
125+
delegate);
126+
} else {
127+
return new PartTreeJdbcQuery(getMappingContext(), queryMethod, getDialect(), getConverter(), getOperations(), rowMapperFactory);
128+
}
129+
}
130+
}
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package tech.ydb.data.repository.support;
2+
3+
import java.io.Serializable;
4+
5+
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean;
6+
import org.springframework.data.repository.Repository;
7+
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
8+
9+
/**
10+
* Custom implementation, specific for YDB Spring Data repositories.
11+
*
12+
* @author Mikhail Polivakha
13+
*/
14+
public class YdbRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends JdbcRepositoryFactoryBean<T, S, ID> {
15+
16+
/**
17+
* Creates a new {@link JdbcRepositoryFactoryBean} for the given repository interface.
18+
*
19+
* @param repositoryInterface must not be {@literal null}.
20+
*/
21+
public YdbRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
22+
super(repositoryInterface);
23+
}
24+
25+
@Override
26+
protected RepositoryFactorySupport doCreateRepositoryFactory() {
27+
return new YdbRepositoryFactory(
28+
dataAccessStrategy,
29+
mappingContext,
30+
converter,
31+
dialect,
32+
publisher,
33+
operations,
34+
entityCallbacks,
35+
queryMappingConfiguration
36+
);
37+
}
38+
}

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbBaseTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package tech.ydb.data;
22

3+
import org.junit.jupiter.api.extension.ExtendWith;
34
import org.junit.jupiter.api.extension.RegisterExtension;
45
import org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc;
56
import org.springframework.boot.test.context.SpringBootTest;

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbJdbcConfiguration.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import org.springframework.context.annotation.Import;
55
import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing;
66
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
7+
78
import tech.ydb.data.repository.config.AbstractYdbJdbcConfiguration;
9+
import tech.ydb.data.repository.support.YdbRepositoryFactoryBean;
810

911
/**
1012
* @author Madiyar Nurgazin
@@ -13,8 +15,11 @@
1315
@Configuration
1416
@EnableJdbcRepositories(
1517
considerNestedRepositories = true,
16-
basePackages = "tech.ydb.data"
18+
basePackages = "tech.ydb.data",
19+
repositoryFactoryBeanClass = YdbRepositoryFactoryBean.class
1720
)
1821
@EnableJdbcAuditing
1922
@Import(AbstractYdbJdbcConfiguration.class)
20-
public class YdbJdbcConfiguration {}
23+
public class YdbJdbcConfiguration {
24+
25+
}

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/AuthorRepository.java spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/AuthorRepository.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tech.ydb.data.books.repository;
1+
package tech.ydb.data.all_types_table.repository;
22

33
import java.util.List;
44
import org.springframework.data.jdbc.repository.query.Query;

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/BookRepository.java spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/BookRepository.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tech.ydb.data.books.repository;
1+
package tech.ydb.data.all_types_table.repository;
22

33
import java.util.List;
44
import java.util.Optional;

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/ReviewRepository.java spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/ReviewRepository.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tech.ydb.data.books.repository;
1+
package tech.ydb.data.all_types_table.repository;
22

33
import java.util.List;
44
import org.springframework.data.domain.Pageable;

spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/RepositoriesIntegrationTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
import tech.ydb.data.books.entity.Book;
1515
import tech.ydb.data.books.entity.BookAuthor;
1616
import tech.ydb.data.books.entity.Review;
17-
import tech.ydb.data.books.repository.AuthorRepository;
18-
import tech.ydb.data.books.repository.BookRepository;
19-
import tech.ydb.data.books.repository.ReviewRepository;
17+
import tech.ydb.data.all_types_table.repository.AuthorRepository;
18+
import tech.ydb.data.all_types_table.repository.BookRepository;
19+
import tech.ydb.data.all_types_table.repository.ReviewRepository;
2020

2121
/**
2222
* @author Madiyar Nurgazin

0 commit comments

Comments
 (0)