01.Spring Data 简介

what:是什么

  • Spring Data是一个用于简化持久层数据访问的开源框架。
  • 主要目标是使得对数据的访问变得方便快捷
  • Spring Data可以极大的简化数据操作的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。
  • 包括CRUD外,还包括如分页、排序等一些常用的功能,几乎可以节省持久层代码80%以上的编码工作量。
  • Spring Data的官网:http://projects.spring.io/spring-data/

02.Spring Data ElasticSearch简介

what:是什么

  • Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API 进行封装 。
  • Spring Data为Elasticsearch项目提供集成搜索引擎。
  • Spring Data Elasticsearch POJO的关键功能区域为中心的模型与Elastichsearch交互文档和轻松地编写一个存储库数据访问层。
  • 官方网站:http://projects.spring.io/spring-data-elasticsearch/

why:为什么使用

  • 用来操作ElasticSearch的框架,使得开发更加便捷

03.环境搭建

实现步骤:

  • 创建SpringBoot的项目:版本为<version>2.1.9.RELEASE</version>
  • 勾选starter依赖坐标
  • 编写持久层接口GoodDao,编写pojo实体类
  • 配置文件,集群配置,ElasticSearch服务地址http://127.0.0.1:9300

实现过程:

  • 创建SpringBoot的项目!
  • 勾选starter依赖坐标,依赖如下
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.lxgzhw</groupId>
    <artifactId>springdata_es_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springdata_es_demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>

编写持久层接口GoodDao,编写pojo实体类

package com.lxgzhw.dao;

public interface GoodDao {
}

pojo实体类,商品good:这里的索引名称不能是已存在的

package com.lxgzhw.pojo;

import lombok.Data;
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;

/**
 * 商品实体类
 *
 * @Document() 注解作用:定义一个索引库,一个类型
 * indexName属性:指定索引库的名称
 * type属性:指定类型名称
 * shards属性:指定分片数
 * replicas属性:指定复制副本数
 */
@Data
@Document(indexName = "lxgzhw1", type = "goods", shards = 5, replicas = 1)
public class Good {
    private Long id;//商品的唯一标识
    private String title;//标题
    private String category;//分类
    private String brand;//品牌
    private Double price;//价格
    private String images;//图片地址
    //getter,setter,toString
}

配置文件:applacation.properties

  • 集群配置,ElasticSearch服务地址http://127.0.0.1:9301
  • 这里的集群名称和服务地址要与之前教程中配置的保持一致
# 配置集群名称
spring.data.elasticsearch.cluster-name=my-Elasticsearch
# 配置es的服务地址
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9301

04.创建和删除索引

几个用到的注解:

  • @Document:声明索引库配置
  • indexName:索引库名称
  • type:类型名称,默认是“docs”
  • shards:分片数量,默认5
  • replicas:副本数量,默认1
  • @Id:声明实体类的id
  • @Field:声明字段属性
  • type:字段的数据类型
  • analyzer:指定分词器类型
  • index:是否创建索引 默认为true
  • store:是否存储 默认为false

实体类配置:

package com.lxgzhw.pojo;

import lombok.Data;
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;

/**
 * 商品实体类
 *
 * @Document() 注解作用:定义一个索引库,一个类型
 * indexName属性:指定索引库的名称
 * type属性:指定类型名称
 * shards属性:指定分片数
 * replicas属性:指定复制副本数
 */
@Data
@Document(indexName = "lxgzhw2", type = "goods", shards = 5, replicas = 1)
public class Good {
    //必须有id,这里的id是全局唯一的标识,等同于es中的“_id”
    @Id
    private Long id;
    /**
     * type: 字段数据类型
     * analyzer: 分词器类型
     * index: 是否索引(默认值:true)
     * store: 是否存储(默认值:false)
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;//标题
    @Field(type = FieldType.Keyword)
    private String category;//分类
    @Field(type = FieldType.Keyword)
    private String brand;//品牌
    @Field(type = FieldType.Double)
    private Double price;//价格
    @Field(type = FieldType.Keyword, index = false)
    private String images;//图片地址
    //getter ,setter ,toString
}

测试类:Demo01IndexOperationTests.java

package com.lxgzhw;

import com.lxgzhw.pojo.Good;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * 创建索引
 * 配置映射
 * 删除索引
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo01IndexOperationTests {
    //模板设计模式
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Test
    public void createIndexAndPutMapping() {
        elasticsearchTemplate.createIndex(Good.class);//创建索引
        elasticsearchTemplate.putMapping(Good.class);//配置映射
    }

    //删除索引
    @Test
    public void deleteIndex() {
        elasticsearchTemplate.deleteIndex(Good.class);
    }

}

05.文档的常见增删改查

修改GoodDao接口:继承ElasticsearchRespository模板接口

package com.lxgzhw.dao;

import com.lxgzhw.pojo.Good;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface GoodDao extends ElasticsearchRepository<Good, Long> {
}

添加测试类:SpringdataEsGoodCRUDTests.java

package com.lxgzhw;

import com.lxgzhw.dao.GoodDao;
import com.lxgzhw.pojo.Good;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdataEsGoodCRUDTests {
    //注入Good业务层实现类
    @Autowired
    private GoodDao goodDao;

    /**
     * 新增
     */
    @Test
    public void save() {
        Good good = new Good();
        good.setId(1l);
        good.setTitle("小米手机");
        good.setCategory("手机");
        good.setBrand("小米");
        good.setPrice(19999.0);
        good.setImages("http://image.leyou.com/12479122.jpg");
        goodDao.save(good);
    }

    //修改
    @Test
    public void update() {
        Good good = new Good();
        good.setId(1l);
        good.setTitle("小米手机");
        good.setCategory("手机");
        good.setBrand("小米");
        good.setPrice(9999.0);
        good.setImages("http://image.leyou.com/12479122.jpg");
        goodDao.save(good);
    }

    //删除
    @Test
    public void delete() {
        Good good = new Good();
        good.setId(1l);
        goodDao.delete(good);
    }

    //根据id查询
    @Test
    public void findById() {
        Good good = goodDao.findById(1l).get();
        System.out.println(good);
    }

    //查询所有
    @Test
    public void findAll() {
        Iterable<Good> goods = goodDao.findAll();
        for (Good good : goods) {
            System.out.println(good);
        }
    }

    //批量新增
    @Test
    public void saveAll() {
        List<Good> goodList = new ArrayList<>();
        for (int i = 0; i < 500; i++) {
            Good good = new Good();
            good.setId((long) i);
            good.setTitle("[" + i + "]小米手机");
            good.setCategory("手机");
            good.setBrand("小米");
            good.setPrice(19999.0 + i);
            good.setImages("http://image.leyou.com/12479122.jpg");
            goodList.add(good);
        }
        goodDao.saveAll(goodList);
    }

    //分页查询
    @Test
    public void findByPageable() {
        //设置排序(排序方式,正序还是倒序,排序的id)
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        int currentPage = 2;//当前页
        int pageSize = 100;//每页显示多少条
        //设置查询分页
        PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sort);
        //分页查询
        System.out.println("分页数据:");
        Page<Good> goodPage = goodDao.findAll(pageRequest);
        for (Good good : goodPage.getContent()) {
            System.out.println(good);
        }
    }
}

07.Search查询

what:是什么

  • ElasticSearch的search方法中QueryBuilders,就是第一章中的查询对象构建对象QueryBuilders。
  • QueryBuilders具备的能力,search方法都具备。
  • 所以,在这里重复内容不赘述,仅举例term查询。

创建测试类:SpringdataEsSearchTests.java

package com.lxgzhw;

import com.lxgzhw.dao.GoodDao;
import com.lxgzhw.pojo.Good;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdataEsSearchTests {
    //注入Good业务层实现类
    @Autowired
    private GoodDao goodDao;

    /**
     * term查询
     * search(termQueryBuilder) 调用搜索方法,参数查询构建器对象
     */
    @Test
    public void termQuery() {
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
        Iterable<Good> goods = goodDao.search(termQueryBuilder);
        for (Good g : goods) {
            System.out.println(g);
        }
    }

    /**
     * term查询加分页
     */
    @Test
    public void termQueryByPage() {
        int currentPage = 0;
        int pageSize = 5;
        //设置查询分页
        PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
        Iterable<Good> goods = goodDao.search(termQueryBuilder, pageRequest);
        for (Good g : goods) {
            System.out.println(g);
        }
    }
}

08.自定义方法名称查询

what:是什么

  • GoodsRepository提供了非常强大的自定义查询功能;只要遵循SpringData提供的语法,我们可以任意定义方法声明
  • 查询语法:findBy+字段名+Keyword+字段名+…

Keyword

Sample

Elasticsearch Query String

And

findByNameAndPrice

{"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Or

findByNameOrPrice

{"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}

Is

findByName

{"bool" : {"must" : {"field" : {"name" : "?"}}}}

Not

findByNameNot

{"bool" : {"must_not" : {"field" : {"name" : "?"}}}}

Between

findByPriceBetween

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

Before

findByPriceBefore

{"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}

After

findByPriceAfter

{"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}

Like

findByNameLike

{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}

StartingWith

findByNameStartingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}

EndingWith

findByNameEndingWith

{"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}

In

findByNameIn(Collection<String>names)

{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}

NotIn

findByNameNotIn(Collection<String>names)

{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}

OrderBy

findByNameOrderByNameDesc

{"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"name" : "?"}}}

持久层接口:

package com.lxgzhw.dao;

import com.lxgzhw.pojo.Good;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

/**
 * ElasticsearchRepository 持久层操作ElasticSearch的模板接口
 */
public interface GoodDao extends ElasticsearchRepository<Good, Long> {
    /**
     * 根据title和价格查询,and的关系
     */
    List<Good> findAllByTitleAndPrice(String title, Double price);

    /**
     * 根据商品价格范围查询
     * 最低价格lowPrice
     * 最高价格highPrice
     */
    List<Good> findByPriceBetween(Double lowPrice, Double highPrice);
}

添加测试类:SpringdataEsCustomMethodQueryTests.java

package com.lxgzhw;

import com.lxgzhw.dao.GoodDao;
import com.lxgzhw.pojo.Good;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdataEsCustomMethodQueryTests {
    //注入Good业务层实现类
    @Autowired
    private GoodDao goodDao;

    /**
     * 根据标题及价格查询
     * 要求价格等于20023且标题的内容包含小米关键词
     */
    @Test
    public void findAllByTitleAndPrice() {
        String title = "小米";
        Double price = 20023.0;
        List<Good> goods = goodDao.findAllByTitleAndPrice(title, price);
        for (Good g : goods) {
            System.out.println(g);
        }
    }

    /**
     * 根据价格范围查询
     * 要求商品价格再3000,到20000之间
     */
    @Test
    public void findPriceBetween() {
        double lowPrice = 3000.0;//最低价
        double highPrice = 20000.0;//最高价
        List<Good> goods = goodDao.findByPriceBetween(lowPrice, highPrice);
        for (Good g : goods) {
            System.out.println(g);
        }
    }
}