Spring Boot - Elatiscsearch CRUD
本例展示一个ElasticSearch CURD 的Demo。 @pdai
注意事项
ElasticSearch 可以直接使用java elasticsearch.jar,也可以使用spring data封装的,还可以直接使用Springboot starter自动配置;在写代码时,你可以直接调用原生的接口,也可以调用ElasticSearchTemplate(ElasticSearchOptions), 还可以调用JPA形式的ElasticSearchRepository类;
常规来说,采用spring-boot-starter-data-elasticsearch会简单很多,但是在有些动态的复合查询时,或者结合多index动态查询时,需要考虑一定的业务二次封装;可以参考我在springboot多数据源中针对ElasticSearch封装代码。
需要注意jar和es版本匹配问题,比如现在最新的Springboot 2.1.7中使用的版本是ES 6.4.3; 而在本例中Springboot 1.5.8.RELEASE使用的对应的ES版本是2.4.x;
在新的ES版本中提供的了性能的提升和功能增强(比如增加了keyword防止默认分词),底层Luence版本提升等等。所以在升级ES版本的时候需要考虑下游应用的兼容性问题,看是否需要同步提升Spring相关组件的版本,及接口适配等。
简单示例相关代码
- Pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.nasir</groupId>
<artifactId>spring-boot-elasticsearch-data-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-boot-elasticsearch-data-demo</name>
<description>Integration of Elasticsearch with SpringData</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!--
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency> -->
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/swagger-bootstrap-ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- application.yml
spring:
data:
elasticsearch:
cluster-name: es-logs-01
cluster-nodes: 10.11.60.5:9300
repositories:
enabled: true
- Swagger 配置
package com.pdai.elasticsearch.springdata.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("com.pdai.elasticsearch")).paths(PathSelectors.any()).build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Pdai's API").description("swagger-bootstrap-ui")
.termsOfServiceUrl("http://localhost:8080/").version("1.0").build();
}
}
- App
package com.pdai.elasticsearch.springdata;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.pdai.elasticsearch.springdata.model.Director;
import com.pdai.elasticsearch.springdata.model.Genre;
import com.pdai.elasticsearch.springdata.model.Movie;
import com.pdai.elasticsearch.springdata.service.MovieService;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* <b>ClassName</b>: ElasticsearchSpringDataApplication <br/>
*
* <b>Description</b>: ElasticsearchSpringDataApplication <br/>
*
* <b>Date</b>: Jan 8, 2019 4:46:36 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@SpringBootApplication
@EnableSwagger2
public class ElasticsearchSpringDataApplication implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(ElasticsearchSpringDataApplication.class);
@Autowired
private MovieService movieService;
public static void main(String[] args) {
SpringApplication.run(ElasticsearchSpringDataApplication.class, args);
}
@Override
public void run(String... strings) throws Exception {
addMovies();
List<Movie> dabanggNamedQuery = movieService.getByName("Batman");
logger.info("Content of Batman name movie {}", dabanggNamedQuery);
List<Movie> readyMovieQuery = movieService.getByName("Batman");
logger.info("Content of Batman name movie {}", readyMovieQuery);
List<Movie> byRating = movieService.getByRatingInterval(7d, 9d);
logger.info("Content of movie by rating 7 9 {}", byRating);
}
private void addMovies() {
Movie movie1 = getFirstMovie();
movieService.addMovie(movie1);
Movie movie2 = getSecondMovie();
movieService.addMovie(movie2);
Movie mvoie3 = getThirdMovie();
movieService.addMovie(mvoie3);
Movie movie4 = getForthMovie();
movieService.addMovie(movie4);
}
private Movie getFirstMovie() {
Movie firstMovie = new Movie();
firstMovie.setId(1l);
firstMovie.setRating(8.4d);
firstMovie.setName("Batman v Superman: Dawn of Justice");
Director director = new Director("Zack Snyder");
firstMovie.setDirector(director);
List<Genre> genres = new ArrayList<Genre>();
genres.add(new Genre("DRAMA"));
genres.add(new Genre("ACTION"));
firstMovie.setGenre(genres);
return firstMovie;
}
private Movie getSecondMovie() {
Movie secondMovie = new Movie();
secondMovie.setId(2l);
secondMovie.setRating(9.4d);
secondMovie.setName("The Dark Knight Rises");
Director director = new Director("Christopher Nolan");
secondMovie.setDirector(director);
List<Genre> genres = new ArrayList<Genre>();
genres.add(new Genre("ROMANTIC"));
genres.add(new Genre("ACTION"));
secondMovie.setGenre(genres);
return secondMovie;
}
private Movie getThirdMovie() {
Movie thirdMovie = new Movie();
thirdMovie.setId(3l);
thirdMovie.setRating(8d);
thirdMovie.setName("Batman Begins");
Director director = new Director("Christopher Nolan");
thirdMovie.setDirector(director);
List<Genre> genres = new ArrayList<Genre>();
genres.add(new Genre("ROMANTIC"));
genres.add(new Genre("ACTION"));
thirdMovie.setGenre(genres);
return thirdMovie;
}
private Movie getForthMovie() {
Movie forthMovie = new Movie();
forthMovie.setId(4l);
forthMovie.setRating(8d);
forthMovie.setName("Batman & Robin");
Director director = new Director("Joel Schumacher");
forthMovie.setDirector(director);
List<Genre> genres = new ArrayList<Genre>();
genres.add(new Genre("ROMANTIC"));
genres.add(new Genre("ACTION"));
forthMovie.setGenre(genres);
return forthMovie;
}
}
- Entity
package com.pdai.elasticsearch.springdata.model;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
/**
* <b>ClassName</b>: Movie <br/>
*
* <b>Description</b>: Movie <br/>
*
* <b>Date</b>: Jan 8, 2019 4:46:05 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "movie-store", type = "movie", shards = 1, replicas = 0)
public class Movie {
@Id
private Long id;
private String name;
@Field(type = FieldType.Nested)
private List<Genre> genre;
private Double rating;
@Field(type = FieldType.Nested)
private Director director;
}
package com.pdai.elasticsearch.springdata.model;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
/**
* <b>ClassName</b>: Genre <br/>
*
* <b>Description</b>: Genre <br/>
*
* <b>Date</b>: Jan 8, 2019 4:45:54 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@Getter
@Setter
@ToString
public class Genre {
@NonNull
private String name;
public Genre() {
}
public Genre(String name) {
super();
this.name = name;
}
}
package com.pdai.elasticsearch.springdata.model;
import java.util.List;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
/**
* <b>ClassName</b>: Director <br/>
*
* <b>Description</b>: Director <br/>
*
* <b>Date</b>: Jan 8, 2019 4:45:47 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@Getter
@Setter
@ToString
public class Director {
@NonNull
private String name;
private List<Movie> movies;
public Director() {
}
public Director(String name) {
super();
this.name = name;
}
public Director(String name, List<Movie> movies) {
super();
this.name = name;
this.movies = movies;
}
}
- Dao
package com.pdai.elasticsearch.springdata.repository;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import com.pdai.elasticsearch.springdata.model.Director;
import com.pdai.elasticsearch.springdata.model.Movie;
import java.util.List;
/**
* <b>ClassName</b>: MovieRepository <br/>
*
* <b>Description</b>: MovieRepository <br/>
*
* <b>Date</b>: Jan 8, 2019 4:45:31 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@Repository
public interface MovieRepository extends ElasticsearchRepository<Movie, Long> {
List<Movie> findByName(String name);
List<Movie> findByRatingBetween(Double start, Double end);
List<Movie> findByDirector(Director director);
}
- Service
package com.pdai.elasticsearch.springdata.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.pdai.elasticsearch.springdata.model.Director;
import com.pdai.elasticsearch.springdata.model.Movie;
import com.pdai.elasticsearch.springdata.repository.MovieRepository;
/**
* <b>ClassName</b>: MovieService <br/>
*
* <b>Description</b>: MovieService <br/>
*
* <b>Date</b>: Jan 8, 2019 4:45:19 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@Service
public class MovieService {
@Autowired
private MovieRepository movieRepository;
public List<Movie> getByName(String name) {
return movieRepository.findByName(name);
}
public List<Movie> getByRatingInterval(Double start, Double end) {
return movieRepository.findByRatingBetween(start, end);
}
public Movie addMovie(Movie movie) {
return movieRepository.save(movie);
}
public void deleteMovie(Long id) {
movieRepository.delete(id);
}
public List<Movie> findByDirector(Director director) {
return movieRepository.findByDirector(director);
}
}
- Controller - ElasticResource
package com.pdai.elasticsearch.springdata.api;
import java.util.Map;
import org.elasticsearch.client.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RestController;
import com.pdai.elasticsearch.springdata.model.Movie;
/**
* <b>ClassName</b>: ElasticResource <br/>
*
* <b>Description</b>: ElasticResource <br/>
*
* <b>Date</b>: Jan 8, 2019 4:46:25 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@RestController
public class ElasticResource {
@Autowired
private ElasticsearchOperations elasticsearchOperations;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@GetMapping("/elastic/details")
public ResponseEntity<Map<String, String>> getElasticInformation() {
Client client = elasticsearchOperations.getClient();
Map<String, String> asMap = client.settings().getAsMap();
return ResponseEntity.ok(asMap);
}
@PutMapping("/elastic/clear-indices")
public void clearIndices() {
elasticsearchTemplate.deleteIndex(Movie.class);
elasticsearchTemplate.createIndex(Movie.class);
elasticsearchTemplate.putMapping(Movie.class);
elasticsearchTemplate.refresh(Movie.class);
}
}
- Controller - MovieResource
package com.pdai.elasticsearch.springdata.api;
import java.net.URI;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import com.pdai.elasticsearch.springdata.model.Movie;
import com.pdai.elasticsearch.springdata.service.MovieService;
/**
* <b>ClassName</b>: MovieResource <br/>
*
* <b>Description</b>: MovieResource <br/>
*
* <b>Date</b>: Jan 8, 2019 4:46:13 PM <br/>
*
* @author pdai
* @version Jan 8, 2019
*
*/
@RestController
public class MovieResource {
private MovieService movieService;
@Autowired
public MovieResource(MovieService movieService) {
this.movieService = movieService;
}
@PostMapping("/movie/add")
public ResponseEntity<Movie> addMovie(@RequestBody Movie newMovie) {
Movie savedMovie = movieService.addMovie(newMovie);
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path(
"/{id}").buildAndExpand(savedMovie.getId()).toUri();
return ResponseEntity.created(location).body(savedMovie);
}
@DeleteMapping("/movie/{id}/delete")
public ResponseEntity<String> deleteMovie(@PathVariable("id") Long movieId) {
movieService.deleteMovie(movieId);
return ResponseEntity.ok("Deleted");
}
@GetMapping("/movie/get-by-name/{name}")
public ResponseEntity<List<Movie>> findMovieByName(@PathVariable("name") String movieName) {
List<Movie> fetchedMovie = movieService.getByName(movieName);
return ResponseEntity.ok(fetchedMovie);
}
}
- 示例
- http://localhost:8080/elastic/clear-indices
- http://localhost:8080/elastic/details
源码示例
@See https://github.com/realpdai/springboot-data-es-demo