基表抽象
在 2. AWS DynamoDB实战之表的设计 一文中,详细介绍了如何设计dto model以及dynamodb的表,仔细观察我们的三个dto,它们具有一些共同点:
- 都有pk和sk的getter和setter
- item的唯一标识符由pk和sk构成
综合上述特点,我们抽象出一个dto mode的基类AbstractDto:
- AbstractDto是一个抽象类
- 包含四个抽象方法,分别为pk和sk的getter和setter
- 包含一个用于获取item唯一标识符的方法,返回的唯一标识符格式为: pk#sk
package com.jessica.dynamodb.favorite.dto;
public abstract class AbstractDto {
public abstract String getPk();
public abstract void setPk(String hashKey);
public abstract String getSk();
public abstract void setSk(String rangeKey);
/**
* return composite keys as unique key string
*
* @return
*/
public String getCompositeKey() {
return String.join("#", getPk(), getSk());
}
}
添加了AbstractDto以后,其他所有的Dto都需要继承该类:
package com.jessica.dynamodb.favorite.dto;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.jessica.dynamodb.constant.DynamoDBConstant;
import com.jessica.dynamodb.utils.KeyGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName = "develop.Favorite")
@EqualsAndHashCode(callSuper = false)
public class TagDto extends AbstractDto {
private static final String DTO_NAME = "Tag";
@DynamoDBIgnore
private String userId;
@DynamoDBIgnore
private String tagId;
@DynamoDBAttribute
private String tagName;
@DynamoDBAttribute
private long createTime;
@DynamoDBAttribute
private long lastAccessTime;
@DynamoDBHashKey
@Override
public String getPk() {
return KeyGenerator.createHashKey(DTO_NAME, userId);
}
@Override
public void setPk(String hashKey) {
String[] keys = KeyGenerator.parseHashKey(DTO_NAME, hashKey);
this.userId = keys[0];
}
@DynamoDBRangeKey
@Override
public String getSk() {
return KeyGenerator.createRangeKey(tagId);
}
@Override
public void setSk(String rangeKey) {
String[] keys = KeyGenerator.parseRangeKey(rangeKey);
this.tagId = keys[0];
}
@DynamoDBIndexRangeKey(localSecondaryIndexName = DynamoDBConstant.LSI_ONE_NAME)
public String getLsiOneSk() {
return KeyGenerator.createRangeKey(tagName);
}
public void setLsiOneSk(String lsiOneSk) {
}
@DynamoDBIndexRangeKey(localSecondaryIndexName = DynamoDBConstant.LSI_TWO_NAME)
public String getLsiTwoSk() {
return KeyGenerator.createRangeKey(String.valueOf(createTime), tagId);
}
public void setLsiTwoSk(String lsiTwoSk) {
}
@DynamoDBIndexRangeKey(localSecondaryIndexName = DynamoDBConstant.LSI_THREE_NAME)
public String getLsiThreeSk() {
return KeyGenerator.createRangeKey(String.valueOf(lastAccessTime), tagId);
}
public void setLsiThreeSk(String lsiThreeSk) {
}
}
package com.jessica.dynamodb.favorite.dto;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConvertedEnum;
import com.jessica.dynamodb.constant.DynamoDBConstant;
import com.jessica.dynamodb.utils.KeyGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName = "develop.Favorite")
@EqualsAndHashCode(callSuper = false)
public class FavoriteDataDto extends AbstractDto {
private static final String DTO_NAME = "FavoriteData";
@DynamoDBIgnore
private String userId;
@DynamoDBIgnore
private String dataId;
@DynamoDBAttribute
private String creatorId;
@DynamoDBAttribute
private String title;
@DynamoDBAttribute
private String thumbnailUrl;
@DynamoDBAttribute
private String contentUrl;
@DynamoDBTypeConvertedEnum
@DynamoDBAttribute
private FavoriteDataType dataType;
@DynamoDBAttribute
private String clipTime;
@DynamoDBHashKey
@Override
public String getPk() {
return KeyGenerator.createHashKey(DTO_NAME, userId);
}
@Override
public void setPk(String hashKey) {
String[] keys = KeyGenerator.parseHashKey(DTO_NAME, hashKey);
this.userId = keys[0];
}
@DynamoDBRangeKey
@Override
public String getSk() {
return KeyGenerator.createRangeKey(dataId);
}
@Override
public void setSk(String rangeKey) {
String[] keys = KeyGenerator.parseRangeKey(rangeKey);
this.dataId = keys[0];
}
@DynamoDBIndexHashKey(globalSecondaryIndexName = DynamoDBConstant.GSI_ONE_NAME)
public String getGsiOnePk() {
return KeyGenerator.createHashKey(DTO_NAME, userId, dataType.getValue());
}
public void setGsiOnePk(String hashKey) {
}
@DynamoDBIndexRangeKey(globalSecondaryIndexName = DynamoDBConstant.GSI_ONE_NAME)
public String getGsiOneSk() {
return KeyGenerator.createRangeKey(clipTime, dataId);
}
public void setGsiOneSk(String rangeKey) {
}
}
package com.jessica.dynamodb.favorite.dto;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAttribute;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIgnore;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBIndexRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBRangeKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConvertedEnum;
import com.jessica.dynamodb.constant.DynamoDBConstant;
import com.jessica.dynamodb.utils.KeyGenerator;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DynamoDBTable(tableName = "develop.Favorite")
@EqualsAndHashCode(callSuper = false)
public class FavoriteDataTagDto extends AbstractDto {
private static final String DTO_NAME = "FavoriteDataTag";
@DynamoDBIgnore
private String userId;
@DynamoDBIgnore
private String dataId;
@DynamoDBIgnore
private String tagId;
@DynamoDBAttribute
private String clipTime;
@DynamoDBTypeConvertedEnum
@DynamoDBAttribute
private FavoriteDataType dataType;
@DynamoDBHashKey
@Override
public String getPk() {
return KeyGenerator.createHashKey(DTO_NAME, userId, dataId);
}
@Override
public void setPk(String hashKey) {
String[] keys = KeyGenerator.parseHashKey(DTO_NAME, hashKey);
this.userId = keys[0];
this.dataId = keys[1];
}
@DynamoDBRangeKey
@Override
public String getSk() {
return KeyGenerator.createRangeKey(tagId);
}
@Override
public void setSk(String rangeKey) {
String[] keys = KeyGenerator.parseRangeKey(rangeKey);
this.tagId = keys[0];
}
@DynamoDBIndexHashKey(globalSecondaryIndexName = DynamoDBConstant.GSI_ONE_NAME)
public String getGsiOnePk() {
return KeyGenerator.createHashKey(DTO_NAME, userId, tagId);
}
public void setGsiOnePk(String hashKey) {
}
@DynamoDBIndexRangeKey(globalSecondaryIndexName = DynamoDBConstant.GSI_ONE_NAME)
public String getGsiOneSk() {
return KeyGenerator.createRangeKey(clipTime, dataId);
}
public void setGsiOneSk(String rangeKey) {
}
@DynamoDBIndexHashKey(globalSecondaryIndexName = DynamoDBConstant.GSI_TWO_NAME)
public String getGsiTwoPk() {
return KeyGenerator.createHashKey(DTO_NAME, userId, tagId, dataType.getValue());
}
public void setGsiTwoPk(String hashKey) {
}
@DynamoDBIndexRangeKey(globalSecondaryIndexName = DynamoDBConstant.GSI_TWO_NAME)
public String getGsiTwoSk() {
return KeyGenerator.createRangeKey(clipTime, dataId);
}
public void setGsiTwoSk(String rangeKey) {
}
}
通用接口抽象
一般来讲,每个dto model都会对应一个dao,用于对该dto进行crud操作.实际实现过程中发现一些基本的crud操作的代码是完全一样的,因此我们也实现一个通用接口来进行这些基本的crud操作.
package com.jessica.dynamodb.favorite.dao;
import java.util.List;
import java.util.Map;
import com.jessica.dynamodb.favorite.data.LazyLoadResult;
public interface BasicDao<T> {
/**
* create or update dto
*
* @param dto
*/
void save(T dto);
/**
* load dto
*
* @param keyDto, pk and sk related fields must set
* @return
*/
T load(T keyDto);
/**
* delete dto
*
* @param keyDto, pk and sk related fields must set
*/
void delete(T keyDto);
/**
* create or update dtos
*
* @param dtos
*/
void batchSave(List<T> dtos);
/**
* load dtos, result list sequence may not be same with keyDtos
*
* @param keyDtos, pk and sk related fields must set
* @return
*/
List<T> batchLoad(List<T> keyDtos);
/**
* load dto map
*
* @param keyDtos, pk and sk related fields must set
* @return key is pk#sk
*/
Map<String, T> batchLoadMap(List<T> keyDtos);
/**
* delete dtos
*
* @param keyDtos, pk and sk related fields must set
* @return
*/
void batchDelete(List<T> keyDtos);
/**
* query with pk in given order
*
* @param clazz
* @param hashKeyDto
* @param asc
* @param lastLoadSk
* @param size
* @return
*/
LazyLoadResult<T> query(Class<T> clazz, T hashKeyDto, boolean asc, String lastLoadSk, Integer size);
/**
* query index with pk in given order
*
* @param clazz
* @param indexName
* @param indexSkName
* @param hashKeyDto
* @param asc
* @param lastLoadSk
* @param size
* @return
*/
LazyLoadResult<T> queryIndex(Class<T> clazz, String indexName, String indexSkName, T hashKeyDto, boolean asc,
String lastLoadSk, Integer size);
}
package com.jessica.dynamodb.favorite.dao.impl;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.amazonaws.services.dynamodbv2.model.ComparisonOperator;
import com.amazonaws.services.dynamodbv2.model.Condition;
import com.jessica.dynamodb.constant.DynamoDBConstant;
import com.jessica.dynamodb.favorite.dao.BasicDao;
import com.jessica.dynamodb.favorite.data.LazyLoadResult;
import com.jessica.dynamodb.favorite.dto.AbstractDto;
public class BasicDaoImpl<T extends AbstractDto> implements BasicDao<T> {
protected DynamoDBMapper dynamoDBMapper = new DynamoDBMapper(AmazonDynamoDBClientBuilder.standard().build());
@Override
public void save(T dto) {
this.dynamoDBMapper.save(dto);
}
@Override
public T load(T keyDto) {
return this.dynamoDBMapper.load(keyDto);
}
@Override
public void delete(T keyDto) {
this.dynamoDBMapper.delete(keyDto);
}
@Override
public void batchSave(List<T> dtos) {
this.dynamoDBMapper.batchSave(dtos);
}
@Override
public List<T> batchLoad(List<T> keyDtos) {
return this.dynamoDBMapper.batchLoad(keyDtos).values().stream().flatMap(Collection::stream)
.map(object -> (T) object).collect(Collectors.toList());
}
@Override
public Map<String, T> batchLoadMap(List<T> keyDtos) {
return this.batchLoad(keyDtos).stream()
.collect(Collectors.toMap(t -> ((AbstractDto) t).getCompositeKey(), Function.identity()));
}
@Override
public void batchDelete(List<T> keyDtos) {
this.dynamoDBMapper.batchDelete(keyDtos);
}
@Override
public LazyLoadResult<T> query(Class<T> clazz, T hashKeyDto, boolean asc, String lastLoadSk, Integer size) {
DynamoDBQueryExpression<T> queryExpression = new DynamoDBQueryExpression<T>().withHashKeyValues(hashKeyDto)
.withScanIndexForward(asc);
if (lastLoadSk != null) {
Condition rangeCondition = new Condition()
.withComparisonOperator(asc ? ComparisonOperator.GT :