diff --git a/.github/workflows/ci-spring-data-jdbc-ydb.yaml b/.github/workflows/ci-spring-data-jdbc-ydb.yaml
index d9042d4..8a18d53 100644
--- a/.github/workflows/ci-spring-data-jdbc-ydb.yaml
+++ b/.github/workflows/ci-spring-data-jdbc-ydb.yaml
@@ -14,13 +14,44 @@ env:
MAVEN_ARGS: --batch-mode --update-snapshots -Dstyle.color=always
jobs:
+ prepare:
+ name: Prepare Maven cache
+ runs-on: ubuntu-24.04
+
+ env:
+ MAVEN_ARGS: --batch-mode -Dstyle.color=always
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: '8'
+ distribution: 'temurin'
+ cache: 'maven'
+
+ - name: Download dependencies (Default)
+ working-directory: ./spring-data-jdbc-ydb
+ run: mvn $MAVEN_ARGS dependency:resolve-plugins dependency:go-offline
+
+ - name: Download dependencies (Spring Boot 3)
+ working-directory: ./spring-data-jdbc-ydb
+ run: mvn $MAVEN_ARGS -Pspring-boot3 dependency:resolve-plugins dependency:go-offline
+
+ - name: Download dependencies (Spring Boot 4)
+ working-directory: ./spring-data-jdbc-ydb
+ run: mvn $MAVEN_ARGS -Pspring-boot4 dependency:resolve-plugins dependency:go-offline
+
build:
- name: Spring Data JDBC YDB Dialect
- runs-on: ubuntu-latest
+ name: Spring Data JDBC YDB Dialect build & tests
+ runs-on: ubuntu-24.04
+ needs: prepare
strategy:
matrix:
- java: [ '17', '21' ]
+ java: [ '17', '21', '24' ]
steps:
- uses: actions/checkout@v4
@@ -32,16 +63,39 @@ jobs:
distribution: 'temurin'
cache: maven
+ - name: Build spring-data-jdbc YDB dialect
+ working-directory: ./spring-data-jdbc-ydb
+ run: mvn $MAVEN_ARGS package
+
+ - name: Tests with Spring Boot 3
+ working-directory: ./spring-data-jdbc-ydb
+ run: mvn $MAVEN_ARGS -Pspring-boot3 test
+
+ - name: Tests with Spring Boot 4
+ working-directory: ./spring-data-jdbc-ydb
+ run: mvn $MAVEN_ARGS -Pspring-boot4 test
+
+ examples:
+ name: Spring Data JDBC YDB Dialect Examples
+ runs-on: ubuntu-24.04
+ needs: build
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: 17
+ distribution: 'temurin'
+ cache: maven
+
- name: Extract spring-data-jdbc YDB dialect version
working-directory: ./spring-data-jdbc-ydb
run: |
VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
echo "SPRING_DATA_JDBC_DIALECT_VERSION=$VERSION" >> "$GITHUB_ENV"
- - name: Download spring-data-jdbc YDB dialect dependencies
- working-directory: ./spring-data-jdbc-ydb
- run: mvn $MAVEN_ARGS dependency:go-offline
-
- name: Build spring-data-jdbc YDB dialect
working-directory: ./spring-data-jdbc-ydb
run: mvn $MAVEN_ARGS install
diff --git a/.github/workflows/publish-spring-data-jdbc-ydb.yaml b/.github/workflows/publish-spring-data-jdbc-ydb.yaml
index 8ee0f56..4f2e772 100644
--- a/.github/workflows/publish-spring-data-jdbc-ydb.yaml
+++ b/.github/workflows/publish-spring-data-jdbc-ydb.yaml
@@ -11,7 +11,7 @@ env:
jobs:
validate:
name: Validate Spring Data JDBC YDB Dialect
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/spring-data-jdbc-ydb/README.md b/spring-data-jdbc-ydb/README.md
index 0321637..f5d2dbd 100644
--- a/spring-data-jdbc-ydb/README.md
+++ b/spring-data-jdbc-ydb/README.md
@@ -6,12 +6,12 @@
## Overview
-This project is an extension for Spring Data JDBC
+This project is an extension for Spring Data JDBC
that provides support for working with [YDB](https://ydb.tech).
### Features
-- Full support for basic operations with Spring Data JDBC
+- Full support for basic operations with Spring Data JDBC
- Supported VIEW INDEX statement from @ViewIndex annotation on method your Repository
- @YdbType explicitly specifies the YDB data type (Json example in String type)
@@ -22,7 +22,7 @@ that provides support for working with [YDB](https://ydb.tech).
To use this Spring Data JDBC YDB Dialect, you'll need:
- Java 17 or above.
-- Spring Data JDBC 3+
+- Spring Data JDBC 3.4+
- [YDB JDBC Driver](https://github.com/ydb-platform/ydb-jdbc-driver)
- Access to a YDB Database instance
@@ -35,7 +35,7 @@ For Maven, add the following dependency to your pom.xml:
tech.ydb.dialects
spring-data-jdbc-ydb
- ${spring.data.jdbc.ydb.version}
+ ${spring.data.jdbc.ydb.version}
```
diff --git a/spring-data-jdbc-ydb/pom.xml b/spring-data-jdbc-ydb/pom.xml
index 08e02cc..b194fd8 100644
--- a/spring-data-jdbc-ydb/pom.xml
+++ b/spring-data-jdbc-ydb/pom.xml
@@ -50,7 +50,6 @@
17
5.10.2
- 3.4.0
4.24.0
2.3.13
@@ -166,6 +165,34 @@
+
+ spring-boot-minimal
+
+ true
+
+
+ 3.4.0
+
+
+
+ spring-boot3
+
+ 3.5.7
+
+
+
+ spring-boot4
+
+ 4.0.0
+
+
+
+ org.springframework.boot
+ spring-boot-starter-liquibase
+ test
+
+
+
ossrh-s01
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 e1c1079..40a1f79 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
@@ -4,6 +4,8 @@
import java.util.function.Function;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
+import org.springframework.data.jdbc.core.convert.JdbcArrayColumns;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.relational.core.dialect.AbstractDialect;
import org.springframework.data.relational.core.dialect.InsertRenderContext;
import org.springframework.data.relational.core.dialect.LimitClause;
@@ -19,9 +21,11 @@
* @author Madiyar Nurgazin
* @author Mikhail Polivakha
*/
-public class YdbDialect extends AbstractDialect {
+public class YdbDialect extends AbstractDialect implements JdbcDialect {
public static final YdbDialect INSTANCE = new YdbDialect();
+ private static final IdentifierProcessing.Quoting QUOTING = new IdentifierProcessing.Quoting("`");
+
private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
@Override
@@ -46,10 +50,12 @@ public Position getClausePosition() {
};
private static final LockClause LOCK_CLAUSE = new LockClause() {
+ @Override
public String getLock(LockOptions lockOptions) {
throw new UnsupportedOperationException("YDB does not support pessimistic locks");
}
+ @Override
public LockClause.Position getClausePosition() {
return null;
}
@@ -99,10 +105,17 @@ public LockClause lock() {
@Override
public IdentifierProcessing getIdentifierProcessing() {
- return IdentifierProcessing.create(
- new IdentifierProcessing.Quoting("`"),
- IdentifierProcessing.LetterCasing.AS_IS
- );
+ return new IdentifierProcessing() {
+ @Override
+ public String quote(String identifier) {
+ return QUOTING.apply(identifier);
+ }
+
+ @Override
+ public String standardizeLetterCase(String identifier) {
+ return identifier;
+ }
+ };
}
@Override
@@ -116,4 +129,10 @@ public InsertRenderContext getInsertRenderContext() {
public OrderByNullPrecedence orderByNullHandling() {
return OrderByNullPrecedence.NONE;
}
+
+ @Override
+ @SuppressWarnings("removal")
+ public JdbcArrayColumns getArraySupport() {
+ return JdbcArrayColumns.Unsupported.INSTANCE;
+ }
}
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 bb22e65..a8fdf23 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
@@ -8,10 +8,12 @@
import org.springframework.data.jdbc.core.convert.JdbcConverter;
import org.springframework.data.jdbc.core.convert.JdbcCustomConversions;
import org.springframework.data.jdbc.core.convert.RelationResolver;
+import org.springframework.data.jdbc.core.dialect.JdbcDialect;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
+
import tech.ydb.data.core.convert.YdbMappingJdbcConverter;
/**
@@ -22,7 +24,24 @@
@Import(JdbcRepositoryBeanPostProcessor.class)
public class AbstractYdbJdbcConfiguration extends AbstractJdbcConfiguration {
- @Override
+ // Spring Boot 4 support
+ @SuppressWarnings({"override", "removal"})
+ public JdbcConverter jdbcConverter(
+ JdbcMappingContext mappingContext,
+ NamedParameterJdbcOperations operations,
+ @Lazy RelationResolver relationResolver,
+ JdbcCustomConversions conversions,
+ JdbcDialect dialect
+ ) {
+ DefaultJdbcTypeFactory jdbcTypeFactory = new DefaultJdbcTypeFactory(
+ operations.getJdbcOperations(), JdbcArrayColumns.Unsupported.INSTANCE
+ );
+
+ return new YdbMappingJdbcConverter(mappingContext, relationResolver, conversions, jdbcTypeFactory);
+ }
+
+ // Spring Boot 3 support
+ @SuppressWarnings({"override", "removal"})
public JdbcConverter jdbcConverter(
JdbcMappingContext mappingContext,
NamedParameterJdbcOperations operations,
diff --git a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/YdbDialectProvider.java b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/YdbDialectProvider.java
index 3d81f85..43dfedd 100644
--- a/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/YdbDialectProvider.java
+++ b/spring-data-jdbc-ydb/src/main/java/tech/ydb/data/repository/config/YdbDialectProvider.java
@@ -9,12 +9,13 @@
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcOperations;
-import org.springframework.lang.Nullable;
+
import tech.ydb.data.core.dialect.YdbDialect;
/**
* @author Madiyar Nurgazin
*/
+@SuppressWarnings("removal") // Spring Boot 3 support
public class YdbDialectProvider extends DialectResolver.DefaultDialectProvider {
@Override
public Optional getDialect(JdbcOperations operations) {
@@ -29,7 +30,6 @@ public Optional getDialect(JdbcOperations operations) {
return super.getDialect(operations);
}
- @Nullable
private static Dialect getDialect(Connection connection) throws SQLException {
if ("ydb".contains(connection.getMetaData().getDatabaseProductName().toLowerCase(Locale.ENGLISH))) {
return YdbDialect.INSTANCE;
diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbBaseTest.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbBaseTest.java
index 93f0a8e..7708793 100644
--- a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbBaseTest.java
+++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/YdbBaseTest.java
@@ -1,25 +1,24 @@
package tech.ydb.data;
import org.junit.jupiter.api.extension.RegisterExtension;
-import org.springframework.boot.test.autoconfigure.data.jdbc.AutoConfigureDataJdbc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.transaction.annotation.Transactional;
+
import tech.ydb.test.junit5.YdbHelperExtension;
/**
* @author Madiyar Nurgazin
*/
@SpringBootTest(classes = YdbJdbcConfiguration.class)
-@AutoConfigureDataJdbc
@Transactional
public abstract class YdbBaseTest {
@RegisterExtension
private static final YdbHelperExtension ydb = new YdbHelperExtension();
@DynamicPropertySource
- private static void propertySource(DynamicPropertyRegistry registry) {
+ public static void propertySource(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", YdbBaseTest::jdbcUrl);
}
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 6f0bca8..0435eae 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
@@ -1,9 +1,10 @@
package tech.ydb.data;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing;
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
+
import tech.ydb.data.repository.config.AbstractYdbJdbcConfiguration;
/**
@@ -16,5 +17,5 @@
basePackages = "tech.ydb.data"
)
@EnableJdbcAuditing
-@Import(AbstractYdbJdbcConfiguration.class)
-public class YdbJdbcConfiguration {}
\ No newline at end of file
+@EnableAutoConfiguration
+public class YdbJdbcConfiguration extends AbstractYdbJdbcConfiguration {}
\ No newline at end of file
diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/AllTypesTableTest.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/AllTypesTableTest.java
index e50df70..f181a12 100644
--- a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/AllTypesTableTest.java
+++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/AllTypesTableTest.java
@@ -5,6 +5,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -12,7 +13,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
-import org.springframework.data.relational.core.conversion.DbActionExecutionException;
+import org.springframework.jdbc.UncategorizedSQLException;
import tech.ydb.data.YdbBaseTest;
import tech.ydb.data.all_types_table.entity.AllTypesEntity;
@@ -57,7 +58,8 @@ public void allTypesTableCrudTest() {
aggregateOperations.insert(entity2);
Assertions.assertEquals(2, repository.countDistinctTextColumn());
- List entities = repository.findAll();
+ List entities = new ArrayList<>();
+ repository.findAll().forEach(entities::add);
Assertions.assertEquals(4, entities.size());
repository.deleteById(1);
@@ -75,8 +77,12 @@ public void allTypesTableCrudTest() {
Assertions.assertEquals(Integer.valueOf(4), entities.get(0).getId());
entity3.setJsonColumn("Not json");
- var ex = Assertions.assertThrows(DbActionExecutionException.class, () -> repository.save(entity3));
- Assertions.assertTrue(ex.getMessage().startsWith("Failed to execute DbAction.UpdateRoot"));
+ Throwable ex = Assertions.assertThrows(Exception.class, () -> repository.save(entity3));
+ if (ex != null && !(ex instanceof UncategorizedSQLException)) {
+ ex = ex.getCause();
+ }
+ Assertions.assertTrue(ex instanceof UncategorizedSQLException);
+ Assertions.assertTrue(ex != null && ex.getMessage().contains("Invalid Json value (S_ERROR)]"));
entity3.setJsonColumn("{\"values\": [1, 2, 3]}");
AllTypesEntity updated = repository.save(entity3);
diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/AllTypesEntityRepository.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/AllTypesEntityRepository.java
index a267e81..ebdb3ef 100644
--- a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/AllTypesEntityRepository.java
+++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/all_types_table/repository/AllTypesEntityRepository.java
@@ -1,16 +1,18 @@
package tech.ydb.data.all_types_table.repository;
import java.util.List;
+
import org.springframework.data.jdbc.repository.query.Query;
-import org.springframework.data.repository.ListCrudRepository;
+import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
+
import tech.ydb.data.all_types_table.entity.AllTypesEntity;
import tech.ydb.data.repository.ViewIndex;
/**
* @author Madiyar Nurgazin
*/
-public interface AllTypesEntityRepository extends ListCrudRepository {
+public interface AllTypesEntityRepository extends CrudRepository {
@Query("select count(distinct text_column) from all_types_table")
long countDistinctTextColumn();
diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/AuthorRepository.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/AuthorRepository.java
index 86582a4..c23c28b 100644
--- a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/AuthorRepository.java
+++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/AuthorRepository.java
@@ -1,16 +1,18 @@
package tech.ydb.data.books.repository;
import java.util.List;
+
import org.springframework.data.jdbc.repository.query.Query;
-import org.springframework.data.repository.ListCrudRepository;
+import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
+
import tech.ydb.data.books.entity.Author;
import tech.ydb.data.repository.ViewIndex;
/**
* @author Madiyar Nurgazin
*/
-public interface AuthorRepository extends ListCrudRepository {
+public interface AuthorRepository extends CrudRepository {
@Query("select authors.* from authors join books_authors on authors.id = books_authors.author_id" +
" where books_authors.book_id = :bookId")
List findAuthorsByBookId(@Param("bookId") long bookId);
diff --git a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/ReviewRepository.java b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/ReviewRepository.java
index dae7191..ca04672 100644
--- a/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/ReviewRepository.java
+++ b/spring-data-jdbc-ydb/src/test/java/tech/ydb/data/books/repository/ReviewRepository.java
@@ -1,17 +1,17 @@
package tech.ydb.data.books.repository;
import java.util.List;
+
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
-import org.springframework.data.repository.ListCrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
+
import tech.ydb.data.books.entity.Review;
/**
* @author Madiyar Nurgazin
*/
-public interface ReviewRepository extends ListCrudRepository,
- CrudRepository, PagingAndSortingRepository {
+public interface ReviewRepository extends CrudRepository, PagingAndSortingRepository {
List findByReader(String reader, Pageable pageable);
}