查询某类商品
Query for products in a certain category
我在 DynamoDb 中有一个简单的“products”table。每个产品都有一个 categories
属性,它是一组类别 ID,如下所示:
[{ "N" : "4" },{ "N" : "5" },{ "N" : "6" },{ "N" : "8" }]
产品 table 具有 id
(哈希键)和 accountId
(范围键)
是否可以在不进行扫描的情况下进行查询以查找类别 6 和 accountId 1 中的所有产品?或者我可以用其他方式建模吗?
如果它是一个关系数据库,我会有一个产品到类别 table,并加入产品。如果我在 Dynamo 中有一个类似的 table,那么我需要为产品 table 中的每个产品制作一个 GetItem,这感觉像是个坏主意?
制作另一个 table,并在更新主 table 时更新它。真的,无论如何,这就是 RDBMS 中发生的事情,只是它在后台。当亚马逊为 table 设置二级索引时,他们基本上只是将人们一直在做的事情自动化。
根据您的描述,听起来最好的方法是使用 GSI。
您的 table 结构如下:
- 哈希键:
id
- 范围键:
accountId
- 属性 -
categories
您将创建具有以下结构的全局二级索引:
- 哈希键:
accountId
- 范围键:
id
- 属性-
categories
然后您将能够使用您提到的条件查询该索引:
accountId = 1
categories contains 6
这是我针对本地 DynamoDB 编写的一个简单示例,它投影了索引上的所有属性。
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Index;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.QueryFilter;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProjectionType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.util.Tables;
public class Whosebug {
private static final String EXAMPLE_TABLE_NAME = "example_table";
private static final String HASH_KEY = "id";
private static final String RANGE_KEY = "accountId";
private static final String GSI = "accountIdToId";
private static final String CATEGORIES = "categories";
public static void main(String[] args) throws InterruptedException {
AmazonDynamoDB
client =
new AmazonDynamoDBClient(new BasicAWSCredentials("accessKey", "secretKey"));
client.setEndpoint("http://localhost:4000");
DynamoDB dynamoDB = new DynamoDB(client);
if (Tables.doesTableExist(client, EXAMPLE_TABLE_NAME)) {
client.deleteTable(EXAMPLE_TABLE_NAME);
}
CreateTableRequest createTableRequest = new CreateTableRequest();
createTableRequest.withTableName(EXAMPLE_TABLE_NAME);
createTableRequest.withKeySchema(new KeySchemaElement(HASH_KEY, KeyType.HASH),
new KeySchemaElement(RANGE_KEY, KeyType.RANGE));
createTableRequest.withAttributeDefinitions(
new AttributeDefinition(HASH_KEY, ScalarAttributeType.S),
new AttributeDefinition(RANGE_KEY, ScalarAttributeType.S));
createTableRequest.withProvisionedThroughput(new ProvisionedThroughput(15l, 15l));
// GSI definition
final GlobalSecondaryIndex
accountIdToId =
new GlobalSecondaryIndex().withIndexName(GSI).withKeySchema(
new KeySchemaElement(RANGE_KEY, KeyType.HASH),
new KeySchemaElement(HASH_KEY, KeyType.RANGE)).withProvisionedThroughput(
new ProvisionedThroughput(10l, 10l)).withProjection(
new Projection().withProjectionType(ProjectionType.ALL));
createTableRequest.withGlobalSecondaryIndexes(accountIdToId);
final Table table = dynamoDB.createTable(createTableRequest);
table.waitForActive();
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "1", RANGE_KEY, "6")
.withNumberSet(CATEGORIES, 1, 2, 5, 6));
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "2", RANGE_KEY, "6")
.withNumberSet(CATEGORIES, 5, 6));
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "5", RANGE_KEY, "6")
.withNumberSet(CATEGORIES, 1, 2));
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "5", RANGE_KEY, "8")
.withNumberSet(CATEGORIES, 1, 2, 6));
System.out.println("Scan the table, no filters");
table.scan().forEach(System.out::println);
System.out.println();
final Index gsi = table.getIndex(GSI);
System.out.println("Scan the GSI without filter");
gsi.scan().forEach(System.out::println);
System.out.println();
System.out.println("Query the GSI with range key condition and contains");
final QuerySpec querySpec = new QuerySpec()
.withHashKey(RANGE_KEY, "6")
.withQueryFilters(new QueryFilter(CATEGORIES).contains(6));
gsi.query(querySpec).forEach(System.out::println);
System.out.println();
}
}
输出:
Scan the table, no filters
{ Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
{ Item: {accountId=6, id=5, categories=[1, 2]} }
{ Item: {accountId=8, id=5, categories=[1, 2, 6]} }
{ Item: {accountId=6, id=2, categories=[5, 6]} }
Scan the GSI without filter
{ Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
{ Item: {accountId=6, id=5, categories=[1, 2]} }
{ Item: {accountId=8, id=5, categories=[1, 2, 6]} }
{ Item: {accountId=6, id=2, categories=[5, 6]} }
Query the GSI with range key condition and contains
{ Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
{ Item: {accountId=6, id=2, categories=[5, 6]} }
我在 DynamoDb 中有一个简单的“products”table。每个产品都有一个 categories
属性,它是一组类别 ID,如下所示:
[{ "N" : "4" },{ "N" : "5" },{ "N" : "6" },{ "N" : "8" }]
产品 table 具有 id
(哈希键)和 accountId
(范围键)
是否可以在不进行扫描的情况下进行查询以查找类别 6 和 accountId 1 中的所有产品?或者我可以用其他方式建模吗?
如果它是一个关系数据库,我会有一个产品到类别 table,并加入产品。如果我在 Dynamo 中有一个类似的 table,那么我需要为产品 table 中的每个产品制作一个 GetItem,这感觉像是个坏主意?
制作另一个 table,并在更新主 table 时更新它。真的,无论如何,这就是 RDBMS 中发生的事情,只是它在后台。当亚马逊为 table 设置二级索引时,他们基本上只是将人们一直在做的事情自动化。
根据您的描述,听起来最好的方法是使用 GSI。
您的 table 结构如下:
- 哈希键:
id
- 范围键:
accountId
- 属性 -
categories
您将创建具有以下结构的全局二级索引:
- 哈希键:
accountId
- 范围键:
id
- 属性-
categories
然后您将能够使用您提到的条件查询该索引:
accountId = 1
categories contains 6
这是我针对本地 DynamoDB 编写的一个简单示例,它投影了索引上的所有属性。
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodbv2.document.DynamoDB;
import com.amazonaws.services.dynamodbv2.document.Index;
import com.amazonaws.services.dynamodbv2.document.Item;
import com.amazonaws.services.dynamodbv2.document.QueryFilter;
import com.amazonaws.services.dynamodbv2.document.Table;
import com.amazonaws.services.dynamodbv2.document.spec.QuerySpec;
import com.amazonaws.services.dynamodbv2.model.AttributeDefinition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.GlobalSecondaryIndex;
import com.amazonaws.services.dynamodbv2.model.KeySchemaElement;
import com.amazonaws.services.dynamodbv2.model.KeyType;
import com.amazonaws.services.dynamodbv2.model.Projection;
import com.amazonaws.services.dynamodbv2.model.ProjectionType;
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.util.Tables;
public class Whosebug {
private static final String EXAMPLE_TABLE_NAME = "example_table";
private static final String HASH_KEY = "id";
private static final String RANGE_KEY = "accountId";
private static final String GSI = "accountIdToId";
private static final String CATEGORIES = "categories";
public static void main(String[] args) throws InterruptedException {
AmazonDynamoDB
client =
new AmazonDynamoDBClient(new BasicAWSCredentials("accessKey", "secretKey"));
client.setEndpoint("http://localhost:4000");
DynamoDB dynamoDB = new DynamoDB(client);
if (Tables.doesTableExist(client, EXAMPLE_TABLE_NAME)) {
client.deleteTable(EXAMPLE_TABLE_NAME);
}
CreateTableRequest createTableRequest = new CreateTableRequest();
createTableRequest.withTableName(EXAMPLE_TABLE_NAME);
createTableRequest.withKeySchema(new KeySchemaElement(HASH_KEY, KeyType.HASH),
new KeySchemaElement(RANGE_KEY, KeyType.RANGE));
createTableRequest.withAttributeDefinitions(
new AttributeDefinition(HASH_KEY, ScalarAttributeType.S),
new AttributeDefinition(RANGE_KEY, ScalarAttributeType.S));
createTableRequest.withProvisionedThroughput(new ProvisionedThroughput(15l, 15l));
// GSI definition
final GlobalSecondaryIndex
accountIdToId =
new GlobalSecondaryIndex().withIndexName(GSI).withKeySchema(
new KeySchemaElement(RANGE_KEY, KeyType.HASH),
new KeySchemaElement(HASH_KEY, KeyType.RANGE)).withProvisionedThroughput(
new ProvisionedThroughput(10l, 10l)).withProjection(
new Projection().withProjectionType(ProjectionType.ALL));
createTableRequest.withGlobalSecondaryIndexes(accountIdToId);
final Table table = dynamoDB.createTable(createTableRequest);
table.waitForActive();
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "1", RANGE_KEY, "6")
.withNumberSet(CATEGORIES, 1, 2, 5, 6));
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "2", RANGE_KEY, "6")
.withNumberSet(CATEGORIES, 5, 6));
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "5", RANGE_KEY, "6")
.withNumberSet(CATEGORIES, 1, 2));
table.putItem(new Item()
.withPrimaryKey(HASH_KEY, "5", RANGE_KEY, "8")
.withNumberSet(CATEGORIES, 1, 2, 6));
System.out.println("Scan the table, no filters");
table.scan().forEach(System.out::println);
System.out.println();
final Index gsi = table.getIndex(GSI);
System.out.println("Scan the GSI without filter");
gsi.scan().forEach(System.out::println);
System.out.println();
System.out.println("Query the GSI with range key condition and contains");
final QuerySpec querySpec = new QuerySpec()
.withHashKey(RANGE_KEY, "6")
.withQueryFilters(new QueryFilter(CATEGORIES).contains(6));
gsi.query(querySpec).forEach(System.out::println);
System.out.println();
}
}
输出:
Scan the table, no filters
{ Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
{ Item: {accountId=6, id=5, categories=[1, 2]} }
{ Item: {accountId=8, id=5, categories=[1, 2, 6]} }
{ Item: {accountId=6, id=2, categories=[5, 6]} }
Scan the GSI without filter
{ Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
{ Item: {accountId=6, id=5, categories=[1, 2]} }
{ Item: {accountId=8, id=5, categories=[1, 2, 6]} }
{ Item: {accountId=6, id=2, categories=[5, 6]} }
Query the GSI with range key condition and contains
{ Item: {accountId=6, id=1, categories=[1, 2, 5, 6]} }
{ Item: {accountId=6, id=2, categories=[5, 6]} }