Spring Data has a starter for Elasticsearch that takes away the boilerplate code of configuring Elasticsearch to work with Spring.
First create a springboot application.
Include this dependency to import Elasticsearch dependencies.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
Configure Elasticsearch.
You can change the host url(localhost:9200) to point to your respective environment like dev, qa, or production when needed.
Make sure to use @EnableElasticsearchRepositories on the configuration class. Also it is important to specify the package where your Elasticsearch repositories are in. Springboot needs to know that so that it cans and configures those repositories and their indexes.
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.lovemesomecoding.es.repository")
public class ElasticsearchConfig {
@Bean
public RestHighLevelClient client() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder().connectedTo("localhost:9200").build();
return RestClients.create(clientConfiguration).rest();
}
@Bean
public ElasticsearchOperations elasticsearchTemplate() {
return new ElasticsearchRestTemplate(client());
}
}
Create your index repository using ElasticsearchRepository.
package com.lovemesomecoding.es.repository;
import java.util.List;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.lovemesomecoding.es.user.User;
public interface UserRepository extends ElasticsearchRepository<User, Long> {
List<User> findByLastName(String lastName);
}
Create your index
I prefer using Spring Data annotations to create and manage my index. This allows Spring Data to manage index mapping, field type, etc which is time consuming I were to do it myself. When your application boots up it will automatically create or update your index and its mapping based on your Index class. Without this you would have to manually create/update index when there are new fields to add.
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(value = Include.NON_NULL)
@JsonAutoDetect(fieldVisibility = Visibility.ANY)
@Document(indexName = "sb_with_es_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id;
@MultiField(mainField = @Field(type = FieldType.Keyword), otherFields = {@InnerField(suffix = "token", type = FieldType.Text)})
private String firstName;
@MultiField(mainField = @Field(type = FieldType.Keyword), otherFields = {@InnerField(suffix = "token", type = FieldType.Text)})
private String lastName;
@Field(type = FieldType.Keyword)
private String middleName;
@MultiField(mainField = @Field(type = FieldType.Keyword), otherFields = {@InnerField(suffix = "token", type = FieldType.Text)})
private String email;
}
Create your DAO(Data Access Object).
It is best practice to have a DAO per index so that you can isolate changes and your code is readable. Having a DAO allows you to wire in your repository(UserRepository) and RestHighLevelClient. With your repository you can take advantage of what Spring Data has developed for you to use. You also has RestHighLevelClient that you can use as a pure java client to query Elasticsearch.
@Repository
@Slf4j
public class UserDAOImp implements UserDAO {
@Autowired
private UserRepository userRepository;
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
public List<User> getAllUsers() {
return null;
}
@Override
public List<User> getUsersByFirstName(String firstName) {
int pageNumber = 0;
int pageSize = 10;
SearchRequest searchRequest = new SearchRequest("sb_with_es_user");
searchRequest.allowPartialSearchResults(true);
searchRequest.indicesOptions(IndicesOptions.lenientExpandOpen());
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from(pageNumber * pageSize);
searchSourceBuilder.size(pageSize);
searchSourceBuilder.timeout(new org.elasticsearch.core.TimeValue(60, TimeUnit.SECONDS));
/**
* fetch only a few fields
*/
// searchSourceBuilder.fetchSource(new String[]{ "id", "firstName", "lastName", "cards" }, new String[]{""});
/**
* Query
*/
/**
* Filter<br>
* term query looks for exact match. Use keyword
*/
searchSourceBuilder.query(QueryBuilders.termQuery("firstName", firstName));
searchRequest.source(searchSourceBuilder);
searchRequest.preference("firstName");
if (searchSourceBuilder.sorts() != null && searchSourceBuilder.sorts().size() > 0) {
log.info("\n{\n\"query\":{}, \"sort\":{}\n}", searchSourceBuilder.query().toString(), searchSourceBuilder.sorts().toString());
} else {
log.info("\n{\n\"query\":{}\n}", searchSourceBuilder.query().toString());
}
List<User> users = null;
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
log.info("isTimedOut={}, totalShards={}, totalHits={}", searchResponse.isTimedOut(), searchResponse.getTotalShards(), searchResponse.getHits().getTotalHits().value);
users = getResponseResult(searchResponse.getHits());
log.info(ObjectUtils.toJson(users));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return users;
}
private List<User> getResponseResult(SearchHits searchHits) {
Iterator<SearchHit> it = searchHits.iterator();
List<User> searchResults = new ArrayList<>();
while (it.hasNext()) {
SearchHit searchHit = it.next();
// log.info("sourceAsString={}", searchHit.getSourceAsString());
try {
User obj = ObjectUtils.getObjectMapper().readValue(searchHit.getSourceAsString(), new TypeReference<User>() {});
// log.info("obj={}", ObjectUtils.toJson(obj));
searchResults.add(obj);
} catch (IOException e) {
log.warn("IOException, msg={}", e.getLocalizedMessage());
}
}
return searchResults;
}
@Override
public List<User> getUsersByLastName(String lastName) {
return userRepository.findByLastName(lastName);
}
}