How to create secondary index in Spring Data Aerospike
Secondary indexes are on a non-primary key, which allows you to model one-to-many relationships.
Spring-data-aerospike supports creating secondary indexes in Aerospike out of the box.
There are two ways to accomplish this task:
Using AerospikeTemplate createIndex method; or
Using @Indexed annotation placed over the field in your entity.
Let’s dive into more details.
Note: Before continuing it is expected that your project has spring-data-aerospike already setup. Please check this guide to find out how to do it.
First approach — creating index via AerospikeTemplate
In this example we will create an index at startup of the application manually.
package com.example.demo.persistence.index;
import com.aerospike.client.query.IndexType;
import com.example.demo.persistence.simplecrud.MovieDocument;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.aerospike.IndexAlreadyExistsException;
import org.springframework.data.aerospike.core.AerospikeTemplate;
@Slf4j
@Configuration
public class AerospikeIndexConfiguration {
private static final String INDEX_NAME = "movie-rating-index";
@Bean
@ConditionalOnProperty(
value = "aerospike." + INDEX_NAME + ".create-on-startup",
havingValue = "true",
matchIfMissing = true)
public boolean createAerospikeIndex(AerospikeTemplate aerospikeTemplate) {
try {
aerospikeTemplate.createIndex(MovieDocument.class, INDEX_NAME, "rating", IndexType.NUMERIC);
log.info("Index {} was successfully created", INDEX_NAME);
} catch (IndexAlreadyExistsException e) {
log.info("Index {} already exists, skipped creating", INDEX_NAME);
}
return true;
}
}
Second approach — creating index via @Indexed annotation
Place @Indexed annotation over the field that you want to index in your entity and specify required types of the index. This will make spring-data-aerospike to auto-create specified secondary index in Aerospike on startup of your application.
Note:@Indexed annotation is not supported for the fields annotated with @Id, @Expiration or @Version annotations.
package com.example.demo.persistence.index;
import lombok.Value;
import org.springframework.data.aerospike.annotation.Indexed;
import org.springframework.data.aerospike.mapping.Document;
import org.springframework.data.annotation.Id;
import java.util.List;
import static com.aerospike.client.query.IndexCollectionType.DEFAULT;
import static com.aerospike.client.query.IndexCollectionType.LIST;
import static com.aerospike.client.query.IndexType.NUMERIC;
import static com.aerospike.client.query.IndexType.STRING;
@Value
@Document
public class IndexedDocument {
@Id
String key;
@Indexed(type = STRING, collectionType = DEFAULT)
String author;
@Indexed(type = NUMERIC, collectionType = DEFAULT)
int likes;
@Indexed(type = NUMERIC, collectionType = LIST)
List<Integer> options;
}
Testing
Verify indexes were created using the following tests:
package com.example.demo;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Info;
import com.aerospike.client.cluster.Node;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
public class IndexTests extends DemoApplicationTests {
@Value("${embedded.aerospike.namespace}")
String namespace;
@Autowired
AerospikeClient client;
@Test
void verifyCustomIndexCreated() {
List<String> existingIndexes = getIndexes(client, namespace);
assertThat(existingIndexes).contains("movie-rating-index");
}
@Test
void verifyAnnotationBasedIndexesCreated() {
List<String> existingIndexes = getIndexes(client, namespace);
assertThat(existingIndexes)
.contains(
"IndexedDocument_author_string_default",
"IndexedDocument_likes_numeric_default",
"IndexedDocument_options_numeric_list");
}
// DO NOT USE THIS CODE IN PRODUCTION
private static List<String> getIndexes(AerospikeClient client, String namespace) {
Node node = client.getNodes()[0];
String response = Info.request(node, "sindex/" + namespace);
return Arrays.stream(response.split(";"))
.map(info -> {
Map<String, String> keyValue = Arrays.stream(info.split(":"))
.map(part -> {
String[] kvParts = part.split("=");
return Map.entry(kvParts[0], kvParts[1]);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return keyValue.get("indexname");
})
.collect(Collectors.toList());
}
}
Demo project is located on GitHub — https://github.com/aerospike-community/spring-data-aerospike-demo.