你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

快速入门:在 Java 中创建 Azure 认知搜索索引

使用 Visual Studio CodeJava 11 SDKAzure SDK for Java 中的 Azure.Search.Documents 客户端库创建、加载和查询搜索索引的 Java 控制台应用程序。 本文提供了有关创建应用程序的分步说明。 此外,还可以下载并运行完整的应用程序

如果没有 Azure 订阅,请在开始之前创建一个免费帐户

先决条件

我们使用了以下软件和服务来构建和测试本快速入门:

获取密钥和 URL

对服务的调用要求每个请求都有一个 URL 终结点和一个访问密钥。 搜索服务是使用这二者创建的,因此,如果向订阅添加了 Azure 认知搜索,则请按以下步骤获取必需信息:

  1. 登录到 Azure 门户,在搜索服务的“概述”页中获取 URL。 示例终结点可能类似于 https://mydemo.search.windows.net

  2. 在“设置”>“密钥”中,获取有关该服务的完全权限的管理员密钥 。 有两个可交换的管理员密钥,为保证业务连续性而提供,以防需要滚动一个密钥。 可以在请求中使用主要或辅助密钥来添加、修改和删除对象。

    获取服务名称以及管理密钥和查询密钥

发送到服务的每个请求都需要一个 API 密钥。 具有有效的密钥可以在发送请求的应用程序与处理请求的服务之间建立信任关系,这种信任关系以每个请求为基础。

设置你的环境

首先打开 Visual Studio Code 并设置一个新项目。

创建项目

  1. 打开 Visual Studio Code。

  2. 安装适用于 Java 的扩展包

  3. 打开命令面板 (Ctrl+Shift+P)。 搜索“创建 Java 项目”。

    创建 Java 项目的屏幕截图。

  4. 选择“Maven”。

    创建 maven 项目的屏幕截图。

  5. 选择“maven-archetype-quickstart”。

    创建 maven 快速入门项目的屏幕截图。

  6. 选择最新版本(目前为 1.4)。

    输入组 ID 的屏幕截图。

  7. 输入 azure.search.sample 作为组 ID。

    输入组 ID 的屏幕截图。

  8. 输入 azuresearchquickstart 作为项目 ID。

    输入项目 ID 的屏幕截图。

  9. 选择要在其中创建项目的文件夹。

  10. 集成终端中完成项目创建。 按 Enter 接受“1.0-SNAPSHOT”默认值,然后键入“y”以确认项目的属性。

    在终端中完成设置的屏幕截图。

  11. 打开在其中创建了项目的文件夹。

指定 Maven 依赖项

  1. 打开 pom.xml 文件并添加以下依赖项

    <dependencies>
        <dependency>
          <groupId>com.azure</groupId>
          <artifactId>azure-search-documents</artifactId>
          <version>11.5.2</version>
        </dependency>
        <dependency>
          <groupId>com.azure</groupId>
          <artifactId>azure-core</artifactId>
          <version>1.34.0</version>
        </dependency>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.11</version>
          <scope>test</scope>
        </dependency>
      </dependencies>
    
  2. 将编译器 Java 版本更改为 11

    <maven.compiler.source>1.11</maven.compiler.source>
    <maven.compiler.target>1.11</maven.compiler.target>
    

创建搜索客户端

  1. 打开 src、main、java、azure、search、sample 下的 App 类。 添加以下 import 指令

    import java.util.Arrays;
    import java.util.ArrayList;
    import java.time.OffsetDateTime;
    import java.time.ZoneOffset;
    import java.time.LocalDateTime;
    import java.time.LocalDate;
    import java.time.LocalTime;
    
    import com.azure.core.credential.AzureKeyCredential;
    import com.azure.core.util.Context;
    import com.azure.search.documents.SearchClient;
    import com.azure.search.documents.SearchClientBuilder;
    import com.azure.search.documents.models.SearchOptions;
    import com.azure.search.documents.indexes.SearchIndexClient;
    import com.azure.search.documents.indexes.SearchIndexClientBuilder;
    import com.azure.search.documents.indexes.models.IndexDocumentsBatch;
    import com.azure.search.documents.indexes.models.SearchIndex;
    import com.azure.search.documents.indexes.models.SearchSuggester;
    import com.azure.search.documents.util.AutocompletePagedIterable;
    import com.azure.search.documents.util.SearchPagedIterable;
    
  2. 以下示例包含搜索服务名称、授予创建和删除权限的管理 API 密钥以及索引名称的占位符。 请将所有三个占位符替换为有效值。 创建两个客户端:SearchIndexClient 创建索引,SearchClient 加载并查询现有索引。 两者都需要服务终结点和管理员 API 密钥才能使用创建和删除权限进行身份验证。

    public static void main(String[] args) {
        var searchServiceEndpoint = "https://<your-service>.search.windows.net";
        var adminKey = new AzureKeyCredential("<your-admin-key>");
        String indexName = "<index-name>";
    
        SearchIndexClient searchIndexClient = new SearchIndexClientBuilder()
            .endpoint(searchServiceEndpoint)
            .credential(adminKey)
            .buildClient();
    
        SearchClient searchClient = new SearchClientBuilder()
            .endpoint(searchServiceEndpoint)
            .credential(adminKey)
            .indexName(indexName)
            .buildClient();
    }
    

1 - 创建索引

本快速入门生成 Hotels 索引,你将在其中加载酒店数据并对其执行查询。 在此步骤中,定义索引中的字段。 每个字段定义都包含名称、数据类型以及确定如何使用该字段的属性。

在此示例中,为了简单和可读性,使用了 azure-search-documents 库的同步方法。 但是,对于生产场景,应使用异步方法来保持应用程序的可缩放性和响应性。 例如,使用 SearchAsyncClient 而不是 SearchClient。

  1. 向项目添加一个空的类定义:Hotel.java

  2. 将以下代码复制到 Hotel.java 以定义酒店文档的结构。 该字段的属性决定字段在应用程序中的使用方式。 例如,IsFilterable 注释必须分配给每个支持筛选表达式的字段

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License.
    
    package azure.search.sample;
    
    import com.azure.search.documents.indexes.SearchableField;
    import com.azure.search.documents.indexes.SimpleField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    import java.time.OffsetDateTime;
    
    /**
     * Model class representing a hotel.
     */
    @JsonInclude(Include.NON_NULL)
    public class Hotel {
        /**
         * Hotel ID
         */
        @JsonProperty("HotelId")
        @SimpleField(isKey = true)
        public String hotelId;
    
        /**
         * Hotel name
         */
        @JsonProperty("HotelName")
        @SearchableField(isSortable = true)
        public String hotelName;
    
        /**
         * Description
         */
        @JsonProperty("Description")
        @SearchableField(analyzerName = "en.microsoft")
        public String description;
    
        /**
         * French description
         */
        @JsonProperty("DescriptionFr")
        @SearchableField(analyzerName = "fr.lucene")
        public String descriptionFr;
    
        /**
         * Category
         */
        @JsonProperty("Category")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String category;
    
        /**
         * Tags
         */
        @JsonProperty("Tags")
        @SearchableField(isFilterable = true, isFacetable = true)
        public String[] tags;
    
        /**
         * Whether parking is included
         */
        @JsonProperty("ParkingIncluded")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Boolean parkingIncluded;
    
        /**
         * Last renovation time
         */
        @JsonProperty("LastRenovationDate")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public OffsetDateTime lastRenovationDate;
    
        /**
         * Rating
         */
        @JsonProperty("Rating")
        @SimpleField(isFilterable = true, isSortable = true, isFacetable = true)
        public Double rating;
    
        /**
         * Address
         */
        @JsonProperty("Address")
        public Address address;
    
        @Override
        public String toString()
        {
            try
            {
                return new ObjectMapper().writeValueAsString(this);
            }
            catch (JsonProcessingException e)
            {
                e.printStackTrace();
                return "";
            }
        }
    }
    

在 Azure.Search.Documents 客户端库中,可以使用 SearchableFieldSimpleField 来简化字段定义。

  • SimpleField 可以是任何数据类型,始终不可搜索(全文搜索查询将忽略它),并且可检索(未隐藏)。 其他属性默认情况下处于关闭状态,但可以启用。 你可能会将 SimpleField 用于仅在筛选器、facet 或计分概要文件中使用的文档 ID 或字段。 如果是这样,请确保应用该方案所需的所有属性,例如为文档 ID 应用 IsKey = true。
  • SearchableField 必须是字符串,并且始终可搜索、可检索。 其他属性默认情况下处于关闭状态,但可以启用。 因为此字段类型是可搜索的,所以它支持同义词和分析器属性的完整补集。

无论使用基本 SearchField API 还是任一帮助程序模型,都必须显式启用筛选器、facet 和排序属性。 例如,isFilterableisSortableisFacetable 必须进行显式属性化,如上例中所示。

  1. 向项目添加第二个空的类定义:Address.cs。 将以下代码复制到类中。

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License.
    
    package azure.search.sample;
    
    import com.azure.search.documents.indexes.SearchableField;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.annotation.JsonInclude.Include;
    
    /**
     * Model class representing an address.
     */
    @JsonInclude(Include.NON_NULL)
    public class Address {
        /**
         * Street address
         */
        @JsonProperty("StreetAddress")
        @SearchableField
        public String streetAddress;
    
        /**
         * City
         */
        @JsonProperty("City")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String city;
    
        /**
         * State or province
         */
        @JsonProperty("StateProvince")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String stateProvince;
    
        /**
         * Postal code
         */
        @JsonProperty("PostalCode")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String postalCode;
    
        /**
         * Country
         */
        @JsonProperty("Country")
        @SearchableField(isFilterable = true, isSortable = true, isFacetable = true)
        public String country;
    }
    
  2. 在 App.java 中,在 main 方法中创建一个 SearchIndex 对象,然后调用 createOrUpdateIndex 方法以在搜索服务中创建索引。 此索引还包括一个 SearchSuggester 以便在指定字段上启用自动完成。

    // Create Search Index for Hotel model
    searchIndexClient.createOrUpdateIndex(
        new SearchIndex(indexName, SearchIndexClient.buildSearchFields(Hotel.class, null))
        .setSuggesters(new SearchSuggester("sg", Arrays.asList("HotelName"))));
    

2 - 加载文档

Azure 认知搜索对存储在服务中的内容进行搜索。 在此步骤中,加载符合刚刚创建的酒店索引的 JSON 文档。

在 Azure 认知搜索中,搜索文档这一数据结构既是索引输入,也是查询输出。 文档输入从外部数据源获取,可能是数据库中的行、Blob 存储中的 blob 或磁盘上的 JSON 文档。 在此示例中,我们采用了快捷方式,并在代码本身中嵌入了四个酒店的 JSON 文档。

上传文档时,必须使用 IndexDocumentsBatch 对象。 IndexDocumentsBatch 对象包含 IndexActions 集合,其中每个操作均包含一个文档和一个属性,该属性用于指示 Azure 认知搜索要执行什么操作(upload、merge、delete 和 mergeOrUpload)。

  1. 在 App.java 中创建文档和索引操作,然后将其传递给 IndexDocumentsBatch。 以下文档符合 hotel 类定义的 hotels-quickstart 索引。

    // Upload documents in a single Upload request.
    private static void uploadDocuments(SearchClient searchClient)
    {
        var hotelList = new ArrayList<Hotel>();
    
        var hotel = new Hotel();
        hotel.hotelId = "1";
        hotel.hotelName = "Secret Point Motel";
        hotel.description = "The hotel is ideally located on the main commercial artery of the city in the heart of New York. A few minutes away is Time's Square and the historic centre of the city, as well as other places of interest that make New York one of America's most attractive and cosmopolitan cities.";
        hotel.descriptionFr = "L'hôtel est idéalement situé sur la principale artère commerciale de la ville en plein cœur de New York. A quelques minutes se trouve la place du temps et le centre historique de la ville, ainsi que d'autres lieux d'intérêt qui font de New York l'une des villes les plus attractives et cosmopolites de l'Amérique.";
        hotel.category = "Boutique";
        hotel.tags = new String[] { "pool", "air conditioning", "concierge" };
        hotel.parkingIncluded = false;
        hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1970, 1, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
        hotel.rating = 3.6;
        hotel.address = new Address();
        hotel.address.streetAddress = "677 5th Ave";
        hotel.address.city = "New York";
        hotel.address.stateProvince = "NY";
        hotel.address.postalCode = "10022";
        hotel.address.country = "USA";
        hotelList.add(hotel);
    
        hotel = new Hotel();
        hotel.hotelId = "2";
        hotel.hotelName = "Twin Dome Motel";
        hotel.description = "The hotel is situated in a  nineteenth century plaza, which has been expanded and renovated to the highest architectural standards to create a modern, functional and first-class hotel in which art and unique historical elements coexist with the most modern comforts.";
        hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.";
        hotel.category = "Boutique";
        hotel.tags = new String[] { "pool", "free wifi", "concierge" };
        hotel.parkingIncluded = false;
        hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1979, 2, 18), LocalTime.of(0, 0)), ZoneOffset.UTC);
        hotel.rating = 3.60;
        hotel.address = new Address();
        hotel.address.streetAddress = "140 University Town Center Dr";
        hotel.address.city = "Sarasota";
        hotel.address.stateProvince = "FL";
        hotel.address.postalCode = "34243";
        hotel.address.country = "USA";
        hotelList.add(hotel);
    
        hotel = new Hotel();
        hotel.hotelId = "3";
        hotel.hotelName = "Triple Landscape Hotel";
        hotel.description = "The Hotel stands out for its gastronomic excellence under the management of William Dough, who advises on and oversees all of the Hotel’s restaurant services.";
        hotel.descriptionFr = "L'hôtel est situé dans une place du XIXe siècle, qui a été agrandie et rénovée aux plus hautes normes architecturales pour créer un hôtel moderne, fonctionnel et de première classe dans lequel l'art et les éléments historiques uniques coexistent avec le confort le plus moderne.";
        hotel.category = "Resort and Spa";
        hotel.tags = new String[] { "air conditioning", "bar", "continental breakfast" };
        hotel.parkingIncluded = true;
        hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(2015, 9, 20), LocalTime.of(0, 0)), ZoneOffset.UTC);
        hotel.rating = 4.80;
        hotel.address = new Address();
        hotel.address.streetAddress = "3393 Peachtree Rd";
        hotel.address.city = "Atlanta";
        hotel.address.stateProvince = "GA";
        hotel.address.postalCode = "30326";
        hotel.address.country = "USA";
        hotelList.add(hotel);
    
        hotel = new Hotel();
        hotel.hotelId = "4";
        hotel.hotelName = "Sublime Cliff Hotel";
        hotel.description = "Sublime Cliff Hotel is located in the heart of the historic center of Sublime in an extremely vibrant and lively area within short walking distance to the sites and landmarks of the city and is surrounded by the extraordinary beauty of churches, buildings, shops and monuments. Sublime Cliff is part of a lovingly restored 1800 palace.";
        hotel.descriptionFr = "Le sublime Cliff Hotel est situé au coeur du centre historique de sublime dans un quartier extrêmement animé et vivant, à courte distance de marche des sites et monuments de la ville et est entouré par l'extraordinaire beauté des églises, des bâtiments, des commerces et Monuments. Sublime Cliff fait partie d'un Palace 1800 restauré avec amour.";
        hotel.category = "Boutique";
        hotel.tags = new String[] { "concierge", "view", "24-hour front desk service" };
        hotel.parkingIncluded = true;
        hotel.lastRenovationDate = OffsetDateTime.of(LocalDateTime.of(LocalDate.of(1960, 2, 06), LocalTime.of(0, 0)), ZoneOffset.UTC);
        hotel.rating = 4.60;
        hotel.address = new Address();
        hotel.address.streetAddress = "7400 San Pedro Ave";
        hotel.address.city = "San Antonio";
        hotel.address.stateProvince = "TX";
        hotel.address.postalCode = "78216";
        hotel.address.country = "USA";
        hotelList.add(hotel);
    
        var batch = new IndexDocumentsBatch<Hotel>();
        batch.addMergeOrUploadActions(hotelList);
        try
        {
            searchClient.indexDocuments(batch);
        }
        catch (Exception e)
        {
            e.printStackTrace();
            // If for some reason any documents are dropped during indexing, you can compensate by delaying and
            // retrying. This simple demo just logs failure and continues
            System.err.println("Failed to index some of the documents");
        }
    }
    

初始化 IndexDocumentsBatch 对象后,可通过对 SearchClient 对象调用 indexDocuments,将其发送到索引。

  1. 将以下行添加到 Main()。 加载文档是使用 SearchClient 完成的。

    // Upload sample hotel documents to the Search Index
    uploadDocuments(searchClient);
    
  2. 由于这是一个按顺序运行所有命令的控制台应用,因此请在索引和查询之间添加 2 秒的等待时间。

    // Wait 2 seconds for indexing to complete before starting queries (for demo and console-app purposes only)
    System.out.println("Waiting for indexing...\n");
    try
    {
        Thread.sleep(2000);
    }
    catch (InterruptedException e)
    {
    }
    

2 秒的延迟可对索引编制进行补偿(这是异步操作),这样可在执行查询之前对所有文档编制索引。 以延迟方式编写代码通常仅在演示、测试和示例应用程序中是必要的。

3 - 搜索索引

对第一个文档编制索引后,可立即获取查询结果,但索引的实际测试应等到对所有文档编制索引后进行。

此部分添加了两个功能:查询逻辑和结果。 对于查询,请使用 Search 方法。 此方法接受搜索文本(查询字符串)以及其他选项。

  1. 在 App.java 中创建 WriteDocuments 方法,用于将搜索结果输出到控制台。

    // Write search results to console
    private static void WriteSearchResults(SearchPagedIterable searchResults)
    {
        searchResults.iterator().forEachRemaining(result ->
        {
            Hotel hotel = result.getDocument(Hotel.class);
            System.out.println(hotel);
        });
    
        System.out.println();
    }
    
    // Write autocomplete results to console
    private static void WriteAutocompleteResults(AutocompletePagedIterable autocompleteResults)
    {
        autocompleteResults.iterator().forEachRemaining(result ->
        {
            String text = result.getText();
            System.out.println(text);
        });
    
        System.out.println();
    }
    
  2. 创建 RunQueries 方法用于执行查询并返回结果。 结果是 Hotel 对象。 此示例显示了方法签名和第一个查询。 此查询演示了 Select 参数,通过该参数可以使用文档中的选定字段来编写结果。

    // Run queries, use WriteDocuments to print output
    private static void RunQueries(SearchClient searchClient)
    {
        // Query 1
        System.out.println("Query #1: Search on empty term '*' to return all documents, showing a subset of fields...\n");
    
        SearchOptions options = new SearchOptions();
        options.setIncludeTotalCount(true);
        options.setFilter("");
        options.setOrderBy("");
        options.setSelect("HotelId", "HotelName", "Address/City");
    
        WriteSearchResults(searchClient.search("*", options, Context.NONE));
    }
    
  3. 在第二个查询中,搜索某个术语,添加筛选器(用于选择评级大于 4 的文档),然后按评级降序排序。 筛选器是布尔表达式,该表达式通过索引中的 isFilterable 字段求值。 筛选器查询包括或排除值。 同样,筛选器查询没有关联的相关性分数。

    // Query 2
    System.out.println("Query #2: Search on 'hotels', filter on 'Rating gt 4', sort by Rating in descending order...\n");
    
    options = new SearchOptions();
    options.setFilter("Rating gt 4");
    options.setOrderBy("Rating desc");
    options.setSelect("HotelId", "HotelName", "Rating");
    
    WriteSearchResults(searchClient.search("hotels", options, Context.NONE));
    
  4. 第三个查询演示了用于将全文搜索操作的范围限定为特定字段的 searchFields

    // Query 3
    System.out.println("Query #3: Limit search to specific fields (pool in Tags field)...\n");
    
    options = new SearchOptions();
    options.setSearchFields("Tags");
    
    options.setSelect("HotelId", "HotelName", "Tags");
    
    WriteSearchResults(searchClient.search("pool", options, Context.NONE));
    
  5. 第四个查询演示了 Facet,可用于构建分面导航结构。

    // Query 4
    System.out.println("Query #4: Facet on 'Category'...\n");
    
    options = new SearchOptions();
    options.setFilter("");
    options.setFacets("Category");
    options.setSelect("HotelId", "HotelName", "Category");
    
    WriteSearchResults(searchClient.search("*", options, Context.NONE));
    
  6. 在第五个查询中,返回一个特定文档。

    // Query 5
    System.out.println("Query #5: Look up a specific document...\n");
    
    Hotel lookupResponse = searchClient.getDocument("3", Hotel.class);
    System.out.println(lookupResponse.hotelId);
    System.out.println();
    
  7. 最后一个查询显示了“自动完成”的语法,它模拟部分用户输入,即“s”,该输入解析为 sourceFields 中的两个可能的匹配项,与你在索引中定义的建议器相关联。

    // Query 6
    System.out.println("Query #6: Call Autocomplete on HotelName that starts with 's'...\n");
    
    WriteAutocompleteResults(searchClient.autocomplete("s", "sg"));
    
  8. 将 RunQueries 添加到 Main()。

    // Call the RunQueries method to invoke a series of queries
    System.out.println("Starting queries...\n");
    RunQueries(searchClient);
    
    // End the program
    System.out.println("Complete.\n");
    

前面的查询显示了在查询中匹配术语的多种方式:全文搜索、筛选器和自动完成。

全文搜索和筛选器是使用 SearchClient.search 方法执行的。 搜索查询可在 searchText 字符串中传递,而筛选表达式则可在 SearchOptions 类的 filter 属性中传递。 若要筛选但不搜索,只需传递“*”作为 search 方法的 searchText 参数。 若要在不筛选的情况下进行搜索,请保持 filter 属性未设置,或者根本不传入 SearchOptions 实例。

运行程序

按 F5 可重新生成应用并完整运行该程序。

输出包含 System.out.println 中的消息,并添加了查询信息和结果。

清理资源

在自己的订阅中操作时,最好在项目结束时确定是否仍需要已创建的资源。 持续运行资源可能会产生费用。 可以逐个删除资源,也可以删除资源组以删除整个资源集。

可以使用左侧导航窗格中的“所有资源”或“资源组”链接 ,在门户中查找和管理资源。

如果使用的是免费服务,请记住只能设置三个索引、索引器和数据源。 可以在门户中删除单个项目,以不超出此限制。

后续步骤

在本 Java 快速入门中,你已完成一系列任务来创建索引、使用文档加载索引,以及运行查询。 在不同的阶段,我们采用快捷方式来简化代码,从而实现可读性和可理解性。 现在你已熟悉了基本概念,请尝试下一教程,在 Web 应用的上下文中调用认知搜索 API。