Grails ElasticSearch
了解如何在 Grails 应用程序中使用 ElasticSearch
作者:Puneet Behl、Sergio del Amo
Grails 版本 3.3.2
1 培训
Grails 培训 - 由创建和积极维护 Grails 框架的人员开发和提供!
2 入门
Elasticsearch 是一款高度可扩展的开源全文搜索和分析引擎。它支持快速存储、搜索和分析海量数据,并且几乎可以实现实时分析。Elasticsearch 通常用作底层引擎/技术,为具有复杂搜索功能和需求的应用程序提供支持。
Grails ElasticSearch 插件旨在实现 Grails 与开源搜索引擎 ElasticSearch 的简单集成,后者基于 Lucene,并提供分布式功能。
在本指南中,您将了解如何使用 Grails Elasticsearch 插件为 Elasticsearch 创建索引,以便实现更快速的搜索。
2.1 功能
-
将域类映射到其在 Elasticsearch 中相应的索引
-
提供 ElasticSearch 服务,用于跨域搜索
-
注入域类方法,用于特定域搜索、索引编制和取消索引编制
-
自动将通过 GORM 进行的任何更改镜像到索引
-
允许为搜索查询使用 Groovy 内容构建器 DSL
-
支持术语高亮显示
2.2 需要什么
要完成本指南,您需要以下内容
-
一些空余时间
-
计算机上已安装 Elasticsearch v5.4.1,ES_HOME 已正确配置
-
一个不错的文本编辑器或 IDE
-
已安装 JDK 1.7 或更高版本,JAVA_HOME 已相应配置
2.3 如何完成本指南
按照下列步骤开始
-
下载并解压源代码
或
-
克隆Git存储库
git clone https://github.com/grails-guides/grails-elasticsearch.git
Grails 指南存储库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用程序,带有额外代码,让你的先期工作更容易。 -
complete
完成的示例。这是按照指南中介绍的步骤进行操作并对initial
文件夹应用这些更改的结果。
要完成指南,请转至 initial
文件夹
-
cd
进入grails-guides/grails-elasticsearch/initial
并按照后续章节中的说明操作。
如果你 cd 进入grails-guides/grails-elasticsearch/complete ,你可以直接转到已完成的示例 |
3 编写指南
3.1 通过 Docker 安装 Elasticsearch
如果你尚未安装 Docker,你需要安装它。
根据你的环境,你可能需要将 Docker 的可用内存增加到 4GB 或更多。
-
通过执行以下命令,确保 Docker 机器正在运行
docker-machine status default
-
如果
default
Docker 机器未运行,则通过执行以下 bash 命令启动机器
docker-machine start default
在执行上述命令后,你应在终端上看到类似以下内容
Starting "default"... (default) Check network to re-create if needed... (default) Waiting for an IP... Machine "default" was started. Waiting for SSH to be available... Detecting the provisioner... Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
-
现在,我们已成功启动 Docker 机器。因此,我们记下
default
Docker 机器 IP 地址,将来我们会使用该地址将应用连接到 Elasticsearch。运行以下命令以查看 Docker 机器 IP 地址
docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default - virtualbox Running tcp://192.168.99.100:2376 v17.12.0-ce
-
启动 Elasticsearch Docker 容器
eval $(docker-machine env default)
docker run \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-e "http.cors.enabled=true" \
-e "http.cors.allow-origin=/https?:\/\/192.168.99.100(:[0-9]+)?/" \
-e "http.cors.allow-headers=Authorization,X-Requested-With,Content-Length,Content-Type" \
-e "http.cors.allow-credentials=true" \
-e "cluster.name=elasticsearch" \
-e "xpack.security.enabled=false" \
docker.elastic.co/elasticsearch/elasticsearch:5.4.3
3.2 配置 ElasticSearch 插件
-
要安装插件,将以下属性添加到
build.gradle
def elasticsearchVersion = '5.4.1' (1)
ext['elasticsearch.version'] = elasticsearchVersion
1 | 定义 Elasticsearch 版本。 |
compile "org.grails.plugins:elasticsearch:2.4.0"
-
为了将应用程序连接到 Elasticsearch,在
application.yml
中定义 Elasticsearch 插件配置,如下所示
elasticSearch:
datastoreImpl: hibernateDatastore (1)
client:
mode: transport (2)
hosts:
- {host: 192.168.99.100, port: 9300} (3)
cluster.name: elasticsearch (4)
disableAutoIndex: false (5)
bulkIndexOnStartup: true (6)
1 | 应监控 Hibernate 数据存储实现的存储事件。 |
2 | 该插件创建一个传输客户端,该客户端将在不加入集群的情况下连接到远程 ElasticSearch 实例。TransportClient 使用传输模块远程连接到 Elasticsearch 集群,并在每个操作上使用循环顺序与它们通信。 |
3 | TransportClient 进行连接的主机地址。 |
4 | Elasticsearch 集群的名称。 |
5 | 确定该插件应自动对 ES 实例中的任何数据库保存/更新/删除进行反映。默认为 false。 |
6 | 应用程序应在启动时启动批量索引操作。 |
3.3 Elasticsearch 日志
在 logback.groovy
末尾添加以下内容,以查看 ElasticSearch 相关的日志
logger("grails.plugins.elasticsearch", DEBUG, ['STDOUT'], false)
3.4 创建 Domain (域) 类
您可以创建一个 domain 类
package demo
class Book {
String title
String author
String about
String href
static mapping = {
about type: 'text'
}
}
3.5 标记 Domains (域) 可搜索
为了在 Elasticsearch 中创建此 domain (域) 的索引,请在前面定义的 Book
domain 类中定义一个名为 searchable
的static
属性
static searchable = {
title boost: 2.0 (1)
about boost: 1.0 (2)
except = ['href'] (3)
}
1 | “boost”通常用于通过给文档赋予更大的权重来微调每个文档的相关性_score 。在此情况下,如果搜索查询与文档的title 相匹配,那么与其他结果相比,该文档的相关性_score 将增加一倍。 |
2 | 如果搜索查询与about 字段匹配,那么相关性_score 将是其他结果的1.0 倍。 |
3 | href 属性包含在不是searchable 的属性列表中。 |
前面的示例将创建以下 Elasticsearch 映射
{
"mappings": {
"book": {
"properties": {
"author": {
"include_in_all": true,
"term_vector": "with_positions_offsets",
"type": "text"
},
"about": {
"include_in_all": true,
"term_vector": "with_positions_offsets",
"type": "text"
},
"title": {
"include_in_all": true,
"term_vector": "with_positions_offsets",
"boost": 2,
"type": "text"
}
}
}
}
}
3.6 GORM 数据服务
创建一个GORM 数据服务以简化Book
CRUD 操作。
package demo
import grails.gorm.services.Service
import groovy.transform.CompileStatic
@CompileStatic
@Service(Book)
interface BookDataService {
Book save(String title, String author, String about, String href)
Number count()
}
3.7 保存初始数据
通过如下修改 BootStrap.groovy
创建一些书籍
package demo
import grails.util.BuildSettings
import grails.util.Environment
import groovy.transform.CompileStatic
@CompileStatic
class BootStrap {
public final static List< Map<String, String> > GRAILS_BOOKS = [
[
title : 'Grails 3 - Step by Step',
author: 'Cristian Olaru',
href: 'https://grailsthreebook.com/',
about : 'Learn how a complete greenfield application can be implemented quickly and efficiently with Grails 3 using profiles and plugins. Use the sample application that accompanies the book as an example.'
],
[
title : 'Practical Grails 3',
author: ' Eric Helgeson',
href : 'https://www.grails3book.com/',
about : 'Learn the fundamental concepts behind building Grails applications with the first book dedicated to Grails 3. Real, up-to-date code examples are provided, so you can easily follow along.'
],
[
title : 'Falando de Grails',
author: 'Henrique Lobo Weissmann',
href : 'http://www.casadocodigo.com.br/products/livro-grails',
about : 'This is the best reference on Grails 2.5 and 3.0 written in Portuguese. It's a great guide to the framework, dealing with details that many users tend to ignore.'
],
[
title : 'Grails Goodness Notebook',
author: 'Hubert A. Klein Ikkink',
href : 'https://leanpub.com/grails-goodness-notebook',
about : 'Experience the Grails framework through code snippets. Discover (hidden) Grails features through code examples and short articles. The articles and code will get you started quickly and provide deeper insight into Grails.'
],
[
title : 'The Definitive Guide to Grails 2',
author: 'Jeff Scott Brown and Graeme Rocher',
href : 'http://www.apress.com/9781430243779',
about : 'As the title states, this is the definitive reference on the Grails framework, authored by core members of the development team.'
],
[
title : 'Grails in Action',
author: 'Glen Smith and Peter Ledbrook',
href : 'http://www.manning.com/gsmith2/',
about : 'The second edition of Grails in Action is a comprehensive introduction to Grails 2 focused on helping you become super-productive fast.'
],
[
title : 'Grails 2: A Quick-Start Guide',
author: 'Dave Klein and Ben Klein',
href : 'http://www.amazon.com/gp/product/1937785777?tag=misa09-20',
about : 'This revised and updated edition shows you how to use Grails by iteratively building a unique, working application.'
],
[
title : 'Programming Grails',
author: 'Burt Beckwith',
href : 'http://shop.oreilly.com/product/0636920024750.do',
about : 'Dig deeper into Grails architecture and discover how this application framework works its magic.'
]
] as List< Map<String, String> >
public final static List< Map<String, String> > GROOVY_BOOKS = [
[
title: 'Making Java Groovy',
author: 'Ken Kousen',
href: 'http://www.manning.com/kousen/',
about: 'Make Java development easier by adding Groovy. Each chapter focuses on a task Java developers do, like building, testing, or working with databases or restful web services, and shows ways Groovy can make those tasks easier.'],
[
title: 'Groovy in Action, 2nd Edition',
author: 'Dierk König, Guillaume Laforge, Paul King, Cédric Champeau, Hamlet D\'Arcy, Erik Pragt, and Jon Skeet',
href: 'http://www.manning.com/koenig2/',
about: 'This is the undisputed, definitive reference on the Groovy language, authored by core members of the development team.'],
[
title: 'Groovy for Domain-Specific Languages',
author: 'Fergal Dearle',
href: 'http://www.packtpub.com/groovy-for-domain-specific-languages-dsl/book',
about: 'Learn how Groovy can help Java developers easily build domain-specific languages into their applications.'
],
[
title: 'Groovy 2 Cookbook',
author: 'Andrey Adamovitch, Luciano Fiandeso',
href: 'http://www.packtpub.com/groovy-2-cookbook/book',
about: 'This book contains more than 90 recipes that use the powerful features of Groovy 2 to develop solutions to everyday programming challenges.'
],
[
title: 'Programming Groovy 2',
author: 'Venkat Subramaniam',
href: 'http://pragprog.com/book/vslg2/programming-groovy-2',
about: 'This book helps experienced Java developers learn to use Groovy 2, from the basics of the language to its latest advances.'
],
] as List< Map<String, String> >
BookDataService bookDataService
def init = { servletContext ->
for (Map<String, String> bookInfo : (GRAILS_BOOKS + GROOVY_BOOKS)) {
bookDataService.save(bookInfo.title, bookInfo.author, bookInfo.about, bookInfo.href)
}
}
}
3.8 创建服务
创建BookSearchService.groovy
,它借助ElasticSearch Grails Plugin对 Elasticsearch 进行查询。
package demo
import grails.plugins.elasticsearch.ElasticSearchService
import groovy.transform.CompileStatic
@CompileStatic
class BookSearchService {
ElasticSearchService elasticSearchService
Map search(String query) {
elasticSearchService.search(query, [indices: Book, types: Book, score: true]) as Map (1)
}
}
1 | 在索引中搜索指定的搜索查询。 |
该服务返回一个包含总项数(表示找到的点击总数)、searchResults 项(包含点击信息)和 scores 项(包含点击得分)的Map
。
创建一个单元测试,以验证elasticSearchService
使用提供的query
方法一次调用。
package demo
import grails.plugins.elasticsearch.ElasticSearchService
import grails.testing.services.ServiceUnitTest
import spock.lang.Specification
class BookSearchServiceSpec extends Specification implements ServiceUnitTest<BookSearchService> {
def "search uses indices and types `Book` and score `true`"() {
service.elasticSearchService = Mock(ElasticSearchService)
when:
service.search('Grails')
then:
1 * service.elasticSearchService.search('Grails', [indices: Book, types: Book, score: true])
}
}
3.9 创建控制器
在BookController.groovy
中实现search
操作,该操作将查询 Elasticsearch。
package demo
import grails.plugins.elasticsearch.ElasticSearchService
import grails.validation.ValidationException
import groovy.transform.CompileStatic
import static org.springframework.http.HttpStatus.*
@CompileStatic
class BookController {
BookSearchService bookSearchService
static responseFormats = ['json']
static allowedMethods = [
search: "GET"
]
def search(String q) {
if ( !q ) {
render status: NOT_FOUND (1)
return
}
respond bookSearchService.search(q)
}
}
1 | 如果没有在请求中传递查询字符串,则仅返回Not Found(404) 。 |
创建一个测试,以验证控制器仅对 HTTP GET 请求做出响应。
package demo
import grails.testing.web.controllers.ControllerUnitTest
import spock.lang.Specification
import spock.lang.Unroll
import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND
class BookControllerSpec extends Specification implements ControllerUnitTest<BookController> {
@Unroll
def "test BookController.search does not accept #method requests"(String method) {
when:
request.method = method
controller.search()
then:
response.status == SC_METHOD_NOT_ALLOWED
where:
method << ['PATCH', 'DELETE', 'POST', 'PUT']
}
def "test BookController.search accepts GET requests"() {
when:
request.method = 'GET'
controller.search()
then:
response.status == SC_NOT_FOUND
}
}
3.10 JSON 视图
现在,在/views/book/search.gson
下创建一个名为search
的视图,该视图将用于显示结果JSON
。
model { Long total List searchResults Map scores } json { totalCount total results g.render(searchResults) scores g.render(scores) }
3.11 修改 UrlMapping
通过向mappings
块的末尾添加以下内容来修改UrlMappings.groovy
get "/book/search/$q?"(controller: 'book', action: "search")
测试映射
package demo
import grails.testing.web.UrlMappingsUnitTest
import spock.lang.Specification
class UrlMappingsSpec extends Specification implements UrlMappingsUnitTest<UrlMappings> {
void setup() {
mockController(BookController)
}
void "book/search endpoint mapping includes query term in path"() {
expect:
verifyForwardUrlMapping("/book/search/grails", controller: 'book', action: 'search') {
q = 'grails'
}
}
}
3.12 功能测试
功能测试要求应用程序正在运行,其设计尽可能模拟用户使用 HTTP 请求执行应用。这往往是最复杂的测试。
Grails 使用的测试框架是 Spock。Spock 提供了一种用于编写基于 Groovy 语言的测试用例的表达式语法,所以它非常适合 Grails。它包含一种 JUnit 运行器,这意味着它的 IDE 支持非常普遍(任何可以运行 JUnit 测试的 IDE 都可以运行 Spock 测试)。 |
以下是 BookController
的 search
操作的功能测试
package demo
import grails.plugins.rest.client.RequestCustomizer
import grails.plugins.rest.client.RestBuilder
import grails.plugins.rest.client.RestResponse
import spock.lang.Specification
class RestSpec extends Specification {
RestBuilder rest = new RestBuilder()
RestResponse get(String path, @DelegatesTo(RequestCustomizer) Closure customizer = null) {
rest.get("http://localhost:${serverPort}${path}", customizer)
}
}
package demo
import grails.plugins.rest.client.RestResponse
import grails.testing.mixin.integration.Integration
import spock.lang.IgnoreIf
@IgnoreIf( { System.getenv('TRAVIS') as boolean } )
@Integration
class BookControllerFunctionalSpec extends RestSpec {
BookDataService bookDataService
void "Test the search action correctly searches"() {
expect:
bookDataService.count()
when:
RestResponse rsp = get("/book/search/Beckwith")
then: "The list is returned with only one instance"
rsp.json.totalCount == 1
rsp.json.results.first().author == 'Burt Beckwith'
}
}
您需要运行 ElasticSearch 实例,以便通过此测试。 |
4 摘要
在这一指南中,我们了解了如何配置 Elasticsearch Grails 插件。该插件目前主要用于将 Grails 域类映射出来。它极大地采用了现有的 Searchable 插件作为其语法和行为的参考。
请参阅 Grails Elasticsearch 插件的文档 了解更多信息。