From a6fb459d9557ddc4793ef658cb06bbdb73525143 Mon Sep 17 00:00:00 2001 From: mipo256 Date: Sun, 16 Feb 2025 22:03:01 +0300 Subject: [PATCH 1/2] Spring version bump --- spring-data-jdbc-ydb/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spring-data-jdbc-ydb/pom.xml b/spring-data-jdbc-ydb/pom.xml index 00d767f..acfe785 100644 --- a/spring-data-jdbc-ydb/pom.xml +++ b/spring-data-jdbc-ydb/pom.xml @@ -51,7 +51,7 @@ 5.10.2 1.18.30 - 3.2.1 + 3.4.0 4.24.0 2.2.9 From 5ca83c95a70944b45b9cd606f69a9de92c93f6f1 Mon Sep 17 00:00:00 2001 From: mipo256 Date: Sun, 16 Feb 2025 22:03:34 +0300 Subject: [PATCH 2/2] Added Reposiotories post processor --- .../ydb/data/core/dialect/YdbDialect.java | 20 +++++- .../config/AbstractYdbJdbcConfiguration.java | 4 ++ .../JdbcRepositoryBeanPostProcessor.java | 45 ++++++++++++ .../tech/ydb/data/YdbJdbcConfiguration.java | 6 +- .../JdbcRepositoryBeanPostProcessorTest.java | 72 +++++++++++++++++++ 5 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessor.java create mode 100644 spring-data-jdbc-ydb/src/test/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessorTest.java diff --git a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/core/dialect/YdbDialect.java b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/core/dialect/YdbDialect.java index f82b25c..e1c1079 100644 --- a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/core/dialect/YdbDialect.java +++ b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/core/dialect/YdbDialect.java @@ -1,6 +1,8 @@ package tech.ydb.data.core.dialect; +import java.lang.reflect.Method; import java.util.function.Function; + import org.springframework.aop.interceptor.ExposeInvocationInterceptor; import org.springframework.data.relational.core.dialect.AbstractDialect; import org.springframework.data.relational.core.dialect.InsertRenderContext; @@ -10,10 +12,12 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.LockOptions; import org.springframework.data.relational.core.sql.Select; + import tech.ydb.data.repository.ViewIndex; /** * @author Madiyar Nurgazin + * @author Mikhail Polivakha */ public class YdbDialect extends AbstractDialect { public static final YdbDialect INSTANCE = new YdbDialect(); @@ -55,13 +59,23 @@ public LockClause.Position getClausePosition() { protected Function getAfterFromTable() { return select -> { var tables = select.getFrom().getTables(); + if (tables.size() != 1) { return ""; } - var viewIndex = ExposeInvocationInterceptor.currentInvocation() - .getMethod() - .getAnnotation(ViewIndex.class); + Method repositoryMethod = null; + try { + repositoryMethod = ExposeInvocationInterceptor.currentInvocation().getMethod(); + } catch (IllegalStateException e) { + // the assumption is that JdbcRepositoryBeanPostProcessor made a choice to not expose metadata for this Spring Data JDBC repository + } + + if (repositoryMethod == null) { + return ""; + } + + var viewIndex = repositoryMethod.getAnnotation(ViewIndex.class); if (viewIndex != null && (viewIndex.tableName().isEmpty() || viewIndex.tableName().equals(tables.get(0).getName().toSql(IdentifierProcessing.NONE)))) { diff --git a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/AbstractYdbJdbcConfiguration.java b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/AbstractYdbJdbcConfiguration.java index b4ef749..bb22e65 100644 --- a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/AbstractYdbJdbcConfiguration.java +++ b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/AbstractYdbJdbcConfiguration.java @@ -1,6 +1,7 @@ package tech.ydb.data.repository.config; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Lazy; import org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory; import org.springframework.data.jdbc.core.convert.JdbcArrayColumns; @@ -15,9 +16,12 @@ /** * @author Madiyar Nurgazin + * @author Mikhail Polivakha */ @Configuration +@Import(JdbcRepositoryBeanPostProcessor.class) public class AbstractYdbJdbcConfiguration extends AbstractJdbcConfiguration { + @Override public JdbcConverter jdbcConverter( JdbcMappingContext mappingContext, diff --git a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessor.java b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessor.java new file mode 100644 index 0000000..e19ecc5 --- /dev/null +++ b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessor.java @@ -0,0 +1,45 @@ +package tech.ydb.data.repository.config; + +import java.util.Arrays; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactoryBean; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.util.ReflectionUtils; + +import tech.ydb.data.repository.ViewIndex; + +/** + * Enabling the exposure of the metadata for the {@link JdbcRepositoryFactoryBean}. Enables + * only for those {@link RepositoryFactoryBeanSupport factory beans} that have any {@link ViewIndex} + * annotated methods. + * + * @author Mikhail Polivakha + */ +@SuppressWarnings("rawtypes") +public class JdbcRepositoryBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof JdbcRepositoryFactoryBean rfbs && hasAnyViewIndexMethods(rfbs)) { + rfbs.setExposeMetadata(true); + } + return bean; + } + + /** + * Unfortunately, {@link JdbcRepositoryFactoryBean#getRepositoryInformation()} call is not possible at this stage, since + * {@link RepositoryFactoryBeanSupport#factory} is not initialized at this point yet. Still, we have + * to use {@link BeanPostProcessor#postProcessBeforeInitialization(Object, String)} since the + * {@link RepositoryFactoryBeanSupport#setExposeMetadata(boolean) expose metadata} call needs to be done before the {@link InitializingBean#afterPropertiesSet()} + * to be propagated into the underlying {@link org.springframework.data.repository.core.support.RepositoryFactorySupport factory support} + */ + private static boolean hasAnyViewIndexMethods(JdbcRepositoryFactoryBean rfbs) { + return Arrays + .stream(ReflectionUtils.getAllDeclaredMethods(rfbs.getObjectType())) + .anyMatch(method -> AnnotationUtils.getAnnotation(method, ViewIndex.class) != null); + } +} diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbJdbcConfiguration.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbJdbcConfiguration.java index 87fc4ad..6f0bca8 100644 --- a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbJdbcConfiguration.java +++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbJdbcConfiguration.java @@ -8,9 +8,13 @@ /** * @author Madiyar Nurgazin + * @author Mikhail Polivakha */ @Configuration -@EnableJdbcRepositories +@EnableJdbcRepositories( + considerNestedRepositories = true, + basePackages = "tech.ydb.data" +) @EnableJdbcAuditing @Import(AbstractYdbJdbcConfiguration.class) public class YdbJdbcConfiguration {} \ No newline at end of file diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessorTest.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessorTest.java new file mode 100644 index 0000000..421acd0 --- /dev/null +++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/repository/config/JdbcRepositoryBeanPostProcessorTest.java @@ -0,0 +1,72 @@ +package tech.ydb.data.repository.config; + +import java.lang.reflect.Field; +import java.util.Optional; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; +import org.springframework.util.ReflectionUtils; + +import tech.ydb.data.YdbBaseTest; +import tech.ydb.data.repository.ViewIndex; + +/** + * Tests for {@link JdbcRepositoryBeanPostProcessor}. + * + * @author Mikhail Polivakha + */ +class JdbcRepositoryBeanPostProcessorTest extends YdbBaseTest { + + @Autowired + private RepositoryFactoryBeanSupport userFactoryBeanSupport; + + @Autowired + private RepositoryFactoryBeanSupport addressFactoryBeanSupport; + + @Test + void shouldExposeMetadataOnlyForRepositoriesWithViewIndexMethods() { + + // given. + Field exposeMetadataField = ReflectionUtils.findField(RepositoryFactoryBeanSupport.class, "exposeMetadata"); + exposeMetadataField.setAccessible(true); + + // when. + Object userFactoryExposedMetadata = ReflectionUtils.getField(exposeMetadataField, userFactoryBeanSupport); + Object addressFactoryExposedMetadata = ReflectionUtils.getField(exposeMetadataField, addressFactoryBeanSupport); + + // then. + Assertions.assertThat(userFactoryExposedMetadata).isEqualTo(true); + Assertions.assertThat(addressFactoryExposedMetadata).isEqualTo(false); + } + + @Table + static class User { + + @Id + private Long id; + + private String name; + } + + @Table + static class Address { + + @Id + private Long id; + } + + interface UserRepository extends CrudRepository { + + @ViewIndex(indexName = "name_authors_index", tableName = "authors") + Optional findUserByName(String name); + } + + interface AddressRepository extends CrudRepository { + + } +}