I have a document with many fields (some nested) indexed on elasticsearch. For example:
{
"id" : 1,
"username" : "...",
"name" : "...",
"surname" : "...",
"address" : "...",
"age": 42,
...
"bookmarks" : [{...}, {...}],
"tags" : [{...}, {...}]
}
Only some filed is mapped in my entity (I don't want to map the entire document):
@Document(indexName = "...", type = "...")
public class User {
@Id
private int id;
private String username;
private String address;
// getter/setter methods
}
In the service class I would like to do a partial update with ElasticsearchRepository, without mapping all document's fields in the entity:
public class UserServiceClass {
@Autowired
private UserElasticsearchRepository userElasticsearchRepository;
public void updateAddress(int id, String updatedAddress) {
User user = userElasticsearchRepository.findOne(id);
user.setAddress(updatedAddress);
userElasticsearchRepository.save(user);
}
}
but save method overwrites the entire document:
{
"id" : 1,
"username" : "...",
"address" : "..."
}
Partial udpdate seems not supported by ElasticsearchRepository. So I used ElasticsearchTemplate, to make a partial update, for example:
public class UserServiceClass {
@Autowired
private UserElasticsearchRepository userElasticsearchRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public void updateAddress(int id, String updatedAddress) {
User user = userElasticsearchRepository.findOne(id);
if (user.getUsername().equals("system")) {
return;
}
IndexRequest indexRequest = new IndexRequest();
indexRequest.source("address", updatedAddress);
UpdateQuery updateQuery = new UpdateQueryBuilder().withId(user.getId()).withClass(User.class).withIndexRequest(indexRequest).build();
elasticsearchTemplate.update(updateQuery);
}
}
but seems a bit redundant to have two similar references (repository and ElasticsearchTemplate).
Can anyone suggest me a better solution?
Instead of having both ElasticsearchTemplate and UserElasticsearchRepository injected into your UserServiceClass, you can implement your own custom repository and let your existing UserElasticsearchRepository extend it.
I assume that your existing UserElasticsearchRepository look something like this.
public interface UserElasticsearchRepository extends ElasticsearchRepository<User, String> {
....
}
You have to create new interface name UserElasticsearchRepositoryCustom. Inside this interface you can list your custom query method.
public interface UserElasticsearchRepositoryCustom {
public void updateAddress(User user, String updatedAddress);
}
Then implement your UserElasticsearchRepositoryCustom by create a class called UserElasticsearchRepositoryImpl and implement your custom method inside with injected ElasticsearchTemplate
public class UserElasticsearchRepositoryImpl implements UserElasticsearchRepositoryCustom {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Override
public void updateAddress(User user, String updatedAddress){
IndexRequest indexRequest = new IndexRequest();
indexRequest.source("address", updatedAddress);
UpdateQuery updateQuery = new UpdateQueryBuilder().withId(user.getId()).withClass(User.class).withIndexRequest(indexRequest).build();
elasticsearchTemplate.update(updateQuery);
}
}
After that, just extends your UserElasticsearchRepository with UserElasticsearchRepositoryCustom so it should look like this.
public interface UserElasticsearchRepository extends ElasticsearchRepository<User, String>, UserElasticsearchRepositoryCustom {
....
}
Finally, you service code should look like this.
public class UserServiceClass {
@Autowired
private UserElasticsearchRepository userElasticsearchRepository;
public void updateAddress(int id, String updatedAddress) {
User user = userElasticsearchRepository.findOne(id);
if (user.getUsername().equals("system")) {
return;
}
userElasticsearchRepository.updateAddress(user,updatedAddress);
}
}
You can also move your user finding logic into the custom repository logic as well so that you can passing only user id and address in the method. Hope this is helpful.