设置一个attribute为null(或有时候没有设置)时,保存数据(org.springframework.data.repository.CrudRepository#save)的执行行为:
一般情况下,会走入下面的分支:
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.SaveObjectHandler#onNullNonKeyAttribute
@Override protected void onNullNonKeyAttribute(String attributeName) { /* * If UPDATE_SKIP_NULL_ATTRIBUTES or APPEND_SET is * configured, we don't delete null value attributes. */ if (getLocalSaveBehavior() == SaveBehavior.UPDATE_SKIP_NULL_ATTRIBUTES || getLocalSaveBehavior() == SaveBehavior.APPEND_SET) { return; } else { /* Delete attributes that are set as null in the object. */ getAttributeValueUpdates() .put(attributeName, new AttributeValueUpdate() .withAction("DELETE")); } }
因为默认保存行为为:SaveBehavior.UPDATE.所以其实会删掉对应的attribute的值,而不是不管之前attribute是否有值,从这点看,试图通过部分attribute设置非Null值、部分attribute设置Null来达到“部分”(patch)更新是做不到的。
仔细查阅上面代码,反过来说明只要修改默认保存行为为UPDATE_SKIP_NULL_ATTRIBUTES,就可以忽略Null值。
副作用:假设一个值之前有值,后来想改成Null,那就实现不了了。所以如果使用UPDATE_SKIP_NULL_ATTRIBUTES,用户场景不能有这种。
实际上,在不修改默认保存方法的情况下,还有另外一种实现“部分”更新的方法,就是提供另外一个专门的entity对象,去除不想更新的attributes。这样也可以实现部分更新。
另外要注意批量Save其实是直接覆盖:
批量save:
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper#batchWrite
本质上用的是PUT:
for ( Object toWrite : objectsToWrite ) { //******* requestItems.add(tableName, new WriteRequest(new PutRequest(transformAttributes(parameters)))); }
而普通的save:
com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper#save
默认用的不是PUT.
boolean usePut = (finalConfig.getSaveBehavior() == SaveBehavior.CLOBBER || finalConfig.getSaveBehavior() == SaveBehavior.PUT) || anyKeyGeneratable(model, object, finalConfig.getSaveBehavior()); SaveObjectHandler saveObjectHandler; if (usePut) { //默认不走入这个分支。
所以:
BATCH SAVE其实是直接覆盖,SAVE可以选择直接覆盖还是部分更新(Update)。个人觉得这个行为不好,不一致。
追根溯源: 批量BatchWriteItemRequest是com.amazonaws.services.dynamodbv2.model.WriteRequest的批量,这个类只接受PUT和DELETE两种Request作为构造器参数。
public WriteRequest(PutRequest putRequest) { setPutRequest(putRequest); } public WriteRequest(DeleteRequest deleteRequest) { setDeleteRequest(deleteRequest); }