基本概念
- Table(表),與其他數據庫的表的概念一致,即為數據的集合。
- Items(數據項),代表一行數據。
- Attributes(屬性),一個數據項可由多個屬性構成,一個屬性由屬性名、屬性值類型和屬性值構成。
- Partition Key(分區鍵),即是最簡單的主鍵,由一個屬性構成,一張表有且只有一個分區鍵。由於 DynamoDB 內部在存儲數據時使用分區鍵的 Hash 值實現跨多個分區的數據項目平均分布,故分區鍵又稱之為 Hash Key。
- Sort Key(排序鍵),排序鍵和分區鍵構成另一種主鍵,用於在分區中按排序鍵排序。由於 DynamoDB 內部按照排序鍵值有序地將具有相同分區鍵的項目存儲在互相緊鄰的物理位置,故排序鍵又稱之為 Range Key。
- Secondary Indexes(二級索引),這樣命名應該是將主鍵視為一級索引。DynamoDB 的二級索引有兩種:
- Global secondary index(GSI),創建跟主鍵不同的分區鍵和排序鍵的索引,DynamoDB 有最多創建 20 個 GSI 的限制;
- Local secondary index(LSI),創建跟主鍵的分區鍵相同但是排序鍵相異的索引,當且僅當創建表時可用,DynamoDB 有最多創建 5 個 LSI 的限制;
配置本地開發環境
下載 DynamoDB 本地版本
從 計算機上的 DynamoDB(可下載版本)- aws 下載,並放置到合適的位置。
配置
> aws configure --profile local AWS Access Key ID [None]: fake-ak
AWS Secret Access Key [None]: fake-sk
Default region name [None]:
Default output format [None]:
啟動
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb
CLI 操作
注意:如果是本地版本需要添加
--endpoint-url http://localhost:8000
# 創建表格
aws dynamodb create-table \
--table-name ab-debug-proxy \
--attribute-definitions \
AttributeName=host,AttributeType=S \
AttributeName=port,AttributeType=N \
AttributeName=channel,AttributeType=S \
--key-schema AttributeName=host,KeyType=HASH AttributeName=port,KeyType=RANGE \
--global-secondary-indexes IndexName=channel-index,KeySchema=["{AttributeName=channel,KeyType=HASH}"],Projection="{ProjectionType=ALL}",ProvisionedThroughput="{ReadCapacityUnits=1,WriteCapacityUnits=1}" \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \
--endpoint-url http://localhost:8000
# 刪除表格
aws dynamodb delete-table \
--table-name ab-debug-proxy \
--endpoint-url http://localhost:8000
# 列出所有表格
aws dynamodb list-tables --endpoint-url http://localhost:8000
# 添加數據項
aws dynamodb put-item \
--table-name ab-debug-proxy \
--item '{"host": {"S": "192.168.1.0"},"port": {"N": "9090"},"channel": {"S": "sw"} }' \
--return-consumed-capacity TOTAL \
--endpoint-url http://localhost:8000
# 掃描數據項
aws dynamodb scan \
--table-name ab-debug-proxy \
--endpoint-url http://localhost:8000
Java 操作
使用 DynamoDBMapper
AmazonDynamoDB amazonDynamoDB = builder.build();
final String prefix = "project-" + AppConfig.getVariant() + "-";
final DynamoDBMapperConfig.TableNameOverride tableNameOverride =
DynamoDBMapperConfig.TableNameOverride.withTableNamePrefix(prefix);
DynamoDBMapperConfig dynamoDBMapperConfig = DynamoDBMapperConfig.builder()
.withTableNameOverride(tableNameOverride)
.build();
DynamoDBMapper DynamoDBMapper(amazonDynamoDB, dynamoDBMapperConfig);
批量加載(Batch Load)
List<KeyPair> keyPairList = new ArrayList<>();
for (Proxy proxy : proxyList) {
KeyPair keyPair = new KeyPair();
keyPair.setHashKey(proxy.getHost());
keyPair.setRangeKey(proxy.getPort());
keyPairList.add(keyPair);
}
Map<Class<?>, List<KeyPair>> keyPairForTable = new HashMap<>();
keyPairForTable.put(Proxy.class, keyPairList);
Map<String, List<Object>> stringListMap = dynamoDBMapper.batchLoad(keyPairForTable);
排錯
DynamoDBMappingException: [class name] :no mapping for HASH key
確保對主鍵設置了以下的注解:
@DynamoDBHashKey
查詢數據時使用 limit 字段但是沒有返回預期的數據
原因是 limit 限制的是去查詢集合而非結果集合,查看注釋可知:
/**
* Sets the limit of items to scan and returns a pointer to this object for
* method-chaining. Please note that this is <b>not</b> the same as the
* number of items to return from the scan operation -- the operation will
* cease and return as soon as this many items are scanned, even if no
* matching results are found.
*
* @see DynamoDBScanExpression#getLimit()
*/
public DynamoDBScanExpression withLimit(Integer limit) {
this.limit = limit;
return this;
}
那么怎么做到對結果集合的 limit 呢?采用其帶 page
的接口分頁查詢,如下:
// page query
do {
QueryResultPage<Proxy> proxyQueryResultPage =
dynamoDBMapper.queryPage(Proxy.class, queryExpression);
proxies.addAll(proxyQueryResultPage.getResults());
queryExpression.setExclusiveStartKey(proxyQueryResultPage.getLastEvaluatedKey());
} while (queryExpression.getExclusiveStartKey() != null && proxies.size() < limit);
設置 SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES
但在調用 batchSave
方法時並沒有跳過為 null 的字段
在創建 DynamoDBMapperConfig 時設置 UPDATE_SKIP_NULL_ATTRIBUTES,如下:
DynamoDBMapperConfig dynamoDBMapperConfig = DynamoDBMapperConfig.builder()
.withTableNameOverride(tableNameOverride)
// 設置 UPDATE_SKIP_NULL_ATTRIBUTES
.withSaveBehavior(DynamoDBMapperConfig.SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES)
.build();
- 關於 SaveBehavior 參考 Using the SaveBehavior Configuration for the DynamoDBMapper
查看 SaveBehavior
源碼,發現:
/**
* UPDATE_SKIP_NULL_ATTRIBUTES is similar to UPDATE, except that it
* ignores any null value attribute(s) and will NOT remove them from
* that item in DynamoDB. It also guarantees to send only one single
* updateItem request, no matter the object is key-only or not.
*/
UPDATE_SKIP_NULL_ATTRIBUTES,
也就是說 不支持 批量的操作!
Scan 過濾 Boolean 類型字段時返回為空
Map<String, AttributeValue> eav = new HashMap<>();
Map<String, String> ean = new HashMap<>();
StringBuilder filterExpressionBuilder = new StringBuilder();
eav.put(":valid", new AttributeValue().withBOOL(proxy.getLocked()));
ean.put("#valid","valid");
filterExpressionBuilder.append("#valid = :valid");
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression().withConsistentRead(false)
.withExpressionAttributeValues(eav)
.withExpressionAttributeNames(ean)
.withFilterExpression(filterExpressionBuilder.toString());
PaginatedScanList<Proxy> result = dynamoDBMapper.scan(Proxy.class, scanExpression);
DynamoDB 存儲 Boolean 類型時其實是使用 Number 類型存儲,進行過濾時不能使用布爾值進行過濾,而是要使用 “0” (false)和 “1”(true),更改如下:
eav.put(":valid", new AttributeValue().withN(proxy.getLocked() ? "1" : "0"));
DynamoDBMappingException
調用 DynamoDB 的 batchSave 接口發生以下錯誤:
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: not supported; requires @DynamoDBTyped or @DynamoDBTypeConverted
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$Rules$NotSupported.set(StandardModelFactories.java:664)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$Rules$NotSupported.set(StandardModelFactories.java:650)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$AbstractRule.convert(StandardModelFactories.java:709)
at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$AbstractRule.convert(StandardModelFactories.java:691)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.convert(DynamoDBMapperFieldModel.java:138)
at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.batchWrite(DynamoDBMapper.java:1107)
at com.amazonaws.services.dynamodbv2.datamodeling.AbstractDynamoDBMapper.batchSave(AbstractDynamoDBMapper.java:173)
原來在注解為 @DynamoDBTable
的實體類中使用了自定義類型的字段,DynamoDB 並不支持。
解決方案:在該自定義類型的字段上添加注解 @DynamoDBTypeConvertedJson
即可,原理是使用 Jackson 將該字段的值 json 化再存儲。如果不想要 Jackson
,可以自定義轉化器,如下:
// CustomTypeConverter.java
public class CustomTypeConverter<T> implements DynamoDBTypeConverter<String, T> {
private Class<T> clazz;
public CustomTypeConverter(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public String convert(T object) {
return Json.toJson(object);
}
@Override
public T unconvert(String json) {
return Json.fromJson(json, clazz);
}
}
// JobTreeConverter.java
public class JobTreeConverter extends CustomTypeConverter<JobTree> {
public JobTreeConverter(Class<JobTree> clazz) {
super(clazz);
}
}
// JobGroup.java
@DynamoDBTable(tableName = "job-group")
public class JobGroup implements Serializable {
@DynamoDBHashKey(attributeName = "id")
@SerializedName("id")
private String id;
@SerializedName("name")
private String name;
@DynamoDBTypeConverted(converter = JobTreeConverter.class)
@DynamoDBAttribute(attributeName = "default_job_tree")
@SerializedName("default_job_tree")
private JobTree defaultJobTree;
//...
}