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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  • application.yml
spring:
  data:
    elasticsearch:
      cluster-name: es-logs-01
      cluster-nodes: 10.11.60.5:9300
      repositories:
        enabled: true
1
2
3
4
5
6
7
  • 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();
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  • 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;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
  • 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;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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;
	}
    
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  • 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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  • 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);
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
  • 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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
  • 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);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
  • 示例
  • http://localhost:8080/elastic/clear-indices
  • http://localhost:8080/elastic/details

源码示例

@See https://github.com/realpdai/springboot-data-es-demo

联系我

添加@pdai微信

PS:添加时请备注Java全栈,谢谢!