之前在自己的mac上安装了elasticsearch(以下简称ES),现在想要在java项目中使用ES。容器选择了spring boot,可以较快开发,数据层使用spring data elasticsearch操作ES。
创建spring boot工程
Eclipse上可以安装Spring IDE插件(也被称为spring tool suite),然后可以File->New->Project->Spring Starter Project->next,输入项目名称以及其它项目配置,勾选NoSql中的ElasticSearch组件,然后一路next,至此,一个spring boot工程创建成功。创建成功的工程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>com.ms</groupId>
<artifactId>boot-springDataES-1-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>boot-springDataES-1-demo</name>
<description>Demo project for Spring Data ElasticSearch</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置ES
打卡ES安装目录下的config中的elasticsearch.yml,配置cluster.name为以下内容:
cluster.name: iengchen
然后打开项目的application.properties文件,添加以下内容:
#cluster-name要和Elastic Search安装目录下的config中的elasticsearch.yml中的cluster.name保持一致
spring.data.elasticsearch.cluster-name=iengchen
spring.data.elasticsearch.cluster-nodes=localhost:9300
终端中启动ES:
$ elasticsearch
输出日志为
[2018-01-11 15:41:49,345][INFO ][node ] [Korvus] version[2.4.6], pid[43622], build[5376dca/2017-07-18T12:17:44Z]
[2018-01-11 15:41:49,345][INFO ][node ] [Korvus] initializing ...
[2018-01-11 15:41:49,730][INFO ][plugins ] [Korvus] modules [reindex, lang-expression, lang-groovy], plugins [], sites []
[2018-01-11 15:41:49,767][INFO ][env ] [Korvus] using [1] data paths, mounts [[/ (/dev/disk1s1)]], net usable_space [280.9gb], net total_space [465.7gb], spins? [unknown], types [apfs]
[2018-01-11 15:41:49,767][INFO ][env ] [Korvus] heap size [989.8mb], compressed ordinary object pointers [true]
[2018-01-11 15:41:49,768][WARN ][env ] [Korvus] max file descriptors [10240] for elasticsearch process likely too low, consider increasing to at least [65536]
[2018-01-11 15:41:50,808][INFO ][node ] [Korvus] initialized
[2018-01-11 15:41:50,808][INFO ][node ] [Korvus] starting ...
[2018-01-11 15:41:50,859][INFO ][transport ] [Korvus] publish_address {127.0.0.1:9300}, bound_addresses {[fe80::1]:9300}, {[::1]:9300}, {127.0.0.1:9300}
[2018-01-11 15:41:50,862][INFO ][discovery ] [Korvus] iengchen/PvX7kAP7QF62_N-H9c7X0Q
[2018-01-11 15:41:54,889][INFO ][cluster.service ] [Korvus] new_master {Korvus}{PvX7kAP7QF62_N-H9c7X0Q}{127.0.0.1}{127.0.0.1:9300}, reason: zen-disco-join(elected_as_master, [0] joins received)
[2018-01-11 15:41:54,904][INFO ][http ] [Korvus] publish_address {127.0.0.1:9200}, bound_addresses {[fe80::1]:9200}, {[::1]:9200}, {127.0.0.1:9200}
[2018-01-11 15:41:54,904][INFO ][node ] [Korvus] started
[2018-01-11 15:41:54,917][INFO ][gateway ] [Korvus] recovered [0] indices into cluster_state
浏览器打开http://127.0.0.1:9200/ 应显示如下内容:
{
"name": "Frey",
"cluster_name": "iengchen",
"cluster_uuid": "J79XWt8SQKinREan86QdHw",
"version": {
"number": "2.4.6",
"build_hash": "5376dca9f70f3abef96a77f4bb22720ace8240fd",
"build_timestamp": "2017-07-18T12:17:44Z",
"build_snapshot": false,
"lucene_version": "5.5.4"
},
"tagline": "You Know, for Search"
}
实体模型编写
建立一个商品的数据模型Product:
@Document(indexName="iengchen",type="product")
public class Product {
@Id
private Long id;
//商品名称
private String prodName;
//商品品牌,eg:松下,华为
private String prodBrand;
//商品分类,eg:服装,电子,软件
private String prodCategory;
//必须有无参构造器,否则会导致Jackson解析错误
public Product() {
// TODO Auto-generated constructor stub
}
public Product(Long id,String prodName,String brand,String prodCategory) {
this.id = id;
this.prodName = prodName;
this.prodBrand = brand;
this.prodCategory = prodCategory;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProdName() {
return prodName;
}
public void setProdName(String prodName) {
this.prodName = prodName;
}
public String getProdCategory() {
return prodCategory;
}
public void setProdCategory(String prodCategory) {
this.prodCategory = prodCategory;
}
public String getProdBrand() {
return prodBrand;
}
public void setProdBrand(String prodBrand) {
this.prodBrand = prodBrand;
}
}
@Document和@Field用于为某个实体类建立索引,@Field用于注解实体类的属性,本案例中没有使用@Field注解。如果只用@Document,会默认为每一个属性都建立索引。如果@Document和@Field同时使用,只会为@Field注解的属性建立索引。查询该实体类对应的索引的方法为在浏览器中输入 http://127.0.0.1:9200/iengchen/_mapping url中的iengchen对应@Document注解中的indexName值。
Dao层编写
使用spring data elasticsearch时Dao只需要声明一个接口即可:
public interface ProductRepository extends ElasticsearchRepository<Product, Long>{
//根据产品名称查询并分页
Page<Product> findByProdName(String prodname,Pageable pageable);
//根据产品分类查询
List<Product> findByProdCategory(String prodcategory);
}
Service层编写
service层分为接口和实现。接口内容为:
public interface ProductService {
//保存商品
Product save(Product product);
//删除全部产品
void deleteAll();
//根据产品id删除指定商品
void deleteByProductId(long id);
//查询全部商品
Iterable<Product> findAll();
//根据商品id查询指定商品
Product findByProductId(long id);
//根据商品名称查询商品,并分页
Page<Product> findByProductName(String name,PageRequest pageRequest);
//根据商品分类查询商品
List<Product> findByProductCategory(String category);
}
service实现为:
//在实现类而不是接口上添加注解
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductRepository prodRepository;
@Override
public Product save(Product product) {
return prodRepository.save(product);
}
@Override
public void deleteByProductId(long id) {
prodRepository.delete(id);
}
@Override
public Product findByProductId(long id) {
return prodRepository.findOne(id);
}
@Override
public Page<Product> findByProductName(String name,PageRequest pageRequest) {
return prodRepository.findByProdName(name,pageRequest);
}
@Override
public List<Product> findByProductCategory(String category) {
return prodRepository.findByProdCategory(category);
}
@Override
public void deleteAll() {
prodRepository.deleteAll();
}
@Override
public Iterable<Product> findAll() {
return prodRepository.findAll();
}
}
测试代码编写
测试类内容为:
@RunWith(SpringRunner.class)
@SpringBootTest
public class BootSpringDataEs1DemoApplicationTests {
@Autowired
private ProductService productService;
private ObjectMapper mapper = new ObjectMapper();
//向ES中插入全部所需商品数据
@Test
public void insertAllData() {
productService.save(new Product(1L, "电吹风","飞利浦", "小家电"));
productService.save(new Product(2L, "电吹风","美的", "小家电"));
productService.save(new Product(3L, "电吹风","松下", "小家电"));
productService.save(new Product(4L, "电吹风","索尼", "小家电"));
productService.save(new Product(5L, "电吹风","华为", "小家电"));
productService.save(new Product(6L, "电吹风","海尔", "小家电"));
productService.save(new Product(7L, "电吹风","戴森", "小家电"));
productService.save(new Product(8L, "电吹风","九阳", "小家电"));
productService.save(new Product(9L, "洗衣机","海尔", "小家电"));
productService.save(new Product(10L, "洗衣机","美的", "小家电"));
productService.save(new Product(11L, "电视机", "飞利浦","小家电"));
productService.save(new Product(12L, "电视机", "索尼","小家电"));
productService.save(new Product(13L, "电视机", "夏普","小家电"));
productService.save(new Product(14L, "电视机", "微鲸","小家电"));
productService.save(new Product(15L, "电视机", "小米","小家电"));
productService.save(new Product(16L, "电视机", "长虹","小家电"));
productService.save(new Product(17L, "电视机", "海尔","小家电"));
productService.save(new Product(18L, "电视机", "乐视","小家电"));;
productService.save(new Product(19L, "冰箱","飞利浦", "小家电"));
productService.save(new Product(20L, "音箱", "飞利浦","小家电"));
productService.save(new Product(21L, "手电筒","飞利浦", "小家电"));
productService.save(new Product(22L, "节能灯", "飞利浦","小家电"));
productService.save(new Product(23L, "手机", "华为","数码电子"));
productService.save(new Product(24L, "手机", "vivo","数码电子"));
productService.save(new Product(25L, "手机", "oppo","数码电子"));
productService.save(new Product(26L, "手机", "魅族","数码电子"));
productService.save(new Product(27L, "手机", "小米","数码电子"));
productService.save(new Product(28L, "手机", "摩托罗拉","数码电子"));
productService.save(new Product(29L, "手机", "金立","数码电子"));
productService.save(new Product(30L, "手机", "酷派","数码电子"));
productService.save(new Product(31L, "手机", "格力","数码电子"));
productService.save(new Product(32L, "摄像机", "飞利浦","数码电子"));
productService.save(new Product(33L, "照相机","飞利浦", "数码电子"));
productService.save(new Product(34L, "mp3", "飞利浦","数码电子"));
productService.save(new Product(35L, "手环", "飞利浦","数码电子"));
productService.save(new Product(36L, "iwatch","飞利浦", "数码电子"));
productService.save(new Product(37L, "床", "飞利浦","家具"));
}
//删除ES中存储的全部商品数据
@Test
public void deleteAllData() {
productService.deleteAll();
}
//根据商品id删除ES中指定产品数据
@Test
public void testDeleteByProdId() {
productService.deleteByProductId(1L);
}
//查询全部商品数据
@Test
public void testFindAll() {
ArrayList<Product> products = new ArrayList<>();
Iterator<Product> pIterator = productService.findAll().iterator();
while (pIterator.hasNext()) {
products.add(pIterator.next());
}
System.out.println("全部数据总数为: " + products.size()+",最初插入总数为37");
}
//根据商品id查询
@Test
public void testFindByProdId() {
Product product = productService.findByProductId(1L);
try {
System.out.println("商品id查询数据: " + mapper.writeValueAsString(product));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//根据商品名称查询,并分页
@Test
public void testFindByProdName() {
//ES中共有8条电吹风数据
Page<Product> prodPage = productService.findByProductName("电吹风", new PageRequest(0, 10));
//无论分页数设置为5还是10,输出的“数据条目总数:”都是8.只不过设置为5时后面打印“prod page:”时只会输出5条数据
System.out.println("商品名查询数据条目总数:" + prodPage.getTotalElements());
try {
System.out.println("商品名查询数据: " + mapper.writeValueAsString(prodPage.getContent()));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//根据商品大类查询
@Test
public void testFindByProdCategory() {
List<Product> products = productService.findByProductCategory("数码电子");
try {
System.out.println("商品分类查询数据: " + mapper.writeValueAsString(products));
} catch (JsonProcessingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
测试
插入全部数据
首先需要向ES中插入数据,执行insertAllData方法。这个案例中插入了37条数据,这些数据是后续执行其它测试方法的基础。可以看到终端中输出以下内容:
[2018-01-11 15:42:12,559][INFO ][cluster.metadata ] [Korvus] [iengchen] creating index, cause [api], templates [], shards [5]/[1], mappings []
[2018-01-11 15:42:12,718][INFO ][cluster.metadata ] [Korvus] [iengchen] create_mapping [product]
[2018-01-11 15:42:12,773][INFO ][cluster.routing.allocation] [Korvus] Cluster health status changed from [RED] to [YELLOW] (reason: [shards started [[iengchen][4]] ...]).
此时浏览器访问http://127.0.0.1:9200/iengchen/_mapping 查看ES索引库中的数据:
{
"iengchen": {
"mappings": {
"product": {
"properties": {
"id": {
"type": "long"
},
"prodBrand": {
"type": "string"
},
"prodCategory": {
"type": "string"
},
"prodName": {
"type": "string"
}
}
}
}
}
}
可以看到索引库中为Product类的每个属性都建立了索引。
查询全部数据
查询刚才插入的全部数据,执行testFindAll方法,eclipse的console中输出:
全部数据总数为: 37,最初插入总数为37
根据商品名称查询数据
执行testFindByProdName,console输出为
商品名查询数据条目总数:8
商品名查询数据: [{"id":8,"prodName":"电吹风","prodBrand":"九阳","prodCategory":"小家电"},{"id":5,"prodName":"电吹风","prodBrand":"华为","prodCategory":"小家电"},{"id":2,"prodName":"电吹风","prodBrand":"美的","prodCategory":"小家电"},{"id":4,"prodName":"电吹风","prodBrand":"索尼","prodCategory":"小家电"},{"id":6,"prodName":"电吹风","prodBrand":"海尔","prodCategory":"小家电"},{"id":1,"prodName":"电吹风","prodBrand":"飞利浦","prodCategory":"小家电"},{"id":7,"prodName":"电吹风","prodBrand":"戴森","prodCategory":"小家电"},{"id":3,"prodName":"电吹风","prodBrand":"松下","prodCategory":"小家电"}]
可以看到查询的结果和最初插入的数据是吻合的。
根据商品分类查询数据
执行testFindByProdCategory,console输出为
商品分类查询数据: [{"id":24,"prodName":"手机","prodBrand":"vivo","prodCategory":"数码电子"},{"id":25,"prodName":"手机","prodBrand":"oppo","prodCategory":"数码电子"},{"id":26,"prodName":"手机","prodBrand":"魅族","prodCategory":"数码电子"},{"id":29,"prodName":"手机","prodBrand":"金立","prodCategory":"数码电子"},{"id":33,"prodName":"照相机","prodBrand":"飞利浦","prodCategory":"数码电子"},{"id":32,"prodName":"摄像机","prodBrand":"飞利浦","prodCategory":"数码电子"},{"id":34,"prodName":"mp3","prodBrand":"飞利浦","prodCategory":"数码电子"},{"id":30,"prodName":"手机","prodBrand":"酷派","prodCategory":"数码电子"},{"id":27,"prodName":"手机","prodBrand":"小米","prodCategory":"数码电子"},{"id":35,"prodName":"手环","prodBrand":"飞利浦","prodCategory":"数码电子"},{"id":36,"prodName":"iwatch","prodBrand":"飞利浦","prodCategory":"数码电子"},{"id":28,"prodName":"手机","prodBrand":"摩托罗拉","prodCategory":"数码电子"},{"id":23,"prodName":"手机","prodBrand":"华为","prodCategory":"数码电子"},{"id":31,"prodName":"手机","prodBrand":"格力","prodCategory":"数码电子"}]
可以看到“数码电子”这个分类下的产品数据都被查询出来。
根据商品id查询
执行testFindByProdId,console输出
商品id查询数据: {"id":1,"prodName":"电吹风","prodBrand":"飞利浦","prodCategory":"小家电"}
可以看到id为1的产品数据被查出来
根据商品id删除
执行testDeleteByProdId,console无输出,再执行上面的testFindByProdId,发现id为1的商品不存在了。再执行testFindAll方法,console输出
全部数据总数为: 36,最初插入总数为37
删除全部数据
执行deleteAllData,console无输出。再执行testFindAll方法,console输出:
全部数据总数为: 0,最初插入总数为37
此时浏览器再次访问http://127.0.0.1:9200/iengchen/_mapping 查看ES索引库中的数据:
{
"iengchen": {
"mappings": {
"product": {
"properties": {
"id": {
"type": "long"
},
"prodBrand": {
"type": "string"
},
"prodCategory": {
"type": "string"
},
"prodName": {
"type": "string"
}
}
}
}
}
}
可以看到全部的索引数据还在,可见删除数据并不会删除索引。
附件
案例代码
本案例中的代码已经托管到github,具体请移步springboot-springDataElasticsearch-1-demo
网友评论