如何使用适用于 Java 的 Azure 表客户端库

适用对象:

提示

本文中的内容适用于 Azure 表存储和 Azure Cosmos DB for Table。 API for Table 是表存储的高级服务,可提供吞吐量优化表、全局分发和自动辅助索引。

本文介绍如何创建表、存储数据以及对该数据执行 CRUD 操作。 示例采用 Java 编写,并使用了适用于 Java 的 Azure 表客户端库。 涉及的方案包括创建列出删除表,以及插入查询修改删除表中的实体。 有关表的详细信息,请参阅后续步骤部分。

重要

支持表存储和 Azure Cosmos DB 表的最后一个 Azure 表客户端库版本为 12+

创建 Azure 服务帐户

可以通过 Azure 表存储或 Azure Cosmos DB 使用表。 若要详细了解这两个服务中的表产品/服务之间的差异,请参阅 API for Table 概述。 需要为所要使用的服务创建一个帐户。 以下部分说明了如何创建 Azure 表存储和 Azure Cosmos DB 帐户,但你只需使用其中一个。

创建 Azure 存储帐户

创建 Azure 存储帐户的最简单方法是使用 Azure 门户。 若要了解更多信息,请参阅 创建存储帐户

也可以使用 Azure PowerShellAzure CLI 创建 Azure 存储帐户。

如果暂时不想创建存储帐户,也可以使用 Azure 存储模拟器在本地环境中运行和测试代码。 有关详细信息,请参阅使用 Azure 存储模拟器进行开发和测试

创建 Azure Cosmos DB 帐户

有关创建适用于表的 Azure Cosmos DB 帐户的说明,请参阅创建数据库帐户

创建 Java 应用程序

若要使用本文中的示例,请执行以下操作:

  1. 安装 Java 开发工具包 (JDK)
  2. 在 Azure 订阅中创建 Azure 存储帐户或 Azure Cosmos DB 帐户。
  3. 验证开发系统是否满足 GitHub 上适用于 Java 的 Azure 表客户端库存储库中列出的最低要求和依赖项。
  4. 按照说明从该存储库将用于 Java 的 Azure 存储库下载并安装到你的系统中。
  5. 创建使用本文中示例的 Java 应用。

配置应用以访问表存储

将以下项添加到 pom.xml 文件的 dependencies 部分:

<dependency>
  <groupId>com.azure</groupId>
  <artifactId>azure-data-tables</artifactId>
  <version>12.1.1</version>
</dependency>

然后,将下列 import 语句添加到需要在其中使用 Azure 表 API 来访问表的 Java 文件的顶部:

// Include the following imports to use table APIs
import com.azure.data.tables.TableClient;
import com.azure.data.tables.TableClientBuilder;
import com.azure.data.tables.TableServiceClient;
import com.azure.data.tables.TableServiceClientBuilder;
import com.azure.data.tables.models.ListEntitiesOptions;
import com.azure.data.tables.models.TableEntity;
import com.azure.data.tables.models.TableEntityUpdateMode;
import com.azure.data.tables.models.TableTransactionAction;
import com.azure.data.tables.models.TableTransactionActionType;

添加连接字符串

可以连接到 Azure 存储帐户,也可以连接到 Azure Cosmos DB for Table 帐户。 根据所使用的帐户类型获取连接字符串。

添加 Azure 存储连接字符串

Azure 表客户端可以使用存储连接字符串来存储用于访问数据管理服务的终结点和凭据。 在客户端应用中运行时,必须提供以下格式的存储连接字符串,并对 AccountName 和 AccountKey 值使用 Azure 门户中列出的存储帐户的名称和存储帐户的主访问密钥。

此示例演示如何声明一个静态字段以保存连接字符串:

// Define the connection-string with your values.
public final String connectionString =
    "DefaultEndpointsProtocol=http;" +
    "AccountName=your_storage_account;" +
    "AccountKey=your_storage_account_key;" +
    "EndpointSuffix=core.chinacloudapi.cn";

添加适用于表的 Azure Cosmos DB 连接字符串

Azure Cosmos DB 帐户使用连接字符串存储表终结点和凭据。 在客户端应用中运行时,必须提供以下格式的 Azure Cosmos DB 连接字符串,并使用 Azure 门户中列出的帐户的 Azure Cosmos DB 帐户名称和主访问密钥作为 AccountNameAccountKey 值。

此示例演示如何声明一个静态字段以保存 Azure Cosmos DB 连接字符串:

public final String connectionString =
    "DefaultEndpointsProtocol=https;" + 
    "AccountName=your_cosmosdb_account;" + 
    "AccountKey=your_account_key;" + 
    "TableEndpoint=https://your_endpoint;";

在 Azure 中的角色内运行的应用中,可以将此字符串存储在服务配置文件 ServiceConfiguration.cscfg 中。 可以通过调用 System.getenv 方法来访问它。 下面是从服务配置文件中名为 ConnectionString 的 Setting 元素中获取连接字符串的示例:

// Retrieve storage account from connection-string.
String connectionString = System.getenv("ConnectionString");

还可以将连接字符串存储在项目的 config.properties 文件中:

connectionString = DefaultEndpointsProtocol=https;AccountName=your_account;AccountKey=your_account_key;TableEndpoint=https://your_table_endpoint/

以下示例假定已使用上述其中一个方法获取存储连接字符串。

创建表

通过 TableServiceClient 对象,可以与表服务交互,以便创建、列出和删除表。 下面的代码可创建 TableServiceClient 对象,并使用它来创建新的 TableClient 对象,该对象表示名为 Employees 的表。

try
{
    final String tableName = "Employees";

    // Create a TableServiceClient with a connection string.
    TableServiceClient tableServiceClient = new TableServiceClientBuilder()
        .connectionString(connectionString)
        .buildClient();

    // Create the table if it not exists.
    TableClient tableClient = tableServiceClient.createTableIfNotExists(tableName);

}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

列出表

若要获取表的列表,请调用 TableServiceClient.listTables 方法来检索表名称的迭代列表。

try
{
    // Create a TableServiceClient with a connection string.
    TableServiceClient tableServiceClient = new TableServiceClientBuilder()
        .connectionString(connectionString)
        .buildClient();

    // Loop through a collection of table names.
    tableServiceClient.listTables().forEach(tableItem -> 
        System.out.printf(tableItem.getName())
    );
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

将实体添加到表

下面的代码创建一个 TableEntity 类的新实例,其中包含要进行存储的部分客户数据。 该代码会在 TableClient 对象上调用 upsertEntity 方法。 该方法将新客户实体插入到 Employees 表中,或者如果该实体已存在,则替换它。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
     TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Create a new employee TableEntity.
    String partitionKey = "Sales";
    String rowKey = "0001";
    Map<String, Object> personalInfo= new HashMap<>();
    personalInfo.put("FirstName", "Walter");
    personalInfo.put("LastName", "Harp");
    personalInfo.put("Email", "Walter@contoso.com");
    personalInfo.put("PhoneNumber", "425-555-0101");
    TableEntity employee = new TableEntity(partitionKey, rowKey).setProperties(personalInfo);
        
    // Upsert the entity into the table
    tableClient.upsertEntity(employee);
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

插入一批实体

可以通过一次写入操作将一批实体插入到表服务。 以下代码创建一个 List<TableTransactionAction> 对象,然后向其中添加三个更新插入操作。 通过创建新 TableEntity 对象、设置其属性,然后对 TableClient 对象调用 submitTransaction 方法添加每个操作。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    String partitionKey = "Sales";
    List<TableTransactionAction> tableTransactionActions = new ArrayList<>();
    
    Map<String, Object> personalInfo1 = new HashMap<>();
    personalInfo1.put("FirstName", "Jeff");
    personalInfo1.put("LastName", "Smith");
    personalInfo1.put("Email", "Jeff@contoso.com");
    personalInfo1.put("PhoneNumber", "425-555-0104");
    
    // Create an entity to add to the table.
    tableTransactionActions.add(new TableTransactionAction(
        TableTransactionActionType.UPSERT_MERGE,
        new TableEntity(partitionKey, "0001")
            .setProperties(personalInfo1)
    ));
    
    Map<String, Object> personalInfo2 = new HashMap<>();
    personalInfo2.put("FirstName", "Ben");
    personalInfo2.put("LastName", "Johnson");
    personalInfo2.put("Email", "Ben@contoso.com");
    personalInfo2.put("PhoneNumber", "425-555-0102");
    
    // Create another entity to add to the table.
    tableTransactionActions.add(new TableTransactionAction(
        TableTransactionActionType.UPSERT_MERGE,
        new TableEntity(partitionKey, "0002")
            .setProperties(personalInfo2)
    ));
    
    Map<String, Object> personalInfo3 = new HashMap<>();
    personalInfo3.put("FirstName", "Denise");
    personalInfo3.put("LastName", "Rivers");
    personalInfo3.put("Email", "Denise@contoso.com");
    personalInfo3.put("PhoneNumber", "425-555-0103");
    
    // Create a third entity to add to the table.
    tableTransactionActions.add(new TableTransactionAction(
        TableTransactionActionType.UPSERT_MERGE,
        new TableEntity(partitionKey, "0003")
            .setProperties(personalInfo3)
    ));

    // Submit transaction on the "Employees" table.
    tableClient.submitTransaction(tableTransactionActions);
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

批处理操作的注意事项如下:

  • 在单次批处理操作中最多可以执行 100 个插入、删除、合并、替换、插入或合并以及插入或替换操作(可以是这些操作的任意组合)。
  • 批处理操作也可以包含检索操作,但前提是检索操作是批处理中仅有的操作。
  • 单次批处理操作中的所有实体都必须具有相同的分区键。
  • 批处理操作的数据负载限制为 4MB。

检索分区中的所有实体

若要对表查询分区中的实体,可以使用 ListEntitiesOptions。 调用 ListEntitiesOptions.setFilter 可创建针对特定表的查询,该查询将返回指定的结果类型。 以下代码指定一个筛选器,以筛选 Sales 为分区键的实体。 通过对 TableClient 对象调用 listEntities 来执行查询时,将返回 TableEntityIterator。 然后,可以利用在“ForEach”循环中返回的 Iterator 来使用结果。 此代码会将查询结果中每个实体的字段打印到控制台。

try
{
    // Define constants for filters.
    final String PARTITION_KEY = "PartitionKey";
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Create a filter condition where the partition key is "Sales".
    ListEntitiesOptions options = new ListEntitiesOptions().setFilter(PARTITION_KEY + " eq 'Sales'");

    // Loop through the results, displaying information about the entities.
    tableClient.listEntities(options, null, null).forEach(tableEntity -> {
        System.out.println(tableEntity.getPartitionKey() +
            " " + tableEntity.getRowKey() +
            "\t" + tableEntity.getProperty("FirstName") +
            "\t" + tableEntity.getProperty("LastName") +
            "\t" + tableEntity.getProperty("Email") +
            "\t" + tableEntity.getProperty("PhoneNumber"));
    });
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

检索分区中的一部分实体

如果不想查询分区中的所有实体,请在筛选器中使用比较运算符来指定一个范围。 以下代码组合了两个筛选器,用于获取分区 Sales 中行键介于“0001”和“0004”之间的所有实体。 然后,该代码打印了查询结果。 如果使用添加到本指南的批量插入部分的表的实体,则此次只返回两个实体(Ben 和 Denise)。

try
{
    // Define constants for filters.
    final String PARTITION_KEY = "PartitionKey";
    final String ROW_KEY = "RowKey";
    final String tableName = "Employees";

    // Create a TableServiceClient with a connection string.
    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Create a filter condition where the partition key is "Sales".
    ListEntitiesOptions options = new ListEntitiesOptions().setFilter(PARTITION_KEY + " eq 'Sales' AND " + ROW_KEY + " lt '0004' AND " + ROW_KEY + " gt '0001'");
    
    // Loop through the results, displaying information about the entities.
    tableClient.listEntities(options, null, null).forEach(tableEntity -> {
        System.out.println(tableEntity.getPartitionKey() +
            " " + tableEntity.getRowKey() +
            "\t" + tableEntity.getProperty("FirstName") +
            "\t" + tableEntity.getProperty("LastName") +
            "\t" + tableEntity.getProperty("Email") +
            "\t" + tableEntity.getProperty("PhoneNumber"));
    });
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

检索单个实体

可以编写查询以检索单个特定实体。 以下代码使用分区键和行键参数调用 TableClient.getEntity 来检索员工“Jeff Smith”对应的实体,而不是创建 ListEntitiesOptions 并使用筛选器来执行同一操作。 执行的检索操作将只返回一个实体,而不会返回一个集合。 如果没有实体具有完全匹配的分区键和行键,则会返回 null 值。 在查询中指定分区键和行键是从表服务中检索单个实体的最快方法。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Get the specific entity.
    TableEntity specificEntity = tableClient.getEntity("Sales", "0001");

    // Output the entity.
    if (specificEntity != null)
    {
        System.out.println(specificEntity.getPartitionKey() +
            " " + specificEntity.getRowKey() +
            "\t" + specificEntity.getProperty("FirstName") +
            "\t" + specificEntity.getProperty("LastName") +
            "\t" + specificEntity.getProperty("Email") +
            "\t" + specificEntity.getProperty("PhoneNumber"));
    }
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

修改实体

要修改实体,请从表服务中检索它,对实体对象进行更改,然后通过替换或合并操作将更改保存回表服务。 以下代码将更改现有客户的电话号码。 此代码不会像我们一样调用 tableClient.upsertEntity 来进行插入,而是调用 tableClient.updateEntity

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Get the specific entity.
    TableEntity specificEntity = tableClient.getEntity("Sales", "0001");

    // Specify a new phone number
    specificEntity.getProperties().put("PhoneNumber", "425-555-0105");

    // Update the specific entity
    tableClient.updateEntity(specificEntity, TableEntityUpdateMode.REPLACE);
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

查询一部分实体属性

对表的查询可以只检索实体中的少数几个属性。 此方法称为“投影”,可减少带宽并提高查询性能,尤其适用于大型实体。 以下代码中的查询使用 ListEntitiesOptions.setSelect 方法,仅返回表中实体的电子邮件地址。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Create a filter condition that retrieves only the Email property.
    List<String> attributesToRetrieve = new ArrayList<>();
    attributesToRetrieve.add("Email");
    
    ListEntitiesOptions options = new ListEntitiesOptions().setSelect(attributesToRetrieve);

    // Loop through the results, displaying the Email values.
    tableClient.listEntities(options, null, null).forEach(tableEntity -> {
        System.out.println(tableEntity.getProperty("Email"));
    });
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

插入或替换实体

经常需要将某个实体添加到表中,但又不知道该实体是否已存在于表中。 插入或替换操作允许发出单个请求。 如果实体不存在,该请求将插入实体;如果实体存在,则替换现有实体。 以下代码基于前面的示例针对“Walter Harp”插入或替换实体。 创建新实体后,此代码调用 TableClient.upsertEntity 方法。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Create a new table entity.
    Map<String, Object> properties = new HashMap<>();
    properties.put("FirstName", "Walter");
    properties.put("LastName", "Harp");
    properties.put("Email", "Walter@contoso.com");
    properties.put("PhoneNumber", "425-555-0101");
        
    TableEntity newEmployee = new TableEntity("Sales", "0004")
        .setProperties(properties);
        
    // Add the new customer to the Employees table.
    tableClient.upsertEntity(newEmployee);
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

删除条目

可以通过 TableClient.deleteEntity 提供实体的分区键和行键来删除实体。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Delete the entity for partition key 'Sales' and row key '0001' from the table.
    tableClient.deleteEntity("Sales", "0001");
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

删除表

最后,以下代码将从帐户中删除一个表。 删除表后约 40 秒,无法重新创建该表。

try
{
    final String tableName = "Employees";

    // Create a TableClient with a connection string and a table name.
    TableClient tableClient = new TableClientBuilder()
        .connectionString(connectionString)
        .tableName(tableName)
        .buildClient();

    // Delete the table and all its data.
    tableClient.deleteTable();
}
catch (Exception e)
{
    // Output the stack trace.
    e.printStackTrace();
}

提示

查看 Azure 存储代码示例存储库

如需易用且能够下载和运行的端到端 Azure 存储代码示例,请查看我们的 Azure 存储示例列表。

后续步骤

有关详细信息,请访问面向 Java 开发人员的 Azure