diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md
index dc0b527..9956fe6 100644
--- a/CONTRIBUTE.md
+++ b/CONTRIBUTE.md
@@ -82,7 +82,7 @@ tarantool-java-sdk (parent POM)
├── tarantool-spring-data-core
├── tarantool-spring-data-27
├── ...
- ├── tarantool-spring-data-34
+ ├── tarantool-spring-data-35
├── testcontainers
└── jacoco-coverage-aggregate-report
```
diff --git a/documentation/docs/documentation/spring-data/index.en.md b/documentation/docs/documentation/spring-data/index.en.md
index 45829d6..4d5f654 100644
--- a/documentation/docs/documentation/spring-data/index.en.md
+++ b/documentation/docs/documentation/spring-data/index.en.md
@@ -23,9 +23,9 @@ using [Tarantool](https://www.tarantool.io) as a data store.
## Project Status
-| tarantool-java-sdk Version | tarantool-spring-data Version | Spring Boot Version |
-|:-------------------------:|:----------------------------:|:-----------------------------------------:|
-| 1.5.x | 1.5.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 |
+| tarantool-java-sdk Version | tarantool-spring-data Version | Spring Boot Version |
+|:-------------------------:|:----------------------------:|:-------------------------------------------------:|
+| 1.5.x | 1.5.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 / 3.5.7 |
### Tarantool Version and Supported Client Modules
@@ -88,7 +88,7 @@ Include the module in your project as follows:
io.tarantool
- tarantool-spring-data-34
+ tarantool-spring-data-35${tarantool-spring-data.version}
diff --git a/documentation/docs/documentation/spring-data/index.md b/documentation/docs/documentation/spring-data/index.md
index 297e1db..17a52c8 100644
--- a/documentation/docs/documentation/spring-data/index.md
+++ b/documentation/docs/documentation/spring-data/index.md
@@ -23,9 +23,9 @@ hide:
## Статус проекта
-| Версия tarantool-java-sdk | Версия tarantool-spring-data | Версия Spring Boot |
-|:-------------------------:|:----------------------------:|:-----------------------------------------:|
-| 1.5.x | 1.5.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 |
+| Версия tarantool-java-sdk | Версия tarantool-spring-data | Версия Spring Boot |
+|:-------------------------:|:----------------------------:|:-------------------------------------------------:|
+| 1.5.x | 1.5.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 / 3.5.7 |
### Версия Tarantool и поддерживаемые модули-клиенты
@@ -84,11 +84,11 @@ Tarantool можно найти
org.springframework.bootspring-boot-starter
- 3.4.12
+ 3.5.7io.tarantool
- tarantool-spring-data-34
+ tarantool-spring-data-35${tarantool-spring-data.version}
diff --git a/tarantool-spring-data/README.md b/tarantool-spring-data/README.md
index 4d9c02d..7269d4e 100644
--- a/tarantool-spring-data/README.md
+++ b/tarantool-spring-data/README.md
@@ -20,13 +20,14 @@ data storage.
## Project Status
-| tarantool-java-sdk Version | tarantool-spring-data Version | Spring Boot Version |
-|:--------------------------:|:-----------------------------:|:------------------------------------------:|
-| 1.0.0 | 1.0.0 | 2.7.18 |
-| 1.1.x | 1.1.x | 2.7.18 / 3.1.10 / 3.2.4 |
-| 1.2.x | 1.2.x | 2.7.18 / 3.1.10 / 3.2.4 |
-| 1.3.x | 1.3.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.11 / 3.4.5 |
-| 1.4.x | 1.4.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 |
+| tarantool-java-sdk Version | tarantool-spring-data Version | Spring Boot Version |
+|:--------------------------:|:-----------------------------:|:-------------------------------------------------:|
+| 1.0.0 | 1.0.0 | 2.7.18 |
+| 1.1.x | 1.1.x | 2.7.18 / 3.1.10 / 3.2.4 |
+| 1.2.x | 1.2.x | 2.7.18 / 3.1.10 / 3.2.4 |
+| 1.3.x | 1.3.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.11 / 3.4.5 |
+| 1.4.x | 1.4.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 |
+| 1.5.x | 1.5.x | 2.7.18 / 3.1.10 / 3.2.4 / 3.3.13 / 3.4.10 / 3.5.7 |
### Tarantool Version and Supported Client Modules
diff --git a/tarantool-spring-data/pom.xml b/tarantool-spring-data/pom.xml
index bb09ae2..bfa1776 100644
--- a/tarantool-spring-data/pom.xml
+++ b/tarantool-spring-data/pom.xml
@@ -23,6 +23,7 @@
tarantool-spring-data-32tarantool-spring-data-33tarantool-spring-data-34
+ tarantool-spring-data-35
@@ -62,6 +63,12 @@
+
+
+ io.tarantool
+ tarantool-client
+
+
org.springframework.data
@@ -94,12 +101,6 @@
${slf4j.version}
-
-
- io.tarantool
- tarantool-client
-
-
org.springframework.boot
diff --git a/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java
index f36b92e..88d7b04 100644
--- a/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java
+++ b/tarantool-spring-data/tarantool-spring-data-27/src/test/java/io/tarantool/spring/data27/integration/BaseIntegrationTest.java
@@ -25,7 +25,7 @@
import io.tarantool.spring.data27.config.properties.TarantoolProperties;
@Testcontainers
-@Timeout(20)
+@Timeout(60)
public abstract class BaseIntegrationTest {
protected static TarantoolContainerOperations> clusterContainer;
diff --git a/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java
index 8b3f3e2..e2d4683 100644
--- a/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java
+++ b/tarantool-spring-data/tarantool-spring-data-31/src/test/java/io/tarantool/spring/data31/integration/BaseIntegrationTest.java
@@ -25,7 +25,7 @@
import io.tarantool.spring.data31.config.properties.TarantoolProperties;
@Testcontainers
-@Timeout(20)
+@Timeout(60)
public abstract class BaseIntegrationTest {
protected static TarantoolContainerOperations> clusterContainer;
diff --git a/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java
index 5f80bbe..f519b07 100644
--- a/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java
+++ b/tarantool-spring-data/tarantool-spring-data-32/src/test/java/io/tarantool/spring/data32/integration/BaseIntegrationTest.java
@@ -25,7 +25,7 @@
import io.tarantool.spring.data32.config.properties.TarantoolProperties;
@Testcontainers
-@Timeout(20)
+@Timeout(60)
public abstract class BaseIntegrationTest {
protected static TarantoolContainerOperations> clusterContainer;
diff --git a/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java
index 2de2974..1f92238 100644
--- a/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java
+++ b/tarantool-spring-data/tarantool-spring-data-33/src/test/java/io/tarantool/spring/data33/integration/BaseIntegrationTest.java
@@ -25,7 +25,7 @@
import io.tarantool.spring.data33.config.properties.TarantoolProperties;
@Testcontainers
-@Timeout(20)
+@Timeout(60)
public abstract class BaseIntegrationTest {
protected static TarantoolContainerOperations> clusterContainer;
diff --git a/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java b/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java
index 532985a..d776956 100644
--- a/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java
+++ b/tarantool-spring-data/tarantool-spring-data-34/src/test/java/io/tarantool/spring/data34/integration/BaseIntegrationTest.java
@@ -25,7 +25,7 @@
import io.tarantool.spring.data34.config.properties.TarantoolProperties;
@Testcontainers
-@Timeout(20)
+@Timeout(60)
public abstract class BaseIntegrationTest {
protected static TarantoolContainerOperations> clusterContainer;
diff --git a/tarantool-spring-data/tarantool-spring-data-35/pom.xml b/tarantool-spring-data/tarantool-spring-data-35/pom.xml
new file mode 100644
index 0000000..a5da94b
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ tarantool-spring-data-35
+ Minimalistic java connector for Tarantool versions 2.11+ based on Netty framework
+ https://tarantool.io
+
+
+ io.tarantool
+ tarantool-spring-data
+ 2.0.0-SNAPSHOT
+
+
+ tarantool-spring-data-35
+ jar
+
+
+ 17
+ 17
+ 3.5.7
+ ${project.parent.parent.basedir}/tarantool-shared-resources/
+ ${project.parent.parent.basedir}/LICENSE_HEADER.txt
+
+
+
+
+ io.tarantool
+ tarantool-spring-data-core
+
+
+
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolBoxKeyValueAdapter.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolBoxKeyValueAdapter.java
new file mode 100644
index 0000000..7ca3136
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolBoxKeyValueAdapter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35;
+
+import java.util.Map;
+
+import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
+import org.springframework.data.util.CloseableIterator;
+import org.springframework.lang.NonNull;
+
+import io.tarantool.client.box.TarantoolBoxClient;
+import io.tarantool.spring.data.ProxyTarantoolBoxKeyValueAdapter;
+
+public class TarantoolBoxKeyValueAdapter extends AbstractKeyValueAdapter {
+
+ private final ProxyTarantoolBoxKeyValueAdapter adapter;
+
+ public TarantoolBoxKeyValueAdapter(@NonNull TarantoolBoxClient tarantoolBoxClient) {
+ adapter = new ProxyTarantoolBoxKeyValueAdapter(tarantoolBoxClient);
+ }
+
+ @Override
+ public Object put(Object id, Object item, String keyspace) {
+ return adapter.put(id, item, keyspace);
+ }
+
+ @Override
+ public boolean contains(Object id, String keyspace) {
+ return adapter.contains(id, keyspace);
+ }
+
+ @Override
+ public Object get(Object id, String keyspace) {
+ return adapter.get(id, keyspace);
+ }
+
+ @Override
+ public Object delete(Object id, String keyspace) {
+ return adapter.delete(id, keyspace);
+ }
+
+ @Override
+ public Iterable> getAllOf(String keyspace) {
+ return adapter.getAllOf(keyspace);
+ }
+
+ @Override
+ public void deleteAllOf(String keyspace) {
+ adapter.deleteAllOf(keyspace);
+ }
+
+ @Override
+ public void clear() {
+ adapter.clear();
+ }
+
+ @Override
+ public long count(String keyspace) {
+ return adapter.count(keyspace);
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ adapter.destroy();
+ }
+
+ @Override
+ public CloseableIterator> entries(String keyspace) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolCrudKeyValueAdapter.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolCrudKeyValueAdapter.java
new file mode 100644
index 0000000..215c908
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolCrudKeyValueAdapter.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35;
+
+import java.util.Collections;
+import java.util.Map.Entry;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.data.keyvalue.core.AbstractKeyValueAdapter;
+import org.springframework.data.util.CloseableIterator;
+import org.springframework.lang.NonNull;
+
+import io.tarantool.client.crud.TarantoolCrudClient;
+import io.tarantool.spring.data.ProxyTarantoolCrudKeyValueAdapter;
+import io.tarantool.spring.data.mapping.model.CompositeKey;
+
+public class TarantoolCrudKeyValueAdapter extends AbstractKeyValueAdapter {
+
+ private final ProxyTarantoolCrudKeyValueAdapter adapter;
+
+ public TarantoolCrudKeyValueAdapter(@NonNull TarantoolCrudClient client) {
+ super(new TarantoolQueryEngine(client));
+ this.adapter = new ProxyTarantoolCrudKeyValueAdapter(client);
+ }
+
+ @Override
+ public Object put(Object id, Object item, String keyspace) {
+ return adapter.put(convertId(id), item, keyspace);
+ }
+
+ @Override
+ public boolean contains(Object id, String keyspace) {
+ return adapter.contains(convertId(id), keyspace);
+ }
+
+ @Override
+ public Object get(Object id, String keyspace) {
+ return adapter.get(convertId(id), keyspace);
+ }
+
+ @Override
+ public T get(Object id, String keyspace, Class type) {
+ return adapter.get(convertId(id), keyspace, type);
+ }
+
+ @Override
+ public Object delete(Object id, String keyspace) {
+ return adapter.delete(convertId(id), keyspace);
+ }
+
+ @Override
+ public T delete(Object id, String keyspace, Class type) {
+ return adapter.delete(convertId(id), keyspace, type);
+ }
+
+ @Override
+ public Iterable> getAllOf(String keyspace) {
+ return adapter.getAllOf(keyspace);
+ }
+
+ @Override
+ public void deleteAllOf(String keyspace) {
+ adapter.deleteAllOf(keyspace);
+ }
+
+ @Override
+ public void clear() {
+ adapter.clear();
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ adapter.destroy();
+ }
+
+ @Override
+ public long count(String keyspace) {
+ return adapter.count(keyspace);
+ }
+
+ @Override
+ public CloseableIterator> entries(String keyspace) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+
+ /**
+ * Convert the identifier to the form required by the tarantool-java-sdk driver.
+ *
+ * @param id identifier object
+ * @return identifier in the required form
+ */
+ private Object convertId(Object id) {
+ if (id instanceof CompositeKey || hasJsonFormatArrayAnnotation(id)) {
+ return id;
+ }
+ return Collections.singletonList(id);
+ }
+
+ /**
+ * Determine whether the identifier type is annotated with the {@link JsonFormat} annotation.
+ *
+ * @param id identifier object
+ * @return return true if the annotation is present, false otherwise
+ */
+ private boolean hasJsonFormatArrayAnnotation(Object id) {
+ final JsonFormat jsonFormatAnnotation =
+ AnnotatedElementUtils.findMergedAnnotation(id.getClass(), JsonFormat.class);
+
+ return jsonFormatAnnotation != null
+ && JsonFormat.Shape.ARRAY.equals(jsonFormatAnnotation.shape());
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolQueryEngine.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolQueryEngine.java
new file mode 100644
index 0000000..843d979
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/TarantoolQueryEngine.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Map.Entry;
+
+import org.springframework.data.keyvalue.core.QueryEngine;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import io.tarantool.client.crud.TarantoolCrudClient;
+import io.tarantool.spring.data.ProxyTarantoolQueryEngine;
+import io.tarantool.spring.data.query.TarantoolCriteria;
+import io.tarantool.spring.data35.query.TarantoolCriteriaAccessor;
+import io.tarantool.spring.data35.query.TarantoolSortAccessor;
+
+/**
+ * Implementation of {@code findBy*()} and {@code countBy*{}} queries.
+ *
+ * @author Artyom Dubinin
+ */
+public class TarantoolQueryEngine
+ extends QueryEngine>> {
+
+ private final ProxyTarantoolQueryEngine engine;
+
+ public TarantoolQueryEngine(TarantoolCrudClient client) {
+ super(new TarantoolCriteriaAccessor(), new TarantoolSortAccessor());
+ this.engine = new ProxyTarantoolQueryEngine(client);
+ }
+
+ @Override
+ @NonNull
+ public Collection> execute(
+ @Nullable final TarantoolCriteria criteria,
+ @Nullable final Comparator> sort,
+ final long offset,
+ final int rows,
+ @NonNull final String keyspace) {
+ return engine.execute(criteria, sort, offset, rows, keyspace);
+ }
+
+ /**
+ * Construct the final query predicate for Tarantool to execute, from the base query plus any
+ * paging and sorting.
+ *
+ *
Variations here allow the base query predicate to be omitted, sorting to be omitted, and
+ * paging to be omitted.
+ *
+ * @param criteria Search criteria, null means match everything
+ * @param sort Possibly null collation
+ * @param offset Start point of returned page, -1 if not used
+ * @param rows Size of page, -1 if not used
+ * @param keyspace The map name
+ * @return Results from Tarantool
+ */
+ @Override
+ @NonNull
+ public Collection execute(
+ @Nullable final TarantoolCriteria criteria,
+ @Nullable final Comparator> sort,
+ final long offset,
+ final int rows,
+ @NonNull final String keyspace,
+ @NonNull Class type) {
+ return engine.execute(criteria, sort, offset, rows, keyspace, type);
+ }
+
+ /**
+ * Execute {@code countBy*()} queries against a Tarantool space.
+ *
+ * @param criteria Predicate to use, not null
+ * @param keyspace The map name
+ * @return Results from Tarantool
+ */
+ @Override
+ public long count(@Nullable final TarantoolCriteria criteria, @NonNull final String keyspace) {
+ return engine.count(criteria, keyspace);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/TarantoolBoxConfiguration.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/TarantoolBoxConfiguration.java
new file mode 100644
index 0000000..de7f7dd
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/TarantoolBoxConfiguration.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.config;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static io.tarantool.spring.data.TarantoolBeanNames.DEFAULT_TARANTOOL_BOX_CLIENT_BEAN_REF;
+import static io.tarantool.spring.data.TarantoolBeanNames.DEFAULT_TARANTOOL_BOX_KEY_VALUE_ADAPTER_REF;
+import io.tarantool.client.box.TarantoolBoxClient;
+import io.tarantool.client.factory.TarantoolBoxClientBuilder;
+import io.tarantool.spring.data.config.BaseTarantoolBoxConfiguration;
+import io.tarantool.spring.data35.TarantoolBoxKeyValueAdapter;
+import io.tarantool.spring.data35.config.properties.TarantoolProperties;
+
+@Configuration(proxyBeanMethods = false)
+public class TarantoolBoxConfiguration extends BaseTarantoolBoxConfiguration {
+
+ public TarantoolBoxConfiguration(
+ ObjectProvider properties,
+ ObjectProvider tarantoolBoxClientBuilder) {
+ super(properties.getIfAvailable(), tarantoolBoxClientBuilder.getIfAvailable());
+ }
+
+ @Bean(name = DEFAULT_TARANTOOL_BOX_KEY_VALUE_ADAPTER_REF)
+ @ConditionalOnMissingBean(TarantoolBoxKeyValueAdapter.class)
+ public TarantoolBoxKeyValueAdapter tarantoolCrudKeyValueAdapter(
+ TarantoolBoxClient tarantoolBoxClient) {
+ return new TarantoolBoxKeyValueAdapter(tarantoolBoxClient);
+ }
+
+ @Bean(name = DEFAULT_TARANTOOL_BOX_CLIENT_BEAN_REF)
+ @ConditionalOnMissingBean(TarantoolBoxClient.class)
+ public TarantoolBoxClient tarantoolBoxClient() throws Exception {
+ return super.tarantoolBoxClient();
+ }
+
+ @Override
+ public TarantoolBoxClientBuilder getClientBuilder() {
+ return super.getClientBuilder();
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/TarantoolCrudConfiguration.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/TarantoolCrudConfiguration.java
new file mode 100644
index 0000000..fd4f4fe
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/TarantoolCrudConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.config;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import static io.tarantool.spring.data.TarantoolBeanNames.DEFAULT_TARANTOOL_CRUD_CLIENT_BEAN_REF;
+import static io.tarantool.spring.data.TarantoolBeanNames.DEFAULT_TARANTOOL_CRUD_KEY_VALUE_ADAPTER_REF;
+import io.tarantool.client.crud.TarantoolCrudClient;
+import io.tarantool.client.factory.TarantoolCrudClientBuilder;
+import io.tarantool.spring.data.config.BaseTarantoolCrudConfiguration;
+import io.tarantool.spring.data35.TarantoolCrudKeyValueAdapter;
+import io.tarantool.spring.data35.config.properties.TarantoolProperties;
+
+@Configuration(proxyBeanMethods = false)
+public class TarantoolCrudConfiguration extends BaseTarantoolCrudConfiguration {
+
+ public TarantoolCrudConfiguration(
+ ObjectProvider properties,
+ ObjectProvider tarantoolClientConfiguration) {
+ super(properties.getIfAvailable(), tarantoolClientConfiguration.getIfAvailable());
+ }
+
+ @Bean(name = DEFAULT_TARANTOOL_CRUD_KEY_VALUE_ADAPTER_REF)
+ @ConditionalOnMissingBean(TarantoolCrudKeyValueAdapter.class)
+ public TarantoolCrudKeyValueAdapter tarantoolCrudKeyValueAdapter(
+ TarantoolCrudClient tarantoolCrudClient) {
+ return new TarantoolCrudKeyValueAdapter(tarantoolCrudClient);
+ }
+
+ @Bean(name = DEFAULT_TARANTOOL_CRUD_CLIENT_BEAN_REF)
+ @ConditionalOnMissingBean(TarantoolCrudClient.class)
+ public TarantoolCrudClient tarantoolCrudClient() throws Exception {
+ return super.tarantoolCrudClient();
+ }
+
+ public TarantoolCrudClientBuilder getClientBuilder() {
+ return super.getClientBuilder();
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/package-info.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/package-info.java
new file mode 100644
index 0000000..0f89063
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/package-info.java
@@ -0,0 +1,3 @@
+/** Config package. */
+@org.springframework.lang.NonNullApi
+package io.tarantool.spring.data35.config;
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/properties/TarantoolProperties.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/properties/TarantoolProperties.java
new file mode 100644
index 0000000..59ad089
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/config/properties/TarantoolProperties.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.config.properties;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import io.tarantool.spring.data.config.properties.BaseTarantoolProperties;
+
+/**
+ * Configuration properties for Tarantool.
+ *
+ * @author Nikolay Belonogov
+ */
+@ConfigurationProperties(prefix = "spring.data.tarantool")
+public class TarantoolProperties extends BaseTarantoolProperties {}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/TarantoolTemplate.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/TarantoolTemplate.java
new file mode 100644
index 0000000..64df77a
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/TarantoolTemplate.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core;
+
+import org.springframework.data.keyvalue.core.IdentifierGenerator;
+import org.springframework.data.keyvalue.core.KeyValueAdapter;
+import org.springframework.data.keyvalue.core.KeyValueTemplate;
+import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
+import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
+import org.springframework.data.mapping.IdentifierAccessor;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.util.ClassUtils;
+
+import io.tarantool.spring.data35.core.annotation.DefaultIdClassResolver;
+import io.tarantool.spring.data35.core.mapping.TarantoolMappingContext;
+
+public class TarantoolTemplate extends KeyValueTemplate {
+
+ public TarantoolTemplate(KeyValueAdapter adapter) {
+ super(adapter, new TarantoolMappingContext<>());
+ }
+
+ public TarantoolTemplate(
+ KeyValueAdapter adapter,
+ MappingContext<
+ ? extends KeyValuePersistentEntity, ?>, ? extends KeyValuePersistentProperty>>
+ mappingContext) {
+ super(adapter, mappingContext);
+ }
+
+ public TarantoolTemplate(
+ KeyValueAdapter adapter,
+ MappingContext<
+ ? extends KeyValuePersistentEntity, ?>, ? extends KeyValuePersistentProperty>>
+ mappingContext,
+ IdentifierGenerator identifierGenerator) {
+ super(adapter, mappingContext, identifierGenerator);
+ }
+
+ @Override
+ public T insert(T objectToInsert) {
+ if (DefaultIdClassResolver.INSTANCE.resolveIdClassType(objectToInsert.getClass()) == null) {
+ return super.insert(objectToInsert);
+ }
+
+ PersistentEntity, ?> entity =
+ getMappingContext().getRequiredPersistentEntity(ClassUtils.getUserClass(objectToInsert));
+
+ IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(objectToInsert);
+ Object id = identifierAccessor.getRequiredIdentifier();
+ return insert(id, objectToInsert);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/annotation/DefaultIdClassResolver.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/annotation/DefaultIdClassResolver.java
new file mode 100644
index 0000000..c0dc13b
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/annotation/DefaultIdClassResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.annotation;
+
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import io.tarantool.spring.data.core.annotation.IdClass;
+import io.tarantool.spring.data.core.annotation.IdClassResolver;
+
+/** Default implementation of {@link IdClassResolver}. */
+public enum DefaultIdClassResolver implements IdClassResolver {
+ INSTANCE;
+
+ public static final String ANNOTATION_TYPE_EXCEPTION =
+ "The class of a composite identifier specified in the @IdClass annotation cannot be"
+ + " annotation!";
+
+ @Nullable
+ @Override
+ public Class> resolveIdClassType(Class> type) {
+ Assert.notNull(type, "Type for IdClass must be not null!");
+
+ IdClass idClassTypeAnnotation = AnnotatedElementUtils.findMergedAnnotation(type, IdClass.class);
+
+ if (idClassTypeAnnotation == null) {
+ return null;
+ }
+
+ Class> idClassTypeValue = idClassTypeAnnotation.value();
+ Assert.isTrue(!idClassTypeValue.isAnnotation(), ANNOTATION_TYPE_EXCEPTION);
+
+ return idClassTypeValue;
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/annotation/package-info.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/annotation/package-info.java
new file mode 100644
index 0000000..93a1a06
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/annotation/package-info.java
@@ -0,0 +1,4 @@
+/** Core annotations of data mapping. */
+@org.springframework.lang.NonNullFields
+@org.springframework.lang.NonNullApi
+package io.tarantool.spring.data35.core.annotation;
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/BasicKeyValueCompositePersistentEntity.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/BasicKeyValueCompositePersistentEntity.java
new file mode 100644
index 0000000..17724d7
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/BasicKeyValueCompositePersistentEntity.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.data.domain.Persistable;
+import org.springframework.data.keyvalue.core.mapping.BasicKeyValuePersistentEntity;
+import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
+import org.springframework.data.mapping.IdentifierAccessor;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.support.IsNewStrategy;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import io.tarantool.spring.data.core.annotation.IdClass;
+import io.tarantool.spring.data.mapping.model.CompositeKey;
+import io.tarantool.spring.data35.core.mapping.model.CompositeIdPropertyAccessor;
+import io.tarantool.spring.data35.core.mapping.model.PersistentCompositeIdIsNewStrategy;
+
+public class BasicKeyValueCompositePersistentEntity>
+ extends BasicKeyValuePersistentEntity implements KeyValueCompositePersistentEntity {
+
+ public static final String TYPE_MISMATCH =
+ "Target bean of type %s is not of type of the persistent entity (%s)";
+
+ private final Class> idClassTypeValue;
+ private @Nullable P idProperty;
+
+ private Map entityIdClassFields;
+
+ /**
+ * @param information must not be {@literal null}.
+ * @param fallbackKeySpaceResolver can be {@literal null}.
+ * @param idClassTypeValue class that specified in {@link IdClass}
+ */
+ public BasicKeyValueCompositePersistentEntity(
+ TypeInformation information,
+ @Nullable KeySpaceResolver fallbackKeySpaceResolver,
+ Class> idClassTypeValue) {
+ super(information, fallbackKeySpaceResolver);
+
+ Assert.notNull(idClassTypeValue, "idClassTypeValue must be not null for this class!");
+ this.idClassTypeValue = idClassTypeValue;
+ }
+
+ @Override
+ public IdentifierAccessor getIdentifierAccessor(Object bean) {
+ verifyBeanType(bean);
+
+ if (Persistable.class.isAssignableFrom(getType())) {
+ throw new IllegalArgumentException(
+ "Persistable override is not currently supported for entities with a composite key.");
+ }
+
+ return hasIdProperty()
+ ? new CompositeIdPropertyAccessor(bean, this.entityIdClassFields, getIdClassType())
+ : new AbsentIdentifierAccessor();
+ }
+
+ @Override
+ public Class> getIdClassType() {
+ return this.idClassTypeValue;
+ }
+
+ @Override
+ public void verify() {
+ super.verify();
+
+ if (this.idProperty != null) {
+ this.entityIdClassFields = KeyPartTypeChecker.getFieldMapIfTypesValid(this, this.idProperty);
+ }
+ }
+
+ @Override
+ protected IsNewStrategy getFallbackIsNewStrategy() {
+ return PersistentCompositeIdIsNewStrategy.of(this);
+ }
+
+ @Override
+ protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) {
+ Assert.isInstanceOf(Identifier.class, property, "property must be Identifier for this class");
+
+ if (!property.isIdProperty()) {
+ return null;
+ }
+
+ if (this.idProperty == null) {
+ this.idProperty = property;
+ return this.idProperty;
+ }
+
+ this.idProperty.addPart(property);
+
+ return this.idProperty;
+ }
+
+ /**
+ * Verifies the given bean type to no be {@literal null} and of the type of the current {@link
+ * PersistentEntity}.
+ *
+ * @param bean must not be {@literal null}.
+ */
+ private void verifyBeanType(Object bean) {
+
+ Assert.notNull(bean, "Target bean must not be null");
+ Assert.isInstanceOf(
+ getType(),
+ bean,
+ () -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
+ }
+
+ /**
+ * A null-object implementation of {@link IdentifierAccessor} to be able to return an accessor for
+ * entities that do not have an identifier property.
+ */
+ private static class AbsentIdentifierAccessor implements IdentifierAccessor {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.IdentifierAccessor#getIdentifier()
+ */
+ @Override
+ @Nullable
+ public Object getIdentifier() {
+ return null;
+ }
+ }
+
+ /**
+ * A class that checks the types of fields of a composite key. In addition, the correspondence
+ * between types and field names marked with {@code @Id}.
+ */
+ public static class KeyPartTypeChecker {
+
+ public static final String COMPOSITE_KEY_FIELDS_NUMBER_EXCEPTION =
+ "Number of fields specified in domain class and composite class the key is different!";
+
+ public static final String COMPOSITE_KEY_FIELD_DIFFERENT_EXCEPTION =
+ "Domain class fields marked with @Id differ from fields specified in the composite key"
+ + " class";
+
+ /**
+ * Check the number and types of entity fields, annotated {@code @Id} and fields {@link
+ * CompositeKey} and if the types and quantities match return the id part fields mapping.
+ *
+ * @param persistentEntity persistent entity
+ * @param idProperty id property
+ * @return mapping between fields of composite key class and fields annotated {@code @Id} in
+ * entity.
+ */
+ public static Map getFieldMapIfTypesValid(
+ KeyValueCompositePersistentEntity, ?> persistentEntity, Identifier> idProperty) {
+ Map entityIdClassFields = new HashMap<>();
+
+ List compositeKeyFields = persistentEntity.getIdClassTypeFields();
+
+ Field[] entityFields = idProperty.getFields();
+
+ if (compositeKeyFields.size() != entityFields.length) {
+ throw new IllegalArgumentException(COMPOSITE_KEY_FIELDS_NUMBER_EXCEPTION);
+ }
+
+ for (int i = 0; i < compositeKeyFields.size(); i++) {
+ Field compositeKeyField = compositeKeyFields.get(i);
+ Field entityField = entityFields[i];
+
+ if (!equalFields(compositeKeyField, entityField)) {
+ throw new IllegalArgumentException(COMPOSITE_KEY_FIELD_DIFFERENT_EXCEPTION);
+ }
+
+ entityIdClassFields.put(entityField, compositeKeyField);
+ }
+ return entityIdClassFields;
+ }
+
+ /**
+ * Compares two class fields by name and type.
+ *
+ * @param firstField first field
+ * @param secondField second field
+ * @return true if fields are equal
+ */
+ private static boolean equalFields(Field firstField, Field secondField) {
+ return firstField.getName().equals(secondField.getName())
+ && firstField.getType().equals(secondField.getType());
+ }
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/Identifier.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/Identifier.java
new file mode 100644
index 0000000..843b72b
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/Identifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+
+import org.springframework.data.mapping.PersistentProperty;
+
+/** The interface of the identifier class that will contain information about the composite key. */
+public interface Identifier
> {
+
+ /**
+ * Add a {@link PersistentProperty} tagged with {@code Id}.
+ *
+ * @param property {@link PersistentProperty} tagged {@code Id}
+ */
+ void addPart(P property);
+
+ /**
+ * Return the parts of a composite key that are {@link PersistentProperty}.
+ *
+ * @return parts of a composite key
+ */
+ Collection
getParts();
+
+ /**
+ * Return a list {@link Field} of fields that are annotated {@code @Id}. Unlike method {@link
+ * #getParts()} this method returns an array {@link Field}, where {@link Field} is taken from each
+ * element of the collection returned by the {@link #getParts()} method. Necessary in order not to
+ * write polluting code.
+ *
+ * @return fields of a composite key
+ */
+ Field[] getFields();
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/KeyValueCompositePersistentEntity.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/KeyValueCompositePersistentEntity.java
new file mode 100644
index 0000000..dc0c7e9
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/KeyValueCompositePersistentEntity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.mapping.PersistentProperty;
+import org.springframework.data.mapping.model.MutablePersistentEntity;
+
+import io.tarantool.spring.data.core.annotation.IdClass;
+
+/**
+ * An interface to enable {@link PersistentEntity}-specific operations with a composite key.
+ *
+ * @param domain class type
+ * @param
{@link PersistentProperty} type
+ */
+public interface KeyValueCompositePersistentEntity>
+ extends MutablePersistentEntity {
+
+ /**
+ * Returns the fields of the class specified in the {@link IdClass} annotation.
+ *
+ * @return all fields that are in composite key class
+ */
+ default List getIdClassTypeFields() {
+ List fields = new ArrayList<>();
+
+ for (Field field : getIdClassType().getDeclaredFields()) {
+ if (!field.isSynthetic()) {
+ fields.add(field);
+ }
+ }
+ return fields;
+ }
+
+ /**
+ * Returns the type of the class specified in the {@link IdClass} annotation.
+ *
+ * @return class that specified in {@link IdClass}
+ */
+ Class> getIdClassType();
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/KeyValueCompositeProperty.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/KeyValueCompositeProperty.java
new file mode 100644
index 0000000..9f53060
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/KeyValueCompositeProperty.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
+import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.mapping.model.Property;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+import org.springframework.util.Assert;
+
+public class KeyValueCompositeProperty
>
+ extends KeyValuePersistentProperty
implements Identifier
{
+
+ private final List
identifierPartsWithoutFirst;
+
+ public KeyValueCompositeProperty(
+ Property property, PersistentEntity, P> owner, SimpleTypeHolder simpleTypeHolder) {
+ super(property, owner, simpleTypeHolder);
+ this.identifierPartsWithoutFirst = new ArrayList<>();
+ }
+
+ @Override
+ public void addPart(P property) {
+ Assert.notNull(property, "property must be not null");
+
+ if (equals(property) || this.identifierPartsWithoutFirst.contains(property)) {
+ return;
+ }
+ this.identifierPartsWithoutFirst.add(property);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Collection
getParts() {
+ List
resultList = new ArrayList<>(this.identifierPartsWithoutFirst);
+ resultList.add(0, (P) this);
+
+ return resultList;
+ }
+
+ @Override
+ public Field[] getFields() {
+ int totalSize = this.identifierPartsWithoutFirst.size() + 1;
+
+ final Field[] fields = new Field[totalSize];
+ fields[0] = getField();
+
+ for (int i = 1; i < totalSize; i++) {
+ fields[i] = this.identifierPartsWithoutFirst.get(i - 1).getField();
+ }
+ return fields;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ KeyValueCompositeProperty> other = (KeyValueCompositeProperty>) o;
+
+ if (this.identifierPartsWithoutFirst.isEmpty() && other.identifierPartsWithoutFirst.isEmpty()) {
+ return super.equals(o);
+ }
+
+ if (this.identifierPartsWithoutFirst.size() != other.identifierPartsWithoutFirst.size()
+ || !other.getProperty().equals(getProperty())) {
+ return false;
+ }
+
+ for (int i = 0; i < this.identifierPartsWithoutFirst.size(); i++) {
+ // take Property from both parts
+ Property thisKeyPartProperty =
+ ((KeyValueCompositeProperty>) this.identifierPartsWithoutFirst.get(i)).getProperty();
+ Property otherKeyPartProperty =
+ ((KeyValueCompositeProperty>) other.identifierPartsWithoutFirst.get(i)).getProperty();
+
+ if (!thisKeyPartProperty.equals(otherKeyPartProperty)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int hashCode = super.hashCode();
+
+ if (this.identifierPartsWithoutFirst.isEmpty()) {
+ return hashCode;
+ }
+
+ // sublist to avoid endless recursion
+ return Objects.hash(hashCode, this.identifierPartsWithoutFirst);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/TarantoolMappingContext.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/TarantoolMappingContext.java
new file mode 100644
index 0000000..7428f41
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/TarantoolMappingContext.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping;
+
+import org.springframework.data.keyvalue.core.mapping.BasicKeyValuePersistentEntity;
+import org.springframework.data.keyvalue.core.mapping.KeySpaceResolver;
+import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
+import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentProperty;
+import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
+import org.springframework.data.mapping.model.Property;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+import org.springframework.data.util.TypeInformation;
+import org.springframework.lang.Nullable;
+
+import io.tarantool.spring.data.core.annotation.IdClassResolver;
+import io.tarantool.spring.data35.core.annotation.DefaultIdClassResolver;
+
+public class TarantoolMappingContext<
+ E extends KeyValuePersistentEntity, P>, P extends KeyValuePersistentProperty
>
+ extends KeyValueMappingContext {
+
+ private final IdClassResolver idClassResolver;
+
+ private @Nullable KeySpaceResolver keySpaceResolver;
+
+ public TarantoolMappingContext() {
+ super();
+ this.idClassResolver = DefaultIdClassResolver.INSTANCE;
+ }
+
+ @Override
+ public void setKeySpaceResolver(KeySpaceResolver keySpaceResolver) {
+ super.setKeySpaceResolver(keySpaceResolver);
+ this.keySpaceResolver = keySpaceResolver;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected E createPersistentEntity(TypeInformation typeInformation) {
+ final Class> idClassTypeValue =
+ this.idClassResolver.resolveIdClassType(typeInformation.getType());
+ if (idClassTypeValue == null) {
+ return (E) new BasicKeyValuePersistentEntity(typeInformation, this.keySpaceResolver);
+ }
+ return (E)
+ new BasicKeyValueCompositePersistentEntity<>(
+ typeInformation, this.keySpaceResolver, idClassTypeValue);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected P createPersistentProperty(
+ Property property, E owner, SimpleTypeHolder simpleTypeHolder) {
+ if (KeyValueCompositePersistentEntity.class.isAssignableFrom(owner.getClass())) {
+ return (P) new KeyValueCompositeProperty<>(property, owner, simpleTypeHolder);
+ }
+ return (P) new KeyValuePersistentProperty<>(property, owner, simpleTypeHolder);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/CompositeIdPropertyAccessor.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/CompositeIdPropertyAccessor.java
new file mode 100644
index 0000000..56b211b
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/CompositeIdPropertyAccessor.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping.model;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.ReflectionUtils;
+
+import io.tarantool.spring.data.mapping.model.CompositeKey;
+
+/** Class that allows to convert {@code @Id} annotated fields in an entity into a composite key. */
+public class CompositeIdPropertyAccessor extends TargetAwareIdentifierAccessor {
+
+ private final Map entityIdClassFields;
+
+ private final Class> idClassType;
+
+ private final Object target;
+
+ public CompositeIdPropertyAccessor(
+ Object target, @NonNull Map entityIdClassFields, Class> idClassType) {
+
+ super(target);
+ this.target = target;
+ this.entityIdClassFields = entityIdClassFields;
+ this.idClassType = idClassType;
+ }
+
+ @Nullable
+ public Object getIdentifier() {
+ try {
+ return generateIdentifier();
+ } catch (InstantiationException
+ | IllegalAccessException
+ | NoSuchMethodException
+ | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Generates a composite identifier from those marked with the {@code @Id} annotation in a domain
+ * class based on fields, specified in the composite key class.
+ *
+ * @return identifier
+ * @throws IllegalAccessException if the class or its nullary constructor is not accessible.
+ * @throws InstantiationException if this {@code Class} represents an abstract class, an
+ * interface, an array class, a primitive type, or void; or if the class has no nullary
+ * constructor; or if the instantiation fails for some other reason.
+ */
+ private Object generateIdentifier()
+ throws InstantiationException,
+ IllegalAccessException,
+ NoSuchMethodException,
+ InvocationTargetException {
+ Assert.notNull(this.target, "target object must be not null!");
+ Object compositeKey = idClassType.getDeclaredConstructor().newInstance();
+ for (Map.Entry fieldPair : this.entityIdClassFields.entrySet()) {
+ writeValuesFromEntityId(compositeKey, fieldPair.getKey(), fieldPair.getValue());
+ }
+ return compositeKey;
+ }
+
+ /**
+ * Write the composite primary key fields provided in the entity to the class which implements
+ * {@link CompositeKey}.
+ *
+ * @param compositeKey object of the composite key into which the values are written.
+ * @param entityField field from the represented entity.
+ * @param compositeKeyField field from the composite key class.
+ */
+ private void writeValuesFromEntityId(
+ Object compositeKey, Field entityField, Field compositeKeyField) {
+ ReflectionUtils.makeAccessible(entityField);
+ ReflectionUtils.makeAccessible(compositeKeyField);
+
+ Object value = ReflectionUtils.getField(entityField, this.target);
+ ReflectionUtils.setField(compositeKeyField, compositeKey, value);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/PersistentCompositeIdIsNewStrategy.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/PersistentCompositeIdIsNewStrategy.java
new file mode 100644
index 0000000..1c1867d
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/PersistentCompositeIdIsNewStrategy.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.core.mapping.model;
+
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.support.IsNewStrategy;
+import org.springframework.util.Assert;
+
+/**
+ * Strategy class for entity with composite key to determine whether a given entity is to be
+ * considered new.
+ */
+public class PersistentCompositeIdIsNewStrategy implements IsNewStrategy {
+
+ /**
+ * Create a new {@link PersistentCompositeIdIsNewStrategy} for the given entity.
+ *
+ * @param entity must not be {@literal null}.
+ * @param idOnly check only id to determine
+ */
+ private PersistentCompositeIdIsNewStrategy(PersistentEntity, ?> entity, boolean idOnly) {
+ // TODO сделать реализацию версионирования
+ // TODO add idOnly support
+ Assert.notNull(entity, "PersistentEntity must not be null");
+ }
+
+ /**
+ * Create a new {@link PersistentCompositeIdIsNewStrategy} to only consider the identifier of the
+ * given entity.
+ *
+ * @param entity must not be {@literal null}.
+ * @return strategy to determine whether entity is new or not
+ */
+ public static PersistentCompositeIdIsNewStrategy forIdOnly(PersistentEntity, ?> entity) {
+ return new PersistentCompositeIdIsNewStrategy(entity, true);
+ }
+
+ /**
+ * Create a new {@link PersistentCompositeIdIsNewStrategy} to consider version properties before
+ * falling back to the identifier.
+ *
+ * @param entity must not be {@literal null}.
+ * @return strategy to determine whether entity is new or not
+ */
+ public static PersistentCompositeIdIsNewStrategy of(PersistentEntity, ?> entity) {
+ return new PersistentCompositeIdIsNewStrategy(entity, false);
+ }
+
+ /**
+ * Determine whether the current domain entity type object is new. Currently stub and always
+ * returns false.
+ *
+ * @param entity must not be {@literal null}.
+ * @return result of the question of entity is new or not
+ */
+ @Override
+ public boolean isNew(Object entity) {
+ return false;
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/package-info.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/package-info.java
new file mode 100644
index 0000000..2e491de
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/model/package-info.java
@@ -0,0 +1,4 @@
+/** Core implementation of the mapping subsystem's model. */
+@org.springframework.lang.NonNullFields
+@org.springframework.lang.NonNullApi
+package io.tarantool.spring.data35.core.mapping.model;
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/package-info.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/package-info.java
new file mode 100644
index 0000000..df37bf9
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/mapping/package-info.java
@@ -0,0 +1,4 @@
+/** Base package for the mapping subsystem. */
+@org.springframework.lang.NonNullFields
+@org.springframework.lang.NonNullApi
+package io.tarantool.spring.data35.core.mapping;
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/package-info.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/package-info.java
new file mode 100644
index 0000000..ec08e37
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/core/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Core package for integrating Tarantool with Spring
+ * concepts.
+ */
+@org.springframework.lang.NonNullFields
+@org.springframework.lang.NonNullApi
+package io.tarantool.spring.data35.core;
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/Field.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/Field.java
new file mode 100644
index 0000000..66e69ab
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/Field.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.core.annotation.AliasFor;
+
+/**
+ * Allows adding some metadata to the class fields relevant for storing them in the Tarantool space
+ *
+ * @author Artyom Dubinin
+ */
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE})
+public @interface Field {
+
+ /**
+ * The target Tarantool space field for storing the marked class field. Alias for {@link #name()}.
+ *
+ * @return the name of a field in space
+ */
+ @AliasFor("name")
+ String value() default "";
+
+ /**
+ * The target Tarantool space field for storing the marked class field. Alias for {@link
+ * #value()}.
+ *
+ * @return the name of a field in space
+ */
+ @AliasFor("value")
+ String name() default "";
+
+ /**
+ * The order in which fields shall be stored in the tuple. Has to be a positive integer.
+ *
+ * @return the order the field shall have in the tuple or -1 if undefined.
+ */
+ int order() default -1;
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/PaginationUtils.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/PaginationUtils.java
new file mode 100644
index 0000000..9db34a3
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/PaginationUtils.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.util.List;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.keyvalue.core.IterableConverter;
+import org.springframework.data.keyvalue.core.KeyValueOperations;
+import org.springframework.data.keyvalue.core.query.KeyValueQuery;
+import org.springframework.util.Assert;
+
+import io.tarantool.spring.data.query.PaginationDirection;
+import io.tarantool.spring.data.query.TarantoolCriteria;
+
+public class PaginationUtils {
+
+ private static final String CRITERIA_NULL_EXC_MSG = "TarantoolCriteria must be not null";
+
+ /**
+ * Returns {@link TarantoolPageable} cast from {@link Pageable} with type checking.
+ *
+ * @param sliceParams pageable
+ * @return {@link TarantoolPageable}
+ */
+ public static TarantoolPageable> castToTarantoolPageable(Pageable sliceParams) {
+ Assert.isInstanceOf(TarantoolPageable.class, sliceParams, "Pageable must be TarantoolPageable");
+ return (TarantoolPageable>) sliceParams;
+ }
+
+ /**
+ * A general method for retrieving data for paginated queries.
+ *
+ * @param query query
+ * @param pageRequest {@link TarantoolPageable}
+ * @param pageSize page size
+ * @return selection result.
+ */
+ public static List> doPaginationQuery(
+ final KeyValueQuery> query,
+ TarantoolPageable> pageRequest,
+ int pageSize,
+ KeyValueOperations keyValueOperations,
+ Class> targetType) {
+
+ TarantoolCriteria criteria = (TarantoolCriteria) query.getCriteria();
+ PaginationDirection paginationDirection = pageRequest.getPaginationDirection();
+
+ Assert.notNull(criteria, CRITERIA_NULL_EXC_MSG);
+ criteria.withAfter(pageRequest.getTupleCursor());
+
+ query.setRows(pageSize * paginationDirection.getMultiplier());
+
+ return IterableConverter.toList(keyValueOperations.find(query, targetType));
+ }
+
+ public static Page> doPageQuery(
+ Pageable pageable,
+ KeyValueQuery> query,
+ KeyValueOperations keyValueOperations,
+ Class> targetType) {
+ if (pageable.isUnpaged()) {
+ return new TarantoolPageImpl<>();
+ }
+ TarantoolPageable> resultSliceParams = castToTarantoolPageable(pageable);
+
+ int pageSize = resultSliceParams.getPageSize();
+
+ // non transactional calls
+ List> content =
+ doPaginationQuery(query, resultSliceParams, pageSize, keyValueOperations, targetType);
+
+ if (content.isEmpty()) {
+ return new TarantoolPageImpl<>();
+ }
+
+ long totalElements = keyValueOperations.count(query, targetType);
+ return new TarantoolPageImpl<>(content, resultSliceParams, totalElements);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolChunk.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolChunk.java
new file mode 100644
index 0000000..91ab95f
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolChunk.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Slice;
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+
+/**
+ * A chunk of data restricted by the configured {@link Pageable} for Tarantool.
+ *
+ * @author Nikolay Belonogov
+ */
+abstract class TarantoolChunk implements Slice, Serializable {
+
+ private static final long serialVersionUID = 867755909294344406L;
+
+ private final List content = new ArrayList<>();
+
+ private final Pageable pageable;
+
+ /**
+ * Creates a new {@link TarantoolChunk} with the given content and the given governing {@link
+ * TarantoolPageable}.
+ *
+ * @param content must not be {@literal null}.
+ * @param pageable must not be {@literal null}.
+ */
+ public TarantoolChunk(List content, Pageable pageable) {
+
+ Assert.notNull(content, "Content must not be null");
+ Assert.notNull(pageable, "Pageable must not be null");
+
+ if (pageable.isPaged() && !(pageable instanceof TarantoolPageable)) {
+ throw new IllegalArgumentException("Pageable must be TarantoolPageable or Unpaged type");
+ }
+
+ this.content.addAll(content);
+ this.pageable = pageable;
+ }
+
+ @Override
+ public boolean isFirst() {
+ return !hasPrevious();
+ }
+
+ @Override
+ public boolean isLast() {
+ return !hasNext();
+ }
+
+ @Override
+ public boolean hasPrevious() {
+ return getNumber() > 0 && hasContent();
+ }
+
+ @Override
+ public boolean hasContent() {
+ return !content.isEmpty();
+ }
+
+ /**
+ * Returns the {@link Pageable} to request the next {@link Slice}. Can be {@link
+ * Pageable#unpaged()} in case the current {@link Slice} is already the last one or when data
+ * content is empty. Clients should check {@link #hasNext()} and {@link #hasContent()} before
+ * calling this method.
+ *
+ * @see #nextOrLastPageable()
+ */
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public Pageable nextPageable() {
+ if (hasNext() && pageable.isPaged()) {
+ return ((TarantoolPageable) pageable).next(content.get(content.size() - 1));
+ }
+ return Pageable.unpaged();
+ }
+
+ /**
+ * Returns the {@link Pageable} to request the previous {@link Slice}. Can be {@link
+ * Pageable#unpaged()} in case the current {@link Slice} is already the first one or data content
+ * is empty. Clients should check {@link #hasPrevious()} and {@link #hasContent()} before calling
+ * this method.
+ *
+ * @see #previousOrFirstPageable()
+ */
+ @NonNull
+ @Override
+ @SuppressWarnings("unchecked")
+ public Pageable previousPageable() {
+ if (hasPrevious() && pageable.isPaged()) {
+ return ((TarantoolPageable) pageable).previousOrFirst(content.get(0));
+ }
+ return Pageable.unpaged();
+ }
+
+ @Override
+ public Iterator iterator() {
+ return content.iterator();
+ }
+
+ @NonNull
+ @Override
+ public List getContent() {
+ return Collections.unmodifiableList(content);
+ }
+
+ @NonNull
+ @Override
+ public Pageable getPageable() {
+ return pageable;
+ }
+
+ @NonNull
+ @Override
+ public Sort getSort() {
+ return pageable.getSort();
+ }
+
+ @Override
+ public int getNumber() {
+ if (pageable.isPaged()) {
+ return pageable.getPageNumber();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getSize() {
+ if (pageable.isPaged()) {
+ return pageable.getPageSize();
+ }
+ return content.size();
+ }
+
+ @Override
+ public int getNumberOfElements() {
+ return content.size();
+ }
+
+ /**
+ * Applies the given {@link Function} to the content of the {@link TarantoolChunk}.
+ *
+ * @param converter must not be {@literal null}.
+ */
+ protected List getConvertedContent(Function super T, ? extends U> converter) {
+
+ Assert.notNull(converter, "Function must not be null");
+
+ return this.stream().map(converter).collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TarantoolChunk)) {
+ return false;
+ }
+ TarantoolChunk> that = (TarantoolChunk>) o;
+ return content.equals(that.content) && pageable.equals(that.pageable);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(content, pageable);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolCriteriaAccessor.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolCriteriaAccessor.java
new file mode 100644
index 0000000..11ac6ed
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolCriteriaAccessor.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import org.springframework.data.keyvalue.core.CriteriaAccessor;
+import org.springframework.data.keyvalue.core.query.KeyValueQuery;
+
+import io.tarantool.spring.data.query.TarantoolCriteria;
+
+/**
+ * Provide a mechanism to convert the abstract query into the direct implementation in Tarantool.
+ *
+ * @author Artyom Dubinin
+ */
+public class TarantoolCriteriaAccessor implements CriteriaAccessor {
+
+ /**
+ * @param query in Spring form
+ * @return The same in Tarantool form
+ */
+ public TarantoolCriteria resolve(KeyValueQuery> query) {
+ return (TarantoolCriteria) query.getCriteria();
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolKeysetScrollPosition.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolKeysetScrollPosition.java
new file mode 100644
index 0000000..6e1aed6
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolKeysetScrollPosition.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+import static io.tarantool.spring.data.query.PaginationDirection.BACKWARD;
+import static io.tarantool.spring.data.query.PaginationDirection.FORWARD;
+import io.tarantool.spring.data.query.PaginationDirection;
+import io.tarantool.spring.data.utils.Pair;
+
+final class TarantoolKeysetScrollPosition implements TarantoolScrollPosition {
+
+ private final Pair indexKey;
+
+ private final PaginationDirection direction;
+
+ private final Object cursor;
+
+ TarantoolKeysetScrollPosition(
+ Pair indexKey, PaginationDirection direction, @Nullable Object cursor) {
+
+ Assert.notNull(direction, "PaginationDirection must not be null");
+ Assert.notNull(indexKey, "indexKey must not be null");
+
+ this.indexKey = indexKey;
+ this.direction = direction;
+ this.cursor = cursor;
+ }
+
+ /**
+ * Creates a new {@link TarantoolKeysetScrollPosition} from a key set and {@link
+ * PaginationDirection}.
+ *
+ * @param indexKey must not be {@literal null}.
+ * @return will never be {@literal null}.
+ */
+ static TarantoolScrollPosition forward(Pair indexKey) {
+ return new TarantoolKeysetScrollPosition(indexKey, FORWARD, null);
+ }
+
+ static TarantoolScrollPosition backward(Pair indexKey) {
+ return new TarantoolKeysetScrollPosition(indexKey, BACKWARD, null);
+ }
+
+ /**
+ * Returns whether the current scroll position is the initial one (from begin or from end) (see
+ * {@link PaginationDirection}).
+ *
+ * @return {@link Boolean} object.
+ */
+ @Override
+ public boolean isInitial() {
+ if (indexKey.getSecond() instanceof List> startingList) {
+ return startingList.isEmpty() && cursor == null;
+ }
+ return false;
+ }
+
+ @Override
+ public TarantoolScrollPosition reverse() {
+ Pair newIndexKey = Pair.of(indexKey.getFirst(), indexKey.getSecond());
+ return new TarantoolKeysetScrollPosition(newIndexKey, direction.reverse(), cursor);
+ }
+
+ @Override
+ public boolean isScrollsBackward() {
+ return direction.equals(BACKWARD);
+ }
+
+ /**
+ * Return the cursor relative to which the data is being found.
+ *
+ * @return returns cursor
+ */
+ Object getCursor() {
+ return cursor;
+ }
+
+ /**
+ * @return the scroll direction.
+ */
+ PaginationDirection getDirection() {
+ return direction;
+ }
+
+ /**
+ * @return the indexKey.
+ */
+ Pair getIndexKey() {
+ return indexKey;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TarantoolKeysetScrollPosition that)) {
+ return false;
+ }
+ return indexKey.equals(that.indexKey)
+ && direction == that.direction
+ && Objects.equals(cursor, that.cursor);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(indexKey, direction, cursor);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("TarantoolKeysetScrollPosition [%s, %s, %s]", direction, indexKey, cursor);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageImpl.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageImpl.java
new file mode 100644
index 0000000..680f63d
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageImpl.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.io.Serial;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.lang.NonNull;
+
+/**
+ * Basic {@code Page} implementation for Tarantool.
+ *
+ * @param domain class type.
+ * @author Nikolay Belonogov
+ */
+class TarantoolPageImpl extends TarantoolChunk implements Page {
+
+ @Serial private static final long serialVersionUID = 867755909294344406L;
+
+ private final long total;
+
+ /**
+ * Creates a new {@link TarantoolPageImpl} with empty content. This will result in the created
+ * {@link Page} being identical to the entire {@link List}.
+ */
+ public TarantoolPageImpl() {
+ this(Collections.emptyList(), Pageable.unpaged(), 0L);
+ }
+
+ /**
+ * Creates a new {@link TarantoolPageImpl} with the given content. This will result in the created
+ * {@link Page} being identical to the entire {@link List}.
+ *
+ * @param content must not be {@literal null}.
+ */
+ public TarantoolPageImpl(List content) {
+ this(content, Pageable.unpaged(), content == null ? 0L : content.size());
+ }
+
+ /**
+ * Constructor of {@link TarantoolPageImpl}.
+ *
+ * @param content the content of this page, must not be {@literal null}.
+ * @param pageable the paging information, must not be {@literal null}.
+ * @param total the total amount of items available. The total might be adapted considering the
+ * length of the content given, if it is going to be the content of the last page. This is in
+ * place to mitigate inconsistencies.
+ */
+ public TarantoolPageImpl(List content, Pageable pageable, long total) {
+
+ super(content, pageable);
+
+ this.total =
+ pageable
+ .toOptional()
+ .filter(it -> !content.isEmpty())
+ .filter(it -> it.getOffset() + it.getPageSize() > total)
+ .map(it -> it.getOffset() + content.size())
+ .orElse(total);
+ }
+
+ @Override
+ public int getTotalPages() {
+ return getSize() == 0 ? 1 : (int) Math.ceil((double) total / (double) getSize());
+ }
+
+ @Override
+ public long getTotalElements() {
+ return total;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return getNumber() + 1 < getTotalPages() && hasContent();
+ }
+
+ @Override
+ public boolean isLast() {
+ return !hasNext();
+ }
+
+ @Override
+ @NonNull
+ public Page map(@NonNull Function super T, ? extends U> converter) {
+ return new PageImpl<>(getConvertedContent(converter), getPageable(), total);
+ }
+
+ @Override
+ public String toString() {
+
+ List content = getContent();
+ boolean canGetContentType = !content.isEmpty() && content.get(0) != null;
+
+ String contentType = canGetContentType ? content.get(0).getClass().getName() : "UNKNOWN";
+
+ return String.format(
+ "Page %s of %d containing %s instances", getNumber() + 1, getTotalPages(), contentType);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TarantoolPageImpl> that)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ return total == that.total;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), total);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageRequest.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageRequest.java
new file mode 100644
index 0000000..98be0fa
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageRequest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.io.Serial;
+import java.util.Objects;
+
+import org.springframework.data.domain.AbstractPageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+
+import static io.tarantool.spring.data.query.PaginationDirection.BACKWARD;
+import static io.tarantool.spring.data.query.PaginationDirection.FORWARD;
+import io.tarantool.spring.data.query.PaginationDirection;
+
+/**
+ * Basic implementation of {@link TarantoolPageable} for Tarantool.
+ *
+ *
Important: When specifying a cursor tuple, you must adhere to the following
+ * rules:
+ *
+ *
1. To specify a page starting from the beginning of {@code space} (the first element of the
+ * page is the first element found in {@code space}), use {@code tupleCursor == null}.
+ *
+ *
2. To specify an arbitrary page (the first element of the page is the next/previous one from
+ * the given one tuple cursor depending on the methods used and page number), use {@code tupleCursor
+ * == someTupleCursor} .
+ *
+ * @param domain entity type
+ */
+public final class TarantoolPageRequest extends AbstractPageRequest
+ implements TarantoolPageable {
+
+ @Serial private static final long serialVersionUID = -4541509938956089562L;
+
+ private final Sort sort;
+
+ private final T tupleCursor;
+
+ private final PaginationDirection paginationDirection;
+
+ /**
+ * Creates a new unsorted {@link TarantoolPageRequest} from begin of {@code space}.
+ *
+ * @param pageSize the size of the page to be returned, must be greater than 0.
+ */
+ public TarantoolPageRequest(int pageSize) {
+ this(0, pageSize, null);
+ }
+
+ /**
+ * Creates a new unsorted {@link TarantoolPageRequest}.
+ *
+ *
Important: Use this method with caution! It is important to know the exact
+ * match of number pages and the tuple after (before) which the page goes (without including this
+ * tuple in the page itself). A mistake in compliance results in the appearance of blank pages
+ * with unpaged pageable even when using methods {@link
+ * TarantoolPageImpl#previousOrFirstPageable()} / {@link TarantoolPageImpl#nextOrLastPageable()}
+ * and {@link TarantoolSliceImpl#previousOrFirstPageable()} / {@link
+ * TarantoolSliceImpl#nextOrLastPageable()}.
+ *
+ * @param page zero-based page index (virtual), must not be negative.
+ * @param size the size of the page to be returned, must be greater than 0.
+ * @param tupleCursor tuple cursor from which the page count begins. More: {@link
+ * TarantoolPageRequest}.
+ */
+ public TarantoolPageRequest(int page, int size, T tupleCursor) {
+ this(page, size, Sort.unsorted(), tupleCursor, FORWARD);
+ }
+
+ /**
+ * Private constructor to create an instance with a specific {@link PaginationDirection}.
+ *
+ * @param page zero-based page index, must not be negative.
+ * @param size the size of the page to be returned, must be greater than 0.
+ * @param sort must not be {@literal null}, use {@link Sort#unsorted()} instead.
+ * @param tupleCursor tuple cursor from which the page count begins. More: {@link
+ * TarantoolPageRequest}.
+ * @param direction direction of pagination.
+ */
+ TarantoolPageRequest(
+ int page, int size, Sort sort, T tupleCursor, @NonNull PaginationDirection direction) {
+ super(page, size);
+ Assert.notNull(sort, "Sort must not be null");
+ Assert.notNull(direction, "PaginationDirection must not be null");
+
+ this.sort = sort;
+ this.tupleCursor = tupleCursor;
+ this.paginationDirection = direction;
+ }
+
+ /** Non supported for Tarantool. */
+ @Override
+ @NonNull
+ public TarantoolPageRequest withPage(int pageNumber) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("method \"withPage(int pageNumber)\" unsupported");
+ }
+
+ @Override
+ @NonNull
+ public TarantoolPageable first() {
+ return new TarantoolPageRequest<>(getPageSize());
+ }
+
+ /** Non supported for Tarantool. Use {@link #next(Object)}. */
+ @Override
+ @NonNull
+ public TarantoolPageRequest next() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "method \"next()\" unsupported, use next(T nextTupleCursor)");
+ }
+
+ @Override
+ public TarantoolPageRequest next(T tupleCursor) {
+ return new TarantoolPageRequest<>(
+ getPageNumber() + 1, getPageSize(), getSort(), tupleCursor, FORWARD);
+ }
+
+ /** Non supported for Tarantool. Use {@link #previous(Object)}. */
+ @Override
+ @NonNull
+ public TarantoolPageRequest previous() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "method \"previous()\" unsupported, use previous(T prevTupleCursor)");
+ }
+
+ private TarantoolPageRequest previous(T tupleCursor) {
+ if (hasPrevious()) {
+ return new TarantoolPageRequest<>(
+ getPageNumber() - 1, getPageSize(), getSort(), tupleCursor, BACKWARD);
+ }
+ return this;
+ }
+
+ /** Non supported for Tarantool. Use {@link #previousOrFirst(Object)}. */
+ @Override
+ @NonNull
+ public TarantoolPageable previousOrFirst() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "method \"previousOrFirst()\" unsupported, use previousOrFirst(T prevTupleCursor)");
+ }
+
+ @Override
+ public TarantoolPageable previousOrFirst(T tupleCursor) {
+ if (hasPrevious()) {
+ return previous(tupleCursor);
+ }
+ return new TarantoolPageRequest<>(0, getPageSize(), getSort(), null, BACKWARD);
+ }
+
+ @Override
+ public PaginationDirection getPaginationDirection() {
+ return this.paginationDirection;
+ }
+
+ @Override
+ @NonNull
+ public Sort getSort() {
+ return this.sort;
+ }
+
+ @Override
+ public T getTupleCursor() {
+ return this.tupleCursor;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TarantoolPageRequest> that)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ return sort.equals(that.sort)
+ && Objects.equals(tupleCursor, that.tupleCursor)
+ && paginationDirection == that.paginationDirection;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), sort, tupleCursor, paginationDirection);
+ }
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageable.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageable.java
new file mode 100644
index 0000000..528b3ac
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPageable.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
+
+import io.tarantool.spring.data.query.PaginationDirection;
+
+/**
+ * Abstract interface for pagination information in Tarantool.
+ *
+ * @param domain class type.
+ * @author Nikolay Belonogov
+ */
+public interface TarantoolPageable extends Pageable {
+
+ /**
+ * Returns tuple cursor of domain type. Can be {@code null}. If the cursor is {@code null} then
+ * the page is counted from the first tuple in Tarantool.
+ *
+ * @return tuple cursor of domain type.
+ */
+ @Nullable
+ T getTupleCursor();
+
+ /**
+ * Returns the {@link TarantoolPageable} for previous page or the {@link TarantoolPageable} for
+ * first page if the current one already is the first one. Important: this method
+ * always returns {@link TarantoolPageable} c {@link PaginationDirection#BACKWARD}.
+ *
+ * @param tupleCursor tuple cursor of domain type for previous page.
+ * @return {@link TarantoolPageable} instance.
+ */
+ TarantoolPageable previousOrFirst(T tupleCursor);
+
+ /**
+ * Returns the {@link TarantoolPageable} requesting the next {@link
+ * org.springframework.data.domain.Page}.
+ *
+ *
Important: method always creates a new {@link TarantoolPageable} with the
+ * same parameters Sort and page size. Pagination direction is {@link
+ * PaginationDirection#FORWARD}, the page number always has value {@code n+1}, n is the current
+ * page number.
+ *
+ * @param tupleCursor tuple cursor of domain type for next page.
+ * @return {@link TarantoolPageable} object
+ */
+ TarantoolPageable next(T tupleCursor);
+
+ /**
+ * Returns pagination direction by {@link PaginationDirection} enumeration.
+ *
+ * @return {@link PaginationDirection}.
+ */
+ PaginationDirection getPaginationDirection();
+
+ /**
+ * Returns a {@link TarantoolPageable} that points to the first page with {@link
+ * PaginationDirection#FORWARD} pagination direction.
+ *
+ * @return {@link TarantoolPageable} which has {@code cursor == null}, page number is 0, the rest
+ * parameters are equivalent to the parameters of the current {@link TarantoolPageable}.
+ */
+ @NonNull
+ TarantoolPageable first();
+}
diff --git a/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPartTreeQuery.java b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPartTreeQuery.java
new file mode 100644
index 0000000..9e2ff41
--- /dev/null
+++ b/tarantool-spring-data/tarantool-spring-data-35/src/main/java/io/tarantool/spring/data35/query/TarantoolPartTreeQuery.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (c) 2025 VK DIGITAL TECHNOLOGIES LIMITED LIABILITY COMPANY
+ * All Rights Reserved.
+ */
+
+package io.tarantool.spring.data35.query;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.data.domain.Limit;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.ScrollPosition;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Window;
+import org.springframework.data.keyvalue.core.IterableConverter;
+import org.springframework.data.keyvalue.core.KeyValueOperations;
+import org.springframework.data.keyvalue.core.query.KeyValueQuery;
+import org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery;
+import org.springframework.data.repository.query.Parameter;
+import org.springframework.data.repository.query.Parameters;
+import org.springframework.data.repository.query.ParametersParameterAccessor;
+import org.springframework.data.repository.query.QueryMethod;
+import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.RepositoryQuery;
+import org.springframework.data.repository.query.parser.AbstractQueryCreator;
+import org.springframework.data.repository.query.parser.Part;
+import org.springframework.data.repository.query.parser.PartTree;
+import org.springframework.data.util.StreamUtils;
+import org.springframework.lang.NonNull;
+import org.springframework.util.Assert;
+
+import static io.tarantool.client.crud.ConditionOperator.GREATER_EQ;
+import static io.tarantool.client.crud.ConditionOperator.LESS_EQ;
+import static io.tarantool.spring.data.query.PaginationDirection.FORWARD;
+import static io.tarantool.spring.data35.query.PaginationUtils.doPageQuery;
+import static io.tarantool.spring.data35.query.PaginationUtils.doPaginationQuery;
+import io.tarantool.client.crud.Condition;
+import io.tarantool.client.crud.options.SelectOptions;
+import io.tarantool.spring.data.query.PaginationDirection;
+import io.tarantool.spring.data.query.TarantoolCriteria;
+import io.tarantool.spring.data.utils.Pair;
+
+/**
+ * There is one instance for each query method defined for a repository, providing a query from the
+ * bind parameters.
+ *
+ * @author Artyom Dubinin
+ */
+public class TarantoolPartTreeQuery extends KeyValuePartTreeQuery {
+
+ public static final String ILLEGAL_RETURN_TYPE_FOR_DELETE =
+ "Illegal returned type: %s. The operation 'deleteBy' accepts only 'long' and 'Collection' as"
+ + " the returned object type";
+ public static final String QUERY_METHOD_S_NOT_SUPPORTED = "Query method '%s' not supported.";
+ private final QueryMethod queryMethod;
+ private final KeyValueOperations keyValueOperations;
+ private final PartTree tree;
+
+ private final boolean isCount;
+ private final boolean isDelete;
+ private final boolean isDistinct;
+ private final boolean isExists;
+ private final Class> targetType;
+ private final Class> returnType;
+
+ private boolean isRearrangeKnown;
+ private boolean isRearrangeRequired;
+ private int[] rearrangeIndex;
+
+ private final List> EMPTY_KEY = Collections.emptyList();
+
+ /**
+ * Create a {@link RepositoryQuery} implementation for each query method defined in a tarantool
+ * repository.
+ *
+ * @param queryMethod Method defined in Tarantool Repositories
+ * @param evaluationContextProvider Not used
+ * @param keyValueOperations Interface to Tarantool
+ * @param queryCreator Not used
+ */
+ public TarantoolPartTreeQuery(
+ QueryMethod queryMethod,
+ QueryMethodEvaluationContextProvider evaluationContextProvider,
+ KeyValueOperations keyValueOperations,
+ Class extends AbstractQueryCreator, ?>> queryCreator) {
+ super(queryMethod, evaluationContextProvider, keyValueOperations, queryCreator);
+ this.queryMethod = queryMethod;
+ this.keyValueOperations = keyValueOperations;
+ this.isRearrangeKnown = false;
+ this.targetType = queryMethod.getEntityInformation().getJavaType();
+ this.returnType = queryMethod.getReturnedObjectType();
+ this.tree = new PartTree(getQueryMethod().getName(), targetType);
+ if (queryMethod.getParameters().getNumberOfParameters() > 0) {
+ this.isCount = tree.isCountProjection();
+ this.isDelete = tree.isDelete();
+ this.isDistinct = tree.isDistinct();
+ this.isExists = tree.isExistsProjection();
+ } else {
+ this.isCount = false;
+ this.isDelete = false;
+ this.isDistinct = false;
+ this.isExists = false;
+ }
+ }
+
+ /**
+ * Execute this query instance, using any invocation parameters.
+ *
+ *
Expecting {@code findBy...()}, {@code countBy...()} or {@code deleteBy...()}
+ *
+ * @param parameters Any parameters
+ * @return Query result
+ */
+ @Override
+ public Object execute(@NonNull Object[] parameters) {
+ ParametersParameterAccessor accessor = this.prepareAccessor(parameters, tree);
+
+ KeyValueQuery> query = prepareQuery(accessor);
+
+ if (this.isCount) {
+ if (this.isDistinct) {
+ final Iterable> iterable = this.keyValueOperations.find(query, targetType);
+ return StreamUtils.createStreamFromIterator(iterable.iterator()).distinct().count();
+ }
+ return this.keyValueOperations.count(query, targetType);
+ }
+
+ if (this.isDelete) {
+ return this.executeDeleteQuery(query);
+ }
+
+ if (this.isExists) {
+ query.setOffset(0);
+ query.setRows(1);
+ final Iterable> result = this.keyValueOperations.find(query, targetType);
+ return result.iterator().hasNext();
+ }
+
+ if (queryMethod.isPageQuery()) {
+ return this.executePageQuery(query, accessor);
+ }
+
+ if (queryMethod.isSliceQuery()) {
+ return this.executeSliceQuery(query, accessor);
+ }
+
+ if (queryMethod.isScrollQuery()) {
+ return this.executeScrollQuery(query, accessor);
+ }
+
+ if (queryMethod.isCollectionQuery()
+ || queryMethod.isQueryForEntity()
+ || queryMethod.isStreamQuery()) {
+ return this.executeFindQuery(query);
+ }
+
+ throw new UnsupportedOperationException(
+ String.format(QUERY_METHOD_S_NOT_SUPPORTED, queryMethod.getName()));
+ }
+
+ /**
+ * Execute a "delete" query, not really a query more of an operation.
+ *
+ *
+ *
+ * @param query The query to run
+ * @return Collection of deleted objects or the number of deleted objects
+ */
+ private Object executeDeleteQuery(final KeyValueQuery> query) {
+
+ Iterable> resultSet = this.keyValueOperations.find(query, targetType);
+ Iterator> iterator = resultSet.iterator();
+
+ List