3. AWS DynamoDB实战之CRUD

本文介绍了在AWS DynamoDB实战中,如何通过抽象基类`AbstractDto`来统一DTO模型,并实现通用的CRUD接口`BasicDao`。接着详细展示了`TagDao`、`FavoriteDataDao`和`FavoriteDataTagDao`的实现,包括特殊查询功能的添加,并提供了相应的JUnit测试用例。最后给出了完整代码的GitHub链接。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基表抽象

 在 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 :
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值