java - DynamoDBMapper:使用嵌套对象属性的过滤器进行查询/扫描
问题描述
我正在创建一个 DAO 类,它有一个 API 可以逐页获取产品。对 API 的请求将包含一个过滤器列表。过滤对原始和字符串属性按预期工作。
@Getter
@Setter
@DynamoDBTable(tableName = "Entity")
public abstract class EntityDO {
@DynamoDBHashKey(attributeName = "uid")
@DynamoDBAutoGeneratedKey
private String uid;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@DynamoDBDocument
@DynamoDBTable(tableName = "CATEGORIES")
public class CategoryDO extends EntityDO {
@DynamoDBAttribute(attributeName = "name")
private String name;
@DynamoDBAttribute(attributeName = "description")
private String description;
@DynamoDBAttribute(attributeName = "imageUrl")
private String imageUrl;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@DynamoDBTable(tableName = "bs-PRODUCTS")
public class ProductDO extends EntityDO {
@DynamoDBAttribute(attributeName = "name")
private String name;
@DynamoDBAttribute(attributeName = "description")
private String description;
@DynamoDBAttribute(attributeName = "longDescription")
private String longDescription;
@DynamoDBAttribute(attributeName = "category")
private CategoryDO category;
@DynamoDBAttribute(attributeName = "imageUrl")
private String imageUrl;
@DynamoDBAttribute(attributeName = "mrp")
private float mrp;
@DynamoDBAttribute(attributeName = "discount")
private float discount;
@DynamoDBAttribute(attributeName = "tags")
private List<String> tags;
}
public class ProductDAO extends EntityDAO<ProductDO> {
@Autowired
private Logger logger;
@Autowired
private DynamoDBMapper dynamoDBMapper;
@Autowired
private Map<Filter.Operation, ComparisonOperator> operatorMap;
@Autowired
private ProductConverter converter;
@Override
public Optional<ProductDO> get(String id) {
return Optional.ofNullable(dynamoDBMapper.load(ProductDO.class, id));
}
@Override
public List<ProductDO> getAll() {
return dynamoDBMapper.scan(ProductDO.class, new DynamoDBScanExpression());
}
@Override
public List<ProductDO> get(List<Filter> filters, String previousPageLastKey, int count) {
Map<String, Condition> scanFilters = new HashMap<>();
for (Filter filter: filters) {
ComparisonOperator comparisonOperator = operatorMap.get(filter.getOperation());
Condition condition = new Condition()
.withComparisonOperator(comparisonOperator)
.withAttributeValueList(new AttributeValue().withS(filter.getAttributeValue()));
scanFilters.put(filter.getAttributeName(), condition);
}
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
.withLimit(count)
.withScanFilter(scanFilters);
if (previousPageLastKey != null) {
Map<String, AttributeValue> exclusiveStartKey = new HashMap<>();
exclusiveStartKey.put(Constants.UID, new AttributeValue().withS(previousPageLastKey));
scanExpression.setExclusiveStartKey(exclusiveStartKey);
}
return dynamoDBMapper.scan(ProductDO.class, scanExpression);
}
}
ScanExpression 中应更改哪些内容以过滤 category.uid?
我尝试传递属性名称,category.uid
但没有帮助。
如果方法在设计方面不正确,我很高兴得到评论家的看法。如果方法不止一种,可以详细说明利弊。
此外,我在 AWS 控制台上尝试了它,但它在那里也不起作用。我的表如下所示
检查类别字段,这是一个包含name
和的地图uid
。我尝试搜索类别名称,但没有出现任何结果。
AWS DynamoDB 是否甚至支持过滤嵌套属性。
解决方案
我用 ScanFilters 走到了尽头,基本上它们不支持嵌套属性来搜索 Dynamodb 文档(嵌套对象)。这是进行过滤的旧方法,FilterExpressions 是它的替代品。
FilterExpressions 也支持嵌套属性。过滤器表达式基本上是字符串,其中包含属性名称和属性值的占位符。可能不会选择使用占位符,但是 dynamodb 中保留的关键字太多,推荐的方式。这些占位符是使用地图提供的,expressionAttributeNames
and expressionAttributeValues
。
FilterExpression 看起来像#category.#uid = :categoryuid
我的属性名称映射{"#category":"category","#uid":"uid"}
和属性值映射的样子{":categoryuid":{"s":"1d5e9cea-3c4d-4a73-8e1e-aeaa868b9d89"}}
。
这对我来说非常好,为了更好地组织代码,我写了一个包装类,
public class FilterExpression {
private List<Filter> filters;
private String filterExpression;
private Map<String, AttributeValue> attributeValues;
private Map<String, String> attributeNames;
public FilterExpression(List<Filter> filters) {
this.filters = filters;
populateFilterExpression();
}
private void populateFilterExpression() {
StringBuilder filterExpressionBuilder = new StringBuilder();
attributeNames = new HashMap<>();
attributeValues = new HashMap<>();
for (Filter filter: filters) {
if (filterExpressionBuilder.length() > 0) {
filterExpressionBuilder.append(" AND ");
}
String attributeName = filter.getAttributeName();
String[] attributes = attributeName.split("\\.");
StringBuilder expNestedAttributes = new StringBuilder();
for (String attributeInPath: attributes) {
attributeNames.put("#"+attributeInPath, attributeInPath);
if(expNestedAttributes.length() > 0) {
expNestedAttributes.append(".");
}
expNestedAttributes.append("#" + attributeInPath);
}
String attributeValueKey = ":" + String.join("", attributes);
AttributeValue attributeValue;
switch (filter.getAttributeType()) {
case STRING:
attributeValue = new AttributeValue().withS(filter.getAttributeValue());
break;
case NUMBER:
attributeValue = new AttributeValue().withN(filter.getAttributeValue());
break;
default:
throw new UnsupportedOperationException("The attribute type is not supported");
}
attributeValues.put(attributeValueKey, attributeValue);
switch (filter.getOperation()) {
case EQ:
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(" = ");
filterExpressionBuilder.append(attributeValueKey);
break;
case GE:
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(" >= ");
filterExpressionBuilder.append(attributeValueKey);
break;
case LE:
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(" <= ");
filterExpressionBuilder.append(attributeValueKey);
break;
case GT:
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(" > ");
filterExpressionBuilder.append(attributeValueKey);
break;
case LT:
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(" < ");
filterExpressionBuilder.append(attributeValueKey);
break;
case STARTS_WITH:
filterExpressionBuilder.append("begins_with (");
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(", ");
filterExpressionBuilder.append(attributeValueKey);
filterExpressionBuilder.append(")");
break;
case CONTAINS:
filterExpressionBuilder.append("contains (");
filterExpressionBuilder.append(expNestedAttributes);
filterExpressionBuilder.append(", ");
filterExpressionBuilder.append(attributeValueKey);
filterExpressionBuilder.append(")");
break;
default:
throw new UnsupportedOperationException("filter is not supported");
}
}
filterExpression = filterExpressionBuilder.toString();
}
public String getFilterExpression() {
return filterExpression;
}
public Map<String, AttributeValue> getAttributeValues() {
return attributeValues;
}
public Map<String, String> getAttributeNames() {
return attributeNames;
}
@Override
public String toString() {
return filterExpression;
}
}
我的Filter
样子如下:
public class Filter {
private String attributeName;
private String attributeValue;
private Operation operation;
private AttributeType attributeType;
public enum Operation {
EQ, GE, LE, GT, LT, CONTAINS, STARTS_WITH
}
public enum AttributeType {
STRING, NUMBER
}
}
片段DAO
看起来像
public List<ProductDO> get(List<Filter> filters, String previousPageLastKey, int count) {
FilterExpression filterExpression = new FilterExpression(filters);
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
.withLimit(count)
.withFilterExpression(filterExpression.getFilterExpression())
.withExpressionAttributeNames(filterExpression.getAttributeNames())
.withExpressionAttributeValues(filterExpression.getAttributeValues());
dynamoDBMapper.scan(ProductDO.class, scanExpression)
}
发布它,因为我没有找到与此相关的解决方案。
推荐阅读
- java - 将 for 循环示例转换为 while 循环
- json - JSON 错误:试图从 JSON 结果中获取信息
- javascript - 即使事件未执行,事件侦听器也会立即触发
- javascript - 使用 AngularJS 将所选值绑定到多选 ListboxFor 控件
- android - Firestore:库中的包引用无效
- node.js - Azure Active Directory 动态回复 url
- java - Java8:使用 IntStream 作为自定义收集器的供应商参数
- android - 使用 Glide 下载时,图像之间有巨大的空间分隔
- assembly - 如何将字符串保存到程序集中的变量中?
- mysql - SQL请求对具有另一列符号函数的列求和