Sometimes it is required to change default conversion logic of entities. You can either create custom converter for one field or for the whole entity.
If you need to modify conversion only for save-related operations then you’ll need to create writing converter that
implements Converter<%Your field type here%, Map<String, Object>>
; for read-related operations — reading converter
that implements Converter<Map<String, Object>, %Your field type here%>
. In case read conversion depends on
write conversion and vice versa — you’ll need to have both of the converters.
Let’s have a look at a simple document:
@Value
@Document
public class UserDocument {
@Id
long id;
@Field
UserData data;
@Value
public static class UserData {
String address;
String country;
}
}
In this example we want to create both writing and reading converters for the UserData data
field:
public class UserDataConverters {
@WritingConverter
public enum UserDataToMapConverter implements Converter<UserDocument.UserData, Map<String, Object>> {
INSTANCE;
@Override
public Map<String, Object> convert(UserDocument.UserData source) {
return Map.of(
"addr", source.address().toUpperCase(),
"country", source.country().toUpperCase()
);
}
}
@ReadingConverter
public enum MapToUserDataToConverter implements Converter<Map<String, Object>, UserDocument.UserData> {
INSTANCE;
@Override
public UserDocument.UserData convert(Map<String, Object> source) {
String address = (String) source.getOrDefault("addr", "N/A");
String country = (String) source.getOrDefault("country", "N/A");
return new UserDocument.UserData(address, country);
}
}
}
Custom converters need to be registered in customConverters
method in a configuration class that
extends AbstractAerospikeDataConfiguration
:
@Configuration
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
// other code omitted
@Override
protected List<?> customConverters() {
return List.of(
UserDataConverters.MapToUserDataToConverter.INSTANCE,
UserDataConverters.UserDataToMapConverter.INSTANCE
);
}
}
If you need to modify conversion only for save-related operations then you’ll need to create writing converter
that implements Converter<%Your entity type here%, AerospikeWriteData>
; for read-related operations — reading
converter that implements Converter<AerospikeReadData, %Your entity type here%>
. In case read conversion depends
on write conversion and vice versa — you’ll need to have both of the converters.
In this example we will use simple ArticleDocument
as our entity:
@Value
@Document(collection = ArticleDocument.SET_NAME)
public class ArticleDocument {
public static final String SET_NAME = "demo-service-articles";
@Id
String id;
String author;
String content;
boolean draft;
}
Let’s create custom converters. In this specific example we are going to set expiration for the draft article to 10 seconds and for all other we will set expiration to none:
ArticleDocumentConverters.java
public class ArticleDocumentConverters {
@WritingConverter
@RequiredArgsConstructor
public static class ArticleDocumentToAerospikeWriteDataConverter implements Converter<ArticleDocument, AerospikeWriteData> {
private static final int TEN_SECONDS = 10;
private static final int NEVER_EXPIRE = -1;
private final String namespace;
private final String setName;
@Override
public AerospikeWriteData convert(ArticleDocument source) {
Key key = new Key(namespace, setName, source.getId());
int expiration = source.isDraft() ? TEN_SECONDS : NEVER_EXPIRE;
Integer version = null; // not versionable document
Collection<Bin> bins = List.of(
new Bin("author", source.getAuthor()),
new Bin("content", source.getContent()),
new Bin("draft", source.isDraft())
);
return new AerospikeWriteData(key, bins, expiration, version);
}
}
@ReadingConverter
public enum AerospikeReadDataToArticleDocumentToConverter implements Converter<AerospikeReadData, ArticleDocument> {
INSTANCE;
@Override
public ArticleDocument convert(AerospikeReadData source) {
String id = (String) source.getKey().userKey.getObject();
String author = (String) source.getValue("author");
String content = (String) source.getValue("content");
boolean draft = (boolean) source.getValue("draft");
return new ArticleDocument(id, author, content, draft);
}
}
}
Now we need to register custom converters in customConverters
method:
@Configuration
@EnableAerospikeRepositories(basePackages = "com.demo.customconverters.repository")
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
@Value("${spring.data.aerospike.namespace}")
private String namespace;
@Override
protected List<Object> customConverters() {
return List.of(
UserDataConverters.MapToUserDataToConverter.INSTANCE,
UserDataConverters.UserDataToMapConverter.INSTANCE,
ArticleDocumentConverters.AerospikeReadDataToArticleDocumentToConverter.INSTANCE,
new ArticleDocumentConverters.ArticleDocumentToAerospikeWriteDataConverter(namespace,
ArticleDocument.SET_NAME)
);
}
}
To see demo application go to Custom Converters Demo.