クイックスタート: Java SDK と Azure Cosmos DB で Table 用 API アプリを構築する
適用対象: Table
このクイック スタートでは、Java アプリケーションから Azure Cosmos DB Tables API にアクセスする方法について説明します。 Azure Cosmos DB Tables API はスキーマレス データ ストアであり、これによりアプリケーションは構造化された NoSQL データをクラウドに格納できます。 データはスキーマレス デザインで格納されるので、新しい属性を持つオブジェクトがテーブルに追加されると、新しいプロパティ (列) がテーブルに自動的に追加されます。
Java アプリケーションは、azure-data-tables クライアント ライブラリを使用して Azure Cosmos DB Tables API にアクセスできます。
前提条件
サンプル アプリケーションは Spring Boot 2.6.4 で記述されています。Visual Studio Code、または IntelliJ IDEA を IDE として使用できます。
Azure サブスクリプションをお持ちでない場合は、開始する前に無料アカウントを作成してください。
サンプル アプリケーション
このチュートリアルのサンプル アプリケーションは、リポジトリ (https://github.com/Azure-Samples/msdocs-azure-data-tables-sdk-java) からクローンまたはダウンロードできます。 サンプル リポジトリには、スターターと完成したアプリの両方が含まれています。
git clone https://github.com/Azure-Samples/msdocs-azure-data-tables-sdk-java
サンプル アプリケーションでは、気象データを例に Tables API の機能を説明しています。 気象観測を表すオブジェクトの格納と取得には Table 用 API が使用されます。これには、Tables API のスキーマレス機能を示すために追加のプロパティを持つオブジェクトを格納する操作が含まれます。
1 - Azure Cosmos DB アカウントを作成する
まず、自分のアプリケーションで使用するテーブルが組み込まれる Azure Cosmos DB Tables API アカウントを作成します。 これを行うには、Azure portal、Azure CLI、または Azure PowerShell を使用します。
Azure portal にサインインし、これらの手順に従って Azure Cosmos DB アカウントを作成します。
2 - テーブルを作成する
次に、アプリケーションで使用するテーブルを Azure Cosmos DB アカウント内に作成する必要があります。 従来のデータベースとは異なり、指定する必要があるのはテーブルの名前のみであり、テーブルのプロパティ (列) は指定する必要はありません。 データがテーブルに読み込まれると、必要に応じてプロパティ (列) が自動的に作成されます。
Azure portal で次の手順を実行して、Azure Cosmos DB アカウント内にテーブルを作成します。
3 - Azure Cosmos DB 接続文字列を取得する
Azure Cosmos DB 内のテーブルにアクセスするには、アプリに CosmosDB Storage アカウントのテーブル接続文字列が必要です。 この接続文字列は、Azure portal、Azure CLI、または Azure PowerShell を使用して取得できます。
手順 | Screenshot |
---|---|
Azure Cosmos DB アカウント ページの左側にある [設定] ヘッダーの下で [接続文字列] という名前のメニュー項目を見つけて選択します。 ストレージ アカウントの接続文字列を取得できるページが表示されます。 | |
[プライマリ接続文字列] の値をコピーして、アプリケーションで使用します。 |
Azure Cosmos DB アカウントの接続文字列はアプリのシークレットと見なされ、他のアプリのシークレットやパスワードと同様に保護する必要があります。 この例では POM を使用して、開発中に接続文字列を保管し、アプリケーションで使用できるようにします。
<profiles>
<profile>
<id>local</id>
<properties>
<azure.tables.connection.string>
<![CDATA[YOUR-DATA-TABLES-SERVICE-CONNECTION-STRING]]>
</azure.tables.connection.string>
<azure.tables.tableName>WeatherData</azure.tables.tableName>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
</profiles>
4 - azure-data-tables パッケージを含める
Java アプリケーションから Azure Cosmos DB Tables API にアクセスするには、azure-data-tables パッケージを含めます。
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-data-tables</artifactId>
<version>12.2.1</version>
</dependency>
5 - TableServiceConfig.java に Table クライアントを構成する
Azure SDK は、クライアント オブジェクトを使用して Azure と通信し、Azure に対してさまざまな操作を実行します。 TableClient オブジェクトは、Azure Cosmos DB Tables API と通信するために使用されるオブジェクトです。
通常、アプリケーションではテーブルごとに 1 つの TableClient オブジェクトが作成され、アプリケーション全体で使用されます。 Spring コンテナーで管理し、これを実現するシングルトンとして TableClient オブジェクト Bean をメソッドで生成することを示すことをお勧めします。
アプリケーションの TableServiceConfig.java
ファイルで、次のコード スニペットと一致するように tableClientConfiguration()
メソッドを編集します。
@Configuration
public class TableServiceConfiguration {
private static String TABLE_NAME;
private static String CONNECTION_STRING;
@Value("${azure.tables.connection.string}")
public void setConnectionStringStatic(String connectionString) {
TableServiceConfiguration.CONNECTION_STRING = connectionString;
}
@Value("${azure.tables.tableName}")
public void setTableNameStatic(String tableName) {
TableServiceConfiguration.TABLE_NAME = tableName;
}
@Bean
public TableClient tableClientConfiguration() {
return new TableClientBuilder()
.connectionString(CONNECTION_STRING)
.tableName(TABLE_NAME)
.buildClient();
}
}
また、次の using ステートメントを TableServiceConfig.java
ファイルの先頭に追加する必要もあります。
import com.azure.data.tables.TableClient;
import com.azure.data.tables.TableClientBuilder;
6 - Azure Cosmos DB テーブル操作を実装する
サンプル アプリケーションの Azure Cosmos DB テーブル操作はすべて、Services ディレクトリにあるクラス TablesServiceImpl
に実装されます。 com.azure.data.tables
SDK パッケージをインポートする必要があります。
import com.azure.data.tables.TableClient;
import com.azure.data.tables.models.ListEntitiesOptions;
import com.azure.data.tables.models.TableEntity;
import com.azure.data.tables.models.TableTransactionAction;
import com.azure.data.tables.models.TableTransactionActionType;
TableServiceImpl
クラスの先頭に、TableClient オブジェクトのメンバー変数と、TableClient オブジェクトをクラスに挿入するためのコンストラクターを追加します。
@Autowired
private TableClient tableClient;
テーブルから行を取得する
TableClient クラスに含まれている listEntities メソッドにより、テーブルから行を選択できるようになります。 この例では、このメソッドにパラメーターが渡されないため、テーブルからすべての行が選択されます。
このメソッドは、返されるモデル クラス データを指定する TableEntity 型のジェネリック パラメーターも取ります。 このケースでは、組み込みの TableEntity クラスが使用されます。つまり、listEntities
メソッドは結果として PagedIterable<TableEntity>
コレクションを返します。
public List<WeatherDataModel> retrieveAllEntities() {
List<WeatherDataModel> modelList = tableClient.listEntities().stream()
.map(WeatherDataUtils::mapTableEntityToWeatherDataModel)
.collect(Collectors.toList());
return Collections.unmodifiableList(WeatherDataUtils.filledValue(modelList));
}
com.azure.data.tables.models
パッケージで定義されている TableEntity クラスには、テーブルのパーティション キーと行キーの値のプロパティが含まれています。 この 2 つの値により、テーブル内の行の一意のキーが形成されます。 このサンプル アプリケーションでは、観測所 (市町村) の名前がパーティション キーに格納され、観測日時が行キーに格納されます。 その他のプロパティ (温度、湿度、風速) はすべて、TableEntity
オブジェクトのディクショナリに格納されます。
一般的に、TableEntity オブジェクトは各自で定義するオブジェクトにマップします。 サンプル アプリケーションでは、この目的で Models ディレクトリに WeatherDataModel
クラスが定義されます。 このクラスには観測所名と観測日のプロパティが含まれており、これらのプロパティにパーティション キーと行キーがマップされます。これにより、これらの値についてよりわかりやすいプロパティ名が提供されます。 そして、オブジェクトのその他のプロパティをすべて格納するため、ディクショナリが使用されます。 これはテーブル ストレージを使用する場合の一般的なパターンです。行には任意の数のプロパティを追加でき、モデル オブジェクトでそれらをすべて取り込む必要があるためです。 このクラスには、クラスのプロパティをリストするメソッドも含まれています。
public class WeatherDataModel {
public WeatherDataModel(String stationName, String observationDate, OffsetDateTime timestamp, String etag) {
this.stationName = stationName;
this.observationDate = observationDate;
this.timestamp = timestamp;
this.etag = etag;
}
private String stationName;
private String observationDate;
private OffsetDateTime timestamp;
private String etag;
private Map<String, Object> propertyMap = new HashMap<String, Object>();
public String getStationName() {
return stationName;
}
public void setStationName(String stationName) {
this.stationName = stationName;
}
public String getObservationDate() {
return observationDate;
}
public void setObservationDate(String observationDate) {
this.observationDate = observationDate;
}
public OffsetDateTime getTimestamp() {
return timestamp;
}
public void setTimestamp(OffsetDateTime timestamp) {
this.timestamp = timestamp;
}
public String getEtag() {
return etag;
}
public void setEtag(String etag) {
this.etag = etag;
}
public Map<String, Object> getPropertyMap() {
return propertyMap;
}
public void setPropertyMap(Map<String, Object> propertyMap) {
this.propertyMap = propertyMap;
}
}
mapTableEntityToWeatherDataModel
メソッドは、TableEntity オブジェクトを WeatherDataModel
オブジェクトにマップするために使用されます。 mapTableEntityToWeatherDataModel
メソッドは、PartitionKey
、RowKey
、Timestamp
、および Etag
の各プロパティを直接マップし、properties.keySet
を使用して TableEntity
オブジェクト内の他のプロパティを反復処理し、これらのプロパティのうち、既に直接マップされているプロパティを除くすべてを WeatherDataModel
オブジェクトにマップします。
次のコード ブロックと一致するように mapTableEntityToWeatherDataModel
メソッドのコードを編集します。
public static WeatherDataModel mapTableEntityToWeatherDataModel(TableEntity entity) {
WeatherDataModel observation = new WeatherDataModel(
entity.getPartitionKey(), entity.getRowKey(),
entity.getTimestamp(), entity.getETag());
rearrangeEntityProperties(observation.getPropertyMap(), entity.getProperties());
return observation;
}
private static void rearrangeEntityProperties(Map<String, Object> target, Map<String, Object> source) {
Constants.DEFAULT_LIST_OF_KEYS.forEach(key -> {
if (source.containsKey(key)) {
target.put(key, source.get(key));
}
});
source.keySet().forEach(key -> {
if (Constants.DEFAULT_LIST_OF_KEYS.parallelStream().noneMatch(defaultKey -> defaultKey.equals(key))
&& Constants.EXCLUDE_TABLE_ENTITY_KEYS.parallelStream().noneMatch(defaultKey -> defaultKey.equals(key))) {
target.put(key, source.get(key));
}
});
}
テーブルから返された行をフィルター処理する
テーブルから返された行をフィルター処理するには、OData スタイルのフィルター文字列を listEntities メソッドに渡します。 たとえば、2021 年 7 月 1 日午前 0 時から 2021 年 7 月 2 日午前 0 時 (午前 0 時を含む) までの Chicago の気象記録をすべて取得するには、次のフィルター文字列を渡します。
PartitionKey eq 'Chicago' and RowKey ge '2021-07-01 12:00 AM' and RowKey le '2021-07-02 12:00 AM'
すべての OData フィルター演算子については、OData Web サイトの Filter System Query Option に関するセクションで確認できます。
このサンプル アプリケーションでは、FilterResultsInputModel
オブジェクトは、ユーザーが指定したフィルター条件をすべて取り込むように設計されています。
public class FilterResultsInputModel implements Serializable {
private String partitionKey;
private String rowKeyDateStart;
private String rowKeyTimeStart;
private String rowKeyDateEnd;
private String rowKeyTimeEnd;
private Double minTemperature;
private Double maxTemperature;
private Double minPrecipitation;
private Double maxPrecipitation;
public String getPartitionKey() {
return partitionKey;
}
public void setPartitionKey(String partitionKey) {
this.partitionKey = partitionKey;
}
public String getRowKeyDateStart() {
return rowKeyDateStart;
}
public void setRowKeyDateStart(String rowKeyDateStart) {
this.rowKeyDateStart = rowKeyDateStart;
}
public String getRowKeyTimeStart() {
return rowKeyTimeStart;
}
public void setRowKeyTimeStart(String rowKeyTimeStart) {
this.rowKeyTimeStart = rowKeyTimeStart;
}
public String getRowKeyDateEnd() {
return rowKeyDateEnd;
}
public void setRowKeyDateEnd(String rowKeyDateEnd) {
this.rowKeyDateEnd = rowKeyDateEnd;
}
public String getRowKeyTimeEnd() {
return rowKeyTimeEnd;
}
public void setRowKeyTimeEnd(String rowKeyTimeEnd) {
this.rowKeyTimeEnd = rowKeyTimeEnd;
}
public Double getMinTemperature() {
return minTemperature;
}
public void setMinTemperature(Double minTemperature) {
this.minTemperature = minTemperature;
}
public Double getMaxTemperature() {
return maxTemperature;
}
public void setMaxTemperature(Double maxTemperature) {
this.maxTemperature = maxTemperature;
}
public Double getMinPrecipitation() {
return minPrecipitation;
}
public void setMinPrecipitation(Double minPrecipitation) {
this.minPrecipitation = minPrecipitation;
}
public Double getMaxPrecipitation() {
return maxPrecipitation;
}
public void setMaxPrecipitation(Double maxPrecipitation) {
this.maxPrecipitation = maxPrecipitation;
}
}
このオブジェクトが TableServiceImpl
クラスの retrieveEntitiesByFilter
メソッドに渡されると、null 以外のプロパティ値ごとにフィルター文字列が作成されます。 次に、"and" 句を使用してすべての値を結合することで、結合フィルター文字列が作成されます。 この結合フィルター文字列は TableClient オブジェクトの listEntities メソッドに渡され、フィルター文字列に一致する行だけが返されます。 同様のメソッドをコード内で使用して、各自のアプリケーションで必要に応じて適切なフィルター文字列を作成できます。
public List<WeatherDataModel> retrieveEntitiesByFilter(FilterResultsInputModel model) {
List<String> filters = new ArrayList<>();
if (!StringUtils.isEmptyOrWhitespace(model.getPartitionKey())) {
filters.add(String.format("PartitionKey eq '%s'", model.getPartitionKey()));
}
if (!StringUtils.isEmptyOrWhitespace(model.getRowKeyDateStart())
&& !StringUtils.isEmptyOrWhitespace(model.getRowKeyTimeStart())) {
filters.add(String.format("RowKey ge '%s %s'", model.getRowKeyDateStart(), model.getRowKeyTimeStart()));
}
if (!StringUtils.isEmptyOrWhitespace(model.getRowKeyDateEnd())
&& !StringUtils.isEmptyOrWhitespace(model.getRowKeyTimeEnd())) {
filters.add(String.format("RowKey le '%s %s'", model.getRowKeyDateEnd(), model.getRowKeyTimeEnd()));
}
if (model.getMinTemperature() != null) {
filters.add(String.format("Temperature ge %f", model.getMinTemperature()));
}
if (model.getMaxTemperature() != null) {
filters.add(String.format("Temperature le %f", model.getMaxTemperature()));
}
if (model.getMinPrecipitation() != null) {
filters.add(String.format("Precipitation ge %f", model.getMinPrecipitation()));
}
if (model.getMaxPrecipitation() != null) {
filters.add(String.format("Precipitation le %f", model.getMaxPrecipitation()));
}
List<WeatherDataModel> modelList = tableClient.listEntities(new ListEntitiesOptions()
.setFilter(String.join(" and ", filters)), null, null).stream()
.map(WeatherDataUtils::mapTableEntityToWeatherDataModel)
.collect(Collectors.toList());
return Collections.unmodifiableList(WeatherDataUtils.filledValue(modelList));
}
TableEntity オブジェクトを使用してデータを挿入する
テーブルにデータを追加する最も簡単な方法は TableEntity オブジェクトを使用する方法です。 この例では、データが入力モデル オブジェクトから TableEntity オブジェクトにマップされます。 入力オブジェクトで観測所名と観測日時を表すプロパティは、PartitionKey
および RowKey
プロパティにそれぞれマップされ、これらによりテーブル内の行の一意のキーが形成されます。 その後、入力モデル オブジェクトの追加プロパティが TableClient オブジェクトのディクショナリ プロパティにマップされます。 最後に TableClient オブジェクトの createEntity メソッドを使用して、テーブルにデータが挿入されます。
サンプル アプリケーションの insertEntity
クラスを変更して、次のコードを含めます。
public void insertEntity(WeatherInputModel model) {
tableClient.createEntity(WeatherDataUtils.createTableEntity(model));
}
TableEntity オブジェクトを使用してデータをアップサートする
テーブルに既に存在するパーティション キーと行キーの組み合わせでそのテーブルに行を挿入しようとすると、エラーが発生します。 このため多くの場合、テーブルに行を追加するときには insertEntity
メソッドの代わりに upsertEntity を使用することが推奨されます。 指定されたパーティション キーと行キーの組み合わせがテーブルに既に存在する場合は、upsertEntity メソッドにより既存の行が更新されます。 それ以外の場合は行がテーブルに追加されます。
public void upsertEntity(WeatherInputModel model) {
tableClient.upsertEntity(WeatherDataUtils.createTableEntity(model));
}
可変プロパティを使用してデータを挿入またはアップサートする
Azure Cosmos DB Tables API を使用する利点の 1 つは、テーブルに読み込まれるオブジェクトに新しいプロパティが含まれている場合、それらのプロパティがテーブルに自動的に追加され、値が Azure Cosmos DB に格納されることです。 従来のデータベースのように、ALTER TABLE
などの DDL ステートメントを実行して列を追加する必要はありません。
このモデルを使用すると、時間の経過とともに取り込む必要があるデータを追加または変更する可能性のあるデータ ソースを扱う場合や、異なる入力によりアプリケーションに異なるデータを提供する場合に、アプリケーションに柔軟性が加わります。 サンプル アプリケーションでは、基本的な気象データの他にいくつかの追加の値も送信する観測所をシミュレートできます。 このような新しいプロパティを持つオブジェクトが初めてテーブルに格納されるときに、対応するプロパティ (列) がテーブルに自動的に追加されます。
サンプル アプリケーションでは、オブジェクトのあらゆるプロパティに対応できるように、内部ディクショナリをに基づいて ExpandableWeatherObject
クラスが作成されています。 このクラスは、オブジェクトに任意のプロパティ セットが含まれている必要がある場合の一般的なパターンを表します。
public class ExpandableWeatherObject {
private String stationName;
private String observationDate;
private Map<String, Object> propertyMap = new HashMap<String, Object>();
public String getStationName() {
return stationName;
}
public void setStationName(String stationName) {
this.stationName = stationName;
}
public String getObservationDate() {
return observationDate;
}
public void setObservationDate(String observationDate) {
this.observationDate = observationDate;
}
public Map<String, Object> getPropertyMap() {
return propertyMap;
}
public void setPropertyMap(Map<String, Object> propertyMap) {
this.propertyMap = propertyMap;
}
public boolean containsProperty(String key) {
return this.propertyMap.containsKey(key);
}
public Object getPropertyValue(String key) {
return containsProperty(key) ? this.propertyMap.get(key) : null;
}
public void putProperty(String key, Object value) {
this.propertyMap.put(key, value);
}
public List<String> getPropertyKeys() {
List<String> list = Collections.synchronizedList(new ArrayList<String>());
Iterator<String> iterators = this.propertyMap.keySet().iterator();
while (iterators.hasNext()) {
list.add(iterators.next());
}
return Collections.unmodifiableList(list);
}
public Integer getPropertyCount() {
return this.propertyMap.size();
}
}
Table 用 API を使用してこのようなオブジェクトを挿入またはアップサートするには、展開可能なオブジェクトのプロパティをTableEntity オブジェクトにマップし、必要に応じてTableClient オブジェクトの createEntity または upsertEntity メソッドを使用します。
public void insertExpandableEntity(ExpandableWeatherObject model) {
tableClient.createEntity(WeatherDataUtils.createTableEntity(model));
}
public void upsertExpandableEntity(ExpandableWeatherObject model) {
tableClient.upsertEntity(WeatherDataUtils.createTableEntity(model));
}
エンティティを更新する
エンティティを更新するには、TableClient オブジェクトの updateEntity メソッドを呼び出します。 Tables API を使用して格納されるエンティティ (行) には任意のプロパティ セットが含まれている可能性があるため、多くの場合、前述の ExpandableWeatherObject
と同様にディクショナリ オブジェクトに基づいて更新オブジェクトを作成すると便利です。 ここで唯一異なる点は、更新中の同時実行制御に使用される etag
プロパティを追加することです。
public class UpdateWeatherObject {
private String stationName;
private String observationDate;
private String etag;
private Map<String, Object> propertyMap = new HashMap<String, Object>();
public String getStationName() {
return stationName;
}
public void setStationName(String stationName) {
this.stationName = stationName;
}
public String getObservationDate() {
return observationDate;
}
public void setObservationDate(String observationDate) {
this.observationDate = observationDate;
}
public String getEtag() {
return etag;
}
public void setEtag(String etag) {
this.etag = etag;
}
public Map<String, Object> getPropertyMap() {
return propertyMap;
}
public void setPropertyMap(Map<String, Object> propertyMap) {
this.propertyMap = propertyMap;
}
}
サンプル アプリケーションでは、このオブジェクトは TableServiceImpl
クラスの updateEntity
メソッドに渡されます。 このメソッドは、最初に TableClient の getEntity メソッドを使用して Tables API から既存のエンティティを読み込みます。 その後、そのエンティティ オブジェクトを更新し、updateEntity
メソッドを使用して更新内容をデータベースに保存します。 updateEntity メソッドがオブジェクトの現在の Etag を取得して、オブジェクトが最初に読み込まれてから変更されていないことを確認する方法に注目してください。 それに関係なくエンティティを更新する必要がある場合は、etag
の値を updateEntity
メソッドに渡します。
public void updateEntity(UpdateWeatherObject model) {
TableEntity tableEntity = tableClient.getEntity(model.getStationName(), model.getObservationDate());
Map<String, Object> propertiesMap = model.getPropertyMap();
propertiesMap.keySet().forEach(key -> tableEntity.getProperties().put(key, propertiesMap.get(key)));
tableClient.updateEntity(tableEntity);
}
エンティティを削除する
テーブルからエンティティを削除するには、TableClient オブジェクトの deleteEntity メソッドを呼び出します。その際に、このオブジェクトのパーティション キーと行キーを指定します。
public void deleteEntity(WeatherInputModel model) {
tableClient.deleteEntity(model.getStationName(),
WeatherDataUtils.formatRowKey(model.getObservationDate(), model.getObservationTime()));
}
7 - コードを実行する
サンプル アプリケーションを実行して、Azure Cosmos DB Tables API を操作します。 アプリケーションを初めて実行する場合にはテーブルが空であるため、データがありません。 アプリケーションの上部にあるいずれかのボタンを使用して、テーブルにデータを追加します。
[Insert using Table Entity](テーブル エンティティを使用して挿入) ボタンを選択すると、TableEntity
オブジェクトを使用して新しい行を挿入またはアップサートできるダイアログが開きます。
[Insert using Expandable Data] (展開可能なデータを使用して挿入) ボタンを選択すると、カスタム プロパティを持つオブジェクトを挿入できるダイアログが表示され、Azure Cosmos DB Tables API によって必要に応じてプロパティ (列) がテーブルに自動的に追加される方法が示されます。 [カスタム フィールドの追加] ボタンを使用して、1 つ以上の新しいプロパティを追加し、この機能を示します。
[サンプル データを挿入] ボタンを使用して、一部のサンプル データを Azure Cosmos DB テーブルに読み込みます。
上部のメニューの [Filter Results](結果のフィルター処理) 項目を選択すると、[Filter Results](結果のフィルター処理) ページが表示されます。 フィルター句を作成して Azure Cosmos DB Tables API に渡す方法を示すため、このページでフィルター条件を入力します。
リソースをクリーンアップする
サンプル アプリケーションの使用が完了したら、この記事に関連するすべての Azure リソースを Azure アカウントから削除する必要があります。 このためには、リソース グループを削除します。
Azure portal を使用してリソース グループを削除するには、次の手順を実行します。
次のステップ
このクイック スタートでは、Azure Cosmos DB アカウントを作成し、データ エクスプローラーを使用してテーブルを作成し、アプリを実行する方法を説明しました。 これで、Table 用 API を使用してデータに対してクエリを実行できるようになりました。