【问题标题】:Is there a way to query multiple hash keys in DynamoDB?有没有办法在 DynamoDB 中查询多个哈希键?
【发布时间】:2013-07-15 20:50:43
【问题描述】:

有没有办法在亚马逊的 AWS SDK for Java 中使用单个查询来查询多个哈希键?

这是我的问题;我有一个项目状态的数据库表。哈希键是项目的状态(即:新建、分配、处理或完成)。范围键是一组项目 ID。目前,我有一个查询设置来简单地查找列为“已分配”状态(哈希)的所有项目和另一个查询设置来查找“处理”状态。有没有办法使用单个查询来执行此操作,而不是为我需要查找的每个状态发送多个查询?代码如下:

    DynamoDBMapper mapper = new DynamoDBMapper(new AmazonDynamoDBClient(credentials));
    PStatus assignedStatus = new PStatus();
    assignedStatus.setStatus("assigned");
    PStatus processStatus = new PStatus();
    processStatus.setStatus("processing");

    DynamoDBQueryExpression<PStatus> queryAssigned = new DynamoDBQueryExpression<PStatus>().withHashKeyValues(assignedStatus);
    DynamoDBQueryExpression<PStatus> queryProcessing = new DynamoDBQueryExpression<PStatus>().withHashKeyValues(processStatus);

    List<PStatus> assigned = mapper.query(PStatus.class, queryAssigned);
    List<PStatus> process = mapper.query(PStatus.class, queryProcessing);

所以基本上,我想知道是否有可能消除 queryAssignedassigned 变量并通过相同的查询 process 处理 assignedStatusprocessStatus,以找到符合以下条件的项目不是新的或完整的。

【问题讨论】:

  • 抱歉,这与我所要求的甚至不接近,而且我已经知道二级索引。
  • 我认为您的问题表明您的架构需要有所不同。如果您反复需要查询 2 个哈希键,那么它本身可能应该是一个特殊的哈希键(复制两个状态的数据)。
  • 您可以构造一个派生属性,将分配和处理状态的项目标记为真,然后在该属性上创建一个稀疏索引。这将允许您在一个查询中有效地检索您想要的结果。您的问题听起来与 DynamoDB 最佳实践文档中的稀疏索引使用示例非常相似。检查docs.aws.amazon.com/amazondynamodb/latest/developerguide/… 中的示例用例

标签: java amazon-web-services amazon-dynamodb multivalue


【解决方案1】:

不,截至今天,无法在同一个请求中发送多个查询。如果您担心延迟,您可以在不同的线程中同时发出多个请求。如果 Dynamo 提供它,这将需要与“双重查询”相同数量的网络带宽(假设您制作 2​​ 个,而不是数百个)。

【讨论】:

  • 更多的想法是发送一次字符串并让服务器在移动到下一个要检查的项目之前将它正在检查的当前项目与两者进行比较。查询它是否可以做到这一点会更有效,但我想我将不得不等待,看看他们是否决定在某一天把它放进去。 有希望,但期望不高至少感谢您的回复...我现在必须进行双重/三重查询。
  • @DGolberg 实际上,据我所知,dynamodb 将每个散列键的范围键索引完全分开存储,甚至可能在不同的分区/主机上。因此,通过实现多查询,发电机将不得不增加诸如“当 1 失败而 1 不失败时会发生什么?”之类的开销。 “如果一个被限制怎么办?”,“如果一个更快怎么办?(哪个主机等待并加入数据)?”等等。这是我对他们为什么没有实现它的猜测。也就是说,“简单”的胜利对客户来说是相当大的,所以如果他们有一天添加它,我不会感到惊讶。
  • 有趣。我还不太熟悉 DynamoDB 的内部工作原理,所以感谢您提供的信息。如果/当他们实现类似的东西时,我必须确保更新它,因为当需要多个查询时,它肯定有助于加快速度。
  • @CoryKendall 这个答案怎么样? stackoverflow.com/questions/32100038/…UNION 是什么?
【解决方案2】:

无法通过多个哈希键进行查询,但从 2014 年 4 月起,您可以使用 QueryFilter,以便您可以通过除哈希键字段之外的非键字段进行过滤。

在 2014 年 4 月 24 日的一篇博文中,AWS 宣布发布“QueryFilter”选项:

在今天的版本中,我们扩展了这个模型,支持对非关键属性的查询过滤。您现在可以在调用 Query 函数时包含 QueryFilter。过滤器在基于键的检索之后和​​结果返回给您之前应用。以这种方式进行过滤可以减少返回给您的应用程序的数据量,同时还可以简化和精简您的代码

去那里看看 http://aws.amazon.com/blogs/aws/improved-queries-and-updates-for-dynamodb/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+AmazonWebServicesBlog+%28Amazon+Web+Services+Blog%29

【讨论】:

  • @AybatDuyshokov QueryFilter过滤部分的性能和Scan一样吗?
  • Tuukka Mustonen 1. 通过哈希键选择 2. 使用查询过滤器过滤结果集(是的,这一步的性能与扫描相同) 3. 返回最终结果集。
【解决方案3】:

在 C# 中试试这个。我认为它在Java中是相似的。 UserId 它是哈希键。

        var table = Table.LoadTable(DynamoClient, "YourTableName");
        var batchGet = table.CreateBatchGet();
        batchGet.AddKey(new Dictionary<string, DynamoDBEntry>() { { "UserId", 123 } });
        batchGet.AddKey(new Dictionary<string, DynamoDBEntry>() { { "UserId", 456 } });
        batchGet.Execute();
        var results = batchGet.Results;

【讨论】:

    【解决方案4】:

    为后代分享我的工作答案。截至 2020 年 10 月,有一种方法可以使用 aws-java-sdk-dynamodb-1.11.813.jar 使用单个查询来查询多个哈希键。我有相同的要求,我必须基于多个哈希键(分区键)选择项目,您可以将要求与 RDMS 场景相关联,类似于查询select * from photo where id in ('id1','id2','id3'),这里 id 是@987654323 的主键@表。

    代码片段

    • DynamoDB 实体
    package com.test.demo.dynamodb.entity;
    
    import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
    import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.NoArgsConstructor;
    
    import java.io.Serializable;
    
    @NoArgsConstructor
    @AllArgsConstructor
    @lombok.Data
    @DynamoDBTable(tableName = "test_photos")
    @Builder
    public class Photo implements Serializable {
        @DynamoDBHashKey
        private String id;
        private String title;
        private String url;
        private String thumbnailUrl;
    }
    
    
    • DynamoDB 存储库类
    import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
    import com.amazonaws.services.dynamodbv2.datamodeling.KeyPair;
    import com.test.demo.dynamodb.entity.Photo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Repository;
    
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    @Repository
    public class PhotoRepository {
    
        @Autowired
        private DynamoDBMapper dynamoDBMapper = null;
    
        public List<Photo> findByIds(Collection<String> photoIds) {
            //Constructing `KeyPair` instance and setting the HashKey,
            // in this example I have only hash key,
            // if you have RangeKey(Sort) you can set that also here using KeyPair#withRangeKey
    
            List<KeyPair> keyPairs = photoIds.stream()
                                             .map(id -> new KeyPair().withHashKey(id))
                                             .collect(Collectors.toList());
    
            //Creating Map where Key as Class<?> and value as a list of created keyPairs 
            //you can also directly use batchLoad(List<Photo> itemsToGet), the only constraint 
            //is if you didn't specify the Type as key and simply using the 
            //DynamoDBMapper#batchLoad(Iterable<? extends Object> itemsToGet)
            //then the Type of Iterable should have annotated with @DynamoDBTable
    
    
            Map<Class<?>, List<KeyPair>> keyPairForTable = new HashMap<>();
            keyPairForTable.put(Photo.class, keyPairs);
            Map<String, List<Object>> listMap = dynamoDBMapper.batchLoad(keyPairForTable);
    
            //result map contains key as dynamoDBtable name of Photo.class
            //entity(test_photo) and values as matching results of given ids
    
            String tableName = dynamoDBMapper.generateCreateTableRequest(Photo.class)
                                             .getTableName();
            return listMap.get(tableName).stream()
                                         .map(e -> (Photo) e)
                                         .collect(Collectors.toList());
        }
    }
    
    
    • 测试类
    
    import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
    import com.amazonaws.services.dynamodbv2.document.DynamoDB;
    import com.amazonaws.services.dynamodbv2.document.Table;
    import com.amazonaws.services.dynamodbv2.document.TableCollection;
    import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
    import com.amazonaws.services.dynamodbv2.model.ListTablesRequest;
    import com.amazonaws.services.dynamodbv2.model.ListTablesResult;
    import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
    import com.test.demo.dynamodb.Application;
    import com.test.demo.dynamodb.entity.Photo;
    import org.junit.jupiter.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.ActiveProfiles;
    import org.springframework.test.context.junit.jupiter.SpringExtension;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    import java.util.UUID;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    @ActiveProfiles("test")
    @ExtendWith(SpringExtension.class)
    @SpringBootTest(classes = Application.class,
            webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class DynamoDBFindByIdsITest {
    
        @Autowired
        private DynamoDBMapper dynamoDBMapper = null;
    
        @Autowired
        private DynamoDB dynamoDB = null;
    
        @Autowired
        private PhotoRepository photoRepository = null;
    
    
        @Test
        void findByIdsTest() throws InterruptedException {
            //Creating dynamodb table if not already exists
            createDataTableIfNotExists("test", Photo.class);
            int size = 5;
            //creating dummy entries for test and persisting and collecting it to
            //validate with results
            List<Photo> photos = IntStream.range(0, size)
                    .mapToObj(e -> UUID.randomUUID().toString())
                    .map(id ->
                            Photo.builder()
                                    .id(id)
                                    .title("Dummy title")
                                    .url("http://photos.info/" + id)
                                    .thumbnailUrl("http://photos.info/thumbnails/" + id)
                                    .build()
                    ).peek(dynamoDBMapper::save)
                    .collect(Collectors.toList());
    
            //calling findByIds with the Collection of HashKey ids (Partition Key Ids)
            Set<String> photoIds = photos.stream()
                    .map(Photo::getId)
                    .collect(Collectors.toSet());
            List<Photo> photosResultSet = photoRepository.findByIds(photoIds);
    
            Assertions.assertEquals(size, photosResultSet.size());
    
            //validating returned photoIds with the created Ids
            Set<String> resultedPhotoIds = photosResultSet.stream()
                    .map(Photo::getId)
                    .collect(Collectors.toSet());
            Assertions.assertTrue(photoIds.containsAll(resultedPhotoIds));
        }
    
        public <T> void createDataTableIfNotExists(String tablePrefix, Class<T> clazz)
                throws InterruptedException {
            ListTablesRequest listTablesRequest = new ListTablesRequest();
            listTablesRequest.setExclusiveStartTableName(tablePrefix);
            TableCollection<ListTablesResult> tables = dynamoDB.listTables();
            List<String> tablesList = new ArrayList<>();
            tables.forEach((tableResult) -> {
                tablesList.add(tableResult.getTableName());
            });
            String tableName = dynamoDBMapper.generateCreateTableRequest(clazz).getTableName();
            if (!tablesList.contains(tableName)) {
                CreateTableRequest tableRequest = dynamoDBMapper.generateCreateTableRequest(clazz);
                tableRequest.withProvisionedThroughput(new ProvisionedThroughput(5L, 5L));
                Table table = dynamoDB.createTable(tableRequest);
                table.waitForActive();
            }
        }
    }
    
    

    【讨论】:

    • 这不能回答 OP 问题。当表没有排序键时,很容易查询多个id。问题是当表有排序键但没有在查询中指定排序键时如何做到这一点。
    • @AhmadAbdelghany ,实际上,如果您尝试我的答案,您可以找到它。与是否具有排序键(范围键)的表无关,您可以使用上述答案获取多个 id,并且我已经多次实现了多个用例
    • 好吧,我试过了,没用。它给出了一条错误消息,说“不存在 RANGE 键值”。
    【解决方案5】:

    您可以查看BatchGetItem 操作或DynamoDBMapperbatchLoad() 方法。尽管与查询略有不同,因为它不是在哈希键上带有OR 条件的查询,但它允许您完成(通常)相同的事情。这里是language agnostic documentation,这里是Javadoc

    【讨论】:

    • 不起作用。当一个表包含一个或你得到一个DynamoDBMappingException 时,你必须为batchLoad() 操作提供一个范围键。这可以防止您简单地抓取所有哈希键相同的项目。例如;假设您有一个服务器列表。此列表告诉您哪些服务器在线/离线/暂停/等。通过哈希键和服务器名称是通过范围键。我所知道的让所有列为在线的服务器以及那些列为离线的服务器(不知道它们的个人名称)的唯一方法是进行扫描或 2 次查询。
    【解决方案6】:

    Amazon API 不支持多个 hashkey 过滤器,但您可以使用 HASH KEY + RANGE KEY 过滤器通过 batchGetItem 方法获取结果..

    http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/batch-operation-lowlevel-java.html#LowLevelJavaBatchGet

    【讨论】:

    • 这是最后一张海报所建议的,它不适用于这种情况。查询的全部目的是查找与特定状态匹配的 ID(顺便说一下,这也是范围键)。 batchGetItem 请求要求您知道哈希键和范围键。我什至有一个范围键的唯一原因是我可以有多个具有相同值的哈希键可以查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-18
    • 1970-01-01
    • 2016-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多